MCP (Model Context Protocol) 原理与实战

作者:互联网

2026-04-14

⼤语⾔模型脚本

MCP (Model Context Protocol) 原理与实战

目录

  1. MCP 是什么?
  2. 核心原理:stdio 管道通信
  3. JSON-RPC 协议
  4. MCP Server 生命周期
  5. 实战:剖析 next-ai-drawio MCP Server
  6. 动手开发一个简单的 MCP Server
  7. 常见问题与最佳实践

1. MCP 是什么?

1.1 问题背景

在 MCP 出现之前,让 AI 助手(如 Claude、ChatGPT)调用外部工具面临很多问题:

┌─────────────────────────────────────────────────────────────────────────┐
│                     传统工具集成的问题                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   每个工具都需要单独集成                                              │
│   API 格式不统一,适配成本高                                          │
│   安全性问题(API Key 如何传递)                                       │
│   无法跨平台使用(Claude Desktop、Cursor、VS Code 各自有各自的方式)   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

1.2 MCP 的解决方案

MCP (Model Context Protocol) 是 Anthropic 推出的开放协议,标准化了 AI 应用与外部工具的交互方式。

┌─────────────────────────────────────────────────────────────────────────┐
│                        MCP 架构                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│                    ┌─────────────────────────────┐                     │
│                    │      MCP Host (客户端)       │                     │
│                    │  Claude Desktop / Cursor    │                     │
│                    │       / VS Code             │                     │
│                    └──────────────┬──────────────┘                     │
│                                   │                                     │
│                                   │ MCP 协议 (统一标准)                 │
│                                   │                                     │
│          ┌────────────────────────┼────────────────────────┐           │
│          │                        │                        │           │
│          ▼                        ▼                        ▼           │
│   ┌────────────┐          ┌────────────┐          ┌────────────┐      │
│   │ MCP Server │          │ MCP Server │          │ MCP Server │      │
│   │  (文件系统) │          │  (数据库)   │          │ (绘图工具)  │      │
│   └────────────┘          └────────────┘          └────────────┘      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

1.3 MCP 的核心概念

概念说明类比
MCP Host运行 AI 应用的客户端浏览器
MCP Server提供工具能力的服务端Web Server
MCP ClientHost 内部的客户端逻辑HTTP Client
ToolAI 可调用的函数API Endpoint
ResourceAI 可访问的数据文件/数据库
Prompt预定义的提示词模板模板文件

2. 核心原理:stdio 管道通信

2.1 什么是 stdio?

每个进程都有三个标准流:

┌─────────────────────────────────────────────────────────────────────────┐
│                         进程的标准流                                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ┌─────────────────────────────────────────────────────┐              │
│   │                    进程 (Process)                     │              │
│   │                                                      │              │
│   │   stdin  (fd=0) ─────►  标准输入                     │              │
│   │                          (从外部读取数据)             │              │
│   │                                                      │              │
│   │   stdout (fd=1) ─────►  标准输出                     │              │
│   │                          (向外输出数据)               │              │
│   │                                                      │              │
│   │   stderr (fd=2) ─────►  标准错误                     │              │
│   │                          (输出错误信息)               │              │
│   │                                                      │              │
│   └─────────────────────────────────────────────────────┘              │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

2.2 父子进程与管道

MCP 使用 父子进程 + 管道 的方式进行通信:

┌─────────────────────────────────────────────────────────────────────────┐
│                     父子进程管道通信                                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   父进程 (MCP Host)                    子进程 (MCP Server)              │
│   ┌─────────────┐                     ┌─────────────┐                  │
│   │             │                     │             │                  │
│   │  写入数据 ──┼───[管道1]───► stdin │  读取数据   │                  │
│   │             │                     │             │                  │
│   │  读取数据 ◄─┼──[管道2]──── stdout │  写入数据   │                  │
│   │             │                     │             │                  │
│   └─────────────┘                     └─────────────┘                  │
│                                                                         │
│   关键点:                                                              │
│   • 管道是单向的,需要两个管道组成双向通信                              │
│   • 子进程只能操作自己的 stdin/stdout,无法访问父进程的流               │
│   • 父进程关闭时,子进程的 stdin 会收到 EOF                            │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

2.3 为什么选择 stdio?

优势说明
简单不需要网络端口,不需要认证
安全只有父子进程能通信,外部无法访问
通用所有编程语言都支持 stdin/stdout
自动生命周期父进程关闭,子进程自动收到信号
跨平台Windows、Linux、macOS 都支持

