mcp-for-beginners 实战指南:用 TypeScript 编写、运行并调试 MCP 客户端(stdio 传输)

原创2026-10-03 13:31:39710 阅读
文章标签:教程文档人工智能

mcp-for-beginners 实战指南:用 TypeScript 编写、运行并调试 MCP 客户端(stdio 传输)

本篇指南基于 mcp-for-beginners 仓库 03-GettingStarted/02-client 章节的 TypeScript 解决方案,完整讲解如何通过 npm 安装依赖、编译服务器并启动一个基于 stdio 传输的 MCP 客户端,实现工具(tool)、资源模板(resource template)与提示(prompt)三大能力的发现与调用。读完本文,你将掌握一套可直接复现的命令行操作流程,并能对照源码理解 MCP 客户端与服务器之间的协议交互细节。

一、这个示例解决什么问题

在 MCP(Model Context Protocol)生态中,客户端是直接与 MCP 服务器通信的自定义应用或脚本,用于请求资源、工具与提示。与图形化的 MCP Inspector 不同,手写客户端可以嵌入你自己的工作流,实现自动化与定制化集成。

本示例位于 03-GettingStarted/02-client/solution/typescript/ 目录,包含一个 TypeScript 编写的 MCP 服务器与客户端:

solution/typescript/
├── package.json          # 依赖与 npm scripts
├── tsconfig.json         # TypeScript 编译配置
├── src/
│   ├── index.ts          # MCP 服务器(Demo Server)
│   └── client.ts         # MCP 客户端(example-client)
└── README.md             # 运行说明

其中 index.ts 扮演服务器角色,暴露一个 add 工具、一个 file 动态资源模板和一个 review-code 提示;client.ts 作为客户端,通过子进程启动服务器并逐一调用这些能力。

二、运行前的环境准备

关联文档开头提到:推荐安装 uv,但并非必需。需要说明的是,uv 是 Python 生态的包管理器,与本 TypeScript 示例无关——只有当你在 python solution 中运行 client.py 时才需要它。本示例唯一硬性要求是 Node.js 运行时,建议使用能运行 @modelcontextprotocol/sdk 的新版本。

整个流程只需要三条命令:

  1. npm install —— 安装依赖;
  2. npm run build —— 编译 TypeScript 并启动服务器;
  3. npm run client —— 编译并运行客户端。

三、第一步:安装依赖

在 solution/typescript 目录下执行:

npm install

根据 package.json 的依赖声明,此命令会安装以下关键包:

依赖 版本要求 作用
@modelcontextprotocol/sdk >=1.26.0 MCP 官方 TypeScript SDK,提供服务器/客户端核心类
zod ^3.24.2 参数 schema 校验,用于声明工具与提示的参数结构
uuid ^14.0.0 生成唯一标识(SDK 内部使用)
typescript ^5.3.3(dev) 编译 .ts 源码
@types/node ^20.11.24(dev) Node.js 类型定义

注意到 package.json 还声明了 "type": "module",即采用 ES Module 规范,因此源码中使用 import 语法,并且 SDK 导入路径带有 .js 后缀(如 @modelcontextprotocol/sdk/client/index.js),这是 Node ESM 解析规则的要求。

四、第二步:编译并启动服务器

npm run build

该命令由 package.json 中的脚本定义展开为:

tsc && node ./build/index.js

也就是说它做了两件事:先用 tsc 将 src/ 下的 TypeScript 编译到 build/ 目录,再直接运行编译产物启动服务器。编译行为由 tsconfig.json 控制:

