在当今AI技术快速发展的背景下,如何让AI助手与外部系统高效交互成为一个关键问题。Model Context Protocol(MCP)正是为解决这一问题而设计的开放协议。作为一名长期从事Node.js和AI集成的开发者,我认为MCP最吸引人的地方在于它提供了一种标准化的方式来扩展AI的能力边界。
MCP本质上是一套通信规范,它定义了AI助手(如Cursor)与外部服务之间的交互方式。协议的核心在于三个关键概念:
Tools(工具):这是AI可以主动调用的功能单元,比如执行计算、调用API或操作文件系统。每个工具都有明确的输入输出定义,就像给AI安装了一个个"技能插件"。
Resources(资源):提供只读数据访问,类似于传统开发中的GET接口。AI可以查询这些资源获取信息,但不能修改。
Prompts(提示模板):可复用的对话模板,帮助AI在不同场景下生成更符合预期的响应。
在实际应用中,Tools是最常用也是最重要的部分。以我们即将实现的demo为例,add和greet就是两个典型的工具。这种设计模式让AI的能力可以像乐高积木一样灵活组合。
提示:MCP协议采用JSON Schema来严格定义工具接口,这种强类型约束大大减少了AI调用时的歧义性,是工程实践中非常值得借鉴的设计。
选择Node.js作为实现平台主要基于以下几个考虑:
对于类型系统,我强烈推荐使用TypeScript而非纯JavaScript。在接口协议开发中,类型安全能帮我们提前发现大量潜在问题。以下是创建项目的基本步骤:
bash复制mkdir first-mcp && cd first-mcp
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node tsx
如果需要进行更严格的参数校验,可以加装Zod:
bash复制npm install zod
一个规范的MCP项目通常包含以下文件:
code复制/first-mcp
├── src/
│ ├── index.ts # 主入口文件
│ ├── tools/ # 工具实现
│ │ ├── add.ts
│ │ └── greet.ts
│ └── types.ts # 类型定义
├── tsconfig.json # TypeScript配置
└── package.json
这种结构虽然简单,但已经具备了良好的扩展性。当工具数量增加时,可以保持代码的整洁性。
加法计算看似简单,但在MCP框架下需要考虑几个关键点:
以下是add工具的完整实现:
typescript复制// src/tools/add.ts
import { ToolImplementation } from '../types';
export const add: ToolImplementation = {
name: 'add',
description: '计算两数之和',
inputSchema: {
type: 'object',
properties: {
a: { type: 'number', description: '第一个数' },
b: { type: 'number', description: '第二个数' }
},
required: ['a', 'b'],
additionalProperties: false
},
execute: async (args) => {
const a = Number(args.a);
const b = Number(args.b);
if (Number.isNaN(a) || Number.isNaN(b)) {
return {
content: [{ type: 'text', text: '参数 a、b 须为数字' }],
isError: true
};
}
const sum = a + b;
return {
content: [{ type: 'text', text: `${a} + ${b} = ${sum}` }],
isError: false
};
}
};
问候语生成器虽然逻辑更简单,但展示了几个重要特性:
实现代码如下:
typescript复制// src/tools/greet.ts
import { ToolImplementation } from '../types';
export const greet: ToolImplementation = {
name: 'greet',
description: '根据名字返回一句问候语',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: '对方的名字' }
},
required: ['name'],
additionalProperties: false
},
execute: async (args) => {
const name = args.name?.trim() || '朋友';
return {
content: [{ type: 'text', text: `你好,${name}!` }],
isError: false
};
}
};
注意:在实际项目中,应该对用户输入的name进行更严格的安全过滤,防止XSS等攻击。
MCP服务端需要处理以下几个核心职责:
使用官方SDK可以大大简化这些工作。以下是主入口文件的实现:
typescript复制// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { add, greet } from './tools';
const server = new Server(
{
name: "demo-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// 注册工具列表
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [add, greet].map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema
}))
}));
// 处理工具调用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const tool = [add, greet].find(t => t.name === name);
if (!tool) {
return {
content: [{ type: 'text', text: `未知工具: ${name}` }],
isError: true
};
}
return tool.execute(args);
});
// 启动stdio传输
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("demo-mcp 已就绪(stdio)");
在这个示例中我们使用了Stdio传输,这是因为它有几个独特优势:
但实际生产环境中,你可能需要考虑其他传输方式:
在Cursor中配置MCP服务需要注意以下几个关键点:
一个完整的配置示例如下:
json复制{
"mcpServers": {
"demo-mcp": {
"command": "node",
"args": ["${workspaceFolder}/dist/index.js"],
"env": {
"NODE_ENV": "development"
}
}
}
}
在Cursor中调用MCP工具实际上是通过AI代理完成的,整个过程可以分为几个阶段:
这个流程看似复杂,但对最终用户是完全透明的。他们只需要像平常一样与AI对话,AI会在背后智能地调用合适的工具。
健壮的错误处理是MCP开发中的关键。以下是一些实用建议:
一个改进后的错误处理示例:
typescript复制execute: async (args) => {
try {
const a = Number(args.a);
const b = Number(args.b);
if (Number.isNaN(a)) {
throw new ClientError('参数a必须是有效数字');
}
if (Number.isNaN(b)) {
throw new ClientError('参数b必须是有效数字');
}
const sum = a + b;
return {
content: [{ type: 'text', text: `${a} + ${b} = ${sum}` }],
isError: false
};
} catch (err) {
if (err instanceof ClientError) {
return {
content: [{ type: 'text', text: err.message }],
isError: true,
statusCode: 400
};
}
return {
content: [{ type: 'text', text: '服务器内部错误' }],
isError: true,
statusCode: 500
};
}
}
当工具数量增多时,需要考虑性能优化:
一个带缓存的greet工具实现:
typescript复制const cache = new Map<string, string>();
export const greet: ToolImplementation = {
// ...其他配置不变
execute: async (args) => {
const name = args.name?.trim() || '朋友';
if (cache.has(name)) {
return {
content: [{ type: 'text', text: cache.get(name)! }],
isError: false
};
}
const greeting = `你好,${name}!`;
cache.set(name, greeting);
return {
content: [{ type: 'text', text: greeting }],
isError: false
};
}
};
为MCP工具编写测试时,应该覆盖:
使用Jest的测试示例:
typescript复制// tests/add.test.ts
import { add } from '../src/tools/add';
describe('add工具', () => {
it('应该正确计算两数之和', async () => {
const result = await add.execute({ a: 2, b: 3 });
expect(result.content[0].text).toBe('2 + 3 = 5');
});
it('应该处理非数字输入', async () => {
const result = await add.execute({ a: 'foo', b: 3 });
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('须为数字');
});
});
使用supertest进行HTTP传输的集成测试:
typescript复制// tests/integration.test.ts
import request from 'supertest';
import { createServer } from '../src/server';
describe('MCP服务', () => {
let app: Express;
beforeAll(async () => {
app = await createServer(); // 返回配置了HTTP传输的Express实例
});
it('应该返回工具列表', async () => {
const res = await request(app)
.post('/tools/list')
.send({});
expect(res.status).toBe(200);
expect(res.body.tools).toHaveLength(2);
});
});
在生产环境中,建议使用进程管理器来确保MCP服务的稳定性:
bash复制npm install -g pm2
pm2 start dist/index.js --name "demo-mcp"
对应的PM2配置文件:
javascript复制// ecosystem.config.js
module.exports = {
apps: [{
name: 'demo-mcp',
script: 'dist/index.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production'
}
}]
};
良好的监控是生产系统必不可少的:
健康检查端点示例:
typescript复制server.setRequestHandler(
{ type: 'object', properties: {}, required: [] },
async () => ({
status: 'ok',
timestamp: new Date().toISOString()
})
);
所有用户输入都应该进行消毒处理:
typescript复制function sanitizeString(input: unknown): string {
if (typeof input !== 'string') return '';
return input.replace(/[<>"'&]/g, '');
}
// 在greet工具中使用
const safeName = sanitizeString(args.name);
实现简单的API密钥认证:
typescript复制server.setRequestHandler(ListToolsRequestSchema, async (req) => {
if (req.headers?.authorization !== `Bearer ${process.env.API_KEY}`) {
throw new Error('Unauthorized');
}
// ...返回工具列表
});
除了基本的工具调用,还可以实现:
流式响应示例:
typescript复制execute: async (args, stream) => {
for (let i = 0; i < 10; i++) {
await stream.write({ type: 'text', text: `进度: ${i * 10}%` });
await new Promise(resolve => setTimeout(resolve, 500));
}
await stream.end({ type: 'text', text: '完成' });
}
MCP可以成为AI与现有系统之间的桥梁:
数据库工具示例:
typescript复制{
name: 'query-users',
description: '查询用户列表',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', minimum: 1, maximum: 100 }
}
},
execute: async ({ limit = 10 }) => {
const users = await db.query('SELECT * FROM users LIMIT ?', [limit]);
return {
content: [{
type: 'json',
value: users
}]
};
}
}
在实际项目中,我发现MCP最强大的地方在于它让AI的能力扩展变得标准化和模块化。通过良好的工具设计,可以让AI助手逐渐积累起一个强大的"技能库",而无需每次都从头训练模型。这种架构特别适合需要将AI能力与现有业务系统集成的场景。