本文是「从零搭建私人 RAG 知识库」专栏的基础篇。 MCP 是连接 AI 应用与外部工具、数据源的标准协议。本文暂时不引入数据库、RAG 或业务系统,而是从一个最简单的问候工具开始,跑通 MCP Server 的完整流程。
前言
大模型擅长理解和生成文本,但它不能天然读取本地文件、查询数据库或调用我们编写的函数。
假设我们有一个普通函数:
ts
function greet(name: string): string {
return `你好,${name}!`;
}
它可以接收名字并返回问候语,但 AI 客户端并不知道:
- 这个函数是否存在;
- 它有什么作用;
- 调用时需要哪些参数;
- 如何获得执行结果。
MCP 要解决的,就是让 AI 客户端能够用统一方式发现并调用这样的外部能力。
本文将完成一个最小示例:
text
用户:请向小明问好
↓
AI 客户端选择 greet 工具
↓
MCP Server 执行工具
↓
返回:你好,小明!
一、什么是 MCP
MCP 的全称是 Model Context Protocol,中文通常翻译为"模型上下文协议"。
它是一套开放协议,用于规范 AI 应用与外部工具、数据源之间的通信方式。
通过 MCP,AI 应用可以:
- 调用计算工具;
- 读取本地文件;
- 查询数据库;
- 请求第三方 API;
- 获取业务系统中的数据;
- 执行经过授权的操作。
可以把 MCP 理解成 AI 应用与外部能力之间的"通用接口标准"。
MCP 不是什么
理解 MCP 时,需要先区分几个概念:
- MCP 不是大模型:它不负责理解问题和生成回答。
- MCP 不是数据库:它不负责长期保存数据。
- MCP 不是 RAG 框架:它不负责文档分块、向量化和语义检索。
- MCP 不会自动保证安全:权限控制、参数校验和敏感数据保护仍然需要开发者负责。
MCP 主要解决的是:
AI 应用如何发现、调用外部能力,并获得调用结果。
二、MCP 的基本架构
一个典型的 MCP 系统包含三个角色:
text
用户
↓
MCP Host
└── MCP Client
↓ MCP
MCP Server
↓
外部能力
1. MCP Host
Host 是用户实际使用的 AI 应用,例如:
- 桌面 AI 助手;
- IDE;
- 支持工具调用的聊天应用;
- 自己开发的 Agent 应用。
Host 通常负责管理对话、调用大模型以及展示工具调用过程。
2. MCP Client
MCP Client 位于 Host 内部,负责与 MCP Server 通信,包括:
- 建立连接;
- 获取工具列表;
- 发送工具调用请求;
- 接收工具执行结果。
一个 Host 可以连接多个 MCP Server。
3. MCP Server
MCP Server 是外部能力的提供方。
它负责:
- 声明自己提供了哪些能力;
- 描述工具的用途和参数;
- 校验客户端传入的参数;
- 执行对应逻辑;
- 按 MCP 格式返回结果。
本文将实现的 greet 工具就运行在 MCP Server 中。
三、MCP Server 的三类核心能力
MCP Server 主要可以提供 Tools、Resources 和 Prompts 三类能力。
1. Tools:可执行工具
Tool 可以理解为模型能够调用的函数,例如:
text
greet(name)
add(a, b)
get_weather(city)
Tool 适合完成计算、查询或操作任务。
本文使用的 greet 就是一个 Tool。
2. Resources:可读取资源
Resource 是 Server 提供的可读取数据,通常通过 URI 标识,例如:
text
file:///notes/today.md
config://application
它适合向客户端提供文档、配置或其他内容。
3. Prompts:提示词模板
Prompt 是 Server 提供的可复用提示词模板,例如:
- 总结一篇文章;
- 审查一段代码;
- 根据固定格式生成报告。
4. 如何选择
可以先使用下面的简单判断方式:
- 需要执行函数或操作:使用 Tool;
- 需要读取数据:使用 Resource;
- 需要复用任务模板:使用 Prompt。
初学 MCP 时,先掌握 Tool 即可。
四、准备一个最小 TypeScript 项目
1. 前置环境
本文示例基于 Node.js 和 npm。在开始之前,请先准备以下环境:
- Node.js 18 或更高版本,建议使用当前的 LTS 版本;
- npm,安装 Node.js 时通常会一并安装;
- 一个代码编辑器,例如 Visual Studio Code;
- 一个可以执行命令的终端。
可以在终端中执行以下命令,确认 Node.js 和 npm 已正确安装:
bash
node --version
npm --version
如果命令能够输出版本号,说明环境已经可用。例如:
text
v22.17.0
10.9.2
如果终端提示找不到 node 或 npm,请先安装 Node.js。建议从 Node.js 官方网站下载安装 LTS 版本,也可以使用 nvm 等版本管理工具进行安装。
不同操作系统的安装方式可能不同,但后续示例使用的代码和 npm 命令基本一致。
2. 创建项目
创建项目并安装依赖:
bash
mkdir mcp-hello
cd mcp-hello
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node
在 package.json 中启用 ESM,并添加启动命令:
json
{
"type": "module",
"scripts": {
"start": "tsx src/index.ts"
}
}
项目只需要一个源文件:
text
mcp-hello/
├── package.json
└── src/
└── index.ts
五、实现第一个 MCP Server
在 src/index.ts 中写入以下代码:
ts
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: "hello-server",
version: "1.0.0",
});
server.registerTool(
"greet",
{
description: "向指定的人发送一句中文问候",
inputSchema: {
name: z.string().describe("需要问候的人名"),
},
},
async ({ name }) => ({
content: [
{
type: "text",
text: `你好,${name}!`,
},
],
}),
);
const transport = new StdioServerTransport();
await server.connect(transport);
这就是一个可以运行的最小 MCP Server。
六、逐段理解示例代码
1. 创建 Server
ts
const server = new McpServer({
name: "hello-server",
version: "1.0.0",
});
这里创建了一个 MCP Server,并提供名称和版本信息。
2. 注册 Tool
ts
server.registerTool(
"greet",
// 工具定义
// 工具处理函数
);
registerTool() 的三个主要部分是:
- 工具名称;
- 工具定义;
- 工具处理函数。
3. 描述工具和参数
ts
{
description: "向指定的人发送一句中文问候",
inputSchema: {
name: z.string().describe("需要问候的人名"),
},
}
description 告诉模型这个工具有什么作用。
inputSchema 定义调用工具时需要传入的参数。这里表示:
- 参数名为
name; - 参数类型必须是字符串;
- 参数含义是"需要问候的人名"。
工具描述和参数描述不仅是给开发者看的,模型也会根据它们判断何时调用工具以及如何生成参数。
4. 执行工具
ts
async ({ name }) => ({
content: [
{
type: "text",
text: `你好,${name}!`,
},
],
})
当客户端调用 greet 时,处理函数会接收通过校验的 name,然后返回 MCP Tool 结果。
常见的文本结果格式为:
ts
{
content: [
{
type: "text",
text: "工具执行结果",
},
],
}
5. 建立 stdio 连接
ts
const transport = new StdioServerTransport();
await server.connect(transport);
Transport 负责传输 MCP 消息。
StdioServerTransport 使用进程的标准输入和标准输出通信,适合本地 MCP Server。
七、启动 MCP Server
执行:
bash
npm start
程序启动后可能不会显示任何内容,而是一直等待输入。这通常不是程序卡住,而是 Server 正在等待 MCP Client 发送协议消息。
MCP Server 不是普通的命令行交互程序,不应该直接在终端中输入自然语言进行测试。通常需要由支持 MCP 的客户端启动并连接它。
八、在 MCP 客户端中配置 Server
不同客户端的配置文件位置不同,但核心配置通常包括:
- Server 名称;
- 启动命令;
- 命令参数。
示例配置如下:
json
{
"mcpServers": {
"hello": {
"command": "npx",
"args": [
"tsx",
"/绝对路径/mcp-hello/src/index.ts"
]
}
}
}
请将示例路径替换为本机的真实绝对路径。
保存配置并重启客户端后,可以尝试输入:
text
请向小明问好。
如果客户端正确连接 Server,大致会发生以下过程:
- 客户端发现
greet工具; - 模型根据工具描述判断它适合当前请求;
- 模型生成参数:
json
{
"name": "小明"
}
- MCP Client 调用
greet; - MCP Server 返回:
text
你好,小明!
- 模型将工具结果展示给用户。
九、一次 Tool 调用是怎样完成的
完整流程可以概括为:
text
用户提出请求
↓
大模型读取可用工具列表
↓
大模型选择 greet
↓
大模型生成 { "name": "小明" }
↓
MCP Client 发送调用请求
↓
MCP Server 校验参数
↓
工具处理函数生成问候语
↓
MCP Server 返回结果
↓
大模型组织最终回答
这里需要区分三项职责:
- 大模型:理解用户意图,决定是否调用工具;
- MCP Client:按照协议发送请求并接收结果;
- MCP Server:校验参数并可靠地执行工具。
MCP Server 本身不会理解"请向小明问好"这句话,也不会自主决定调用哪个工具。这个判断通常由 Host 中的大模型完成。
十、stdio 使用时的注意事项
1. 不要向 stdout 输出日志
stdio MCP Server 使用标准输出传输协议消息。
下面的代码可能破坏通信:
ts
console.log("Server started");
因为 console.log() 默认写入 stdout,客户端可能把日志误认为 MCP 协议数据。
需要输出日志时,应写入 stderr:
ts
console.error("Server started");
可以简单记成:
text
stdout:传输 MCP 消息
stderr:输出运行日志
2. 客户端配置尽量使用绝对路径
MCP Host 启动 Server 时,工作目录不一定是项目目录。相对路径可能导致脚本找不到,因此建议在客户端配置中使用绝对路径。
3. Server 由客户端启动
stdio 模式下,通常由 MCP Host 创建 Server 子进程,并通过该进程的标准输入和标准输出通信。
因此,不需要为这个最小示例额外监听 HTTP 端口。
十一、MCP Tool 开发建议
1. 工具名称要表达用途
推荐:
text
greet
get_weather
search_document
不推荐:
text
run
do_it
process
清晰的名称能帮助模型选择正确工具。
2. 工具描述要具体
不够清晰:
ts
description: "处理名字"
更清晰:
ts
description: "向指定的人发送一句中文问候"
描述应说明工具做什么,而不是简单重复工具名称。
3. 参数要有类型和说明
ts
inputSchema: {
name: z.string().describe("需要问候的人名"),
}
参数 Schema 一方面帮助模型生成正确参数,另一方面可以阻止不符合要求的数据进入处理函数。
4. 在系统边界校验输入
不要默认信任模型生成的参数。涉及以下内容时尤其需要严格校验:
- 文件路径;
- URL;
- SQL;
- Shell 命令;
- 用户身份;
- 写入或删除操作。
5. 高风险操作需要确认
读取信息和删除数据的风险完全不同。
对于发送消息、修改文件、删除数据等具有副作用的工具,应考虑:
- 要求用户确认;
- 使用最小权限;
- 限制操作范围;
- 记录执行结果;
- 避免返回敏感信息。
这个原则与 MCP 无关,而是所有外部工具调用都应遵守的安全边界。
十二、从最小示例继续扩展
理解 greet 工具后,可以按照同样的结构增加更多能力。
例如增加一个加法工具:
ts
server.registerTool(
"add",
{
description: "计算两个数字之和",
inputSchema: {
a: z.number().describe("第一个数字"),
b: z.number().describe("第二个数字"),
},
},
async ({ a, b }) => ({
content: [
{
type: "text",
text: String(a + b),
},
],
}),
);
虽然工具功能不同,但基本结构没有变化:
text
定义名称
↓
编写描述
↓
定义参数 Schema
↓
实现处理函数
↓
返回 MCP 结果
后续无论接入天气 API、文件系统、数据库还是知识库,本质上都是把已有能力包装成 MCP Tool。
初学时不必立即引入复杂业务。先确认下面这条链路能够正常工作:
text
AI 客户端
↓
发现 Tool
↓
生成参数
↓
MCP Server 执行
↓
返回结果
十三、总结
MCP 的核心价值可以概括为一句话:
MCP 使用标准协议,把 AI 应用与外部工具和数据连接起来。
本文实现的最小 MCP Server 只做了三件事:
- 创建
McpServer; - 使用
registerTool()注册greet工具; - 使用
StdioServerTransport与客户端通信。
核心代码只有以下几部分:
ts
const server = new McpServer({
name: "hello-server",
version: "1.0.0",
});
server.registerTool(
"greet",
{
description: "向指定的人发送一句中文问候",
inputSchema: {
name: z.string().describe("需要问候的人名"),
},
},
async ({ name }) => ({
content: [{ type: "text", text: `你好,${name}!` }],
}),
);
const transport = new StdioServerTransport();
await server.connect(transport);
掌握这个最小结构后,再逐步接入文件、API、数据库和知识库,会更容易理解每个组件在 MCP 系统中的职责。