MCP 协议开发者实战指南:从零构建 AI 工具服务器

深入解析 Model Context Protocol (MCP) 协议原理与实战,手把手教你用 TypeScript 构建 MCP Server,对比 OpenAI Function Calling,掌握 AI 工具集成新标准。

开发者效率 2026-05-28 15 分钟

在 2026 年的 AI 开发生态中,MCP(Model Context Protocol,模型上下文协议)已经成为连接大语言模型与外部工具的事实标准。Anthropic 在 2024 年底开源该协议后,Claude Desktop、Cursor、Windsurf、VS Code 等主流开发工具纷纷接入,GitHub 上的 MCP Server 数量已突破 10,000 个。如果你还在为 AI 应用的工具集成方案纠结,这篇文章将从协议原理到实战代码,帮你彻底搞懂 MCP。

🔧 一、MCP 协议核心原理

1.1 为什么需要 MCP?

在 MCP 出现之前,每个 AI 应用都需要为每个外部工具编写定制化的集成代码。一个 AI 助手要连接 GitHub、Slack、数据库、文件系统,就需要维护 N × M 个集成适配器(N 个模型 × M 个工具)。MCP 的核心价值在于将这个 N × M 的问题简化为 N + M:工具提供方只需实现一个 MCP Server,模型提供方只需实现一个 MCP Client。

MCP 基于 JSON-RPC 2.0 协议,定义了三种核心能力:

  • Tools(工具):模型可以调用的函数,比如查询数据库、发送邮件
  • Resources(资源):模型可以读取的数据源,比如文件内容、API 响应
  • Prompts(提示模板):预定义的交互模板,帮助模型更好地使用工具

💡 **提示:**MCP 不是又一个 Function Calling 方案。它是一个完整的协议层,解决了工具发现、能力协商、权限控制等 Function Calling 没有覆盖的问题。

1.2 传输层:三种通信方式

MCP 支持三种传输方式,开发者可以根据场景选择:

传输方式 协议 适用场景 延迟 复杂度
stdio 标准输入输出 本地 CLI 工具、桌面应用 极低
HTTP + SSE HTTP Server-Sent Events 远程服务、Web 应用
Streamable HTTP HTTP + 双向流 高并发远程服务

stdio 是最简单的传输方式——MCP Client 启动 Server 进程,通过 stdin/stdout 交换 JSON-RPC 消息:

// Client → Server(通过 stdin)
{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}

// Server → Client(通过 stdout)
{"jsonrpc": "2.0", "id": 1, "result": {"tools": [{"name": "query_db", "description": "查询数据库"}]}}

HTTP + SSE 方式则适合远程部署的 MCP Server,Client 通过 HTTP POST 发送请求,Server 通过 SSE 推送响应。

⚠️ **警告:**stdio 传输方式下,Server 的 stdout 只能输出 JSON-RPC 消息。如果你的代码中有 console.log 调试输出,会破坏协议通信。务必使用 stderr 输出日志。

1.3 生命周期:连接到断开

一次完整的 MCP 会话经历以下阶段:

Client                          Server
  │                               │
  │── initialize ───────────────→│  1. 能力协商
  │←── initialize result ────────│
  │── initialized ──────────────→│  2. 确认连接
  │                               │
  │── tools/list ───────────────→│  3. 发现工具
  │←── tools result ─────────────│
  │                               │
  │── tools/call ───────────────→│  4. 调用工具
  │←── call result ──────────────│
  │                               │
  │── notifications/cancelled ──→│  5. 断开连接

initialize 阶段,双方交换支持的协议版本和能力(如是否支持 Resources、Prompts)。这个机制保证了向后兼容性——低版本 Client 可以安全连接高版本 Server。

🚀 二、实战:用 TypeScript 构建 MCP Server

2.1 项目初始化

# 初始化项目
mkdir mcp-server-demo && cd mcp-server-demo
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init

项目结构:

mcp-server-demo/
├── src/
│   └── index.ts        # MCP Server 入口
├── tsconfig.json
└── package.json

📌 记住:@modelcontextprotocol/sdk 是官方 TypeScript SDK,封装了协议细节。生产环境务必使用 SDK 而非手动实现 JSON-RPC。

2.2 实现一个 JSON 格式化工具

我们来构建一个实用的 MCP Server,暴露 JSON 格式化和校验工具:

// src/index.ts — 一个提供 JSON 工具的 MCP Server
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "json-tools",
  version: "1.0.0",
});

// 工具1:JSON 格式化
server.tool(
  "format_json",
  "将 JSON 字符串格式化为可读的缩进格式",
  {
    input: z.string().describe("要格式化的 JSON 字符串"),
    indent: z.number().default(2).describe("缩进空格数,默认2"),
  },
  async ({ input, indent }) => {
    try {
      const parsed = JSON.parse(input);
      const formatted = JSON.stringify(parsed, null, indent);
      return {
        content: [{ type: "text", text: formatted }],
      };
    } catch (err) {
      return {
        content: [{ type: "text", text: `JSON 解析错误: ${(err as Error).message}` }],
        isError: true,
      };
    }
  }
);