{
    "compilerOptions": {
      "target": "ES2022",
      "module": "Node16",
      "moduleResolution": "Node16",
      "outDir": "./build",
      "rootDir": "./src",
      "strict": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true,
      "noImplicitAny": false
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules"]
  }

关键选项包括:outDir: ./build 与 rootDir: ./src 定义了编译输入输出目录;strict: true 开启严格类型检查;module: Node16 与 moduleResolution: Node16 配合 "type": "module" 使用 ESM 解析策略。

那么服务器启动后提供了什么能力?看 src/index.ts 的核心实现:

import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Create an MCP server
const server = new McpServer({
  name: "Demo",
  version: "1.0.0"
});

// Add an addition tool
server.tool("add",
  { a: z.number(), b: z.number() },
  async ({ a, b }) => ({
    content: [{ type: "text", text: String(a + b) }]
  })
);

server.tool() 的第一个参数是工具名 add,第二个参数用 zod 声明了 a、b 两个数字参数,第三个参数是执行函数。注意返回内容采用 MCP 标准内容结构:content: [{ type: "text", text: ... }]。

接着是动态资源模板与提示:

// Add a dynamic greeting resource
server.resource(
  "file",
  new ResourceTemplate("file://{path}", { list: undefined }),
  async (uri, { path }) => ({
    contents: [{
      uri: uri.href,
      text: `File, ${path}!`
    }]
  })
);

server.prompt(
  "review-code",
  { code: z.string() },
  ({ code }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `Please review this code:\n\n${code}`
      }
    }]
  })
);

// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
  • server.resource("file", new ResourceTemplate("file://{path}", ...)) 声明了一个 URI 模板:客户端传入 file://example.txt 时,example.txt 会被映射到 {path} 占位符;
  • server.prompt("review-code", { code: z.string() }, ...) 声明了一个名为 review-code 的提示模板,接受一个 code 字符串参数;
  • 最后通过 StdioServerTransport 将服务器挂到标准输入/输出上——这正是 stdio 传输协议的核心:从 stdin 接收 JSON-RPC 消息、向 stdout 返回响应。

五、第三步:运行客户端

npm run client

此命令展开为:

tsc && node ./build/client.js

由于 npm run build 可能尚未执行(或 build 目录需要更新),client 脚本先编译再运行,确保 build/client.js 存在。客户端核心逻辑在 src/client.ts:

5.1 实例化传输与客户端

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "node",
  args: ["./build/index.js"]
});

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0"
  }
);

await client.connect(transport);

这里有两个值得注意的细节:

  • StdioClientTransport 接收 command 与 args,指示客户端如何启动服务器——本示例中客户端会以 node ./build/index.js 的方式自行拉起服务器子进程,并与之建立 stdio 双向通道。这与 02-client 课程中 command: "node", args: ["server.js"] 的教学示例一致,只是把服务器指向了本 solution 的编译产物;
  • new Client({ name, version }) 按 MCP 协议要求声明客户端自身标识;await client.connect(transport) 完成协议初始化握手。

5.2 发现并调用提示(prompt)

// List prompts
const prompts = await client.listPrompts();

// Get a prompt
const prompt = await client.getPrompt({
  name: "review-code",
  arguments: {
    code: "console.log(\"hello\");"
  }
});

console.log("Prompt: ", prompt.messages[0].content);

listPrompts() 用于发现服务器注册的提示,getPrompt() 则按名称并携带参数获取提示内容。此处 name 必须与服务器端 server.prompt("review-code", ...) 声明的名称完全一致,code 参数与服务器端 { code: z.string() } 的 schema 匹配。

5.3 发现资源模板

// List resources
const resources = await client.listResources();
for(let resource in resources.resources) {
  console.log("Resource: ", resource);
}

// List resource templates
const templates = await client.listResourceTemplates();
for(let template of templates.resourceTemplates) {
  console.log("Resource template: ", template.name);
}

客户端分别调用 listResources() 与 listResourceTemplates()。由于服务器端 ResourceTemplate("file://{path}", { list: undefined }) 显式关闭了静态资源列表(list: undefined),因此 resources.resources 为空,而 resourceTemplates 中会列出 file 模板。

5.4 调用工具

// Call a tool
const result = await client.callTool({
  name: "add",
  arguments: {
    a: 1,
    b: 8
  }
});

console.log("Tool result: ", result);