3. JSON-RPC 协议

MCP 在 stdio 之上使用 JSON-RPC 2.0 协议进行消息传递。

3.1 消息格式

// 请求 (Request)
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "start_session",
    "arguments": {}
  }
}

// 响应 (Response)
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Session started! Session ID: mcp-abc123"
      }
    ]
  }
}

// 错误 (Error)
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Invalid params"
  }
}

3.2 MCP 协议层级

┌─────────────────────────────────────────────────────────────────────────┐
│                        MCP 协议栈                                        │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │                      应用层                                      │   │
│   │   Tools / Resources / Prompts                                   │   │
│   │   (工具调用、资源访问、提示词模板)                                 │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                   │                                     │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │                     MCP 协议层                                   │   │
│   │   initialize / initialized                                      │   │
│   │   tools/list / tools/call                                       │   │
│   │   resources/list / resources/read                               │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                   │                                     │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │                    JSON-RPC 2.0                                  │   │
│   │   请求/响应/通知 的序列化格式                                     │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                   │                                     │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │                     stdio 传输层                                 │   │
│   │   进程间通信的基础设施                                           │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

4. MCP Server 生命周期

4.1 完整生命周期

┌─────────────────────────────────────────────────────────────────────────┐
│                     MCP Server 生命周期                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   1. 启动阶段                                                           │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │   MCP Host 读取配置                                              │   │
│   │        ↓                                                         │   │
│   │   spawn 子进程 (启动 MCP Server)                                 │   │
│   │        ↓                                                         │   │
│   │   建立 stdio 管道连接                                            │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│   2. 初始化阶段                                                         │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │   Host 发送: initialize 请求                                     │   │
│   │        ↓                                                         │   │
│   │   Server 返回: capabilities (支持的功能)                         │   │
│   │        ↓                                                         │   │
│   │   Host 发送: initialized 通知                                    │   │
│   │        ↓                                                         │   │
│   │   连接建立完成,可以开始工作                                      │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│   3. 运行阶段                                                           │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │   AI 决定调用工具                                                │   │
│   │        ↓                                                         │   │
│   │   Host 发送: tools/call 请求                                     │   │
│   │        ↓                                                         │   │
│   │   Server 执行工具,返回结果                                      │   │
│   │        ↓                                                         │   │
│   │   结果返回给 AI,AI 继续处理                                     │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│   4. 关闭阶段                                                           │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │   用户关闭 MCP Host                                              │   │
│   │        ↓                                                         │   │
│   │   Host 关闭 stdin 管道                                           │   │
│   │        ↓                                                         │   │
│   │   Server 检测到 stdin close 事件                                 │   │
│   │        ↓                                                         │   │
│   │   Server 执行清理,退出进程                                      │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

4.2 关闭机制代码

// MCP Server 的关闭处理
let isShuttingDown = false

function gracefulShutdown(reason: string) {
  if (isShuttingDown) return
  isShuttingDown = true
  console.log(`Shutting down: ${reason}`)
  // 清理资源...
  process.exit(0)
}

//  stdin 关闭 (主要方式)
process.stdin.on("close", () => gracefulShutdown("stdin closed"))
process.stdin.on("end", () => gracefulShutdown("stdin ended"))

// 信号 (备用方式)
process.on("SIGINT", () => gracefulShutdown("SIGINT"))
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"))

//  stdout 错误 (管道断裂)
process.stdout.on("error", (err) => {
  if (err.code === "EPIPE") {
    gracefulShutdown("stdout error")
  }
})

5. 实战:剖析 next-ai-drawio MCP Server

5.1 项目结构

packages/mcp-server/
├── src/
│   ├── index.ts           # MCP Server 主入口
│   ├── http-server.ts     # 内嵌 HTTP 服务器
│   ├── diagram-operations.ts  # 图表操作逻辑
│   ├── xml-validation.ts  # XML 验证
│   ├── history.ts         # 历史记录管理
│   └── logger.ts          # 日志工具
├── package.json
└── tsconfig.json

5.2 核心代码分析

入口文件 (index.ts)
#!/usr/bin/env node

// 1. 设置 DOM polyfill (Node.js 环境需要)
import { DOMParser } from "linkedom"
;(globalThis as any).DOMParser = DOMParser