// 工具2:JSON Schema 校验
server.tool(
  "validate_json",
  "校验 JSON 是否符合指定的 Schema",
  {
    data: z.string().describe("JSON 数据"),
    schema: z.string().describe("JSON Schema 定义"),
  },
  async ({ data, schema }) => {
    try {
      JSON.parse(data);
      JSON.parse(schema);
      // 简化示例:实际项目应使用 ajv 等校验库
      return {
        content: [{ type: "text", text: "✅ JSON 数据格式正确" }],
      };
    } catch (err) {
      return {
        content: [{ type: "text", text: `校验失败: ${(err as Error).message}` }],
        isError: true,
      };
    }
  }
);

// 工具3:JSON 转 TypeScript 类型
server.tool(
  "json_to_types",
  "将 JSON 数据转换为 TypeScript 类型定义",
  {
    input: z.string().describe("JSON 数据"),
    typeName: z.string().default("GeneratedType").describe("类型名称"),
  },
  async ({ input, typeName }) => {
    try {
      const parsed = JSON.parse(input);
      const typeStr = generateType(parsed, typeName);
      return {
        content: [{ type: "text", text: typeStr }],
      };
    } catch (err) {
      return {
        content: [{ type: "text", text: `错误: ${(err as Error).message}` }],
        isError: true,
      };
    }
  }
);

function generateType(obj: any, name: string): string {
  if (typeof obj !== "object" || obj === null) {
    return `type ${name} = ${typeof obj};`;
  }
  const entries = Object.entries(obj)
    .map(([key, value]) => {
      if (typeof value === "object" && value !== null && !Array.isArray(value)) {
        return `  ${key}: ${generateInlineType(value)};`;
      }
      if (Array.isArray(value)) {
        const elemType = value.length > 0 ? typeof value[0] : "unknown";
        return `  ${key}: ${elemType}[];`;
      }
      return `  ${key}: ${typeof value};`;
    })
    .join("\n");
  return `interface ${name} {\n${entries}\n}`;
}

function generateInlineType(obj: any): string {
  const entries = Object.entries(obj)
    .map(([key, value]) => `${key}: ${typeof value}`)
    .join("; ");
  return `{ ${entries} }`;
}

// 启动 Server(stdio 传输)
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("JSON Tools MCP Server 已启动");
}

main().catch(console.error);

编译并运行:

npx tsc
node dist/index.js

2.3 在 Claude Desktop 中配置

编辑 Claude Desktop 配置文件 claude_desktop_config.json

{
  "mcpServers": {
    "json-tools": {
      "command": "node",
      "args": ["/path/to/mcp-server-demo/dist/index.js"]
    }
  }
}

重启 Claude Desktop 后,模型会自动发现这三个工具。当用户说「帮我格式化这段 JSON」时,Claude 会自动调用 format_json 工具。

💡 **提示:**配置文件的路径因操作系统而异。macOS 为 ~/Library/Application Support/Claude/claude_desktop_config.json,Windows 为 %APPDATA%\Claude\claude_desktop_config.json

📊 三、MCP vs Function Calling:深度对比

很多开发者会问:「我已经在用 OpenAI Function Calling 了,为什么还需要 MCP?」这是一个好问题。两者确实解决类似的问题,但设计哲学和适用范围完全不同。

对比维度 OpenAI Function Calling MCP
设计目标 模型调用单个函数 模型连接完整工具生态
工具发现 静态定义在请求中 动态发现(tools/list)
传输方式 HTTP(封装在 API 调用中) stdio / HTTP+SSE / Streamable HTTP
跨模型兼容 仅 OpenAI 兼容模型 模型无关的开放协议
资源读取 不支持 原生支持(Resources)
提示模板 不支持 原生支持(Prompts)
工具生态 各自为战 统一标准,可复用
安全控制 请求级别 协议级别(权限协商)
本地工具 需要自建代理 原生支持 stdio

什么时候用 Function Calling?

  • ✅ 你的应用只对接一个模型(如 OpenAI)
  • ✅ 工具数量少(< 5 个),不需要动态发现
  • ✅ 追求最简集成,不想引入额外协议层
  • ✅ 纯远程调用,不需要本地工具支持

什么时候用 MCP?

  • ✅ 你开发的工具要被多个 AI 应用使用
  • ✅ 需要支持本地 CLI 工具和文件系统访问
  • ✅ 工具数量多,需要动态发现和能力协商
  • ✅ 需要统一的安全控制和权限管理

⚡ **关键结论:**Function Calling 是「模型调函数」,MCP 是「模型连生态」。如果你在开发工具产品,选 MCP;如果你在开发 AI 应用,两者都可以用,但 MCP 的生态优势会越来越明显。

🛡️ 四、生产环境避坑指南

4.1 坑点一:错误处理不当

很多开发者在 MCP Server 中直接 throw Error,导致客户端收到的是协议层错误而非工具层错误。正确做法是通过 isError 字段返回业务错误:

// ❌ 错误写法 — 抛出异常,客户端收到协议错误
server.tool("bad_tool", {}, async () => {
  throw new Error("数据格式错误");
});

// ✅ 正确写法 — 通过 isError 返回业务错误
server.tool("good_tool", {}, async () => {
  return {
    content: [{ type: "text", text: "数据格式错误:缺少必填字段 name" }],
    isError: true,
  };
});

4.2 坑点二:stdio 输出污染

stdio 模式下,stdout 是协议通道。任何意外输出都会导致 JSON-RPC 解析失败:

// ❌ 错误写法 — console.log 输出到 stdout,破坏协议
console.log("正在处理请求...");

// ✅ 正确写法 — 使用 stderr 输出日志
console.error("正在处理请求...");
process.stderr.write("调试信息\n");

建议在项目入口设置全局日志重定向:

// 重定向 console.log 到 stderr(仅 stdio 模式需要)
const originalLog = console.log;
console.log = (...args) => console.error("[LOG]", ...args);

4.3 坑点三:Zod Schema 定义不规范

MCP SDK 使用 Zod 进行参数校验。Schema 定义直接影响模型理解工具的方式:

// ❌ 不清晰的 Schema — 模型不知道该传什么
server.tool("query", {
  q: z.string(),
}, handler);

// ✅ 清晰的 Schema — description 会传递给模型
server.tool("search_users", {
  keyword: z.string().describe("搜索关键词,支持用户名或邮箱"),
  limit: z.number().min(1).max(100).default(10).describe("返回结果数量"),
  sort: z.enum(["name", "created_at"]).default("created_at").describe("排序字段"),
}, handler);

⚠️ 警告:description 字段不是给人看的,是给模型看的。写得越清晰,模型调用工具的准确率越高。这是 MCP 开发中最容易被忽视但影响最大的细节。

4.4 坑点四:超时和长时任务

MCP 工具调用默认有超时限制。如果工具执行时间超过 30 秒,客户端可能会断开连接。对于长时间任务(如文件处理、API 调用),应该使用进度通知:

server.tool("process_large_file", {
  filePath: z.string().describe("文件路径"),
}, async ({ filePath }, extra) => {
  const total = 100;
  for (let i = 0; i < total; i++) {
    // 发送进度通知
    await extra.sendNotification({
      method: "notifications/progress",
      params: { progress: i + 1, total },
    });
    // 模拟处理
    await new Promise(r => setTimeout(r, 100));
  }
  return {
    content: [{ type: "text", text: `处理完成: ${filePath}` }],
  };
});

🎯 五、高级模式:Resource 和 Prompt

5.1 Resource:暴露数据源

Resource 让 MCP Server 可以暴露可读取的数据。模型可以通过 URI 访问这些数据:

// 暴露一个配置文件作为 Resource
server.resource(
  "app-config",
  "config://app/settings",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      text: JSON.stringify({
        appName: "MyApp",
        version: "2.0.0",
        features: { darkMode: true, i18n: true },
      }, null, 2),
    }],
  })
);

5.2 Prompt:预定义交互模板

Prompt 模板帮助模型更高效地使用工具:

server.prompt(
  "analyze_json",
  "分析 JSON 数据的结构和内容",
  { data: z.string().describe("JSON 数据") },
  ({ data }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: `请分析以下 JSON 数据:\n1. 数据结构概览\n2. 数据类型统计\n3. 潜在问题\n\n${data}`,
      },
    }],
  })
);

💡 **提示:**Resource 适合暴露相对静态的数据(配置、文档),Prompt 适合定义标准化的工作流程。三者配合使用才能发挥 MCP 的全部威力。

📋 总结与工具推荐

MCP 协议正在成为 AI 工具集成的通用语言。对于开发者而言,现在投入学习 MCP 正是最好的时机——协议已足够成熟,生态正在爆发增长。

核心建议:

  • ✅ 如果你在开发开发者工具,优先考虑提供 MCP Server
  • ✅ 使用官方 SDK(TypeScript / Python),不要手动实现协议
  • ✅ 重视 Zod Schema 的 description,它直接决定模型调用质量
  • ✅ 先用 stdio 传输开发和测试,生产环境再切换 HTTP
  • ❌ 不要在 stdout 中输出非协议内容
  • ❌ 不要忽略错误处理和超时控制

推荐工具与资源:

工具 / 资源 用途 链接
MCP Inspector 调试和测试 MCP Server github.com/modelcontextprotocol/inspector
MCP TypeScript SDK 官方 TypeScript 实现 npm: @modelcontextprotocol/sdk
MCP Python SDK 官方 Python 实现 pip: mcp
Smithery MCP Server 市场 smithery.ai
MCP Hub MCP Server 目录 mcphub.io

本文涉及的工具如 JSON 格式化、JSON 校验等,均可在 jsjson.com 在线免费使用,所有数据本地处理,不上传服务器。

📚 相关文章