callTool() 按工具名 add 与参数 { a: 1, b: 8 } 调用服务器工具,服务器执行 1 + 8 并返回内容数组 [{ type: "text", text: "9" }]。

六、预期输出解读

运行 npm run client 后,关联文档给出的标准输出如下:

Prompt:  {
  type: 'text',
  text: 'Please review this code:\n\nconsole.log("hello");'
}
Resource template:  file
Tool result:  { content: [ { type: 'text', text: '9' } ] }

逐行解读:

  1. Prompt: —— 客户端 getPrompt() 拿到 review-code 提示后打印 prompt.messages[0].content。内容类型为 text,文本把传入的 console.log("hello"); 嵌入到 Please review this code: 模板中,验证了提示模板的参数注入逻辑;
  2. Resource template: file —— 来自 listResourceTemplates() 的遍历输出,证明客户端成功发现服务器端注册的动态资源模板;
  3. Tool result: { content: [ { type: 'text', text: '9' } ] } —— callTool() 调用 add 工具的结果,1 + 8 = 9,以 MCP 标准 content 数组返回。

这三行输出恰好覆盖了 MCP 服务器的三大能力面:提示、资源、工具,是验证"客户端能否正确发现并调用服务器能力"的最直接手段。

七、调试与进阶:利用 Inspector 脚本

package.json 中还预置了一个调试脚本:

"inspector": "npx @modelcontextprotocol/inspector node build/index.js"

运行 npm run inspector 会启动官方 MCP Inspector 图形界面,将 node build/index.js 作为服务器进程加载。相比手写客户端,Inspector 提供了可视化的连接测试、请求构造与响应查看能力,可以作为客户端开发过程中的交叉验证工具——手写客户端与 Inspector 互为补充,正如 02-client 课程 Key Takeaways 所强调的:客户端既能发现也能调用服务器能力,是除 Inspector 之外测试服务器能力的有效途径。

八、扩展:完整的参考客户端实现

如果你希望看到更完整的 TypeScript 客户端——包含启动横幅、服务器发现、加减乘除与 help 工具调用、资源与提示的容错读取、结果文本提取函数——可以研读 client_example_typescript.ts。其中值得借鉴的实现要点包括:

  • extractTextResult() 工具函数:从 content 数组中按 type === "text" 提取纯文本,失败时回退为 JSON.stringify 或 toString(),使各类工具返回结果都能以可读形式打印;
  • 针对资源/提示列表使用 try/catch 容错:当服务器不提供某类能力(如 listResources 返回空或抛错)时,客户端不崩溃而是打印提示继续执行;
  • 对 add、subtract、multiply、divide、help 等工具逐一调用并格式化输出,演示了一个真实客户端如何编排多个工具调用。

九、关键要点

回顾整个 TypeScript MCP 客户端示例,可沉淀以下经验:

  1. 三步运行模型:npm install → npm run build(编译并启动服务器)→ npm run client(编译并运行客户端),顺序不可颠倒,因为客户端需要通过 stdio 子进程拉起 build/index.js 服务器;
  2. stdio 传输的本质:StdioClientTransport 通过 command + args 定义服务器启动方式,双方在标准输入输出上交换 JSON-RPC 消息,适合本机运行场景;HTTP/SSE 等远程传输见后续章节;
  3. 名称即契约:客户端调用 getPrompt、callTool、readResource 时使用的名称、参数结构必须与服务器端 server.prompt、server.tool、server.resource 的声明严格对齐;
  4. 三大能力对应三个客户端 API:listPrompts/getPrompt 对应提示,listResources/listResourceTemplates 对应资源,callTool/listTools 对应工具,是构建任何 MCP 客户端的基本动作集。

下一步,你可以基于此客户端接入大型语言模型,构建具备自主调用 MCP 工具能力的智能体客户端,参见下一章节 03-llm-client(原文档位于 translations/ar/03-GettingStarted/02-client/solution/typescript/README.md)。

登录后查看全文
mcp-for-beginners