// 2. 导入 MCP SDK
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { z } from "zod"

// 3. 创建 MCP Server 实例
const server = new McpServer({
  name: "next-ai-drawio",
  version: "0.2.0",
})

// 4. 注册工具
server.registerTool(
  "start_session",
  {
    description: "Start a new diagram session and open the browser...",
    inputSchema: {},  // 无参数
  },
  async () => {
    // 启动 HTTP 服务器
    const port = await startHttpServer(6002)
    // 创建 session
    const sessionId = `mcp-${Date.now().toString(36)}`
    // 打开浏览器
    await open(`:${port}?mcp=${sessionId}`)
    
    return {
      content: [{ type: "text", text: `Session started!` }]
    }
  }
)

// 5. 注册更多工具...
server.registerTool("create_new_diagram", { ... }, async ({ xml }) => { ... })
server.registerTool("edit_diagram", { ... }, async ({ operations }) => { ... })
server.registerTool("get_diagram", { ... }, async () => { ... })

// 6. 启动服务
async function main() {
  const transport = new StdioServerTransport()
  await server.connect(transport)
}

main().catch(console.error)

5.3 双向通信架构

next-ai-drawio MCP Server 有一个独特的设计:同时支持 stdio 和 HTTP 通信

┌─────────────────────────────────────────────────────────────────────────┐
│                  next-ai-drawio MCP Server 架构                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   Claude Desktop / Cursor                                               │
│        │                                                                │
│        │ stdio (JSON-RPC)                                               │
│        ▼                                                                │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │                    MCP Server (Node.js)                          │   │
│   │                                                                 │   │
│   │   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐            │   │
│   │   │ MCP Handler │  │ HTTP Server │  │ State Store │            │   │
│   │   │ (stdio)     │  │ (port 6002) │  │ (内存 Map)   │            │   │
│   │   └─────────────┘  └─────────────┘  └─────────────┘            │   │
│   │          │               │                 ▲                     │   │
│   │          │               │                 │                     │   │
│   │          │               ▼                 │                     │   │
│   │          │        ┌─────────────┐          │                     │   │
│   │          │        │ 浏览器页面   │──────────┘                     │   │
│   │          │        │ (draw.io)   │   HTTP 轮询                    │   │
│   │          │        └─────────────┘                                │   │
│   │          │                                                      │   │
│   └──────────┼──────────────────────────────────────────────────────┘   │
│              │                                                          │
│              ▼                                                          │
│        工具调用结果                                                      │
│        (返回给 AI)                                                      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

5.4 为什么需要 HTTP 服务器?

因为 MCP 通过 stdio 通信,但浏览器无法直接连接 stdio。解决方案:

┌─────────────────────────────────────────────────────────────────────────┐
│                                                                         │
│   问题: 浏览器无法连接 MCP Server 的 stdio                               │
│                                                                         │
│   解决方案: MCP Server 内嵌一个 HTTP 服务器                              │
│                                                                         │
│   ┌───────────┐      stdio       ┌───────────┐                         │
│   │ Claude    │◄────────────────►│ MCP       │                         │
│   │ Desktop   │   JSON-RPC       │ Server    │                         │
│   └───────────┘                  │           │                         │
│                                  │   ┌───────┤                         │
│                                  │   │ HTTP  │◄──── HTTP ────┐         │
│                                  │   │ Server│     轮询       │         │
│                                  │   └───────┘                │         │
│                                  └───────────┘         ┌──────┴──────┐  │
│                                                         │   浏览器     │  │
│                                                         │  (draw.io)  │  │
│                                                         └─────────────┘  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

6. 动手开发一个简单的 MCP Server

6.1 项目初始化

# 创建项目
mkdir my-mcp-server
cd my-mcp-server
npm init -y

# 安装依赖
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx

6.2 编写代码

// src/index.ts
#!/usr/bin/env node

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

// 创建 Server
const server = new McpServer({
  name: "my-mcp-server",
  version: "1.0.0",
})

// 定义工具
server.registerTool(
  "hello",
  {
    description: "向用户问好",
    inputSchema: {
      name: z.string().describe("用户名称"),
    },
  },
  async ({ name }) => {
    return {
      content: [
        {
          type: "text",
          text: `你好,${name}!很高兴认识你!`,
        },
      ],
    }
  }
)

