原语解释
MCP 协议目前定义了 5 个核心原语,可以按"谁做主"分成 3 组
分组 | 原语 | 控制权 | 作用一句话 | 典型使用场景示例 |
---|---|---|---|---|
用户主导 | Prompts(提示模板) | 用户 | 把常见对话流程变成可复用模板 | IDE 里输入 /explain-code 自动套用"逐行解释"模板 |
应用主导 | Resources(资源) | 应用/客户端 | 把外部只读数据注入上下文 | Claude Desktop 把本地 file:///home/user/budget.xlsx 塞进对话做报表分析 |
模型主导 | Tools(工具) | LLM | 让 LLM 主动调用函数完成动作 | 用户说"把这张表存成 CSV",LLM 调用 save_csv(tool_input) |
客户端辅助 | Roots(根目录) | 客户端 | 告诉服务器"只能动哪些目录" | VS Code 里 MCP Git 插件只被允许读写当前工作区 |
反向调用 | Sampling(采样) | 服务器 | MCP 服务器反过来让 LLM 生成内容 | 服务器先自己写好一段 SQL,再让 LLM 优化 |
代码示例
所有代码都可直接跑在 Node 20+ 环境,先装依赖: npm i @modelcontextprotocol/sdk
- Prompts(提示模板) 场景:用户敲 /explain-code 让 IDE 自动生成逐行讲解。
ts
// server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const server = new Server(
{ name: 'demo-prompts', version: '1.0.0' },
{ capabilities: { prompts: {} } }
);
server.setRequestHandler('list_prompts', async () => ({
prompts: [
{
name: 'explain-code',
description: '逐行解释选中的源码',
arguments: [
{ name: 'code', description: '要解释的源码', required: true }
]
}
]
}));
server.setRequestHandler('get_prompt', async ({ name, arguments: args }) => {
if (name === 'explain-code') {
return {
description: `逐行解释以下代码:\n${args.code}`,
messages: [
{
role: 'user',
content: { type: 'text', text: `逐行解释以下代码:\n${args.code}` }
}
]
};
}
throw new Error('Unknown prompt');
});
const transport = new StdioServerTransport();
server.connect(transport);
- Resources(只读资源) 场景:把本地 todo.md 注入对话,让 AI 基于它回答问题。
js
server.setRequestHandler('list_resources', async () => ({
resources: [
{
uri: 'file:///Users/alice/todo.md',
name: '今日待办',
mimeType: 'text/markdown'
}
]
}));
server.setRequestHandler('read_resource', async ({ uri }) => {
if (uri === 'file:///Users/alice/todo.md') {
const fs = await import('fs/promises');
const text = await fs.readFile('/Users/alice/todo.md', 'utf8');
return { contents: [{ uri, mimeType: 'text/markdown', text }] };
}
throw new Error('Resource not found');
});
- Tools(LLM 可调用的函数) 场景:用户说"把这段文字存成文件",LLM 调用 write_file 工具。
js
import { z } from 'zod';
const WriteFileArgs = z.object({ path: z.string(), content: z.string() });
server.setRequestHandler('list_tools', async () => ({
tools: [
{
name: 'write_file',
description: '把内容写入磁盘',
inputSchema: WriteFileArgs
}
]
}));
server.setRequestHandler('call_tool', async ({ name, arguments: args }) => {
if (name === 'write_file') {
const { path, content } = WriteFileArgs.parse(args);
const fs = await import('fs/promises');
await fs.writeFile(path, content, 'utf8');
return { content: [{ type: 'text', text: `已写入 ${path}` }] };
}
throw new Error('Unknown tool');
});
- Roots(安全根目录) 客户端在连接时把工作目录设为 root,服务器只能访问该目录:
js
server.setRequestHandler('initialize', async (params) => {
const roots = params.roots ?? []; // 如:[{ uri: 'file:///workspace/project' }]
console.log('允许访问的根目录:', roots);
return {
protocolVersion: '2024-11-05',
capabilities: { tools: {}, resources: {}, prompts: {} },
serverInfo: { name: 'demo-roots', version: '1.0.0' }
};
});
- Sampling(服务器反向让 LLM 生成) 场景:MCP 服务器自己写好 SQL 后,再让 LLM 优化。
js
// 假设 server 已具备 sampling 能力
const optimized = await server.request(
{ method: 'sampling/createMessage', params: {
messages: [
{ role: 'user', content: { type: 'text', text: '请把以下 SQL 优化成 JOIN 形式:SELECT * FROM orders, users WHERE orders.user_id = users.id' } }
],
maxTokens: 200
}}
);
console.log('LLM 优化结果:', optimized.content.text);