你是谁?
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 |
--> {"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(提示):预先编写的模板,帮助用户完成特定任务
下面只讲解最小实现,具体构建项目可以参考官方文档,都是简单的命令行执行
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 是如何接受数据的?
// 列出所有提示 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; } }