得一微PCIe Gen5 SSD主控亮相COMPUTEX 2026:AI存力架构赋能端侧AI应用
2026-07-13
2026-07-13 0
在构建 AI Agent 应用时,Tool(工具)是一个核心概念。Tool 让 LLM 拥有了操作外部世界的能力——查询数据库、调用 API、操作文件系统。但传统的 Tool 实现方式存在一些根本性的局限。本文将从一个实际项目(mcp-demo)的思考出发,带你深入理解 MCP(Model Context Protocol)协议是什么、解决了什么问题、以及它是如何工作的。

回顾传统 Agent 开发中 Tool 的实现方式,会发现两个明显的问题:
在大多数框架中,Tool 以函数或类的形式直接写在 Agent 项目的代码里。这意味着:
假设你用 Node.js 写好了一套完美的 Tool 集合,但你的同事或另一个团队使用 Python / Java / Rust 开发 Agent——这些 Tool 就无法直接复用。每种编程语言生态都有各自的优势库和工具,语言壁垒让跨团队协作变得困难重重。
核心矛盾:Tool 应当独立于 LLM,实现本地/远程、跨进程、跨语言的调用。
MCP 全称 Model Context Protocol(模型上下文协议),由 Anthropic 提出并开源。它的核心定位是:
通俗地说,MCP 是一种"通用语言"。它让 AI Agent(充当 MCP 客户端 / Host)可以通过统一的方式,调用各种不同语言、不同位置、不同实现方式的 MCP 服务器所提供的工具和资源。
MCP 设计了两种通信方式,分别覆盖本地和远程两种场景:
这是 MCP 最轻量的通信方式。当一个 Agent 进程需要调用另一个子进程中的工具时,它通过 标准输入输出流(stdin/stdout) 来收发 JSON-RPC 格式的消息。
复制代码┌─────────────┐ stdin (JSON-RPC) ┌──────────────┐
│ AI Agent │ ◄──────────────────────► │ MCP Server │
│ (Host) │ stdout (JSON-RPC) │ (子进程) │
└─────────────┘ └──────────────┘
child_process 等方式启动子进程对于远程工具调用场景,MCP 支持通过 HTTP 协议进行通信。这让你可以:
复制代码┌─────────────┐ HTTP (SSE/JSON-RPC) ┌──────────────┐
│ AI Agent │ ◄─────────────────────────────► │ MCP Server │
│ (Host) │ │ (远程服务) │
└─────────────┘ └──────────────┘
理论讲完了,我们来看一个完整的 MCP Server 实例。以下代码来自 src/my-mcp-server.mjs:
复制代码import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';// 假数据,未来可以走数据库
const database = {
users: {
'001': { id: '001', name: '张三', email: '[email protected]', role: 'admin' },
'002': { id: '002', name: 'nike', email: '[email protected]', role: 'user' },
'003': { id: '003', name: '小红', email: '[email protected]', role: 'user' },
}
};const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0'
});server.registerTool('query_user', {
description: `查询数据库中的用户信息。输入用户ID,
返回该用户的详细信息(姓名、邮箱、角色)`,
inputSchema: {
userId: z.string().describe('用户ID, 例如:001, 002, 003')
}
}, async ({ userId }) => {
const user = database.users[userId];
if (!user) {
return {
content: [
{ type: 'text', text: `用户 ID ${userId} 不存在。
可用的ID: 001, 002, 003` }
]
};
}
return {
content: [
{
type: 'text',
text: `用户 ${user.id} 的信息是:
姓名: ${user.name}, 邮箱: ${user.email}, 角色: ${user.role}`
}
]
};
});// 跨进程通信方式 stdio
const transport = new StdioServerTransport();
await server.connect(transport);
下面我们逐段解析这段代码,看看一个 MCP Server 到底由哪些部分组成。
复制代码import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
这里引入了三个核心模块:
| 模块 | 作用 |
|---|---|
McpServer | MCP 服务器的核心类,负责创建 Server 实例、注册 Tool 和 Resource,是整座房子的"骨架" |
StdioServerTransport | stdio 传输层实现,负责通过标准输入输出流与 Agent Host 通信,是数据进出 MCP Server 的"管道" |
z(Zod) | 一个 TypeScript 优先的 schema 声明与验证库,用来定义 Tool 的输入参数格式。Agent 在调用 Tool 前会先拿到这个 schema,从而知道该传什么参数、参数是什么类型 |
复制代码const database = {
users: {
'001': { id: '001', name: '张三', email: '[email protected]', role: 'admin' },
'002': { id: '002', name: 'nike', email: '[email protected]', role: 'user' },
'003': { id: '003', name: '小红', email: '[email protected]', role: 'user' },
}
};
这里用一个内存对象模拟了数据库。注意一个关键点:这段数据存在于 MCP Server 进程内部,而不是 Agent 项目里。
这就是 MCP 的"解耦"思想在代码层面的体现:
复制代码const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0'
});
两行代码,创建了一个 MCP Server。name 和 version 是必须提供的元信息——当 Agent(Host)连接到这个 Server 时,首先拿到的就是这些基本信息。
类比理解:这就像是启动了一个"微服务",只不过它不是监听某个端口,而是等待父进程通过 stdio 发来的 JSON-RPC 消息。
复制代码server.registerTool('query_user', {
description: `查询数据库中的用户信息。输入用户ID,
返回该用户的详细信息(姓名、邮箱、角色)`,
inputSchema: {
userId: z.string().describe('用户ID, 例如:001, 002, 003')
}
}, async ({ userId }) => {
// ... 处理逻辑
});
registerTool 是 MCP Server 最重要的 API。它接收三个核心参数:
'query_user'这是 Tool 的唯一标识符。Agent 在调用时会说"我要调用 query_user 工具"。命名应该简洁、动词开头、语义清晰。
description + inputSchema这是 MCP 协议的精髓所在——Tool 会"自我介绍":
description:用自然语言告诉 LLM 这个工具是干什么的。LLM 会根据这段描述,自主判断"当前用户的问题是否需要调用这个工具"。描述越清晰精准,LLM 调用工具的时机就越准确。
inputSchema:定义这个工具需要什么参数。这里用了 Zod:
复制代码{ userId: z.string().describe('用户ID, 例如:001, 002, 003') }
它告诉 LLM:"你调用我时需要传一个 userId 参数,它是字符串类型,取值类似 001、002、003"。LLM 看到这段 schema 后,就能从用户的话里提取出 userId,正确地构造调用参数。
复制代码async ({ userId }) => {
const user = database.users[userId];
if (!user) {
return {
content: [
{ type: 'text', text: `用户 ID ${userId} 不存在。可用的ID: 001, 002, 003` }
]
};
}
return {
content: [
{ type: 'text', text: `用户 ${user.id} 的信息是:
姓名: ${user.name}, 邮箱: ${user.email}, 角色: ${user.role}` }
]
};
}
Handler 函数的执行流程非常清晰:
{ userId } 由 MCP 框架自动从 Agent 的调用请求中解析并注入,类型已在 inputSchema 中校验content 数组,每个元素包含 type 和 text返回结构的标准化同样重要:
复制代码{ content: [{ type: 'text', text: '...' }] }
无论成功还是失败,MCP Server 都返回同样结构的结果。Agent 拿到后,会把它注入到 LLM 上下文中——LLM 看到这段文本,就能把查询结果用自然语言转述给用户。
复制代码const transport = new StdioServerTransport();
await server.connect(transport);
这是 MCP Server 的"最后一公里"——把 Server 实例挂载到一个传输层上,让它真正运转起来。
StdioServerTransport 创建了一个 stdio 传输层实例。它做的事情很简单:监听 stdin,把收到的 JSON-RPC 消息转给 server 处理,然后把 server 的返回结果写到 stdout。server.connect(transport) 是一个异步操作,把 server 和传输管道连接起来。连接成功后,server 就处于"就绪"状态——等待 Agent Host 发来的指令。整个数据流可以这样理解:
复制代码Agent (Host) MCP Server (这个文件)
│ │
│ ① 启动子进程: node my-mcp-server.mjs │
│───────────────────────────────────────►│
│ │
│ ② 通过 stdin 发送 JSON-RPC: │
│ {"method":"tools/call", │
│ "params":{"name":"query_user", │
│ "arguments":{"userId":"001"}}} │
│───────────────────────────────────────►│ ③ transport 接收 → server 处理
│ │ → handler 查数据库
│ ④ 通过 stdout 返回 JSON-RPC: │
│ {"content":[{"type":"text", │
│ "text":"用户 001 的信息是:..."}]} │
│◄───────────────────────────────────────│
│ │
│ ⑤ LLM 将结果转述给用户 │
从这 45 行代码中,我们可以抽象出 MCP Server 的几个核心设计原则:
声明式注册:Tool 通过 registerTool 声明式注册,包含名称、描述、参数 schema、处理逻辑。Agent 端不需要任何代码层面的"引入"或"import"——它在连接时自动发现所有 Tool。
标准化通信:无论是 Tool 注册的元数据(description + inputSchema)还是 Tool 调用的结果(content[]),都有固定的结构。这就是 MCP 作为"协议"的价值——双方都按约定好的格式说话,无需猜测。
传输层可替换:这段代码用的是 StdioServerTransport。如果未来要改成 HTTP 模式,只需要把最后两行换成 new StreamableHTTPServerTransport() 并启动 HTTP 服务——Server 本身(注册的 Tool、Handler 逻辑)完全不需要改动。
进程即边界:整个 MCP Server 是一个独立的 Node.js 进程。它和 Agent 之间唯一的耦合就是 stdio(或 HTTP)。这意味着你可以用 Python 写另一个 MCP Server,Agent 同时连接 Node.js Server 和 Python Server——对 Agent 来说,它们都是 MCP 协议下的平等工具提供者。
这是 MCP 最大的特点。无论是本地进程还是远程服务,Agent 都可以通过统一的 MCP 协议来发现和调用工具。你只需要在 Client 端配置好 MCP Server 的连接信息,工具就会自动出现在 Agent 的可用工具列表中。
这里有一个关键认知:MCP 不是简单的接口调用。
普通的 HTTP 调用(fetch)是你向某个 API 发起请求并获取数据,这是"拿数据"的逻辑。而 MCP 的目标更高一层——它是扩展 LLM 的 Context(上下文)。
MCP 为 LLM 提供两类核心扩展:
| 扩展类型 | 说明 | 举例 |
|---|---|---|
| Tool | 让模型能"做更多" | 查数据库、发邮件、操作文件 |
| Resource | 让模型能"知道更多" | 读取文档、获取实时数据、访问知识库 |
基于 MCP,社区已经构建了丰富的工具生态。一个用 Python 写的 MCP Server(比如文件系统操作工具),可以被任何支持 MCP 的 Agent(无论是 Claude Desktop、Cursor、还是你自己写的 Agent)直接使用——无需重写、无需适配、即插即用。
MCP 协议解决了一个根本性的工程问题:如何让 AI Agent 的工具能力可复用、可共享、跨语言、跨平台。 它通过标准化的通信层(stdio + HTTP),让 Tool 从"项目代码的一部分"变成"独立的、可被任意 Agent 调用的服务"。
其设计哲学可以概括为:
MCP 正在成为 AI Agent 时代的"通用接口标准"——就如同 USB 之于外设、HTTP 之于 Web 服务。理解并掌握 MCP,是每一个 AI Agent 开发者的必修课。