引言
随着MCP Server提供的工具数量增长,如何高效地注册、组织和管理工具成为关键问题。本文将介绍从单一工具到大规模工具系统的架构演进。
工具注册基础
静态注册模式
最基本的工具注册方式——在代码中硬编码工具定义:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
const server = new Server(
{ name: "tool-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// 静态工具定义
const toolDefinitions = [
{
name: "search_docs",
description: "搜索文档内容",
inputSchema: {
type: "object",
properties: {
query: { type: "string" },
},
required: ["query"],
},
},
{
name: "get_file",
description: "读取文件内容",
inputSchema: {
type: "object",
properties: {
path: { type: "string" },
},
required: ["path"],
},
},
];
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: toolDefinitions,
}));
适用场景: 工具数量固定、规模较小(< 5个)的Server。
注册器模式
引入工具注册中心,集中管理工具生命周期:
// src/registry/tool-registry.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
interface ToolDefinition {
name: string;
description: string;
inputSchema: object;
handler: (args: any) => Promise<ToolResult>;
}
class ToolRegistry {
private tools: Map<string, ToolDefinition> = new Map();
// 注册一个工具
register(tool: ToolDefinition): void {
if (this.tools.has(tool.name)) {
throw new Error(`工具 '${tool.name}' 已注册`);
}
this.tools.set(tool.name, tool);
}
// 批量注册工具
registerAll(tools: ToolDefinition[]): void {
for (const tool of tools) {
this.register(tool);
}
}
// 获取所有工具定义
getDefinitions(): ToolDefinition[] {
return Array.from(this.tools.values()).map(({ handler, ...def }) => def);
}
// 执行工具
async execute(name: string, args: any): Promise<ToolResult> {
const tool = this.tools.get(name);
if (!tool) {
throw new Error(`工具 '${name}' 未找到`);
}
return tool.handler(args);
}
// 注销工具
unregister(name: string): void {
this.tools.delete(name);
}
// 检查工具是否存在
has(name: string): boolean {
return this.tools.has(name);
}
}
export const toolRegistry = new ToolRegistry();
装饰器模式(TypeScript)
// 使用装饰器进行声明式注册
function tool(config: {
name: string;
description: string;
inputSchema: object;
}) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
toolRegistry.register({
...config,
handler: descriptor.value.bind(target),
});
return descriptor;
};
}
class SearchTools {
@tool({
name: "search_docs",
description: "搜索文档",
inputSchema: {
type: "object",
properties: {
query: { type: "string" },
},
required: ["query"],
},
})
async searchDocs(args: { query: string }) {
return {
content: [{ type: "text", text: `搜索结果: ${args.query}` }],
isError: false,
};
}
@tool({
name: "search_code",
description: "搜索代码",
inputSchema: {
type: "object",
properties: {
pattern: { type: "string" },
},
required: ["pattern"],
},
})
async searchCode(args: { pattern: string }) {
return {
content: [{ type: "text", text: `代码搜索结果: ${args.pattern}` }],
isError: false,
};
}
}
动态注册与热加载
文件系统驱动的动态注册
import fs from "fs/promises";
import path from "path";
class DynamicToolLoader {
private toolsDir: string;
constructor(toolsDir: string) {
this.toolsDir = toolsDir;
}
async loadToolsFromFiles(): Promise<ToolDefinition[]> {
const files = await fs.readdir(this.toolsDir);
const tools: ToolDefinition[] = [];
for (const file of files) {
if (!file.endsWith(".tool.js") && !file.endsWith(".tool.ts")) {
continue;
}
try {
const module = await import(path.join(this.toolsDir, file));
if (module.default && module.default.tool) {
tools.push(module.default.tool);
}
} catch (error) {
console.error(`加载工具文件 ${file} 失败:`, error);
}
}
return tools;
}
}
// 工具文件示例: search.tool.ts
export default {
tool: {
name: "search",
description: "搜索内容",
inputSchema: {
type: "object",
properties: {
query: { type: "string" },
},
required: ["query"],
},
handler: async (args: { query: string }) => ({
content: [{ type: "text", text: `搜索: ${args.query}` }],
isError: false,
}),
},
};
工具分组与命名空间
命名空间策略
为大型工具集引入命名空间管理:
// 工具命名空间
class NamespacedRegistry {
private namespaces: Map<string, ToolRegistry> = new Map();
// 创建命名空间
createNamespace(name: string): ToolRegistry {
if (this.namespaces.has(name)) {
throw new Error(`命名空间 '${name}' 已存在`);
}
const registry = new ToolRegistry();
this.namespaces.set(name, registry);
return registry;
}
// 获取命名空间下的所有工具
getTools(namespace?: string): ToolDefinition[] {
if (namespace) {
const registry = this.namespaces.get(namespace);
return registry ? registry.getDefinitions() : [];
}
// 返回所有工具
const allTools: ToolDefinition[] = [];
for (const [, registry] of this.namespaces) {
allTools.push(...registry.getDefinitions());
}
return allTools;
}
// 执行命名空间下的工具
async execute(fullName: string, args: any): Promise<ToolResult> {
const parts = fullName.split(".");
if (parts.length === 2) {
const [namespace, toolName] = parts;
const registry = this.namespaces.get(namespace);
if (registry) {
return registry.execute(toolName, args);
}
}
throw new Error(`工具 '${fullName}' 未找到`);
}
}
// 使用示例
const registry = new NamespacedRegistry();
// 文件系统工具组
const fsNs = registry.createNamespace("fs");
fsNs.register({
name: "read",
description: "读取文件",
inputSchema: { /* ... */ },
handler: async (args) => { /* ... */ },
});
fsNs.register({
name: "write",
description: "写入文件",
inputSchema: { /* ... */ },
handler: async (args) => { /* ... */ },
});
// 数据库工具组
const dbNs = registry.createNamespace("db");
dbNs.register({
name: "query",
description: "执行查询",
inputSchema: { /* ... */ },
handler: async (args) => { /* ... */ },
});
// 工具列表: "fs.read", "fs.write", "db.query"
参数校验中间件
实现参数校验层
import { z } from "zod";
// 参数校验中间件
function withValidation(schema: z.ZodSchema) {
return (handler: Function) => {
return async (args: unknown) => {
try {
const validated = schema.parse(args);
return await handler(validated);
} catch (error) {
if (error instanceof z.ZodError) {
return {
content: [{
type: "text",
text: `参数验证失败:\n${error.errors
.map(e => ` - ${e.path.join(".")}: ${e.message}`)
.join("\n")}`
}],
isError: true,
};
}
throw error;
}
};
};
}
// 使用
const searchSchema = z.object({
query: z.string().min(1, "查询不能为空").max(200, "查询太长"),
limit: z.number().int().min(1).max(100).default(20),
offset: z.number().int().min(0).default(0),
});
const searchHandler = withValidation(searchSchema)(async (args) => {
// args 已通过Zod校验,类型安全
return {
content: [{ type: "text", text: `搜索 ${args.query}` }],
isError: false,
};
});
运行时管理
工具统计与监控
// 工具调用统计
class ToolStats {
private calls: Map<string, {
count: number;
errors: number;
totalDuration: number;
lastCalled: Date;
}> = new Map();
recordCall(name: string, duration: number, isError: boolean): void {
const stats = this.calls.get(name) || {
count: 0,
errors: 0,
totalDuration: 0,
lastCalled: new Date(),
};
stats.count++;
stats.totalDuration += duration;
if (isError) stats.errors++;
stats.lastCalled = new Date();
this.calls.set(name, stats);
}
getReport(): string {
let report = "工具调用统计:\n";
report += "=".repeat(40) + "\n";
for (const [name, stats] of this.calls) {
const avgDuration = stats.totalDuration / stats.count;
report += `${name}:\n`;
report += ` 调用次数: ${stats.count}\n`;
report += ` 错误次数: ${stats.errors}\n`;
report += ` 平均耗时: ${avgDuration.toFixed(2)}ms\n`;
report += ` 最后调用: ${stats.lastCalled.toISOString()}\n`;
}
return report;
}
}
限流控制
// 简单的令牌桶限流器
class RateLimiter {
private tokens: Map<string, {
count: number;
resetAt: number;
}> = new Map();
constructor(
private maxTokens: number = 10,
private windowMs: number = 60000
) {}
checkLimit(toolName: string): boolean {
const now = Date.now();
let entry = this.tokens.get(toolName);
if (!entry || now > entry.resetAt) {
entry = { count: 0, resetAt: now + this.windowMs };
this.tokens.set(toolName, entry);
}
entry.count++;
return entry.count <= this.maxTokens;
}
}
// 在调用处理中使用
const rateLimiter = new RateLimiter(10, 60000); // 每分钟10次
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name } = request.params;
if (!rateLimiter.checkLimit(name)) {
return {
content: [{
type: "text",
text: `工具 '${name}' 调用频率过高,请稍后再试`,
}],
isError: true,
};
}
// 正常处理...
});
最佳实践
工具组织架构
src/
├── tools/
│ ├── index.ts # 统一注册入口
│ ├── file-tools/ # 文件工具组
│ │ ├── read.ts
│ │ ├── write.ts
│ │ └── search.ts
│ ├── db-tools/ # 数据库工具组
│ │ ├── query.ts
│ │ └── schema.ts
│ └── api-tools/ # API工具组
│ ├── http.ts
│ └── websocket.ts
├── registry/
│ ├── tool-registry.ts
│ └── validation.ts
├── middleware/
│ ├── rate-limit.ts
│ └── logging.ts
└── index.ts
工具管理清单
| 场景 | 推荐方案 |
|---|---|
| < 5个工具 | 静态注册,代码硬编码 |
| 5-20个工具 | 注册器模式 + 文件组织 |
| 20+个工具 | 命名空间 + 动态加载 |
| 需要权限控制 | 中间件链模式 |
| 需要监控 | 调用统计 + 日志 |
| 需要热更新 | 文件系统监听 + 动态注册 |
总结
工具注册与管理是构建生产级MCP Server的基础:
| 模式 | 适用场景 | 优势 |
|---|---|---|
| 静态注册 | 简单Server | 简单直接 |
| 注册器模式 | 中型Server | 集中管理 |
| 命名空间 | 大型Server | 逻辑清晰 |
| 动态加载 | 插件化体系 | 高度灵活 |
下一步学习建议:
本文最后更新于 2024-07-06。