// 定义资源
server.resource(
  "config",
  "config://app",
  async (uri) => {
    return {
      contents: [
        {
          uri: uri.href,
          text: JSON.stringify({ version: "1.0.0", name: "my-app" }),
        },
      ],
    }
  }
)

// 定义提示词
server.prompt(
  "greeting",
  "问候提示词",
  { name: z.string().describe("用户名称") },
  async ({ name }) => {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `请用热情的语气向 ${name} 问好`,
          },
        },
      ],
    }
  }
)

// 启动服务
const transport = new StdioServerTransport()
await server.connect(transport)

6.3 配置 package.json

{
  "name": "my-mcp-server",
  "version": "1.0.0",
  "type": "module",
  "main": "dist/index.js",
  "bin": {
    "my-mcp-server": "./dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

6.4 在 Claude Desktop 中配置

// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
// 或 %APPDATA%Claudeclaude_desktop_config.json (Windows)

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

7. 常见问题与最佳实践

7.1 常见问题

Q1: 多个 AI 客户端同时启动 MCP Server 会怎样?
┌─────────────────────────────────────────────────────────────────────────┐
│                     多实例场景                                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   Codex 启动 MCP Server → localhost:6002                               │
│   Claude Code 启动 MCP Server6002 被占用 → 自动切换到 6003           │
│                                                                         │
│    不会失败(端口自动递增)                                            │
│   ️ 但状态不共享(每个实例独立)                                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
Q2: 关闭客户端后 MCP Server 会关闭吗?
┌─────────────────────────────────────────────────────────────────────────┐
│                                                                         │
│   ️ 不会自动关闭!需要开发者自己处理!                                  │
│                                                                         │
│   很多人的误解:                                                        │
│    "客户端关闭后,MCP Server 进程会自动退出"                          │
│                                                                         │
│   事实是:                                                              │
│   1. 客户端关闭 → stdin 管道关闭 → 子进程收到 EOF                      │
│   2. 但 Node.js 进程默认不会因为 stdin 关闭而退出                      │
│   3. 如果有 HTTP Server 或定时器,进程会继续运行                       │
│   4. 必须 stdin close 事件,手动调用 process.exit()                │
│                                                                         │
│   如果不处理,进程会变成孤儿进程,继续占用端口和内存!                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

必须的关闭处理代码:

// MCP Server 必须自己处理关闭逻辑

// 1.  stdin 关闭(主要方式)
process.stdin.on("close", () => {
  // 清理资源...
  process.exit(0)  // 必须手动退出!
})

// 2. 信号(备用方式)
process.on("SIGINT", () => process.exit(0))
process.on("SIGTERM", () => process.exit(0))

// 3.  stdout 错误(管道断裂)
process.stdout.on("error", (err) => {
  if (err.code === "EPIPE") {
    process.exit(0)
  }
})

7.2 最佳实践

实践说明
优雅关闭 stdin close、SIGINT、SIGTERM
参数验证使用 zod 定义 inputSchema
错误处理返回 isError: true 和错误信息
日志输出输出到 stderr,避免污染 stdout
超时处理工具执行设置合理超时

总结

MCP 的核心要点

  1. 协议层:JSON-RPC 2.0 + stdio 传输
  2. 通信机制:父子进程管道,单向数据流
  3. 生命周期:启动 → 初始化 → 运行 → 关闭
  4. 三大能力:Tools(工具)、Resources(资源)、Prompts(提示词)

架构图总览

┌─────────────────────────────────────────────────────────────────────────┐
│                                                                         │
│                          用户                                           │
│                           │                                             │
│                           ▼                                             │
│                    ┌─────────────┐                                     │
│                    │  MCP Host   │  (Claude Desktop / Cursor / VS Code) │
│                    │             │                                     │
│                    │  MCP Client │                                     │
│                    └──────┬──────┘                                     │
│                           │                                             │
│                           │ stdio (JSON-RPC)                           │
│                           │                                             │
│                           ▼                                             │
│                    ┌─────────────┐                                     │
│                    │ MCP Server  │  (你开发的服务)                      │
│                    │             │                                     │
│                    │ • Tools     │  (可调用的函数)                      │
│                    │ • Resources │  (可访问的数据)                      │
│                    │ • Prompts   │  (提示词模板)                        │
│                    └─────────────┘                                     │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

参考资料

  • MCP 官方文档
  • MCP SDK (TypeScript)
  • JSON-RPC 2.0 规范
  • next-ai-drawio MCP Server 源码