什么是MCP工具?
MCP工具是一种强大的原语,允许服务器向客户端暴露可执行功能。通过工具,大型语言模型(LLM)能够与外部系统交互、执行计算并在现实世界中采取行动。
核心特性
- 模型控制:工具专为AI模型自动调用而设计(在人工监督下)
- 动态发现:客户端可以通过
tools/list
端点列出可用工具 - 灵活调用:使用
tools/call
端点调用工具,服务器执行操作并返回结果 - 广泛适用:从简单计算到复杂API交互
TypeScript SDK环境搭建
首先,让我们建立开发环境:
# 安装MCP TypeScript SDK
npm install @modelcontextprotocol/sdk
# 安装类型定义
npm install --save-dev @types/node typescript
工具定义结构
每个MCP工具都遵循标准的JSON结构:
interface Tool {
name: string; // 唯一标识符
description?: string; // 工具描述
inputSchema: { // JSON Schema定义参数
type: "object";
properties: Record<string, any>;
required?: string[];
};
}
实战演示:构建多功能工具服务器
让我们创建一个功能丰富的MCP服务器,演示不同类型的工具实现:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import * as fs from "fs/promises";
import * as path from "path";
class MCPToolServer {
private server: Server;
constructor() {
this.server = new Server(
{
name: "advanced-tools-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
this.setupTools();
}
private setupTools() {
// 1. 系统操作工具 - 文件管理
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "read_file",
description: "读取指定路径的文件内容",
inputSchema: {
type: "object",
properties: {
filepath: {
type: "string",
description: "要读取的文件路径"
}
},
required: ["filepath"]
}
},
{
name: "write_file",
description: "将内容写入指定文件",
inputSchema: {
type: "object",
properties: {
filepath: {
type: "string",
description: "目标文件路径"
},
content: {
type: "string",
description: "要写入的内容"
}
},
required: ["filepath", "content"]
}
},
{
name: "calculate",
description: "执行数学计算表达式",
inputSchema: {
type: "object",
properties: {
expression: {
type: "string",
description: "数学表达式,如 '2 + 3 * 4'"
}
},
required: ["expression"]
}
},
{
name: "fetch_weather",
description: "获取指定城市的天气信息",
inputSchema: {
type: "object",
properties: {
city: {
type: "string",
description: "城市名称"
},
country: {
type: "string",
description: "国家代码,如 'CN', 'US'",
default: "CN"
}
},
required: ["city"]
}
},
{
name: "process_data",
description: "处理和分析JSON数据",
inputSchema: {
type: "object",
properties: {
data: {
type: "array",
description: "要处理的数据数组"
},
operation: {
type: "string",
enum: ["sum", "average", "max", "min", "count"],
description: "要执行的操作类型"
},
field: {
type: "string",
description: "要操作的字段名(可选)"
}
},
required: ["data", "operation"]
}
}
]
};
});
// 工具调用处理器
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "read_file":
return await this.handleReadFile(args);
case "write_file":
return await this.handleWriteFile(args);
case "calculate":
return await this.handleCalculate(args);
case "fetch_weather":
return await this.handleFetchWeather(args);
case "process_data":
return await this.handleProcessData(args);
default:
throw new Error(`未知工具: ${name}`);
}
} catch (error) {
return {
content: [
{
type: "text",
text: `工具执行错误: ${error.message}`
}
],
isError: true
};
}
});
}
// 文件读取工具实现
private async handleReadFile(args: any) {
const { filepath } = args;
// 输入验证
if (!filepath || typeof filepath !== 'string') {
throw new Error('文件路径无效');
}
// 安全检查 - 防止路径遍历攻击
const normalizedPath = path.normalize(filepath);
if (normalizedPath.includes('..')) {
throw new Error('不允许访问上级目录');
}
try {
const content = await fs.readFile(normalizedPath, 'utf8');
return {
content: [
{
type: "text",
text: `文件内容 (${filepath}):\n${content}`
}
]
};
} catch (error) {
throw new Error(`读取文件失败: ${error.message}`);
}
}
// 文件写入工具实现
private async handleWriteFile(args: any) {
const { filepath, content } = args;
if (!filepath || !content) {
throw new Error('文件路径和内容都是必需的');
}
// 安全检查
const normalizedPath = path.normalize(filepath);
if (normalizedPath.includes('..')) {
throw new Error('不允许访问上级目录');
}
try {
await fs.writeFile(normalizedPath, content, 'utf8');
return {
content: [
{
type: "text",
text: `文件写入成功: ${filepath}`
}
]
};
} catch (error) {
throw new Error(`写入文件失败: ${error.message}`);
}
}
// 计算工具实现
private async handleCalculate(args: any) {
const { expression } = args;
if (!expression || typeof expression !== 'string') {
throw new Error('表达式无效');
}
// 安全的数学表达式求值
const sanitizedExpression = expression.replace(/[^0-9+-*/().\s]/g, '');
try {
// 使用Function构造器安全地执行数学运算
const result = Function('"use strict"; return (' + sanitizedExpression + ')')();
return {
content: [
{
type: "text",
text: `计算结果: ${expression} = ${result}`
}
]
};
} catch (error) {
throw new Error(`计算错误: 表达式无效`);
}
}
// 天气API工具实现
private async handleFetchWeather(args: any) {
const { city, country = 'CN' } = args;
if (!city) {
throw new Error('城市名称是必需的');
}
// 模拟天气API调用
const mockWeatherData = {
city,
country,
temperature: Math.floor(Math.random() * 30) + 5,
condition: ['晴天', '多云', '小雨', '阴天'][Math.floor(Math.random() * 4)],
humidity: Math.floor(Math.random() * 50) + 30,
timestamp: new Date().toLocaleString('zh-CN')
};
return {
content: [
{
type: "text",
text: `${city} 天气信息:\n温度: ${mockWeatherData.temperature}°C\n天气: ${mockWeatherData.condition}\n湿度: ${mockWeatherData.humidity}%\n更新时间: ${mockWeatherData.timestamp}`
}
]
};
}
// 数据处理工具实现
private async handleProcessData(args: any) {
const { data, operation, field } = args;
if (!Array.isArray(data)) {
throw new Error('数据必须是数组格式');
}
let result: any;
const values = field ? data.map(item => item[field]).filter(val => typeof val === 'number') : data.filter(val => typeof val === 'number');
switch (operation) {
case 'sum':
result = values.reduce((acc, val) => acc + val, 0);
break;
case 'average':
result = values.length > 0 ? values.reduce((acc, val) => acc + val, 0) / values.length : 0;
break;
case 'max':
result = values.length > 0 ? Math.max(...values) : null;
break;
case 'min':
result = values.length > 0 ? Math.min(...values) : null;
break;
case 'count':
result = values.length;
break;
default:
throw new Error(`不支持的操作: ${operation}`);
}
return {
content: [
{
type: "text",
text: `数据处理结果:\n操作: ${operation}\n${field ? `字段: ${field}\n` : ''}结果: ${result}\n处理项数: ${values.length}`
}
]
};
}
// 启动服务器
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("高级工具服务器已启动");
}
}
// 启动服务器
const server = new MCPToolServer();
server.run().catch(console.error);
工具注解(Tool Annotations)
MCP支持工具注解,提供关于工具行为的额外元数据:
// 带注解的工具定义示例
{
name: "delete_file",
description: "删除指定文件",
inputSchema: {
type: "object",
properties: {
filepath: { type: "string" }
},
required: ["filepath"]
},
annotations: {
title: "文件删除工具",
readOnlyHint: false, // 会修改环境
destructiveHint: true, // 可能造成破坏性更新
idempotentHint: false, // 重复调用有额外效果
openWorldHint: false // 封闭系统操作
}
}
最佳实践
1. 安全考虑
// 输入验证示例
private validateInput(args: any, schema: any): boolean {
// 实现JSON Schema验证
// 清理文件路径
// 验证URL和外部标识符
// 检查参数大小和范围
// 防止命令注入
return true;
}
// 访问控制
private async checkPermissions(toolName: string, args: any): Promise<boolean> {
// 实现身份验证
// 使用适当的授权检查
// 审计工具使用
// 速率限制
return true;
}
2. 错误处理
private handleToolError(error: Error, toolName: string) {
// 记录安全相关错误
// 不向客户端暴露内部错误
// 适当处理超时
// 清理资源
return {
content: [
{
type: "text",
text: `工具 ${toolName} 执行失败: ${error.message}`
}
],
isError: true
};
}
3. 性能优化
// 进度报告示例
private async longRunningOperation(onProgress: (progress: number) => void) {
for (let i = 0; i < 100; i++) {
// 执行操作
await new Promise(resolve => setTimeout(resolve, 10));
onProgress((i + 1) / 100);
}
}
// 超时处理
private async withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
const timeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('操作超时')), timeoutMs)
);
return Promise.race([promise, timeout]);
}
测试策略
// 测试示例
import { jest } from '@jest/globals';
describe('MCP工具测试', () => {
let server: MCPToolServer;
beforeEach(() => {
server = new MCPToolServer();
});
test('计算工具 - 正常情况', async () => {
const result = await server.handleCalculate({ expression: '2 + 3' });
expect(result.content[0].text).toContain('= 5');
});
test('文件读取 - 安全检查', async () => {
try {
await server.handleReadFile({ filepath: '../../../etc/passwd' });
fail('应该抛出安全错误');
} catch (error) {
expect(error.message).toContain('不允许访问上级目录');
}
});
test('数据处理 - 求平均值', async () => {
const result = await server.handleProcessData({
data: [1, 2, 3, 4, 5],
operation: 'average'
});
expect(result.content[0].text).toContain('结果: 3');
});
});
部署和集成
客户端连接示例
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
async function connectToMCPServer() {
const client = new Client(
{
name: "mcp-client",
version: "1.0.0"
},
{
capabilities: {}
}
);
const transport = new StdioClientTransport({
command: "node",
args: ["./dist/server.js"]
});
await client.connect(transport);
// 列出可用工具
const tools = await client.request(
{ method: "tools/list" },
{}
);
console.log("可用工具:", tools);
// 调用工具
const result = await client.request(
{ method: "tools/call" },
{
name: "calculate",
arguments: { expression: "10 * 5 + 2" }
}
);
console.log("计算结果:", result);
}
总结
MCP工具为AI应用提供了强大而灵活的扩展机制。通过TypeScript SDK,开发者可以轻松构建安全、高效的工具服务器,让AI模型能够与现实世界进行丰富的交互。
关键要点:
- 标准化接口:MCP提供统一的工具发现和调用机制
- 安全第一:实施严格的输入验证和访问控制
- 灵活扩展:支持从简单计算到复杂API集成的各种场景
- 开发友好:TypeScript SDK提供完整的类型支持和丰富的功能
随着AI技术的不断发展,MCP工具将成为构建智能、互联AI应用的重要基础设施。掌握MCP工具开发,就是掌握了AI时代的核心技能之一。