MCP (Model Context Protocol) 原理与实战

MCP (Model Context Protocol) 原理与实战

从零开始理解 MCP 协议,通过实战案例掌握 MCP Server 开发

目录

  1. [MCP 是什么?](#MCP 是什么? "#1-mcp-%E6%98%AF%E4%BB%80%E4%B9%88")
  2. [核心原理:stdio 管道通信](#核心原理:stdio 管道通信 "#2-%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86stdio-%E7%AE%A1%E9%81%93%E9%80%9A%E4%BF%A1")
  3. [JSON-RPC 协议](#JSON-RPC 协议 "#3-json-rpc-%E5%8D%8F%E8%AE%AE")
  4. [MCP Server 生命周期](#MCP Server 生命周期 "#4-mcp-server-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F")
  5. [实战:剖析 next-ai-drawio MCP Server](#实战:剖析 next-ai-drawio MCP Server "#5-%E5%AE%9E%E6%88%98%E5%89%96%E6%9E%90-next-ai-drawio-mcp-server")
  6. [动手开发一个简单的 MCP Server](#动手开发一个简单的 MCP Server "#6-%E5%8A%A8%E6%89%8B%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84-mcp-server")
  7. 常见问题与最佳实践

1. MCP 是什么?

1.1 问题背景

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

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

1.2 MCP 的解决方案

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

scss 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                        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 Client Host 内部的客户端逻辑 HTTP Client
Tool AI 可调用的函数 API Endpoint
Resource AI 可访问的数据 文件/数据库
Prompt 预定义的提示词模板 模板文件

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

2.1 什么是 stdio?

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

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

2.2 父子进程与管道

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

c 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                     父子进程管道通信                                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   父进程 (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 消息格式

json 复制代码
// 请求 (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 协议层级

bash 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                        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 完整生命周期

arduino 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                     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 关闭机制代码

typescript 复制代码
// 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 项目结构

bash 复制代码
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)
typescript 复制代码
#!/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(`http://localhost:${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 通信

scss 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                  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。解决方案:

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

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

6.1 项目初始化

bash 复制代码
# 创建项目
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 编写代码

typescript 复制代码
// 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

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 中配置

json 复制代码
// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
// 或 %APPDATA%\Claude\claude_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 会怎样?
arduino 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                     多实例场景                                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   Codex 启动 MCP Server → localhost:6002                               │
│   Claude Code 启动 MCP Server → 6002 被占用 → 自动切换到 6003           │
│                                                                         │
│   ✅ 不会失败(端口自动递增)                                            │
│   ⚠️ 但状态不共享(每个实例独立)                                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
Q2: 关闭客户端后 MCP Server 会关闭吗?
lua 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                                                                         │
│   ⚠️ 不会自动关闭!需要开发者自己处理!                                  │
│                                                                         │
│   很多人的误解:                                                        │
│   ❌ "客户端关闭后,MCP Server 进程会自动退出"                          │
│                                                                         │
│   事实是:                                                              │
│   1. 客户端关闭 → stdin 管道关闭 → 子进程收到 EOF                      │
│   2. 但 Node.js 进程默认不会因为 stdin 关闭而退出                      │
│   3. 如果有 HTTP Server 或定时器,进程会继续运行                       │
│   4. 必须监听 stdin close 事件,手动调用 process.exit()                │
│                                                                         │
│   如果不处理,进程会变成孤儿进程,继续占用端口和内存!                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

必须的关闭处理代码:

typescript 复制代码
// 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(提示词)

架构图总览

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

参考资料


作者:胡哈

本文基于 next-ai-drawio 项目的 MCP Server 实现,深入讲解了 MCP 协议的原理与实战。

相关推荐
-许平安-5 小时前
MCP项目笔记十(客户端 MCPClient)
c++·笔记·ai·raii·mcp·pluginapi·plugin system
蛊明6 小时前
Win11 如何下载安装 Node.js
node.js
进击的野人1 天前
MCP协议:让AI应用像插USB一样连接外部世界
人工智能·agent·mcp
Bruce1231 天前
openclaw学习日常(一)openclaw在WSL中搭建
人工智能·node.js
花千树-0101 天前
MCP + Function Calling:让模型自主驱动工具链完成多步推理
java·agent·react·mcp·toolcall·harness·j-langchain
Hommy881 天前
【开源剪映小助手-客户端】桌面客户端
python·开源·node.js·github·剪映小助手
锵锵锵锵~蒋1 天前
AI全托管处理EXCEL(并接入AI平台)
人工智能·excel·mcp·ai全托管·ai提效’
走粥1 天前
node.js 中的 express 框架 - 基础到进阶
node.js·express
丁劲犇1 天前
改造传统Qt6Widgets程序为多会话MCPServer生产力工具-技巧与实现
qt·ai·agent·并发·mcp·mcpserver·widgets