MCP 协议原理与实战指南

在上一篇文章中,我们探讨了大模型是如何通过“工具调用 (Function Calling)”拥有双手的。但随着工具越来越多,痛点也随之而来:如果每个 AI 平台(如 ChatGPT、Claude、甚至你自研的 Copilot)都需要一套特定的代码来接入你的天气接口或知识库,这无疑是一场维护灾难。

为了解决这个“各自为战”的局面,MCP(Model Context Protocol,模型上下文协议) 诞生了。它就像是 AI 界的 USB-C 接口——只要你的工具遵循 MCP 标准暴露出去,任何支持 MCP 的 AI 都能直接即插即用!

本文将结合实际项目代码,带你深入理解 MCP 的核心原理,并实战演示两种最主流的 MCP 通信方式:Stdio (标准输入输出) 与 SSE (Server-Sent Events)


一、 MCP 的核心架构与原理

MCP 的本质是一个 客户端-服务端 (Client-Server) 架构。它将系统解耦为两部分:

  1. MCP Server (服务端):负责定义工具 (Tools)、提供资源 (Resources) 和执行具体逻辑(比如真正去查天气或搜数据库)。它是能力的提供者

  2. MCP Client (客户端):通常由大模型应用(如我们的 AI Copilot)扮演。它负责将模型生成的意图翻译为 MCP 协议格式,向 Server 发起请求,并将结果返回给模型。它是能力的消费者

在我们的项目中,我们首先需要创建一个通用的 MCP Server 实例,把我们之前写的工具(如查天气、搜 React 文档)统统注册进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// src/app/api/scrape/mcp/create-server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SCRAPE_TOOLS } from "../tools";

export function createScrapeMcpServer() {
// 1. 初始化一个 MCP Server
const server = new McpServer({
name: "ai-copilot-scrape-tools",
version: "1.0.0",
});

// 2. 遍历我们写好的工具,按 MCP 标准注册
for (const [toolName, definition] of Object.entries(SCRAPE_TOOLS)) {
server.registerTool(
toolName,
{
description: definition.description,
inputSchema: definition.schema.shape,
annotations: definition.annotations,
},
async (input: unknown) => {
// 执行底层逻辑,并将结果转换为 MCP 要求的格式
const text = await definition.execute(input as never);
return definition.toMcpResult(text);
}
);
}

return server;
}

亮点:这段代码是纯粹的业务逻辑,它不关心网络是如何传输的。这就是 MCP 设计的美妙之处:逻辑与通信解耦。


二、 实践方式一:基于命令行的 Stdio 传输流

适用场景:本地脚本、后台守护进程、Electron 桌面应用。 原理:AI 客户端通过运行一段终端命令(如 node server.js)唤起一个子进程。它们之间不走 HTTP 网络,而是直接通过操作系统的 stdin (标准输入) 和 stdout (标准输出) 进行 JSON-RPC 消息交互。

1. 搭建 Stdio Server

服务器端只需要将刚才创建的逻辑 Server 挂载到 StdioServerTransport 上即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/app/api/scrape/mcp/stdio-server.ts
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createScrapeMcpServer } from "./create-server";

async function main() {
const server = createScrapeMcpServer();
const transport = new StdioServerTransport(); // 核心:使用标准输入输出传输层

await server.connect(transport);
console.error("[MCP] scrape stdio server running"); // 注意:日志必须用 console.error,因为 stdout 被用来传数据了!
}

main().catch((error) => process.exit(1));

2. 客户端接入 Stdio

客户端需要通过指定执行命令(如 tsx 运行入口文件)来连接:

1
2
3
4
5
6
7
8
9
10
11
12
// src/app/api/scrape/mcp/create-client.ts (Stdio 部分)
if (transport === "stdio") {
const tsxCommand = process.platform === "win32" ? "tsx.cmd" : "tsx";
const mcpServerEntry = path.join(process.cwd(), ...MCP_STDIO_ENTRY_PATH);

return createMCPClient({
transport: new StdioClientTransport({
command: tsxCommand,
args: [mcpServerEntry], // 让客户端去执行这个脚本
}),
});
}

三、 实践方式二:基于 Web 标准的 SSE 传输流

适用场景:Web 应用、前后端分离架构、Next.js 路由、分布式微服务。 原理:Web 环境下无法直接拉起子进程,因此 MCP 提供了一套基于 HTTP 的网络方案。它非常巧妙地运用了两种 HTTP 机制:

  • GET (SSE):Server-Sent Events 充当服务器到客户端的下行通道(单向长连接)。

  • POST:普通的 HTTP POST 请求充当客户端到服务器的上行通道

1. 搭建 SSE Server (Next.js Api Route)

在 Next.js 中,我们需要处理复杂的会话管理,因为 HTTP 是无状态的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// src/pages/api/scrape/mcp.ts
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

// 用全局对象维持长连接 Session 会话
const transports = global.__scrapeMcpSseTransports__ ?? {};

export default async function handler(req, res) {
// 【通道一】:建立下行连接 (SSE)
if (req.method === "GET") {
const transport = new SSEServerTransport("/api/scrape/mcp", res);
transports[transport.sessionId] = transport; // 记录会话

transport.onclose = () => delete transports[transport.sessionId];

const server = createScrapeMcpServer();
await server.connect(transport);
return;
}

// 【通道二】:处理客户端上行的调用请求
if (req.method === "POST") {
const sessionId = req.query.sessionId as string;
const transport = transports[sessionId]; // 找到对应的长连接

if (!transport) return res.status(404).send("Session not found");

// 将客户端传来的参数交给 transport 处理并触发执行
await transport.handlePostMessage(req, res, req.body);
return;
}
}

2. 客户端接入 SSE

而在客户端侧,接入变得异常简单,只需要填入后端的 API 地址即可:

1
2
3
4
5
6
7
// src/app/api/scrape/mcp/create-client.ts (SSE 部分)
return createMCPClient({
transport: {
type: "sse",
url: `${requestUrl.origin}/api/scrape/mcp`, // 直接指向 Next.js 的路由
},
});

四、 总结与最佳实践

从项目源码可以看出,MCP 通过极为优雅的抽象层,让我们只写一次业务逻辑(create-server.ts),就能适配本地进程(Stdio)和网络微服务(SSE)两种完全不同的场景

在实际开发中:

  • 如果你在做诸如 Cursor/Windsurf 这样的本地 IDE 插件或桌面助手,优先选择 Stdio,速度快、无网络开销、权限大。

  • 如果你在做诸如 AI Copilot Web 版这种网页应用,毫无疑问必须选择 SSE,通过 Next.js 的 API 路由暴露出标准的网络接口,安全性高、易于横向扩展。

掌握了 MCP,你的大模型应用就不再是一座孤岛,而是能够随时随地接入万物 API 的超级中枢。

源码已托管至🫱 GitHub,期待你的 Star🌟。


MCP 协议原理与实战指南
http://example.com/2026/05/02/MCP-协议原理与实战指南/
作者
Lingkai Shi
发布于
2026年5月2日
许可协议