你是谁?
Model Context Protocol
一个约定好的协议。使得开发者能够以一致的方式将各种数据源、工具和功能连接到 AI 模型(一个中间协议层),就像 USB-C 让不同设备能够通过相同的接口连接一样。
而约定的三方分别是:
MCP Host
也就是用户使用的客户端,比如cursor,trae ------ 笔记本MCP Client
也就是中转站 ------ 笔记本扩展坞MCP server
要扩展的工具 ------ Type-C接头 这边举个例子:
组件 | 餐厅类比 | 技术职责 |
---|---|---|
用户 | 顾客 | 提出自然语言请求(如"查订单123的物流") |
MCP Host | 服务员+厨师 | 1. 理解用户意图 2. 决定需要调用哪些工具 3. 整合结果生成自然响应 |
MCP Client | 传菜员 | 1. 将Host的指令转为标准协议 2. 管理连接/重试等通信细节 |
MCP Server | 厨房设备 | 实际执行数据查询/API调用等具体操作 |
这三者通过约定好的协议,协调工作
从哪里来?
本质来源于人们懒地 Ctrl C V
,而前置雏形则是prompt engineering(提示词)
。提示词
和MCP
的目的就是让AI
更高效更准确地来工作
假如没有MCP,在使用AI
的过程中,必然需要人工提取关键信息,然后提供。从提出问题到解答变成了多个步骤:
- 提出问题
- AI思考并给出初步答案
- 人工纠错或提供更多信息
- AI再思考最终给出准确答案
最关键纠错和提供信息的过程可能会持续好几个回合 而当MCP出现之后,AI会自动取寻找相关的信息,并自己去获取错误,然后再思考。分步操作
瞬间变成了自动化操作
。
要去往何方
与AI相关的技术,最终目的只有一个:让AI代替人
所以MCP 的目标是创建一个通用标准,使 AI 应用程序的开发和集成变得更加简单和统一,让 AI 能直接调用那些它本不能调用或模糊理解的东西。
如何实现的
MCP其实可以理解为协商过后定义下来的数据格式,来看一个问题到解答的流程图: 根据这张图,我们可以拆解出几个关键的地方:
- 模型 <-------> MCP Client (为什么直接说是模型与MCP Client沟通,因为MCP Host就是客户端,就是个壳子,需要沟通的其实是模型和MCP Client)
- MCP Client <--------> MCP Server
MCP Client 与 MCP Server 之间
client
与 server
两者之间有个明确的数据格式:JSON-RPC
所以同情本质为 HTTP
content-type = json-rpc
JSON-RPC
规定了具体的数据规范,比如:
请求对象(Request Object)
字段名 | 类型 | 是否必须 | 说明 |
---|---|---|---|
jsonrpc | String | 必须 | 协议版本,必须为"2.0" |
method | String | 必须 | 调用的方法名,禁止以rpc.开头 |
params | Array / Object | 可选 | 方法参数,可省略 |
id | String / Number / null | 可选 | 请求标识符。若省略则为通知(无响应);建议不为null且数字为整数 |
响应对象(Response Object)
字段名 | 类型 | 是否必须 | 说明 |
---|---|---|---|
jsonrpc | String | 必须 | 协议版本,必须为"2.0" |
result | Any | 成功时必选 | 方法返回值,与error互斥 |
error | Object | 失败时必选 | 错误信息(含code和message),与result互斥 |
id | String / Number / null | 必须 | 必须与请求的id一致;若请求id无效(如解析错误),则必须为null |
text
--> {"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}
<-- {"jsonrpc": "2.0", "result": 19, "id": 3}
--> {"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 4}
<-- {"jsonrpc": "2.0", "result": 19, "id": 4}
具体可以去官网中查看www.jsonrpc.org/specificati...
MCP Host(模型)与MCP Client 之间
我们直接看deepseek
API中的请求参数: 这里 这边我们看到请求分成了四种:
系统设定(System)
、用户输入(User)
、AI响应(Assistant)
、工具回调(Tool)
而在MCP中我们需要关注的就是工具回调(Tool)
,也就是MCP Server
执行后的结果传入的消息。 至于怎么告诉AI模型有哪些方法可以执行呢?则是通过一下的参数tools
传入
MCP Server 最小实现
相对于 MCP Client
和 MCP Host
来说,平常开发者其实更关注 MCP Server
的开发,毕竟不管是 AI模型开发
还是 客户端开发
都是巨大的工程,反而个人写一个MCP Server
是非常现实的。
所以我们直接先看 MCP Server
,它一共有三大块功能:
- Resources(资源):类似文件的数据,可以被客户端读取(如 API 响应或文件内容)
- Tools(工具):可以被 LLM 调用的函数(需要用户批准)
- Prompts(提示):预先编写的模板,帮助用户完成特定任务
下面只讲解最小实现,具体构建项目可以参考官方文档,都是简单的命令行执行
javascript
import {
McpServer,
ResourceTemplate,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 创建一个McpServer实例,名称为"Demo",版本为"1.0.0"
const server = new McpServer({
name: "Demo",
version: "1.0.0",
});
// 定义一个名为"add"的工具,接受两个数字参数a和b,返回它们的和
server.tool("add", { a: z.number(), b: z.number() }, async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}));
// 定义一个名为"greeting"的资源,根据{name}参数生成问候信息
server.resource(
"greeting",
new ResourceTemplate("greeting://{name}", {
list: async () => ({
resources: [
{
uri: "greeting://",
name: "这是一个描述",
},
],
}),
}),
async (uri, { name }) => ({
contents: [
{
uri: uri.href, // 确保 uri.href 是字符串
text: `Hello, ${name}!`,
},
],
})
);
// 定义一个名为"greet"的提示,生成问候消息,支持多种语言
server.prompt(
"greet",
"Generate a greeting message",
{
name: z.string().describe("Name to greet"),
language: z.enum(["en", "zh", "es"]).describe("Language for greeting"),
},
async ({ name, language }) => {
const greetings = {
en: `Hello, ${name}!`,
zh: `你好,${name}!`,
es: `¡Hola, ${name}!`,
};
return {
messages: [
{
role: "assistant",
content: {
type: "text",
text: greetings[language],
},
},
],
};
}
);
// 使用StdioServerTransport创建一个传输实例,并将server连接到该传输实例
const transport = new StdioServerTransport();
await server.connect(transport);
以上可以看出要写一个简单的MCP Server
是非常简单的,主要按照规定的格式和类型就不会有问题。
官方还提供了个debug的方式npx @modelcontextprotocol/inspector
node main.js
MCP Client 是如何接受数据的?
javascript
// 列出所有提示
async listPrompts() {
try {
const result = await this.mcp.listPrompts();
console.log("Available prompts:", result);
return result.prompts;
} catch (e) {
console.log("Failed to list prompts:", e);
throw e;
}
}
// 调用指定提示
async callPrompt(name, language) {
try {
const result = await this.mcp.getPrompt({
name: "greet",
arguments: { name, language },
});
console.log("Prompt result:", result.messages);
return result.content;
} catch (e) {
console.log("Failed to call prompt:", e);
throw e;
}
}
// 列出所有资源
async listResources() {
try {
const result = await this.mcp.listResources();
console.log("Available resources:", result);
return result.resources;
} catch (e) {
console.log("Failed to list resources:", e);
throw e;
}
}
// 读取指定资源的内容
async readResource(resourceUri, variable) {
try {
const result = await this.mcp.readResource({
uri: resourceUri,
});
console.log("Resource content:", result);
return result.content;
} catch (e) {
console.log("Failed to read resource:", e);
throw e;
}
}
// 列出所有提示
async listPrompts() {
try {
const result = await this.mcp.listPrompts();
console.log("Available prompts:", result);
return result.prompts;
} catch (e) {
console.log("Failed to list prompts:", e);
throw e;
}
}
// 调用指定提示
async callPrompt(name, language) {
try {
const result = await this.mcp.getPrompt({
name: "greet",
arguments: { name, language },
});
console.log("Prompt result:", result.messages);
return result.content;
} catch (e) {
console.log("Failed to call prompt:", e);
throw e;
}
}