MCP Server 封装方案设计
MCP协议摘要:MCP(Model Context Protocol)旨在解决AI应用与外部工具集成的"N×M问题",提供类似USB-C的标准化接口。其三层架构包括Host(AI应用)、Client(连接器)和Server(服务提供方),通过JSON-RPC 2.0实现有状态连接。2025-11-25最新版本新增异步任务、URL诱导、OAuth2等功能,支持结构化输出和增量范围
·
一、架构概览
1.1 MCP
Model Context Protocol(MCP)旨在解决 AI 应用与外部数据源/工具集成的 “N×M 问题”。它就像 AI 世界的"USB-C 接口"——任何符合 MCP 标准的 AI 应用都可以无缝连接任何符合标准的工具服务,无需为每个集成编写定制代码。
1.2 协议架构的三层模型
核心角色说明:
| 角色 | 职责 | 示例 |
|---|---|---|
| Host | LLM 应用,负责协调多个 Client | Claude Desktop、Cursor |
| Client | 维持与 Server 的有状态连接 | 每个 Server 对应一个 Client 实例 |
| Server | 暴露具体能力(Tools/Resources/Prompts) | 文件系统、API 服务、数据库 |
1.3 封装架构
二、协议版本与规范更新
版本演进时间线
三、核心模块设计
3.1 项目结构
mcp-server-wrapper/
├── src/
│ ├── index.ts # 入口文件
│ ├── server/
│ │ ├── McpServer.ts # 服务器主类
│ │ ├── ProtocolHandler.ts # JSON-RPC 协议处理
│ │ ├── Transport.ts # 传输层抽象(支持 Streamable HTTP)
│ │ └── TaskManager.ts # 🆕 异步任务管理(2025-11-25)
│ ├── modules/
│ │ ├── ToolsModule.ts # Tools 模块(支持结构化输出)
│ │ ├── ResourcesModule.ts # Resources 模块(支持订阅)
│ │ ├── PromptsModule.ts # Prompts 模块
│ │ └── SamplingModule.ts # 🆕 采样请求处理
│ ├── types/
│ │ ├── index.ts # 基础类型
│ │ ├── capabilities.ts # 🆕 能力定义(2025-11-25)
│ │ └── tasks.ts # 🆕 任务类型定义
│ ├── utils/
│ │ ├── logger.ts # 日志工具(支持协议日志)
│ │ ├── validator.ts # 输入验证(Zod 集成)
│ │ └── errors.ts # 🆕 MCP 标准错误码
│ └── middleware/
│ ├── auth.ts # 🆕 OAuth2 中间件
│ └── rateLimit.ts # 🆕 速率限制
├── examples/
│ ├── basic-stdio.ts # 基础 stdio 示例
│ ├── http-server.ts # 🆕 Streamable HTTP 示例
│ ├── async-tasks.ts # 🆕 异步任务示例
│ └── elicitation-demo.ts # 🆕 URL 诱导示例
└── package.json
3.2 核心类型定义
// src/types/capabilities.ts
// ⚠️ 关键修正:2025-11-25 完整能力定义
export interface McpCapabilities {
// Server 能力
tools?: {
listChanged?: boolean; // 支持工具列表变更通知
};
resources?: {
subscribe?: boolean; // 支持资源订阅(实时更新)
listChanged?: boolean; // 支持资源列表变更通知
};
prompts?: {
listChanged?: boolean; // 支持提示词列表变更通知
};
sampling?: {
// 🆕 2025-11-25:采样能力需明确声明支持的工具调用
supportedModels?: Array<{
modelName: string;
modelProvider: string;
}>;
};
// 🆕 2025-11-25:实验性 Tasks 支持
tasks?: {
// 指示服务器支持异步任务执行
supported?: boolean;
};
// 🆕 2025-11-25:诱导能力(安全采集用户信息)
elicitation?: {
form?: {}; // 表单模式
url?: {}; // URL 外跳模式
};
// 🆕 2025-11-25:OAuth 支持
oauth?: {
providerMetadata?: string; // .well-known/oauth-authorization-server URL
};
// 🆕 2025-11-25:图标元数据支持
icons?: {
// 服务器可声明图标资源
};
}
// Client 能力(服务器需要了解客户端支持什么)
export interface ClientCapabilities {
roots?: {
listChanged?: boolean; // 支持根目录列表变更通知
};
sampling?: {}; // 客户端支持采样请求
// ... 其他客户端能力
}
// Tool 定义修正:支持 2025-06-18 引入的结构化输出
export interface ToolDefinition {
name: string;
description: string;
inputSchema: {
type: 'object';
properties: Record<string, any>;
required?: string[];
};
// 🆕 2025-06-18:结构化输出模式(替代简单的文本返回)
outputSchema?: {
type: 'object';
properties: Record<string, any>;
};
// 🆕 2025-11-25:工具图标
icon?: {
type: 'emoji' | 'url';
value: string;
};
annotations?: {
title?: string;
readOnlyHint?: boolean;
destructiveHint?: boolean;
idempotentHint?: boolean;
openWorldHint?: boolean;
// 🆕 2025-11-25:性能提示
timeoutHint?: number; // 建议超时时间(毫秒)
memoryHint?: number; // 预估内存使用(MB)
};
}
// 🆕 2025-11-25:实验性 Task 定义
export interface Task {
id: string;
status: 'queued' | 'working' | 'input_required' | 'completed' | 'failed' | 'cancelled';
createdAt: string;
updatedAt: string;
result?: any;
error?: {
code: number;
message: string;
};
// 进度追踪
progress?: {
current: number;
total: number;
message?: string;
};
}
// 标准错误码(原方案完全缺失)
export enum McpErrorCode {
// JSON-RPC 标准错误
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
// MCP 特定错误(2025-11-25)
ResourceNotFound = -32002,
ResourceAccessDenied = -32003,
ToolNotFound = -32004,
ToolExecutionError = -32005,
URLElicitationRequired = -32042, // 🆕 URL 诱导必需
TaskNotFound = -32050, // 🆕 任务相关
TaskCancelled = -32051,
}
3.3 MCP 服务器主类
// src/server/McpServer.ts
import {
McpCapabilities,
ClientCapabilities,
ToolDefinition,
ResourceDefinition,
PromptDefinition,
Task,
McpErrorCode
} from '../types';
import { ProtocolHandler } from './ProtocolHandler';
import { Transport, StdioTransport, StreamableHTTPTransport } from './Transport';
import { EventEmitter } from 'events';
export interface ServerOptions {
name: string;
version: string;
transport?: 'stdio' | 'http';
port?: number; // HTTP 模式端口
capabilities?: McpCapabilities;
}
export class McpServer extends EventEmitter {
private protocol: ProtocolHandler;
private transport: Transport;
private options: ServerOptions; // 🐛 原方案缺失:保存配置
private clientCapabilities?: ClientCapabilities;
// 存储注册项
private tools = new Map<string, ToolDefinition & { handler: Function }>();
private resources = new Map<string, ResourceDefinition & { handler: Function }>();
private resourceTemplates = new Map<string, { pattern: RegExp; handler: Function }>();
private prompts = new Map<string, PromptDefinition & { handler: Function }>();
// 🆕 2025-11-25:任务管理
private tasks = new Map<string, Task>();
private taskSubscriptions = new Map<string, Set<string>>(); // taskId -> clientIds
constructor(options: ServerOptions) {
super();
this.options = options;
// 初始化传输层
if (options.transport === 'http') {
this.transport = new StreamableHTTPTransport(options.port || 3000);
} else {
this.transport = new StdioTransport();
}
this.protocol = new ProtocolHandler(this);
this.setupHandlers();
}
private setupHandlers() {
// 生命周期
this.protocol.on('initialize', this.handleInitialize.bind(this));
this.protocol.on('initialized', this.handleInitialized.bind(this));
// Tools
this.protocol.on('tools/list', this.handleToolsList.bind(this));
this.protocol.on('tools/call', this.handleToolsCall.bind(this));
// Resources
this.protocol.on('resources/list', this.handleResourcesList.bind(this));
this.protocol.on('resources/read', this.handleResourcesRead.bind(this));
this.protocol.on('resources/subscribe', this.handleResourcesSubscribe.bind(this));
this.protocol.on('resources/unsubscribe', this.handleResourcesUnsubscribe.bind(this));
// Prompts
this.protocol.on('prompts/list', this.handlePromptsList.bind(this));
this.protocol.on('prompts/get', this.handlePromptsGet.bind(this));
// 🆕 2025-11-25:Tasks(实验性)
this.protocol.on('tasks/create', this.handleTaskCreate.bind(this));
this.protocol.on('tasks/get', this.handleTaskGet.bind(this));
this.protocol.on('tasks/cancel', this.handleTaskCancel.bind(this));
// 🆕 2025-11-25:Sampling(当 Client 支持时)
this.protocol.on('sampling/createMessage', this.handleSampling.bind(this));
// 日志
this.protocol.on('logging/setLevel', this.handleSetLogLevel.bind(this));
}
// ========== 注册方法(链式调用) ==========
registerTool(tool: ToolDefinition, handler: Function) {
this.tools.set(tool.name, { ...tool, handler });
this.emit('toolsChanged');
return this;
}
registerResource(resource: ResourceDefinition, handler: Function) {
this.resources.set(resource.uri, { ...resource, handler });
this.emit('resourcesChanged');
return this;
}
/**
* 🆕 资源模板注册(支持动态 URI)
* 例如:file:///{path} 匹配 file:///home/user/doc.txt
*/
registerResourceTemplate(
template: string, // 如 "file:///{path}"
meta: Omit<ResourceDefinition, 'uri'>,
handler: (params: Record<string, string>) => Promise<any>
) {
// 将模板转换为正则表达式
const pattern = new RegExp(
'^' + template.replace(/\{(\w+)\}/g, '(?<$1>[^/]+)') + '$'
);
this.resourceTemplates.set(template, { pattern, handler });
return this;
}
registerPrompt(prompt: PromptDefinition, handler: Function) {
this.prompts.set(prompt.name, { ...prompt, handler });
this.emit('promptsChanged');
return this;
}
// ========== 核心协议处理 ==========
private async handleInitialize(params: {
protocolVersion: string;
capabilities: ClientCapabilities;
clientInfo: { name: string; version: string };
}) {
this.clientCapabilities = params.capabilities;
// 协议版本协商:优先使用客户端版本,但不超过服务器支持的最大版本
const supportedVersions = ['2025-11-25', '2025-03-26', '2024-11-05'];
const negotiatedVersion = supportedVersions.find(v => v === params.protocolVersion)
|| '2025-03-26'; // 默认回退
return {
protocolVersion: negotiatedVersion,
capabilities: this.options.capabilities || this.inferCapabilities(),
serverInfo: {
name: this.options.name,
version: this.options.version
}
};
}
private async handleToolsCall(params: {
name: string;
arguments?: object;
// 🆕 2025-11-25:任务提示(用于长时间运行工具)
taskHint?: boolean;
}) {
const tool = this.tools.get(params.name);
if (!tool) {
throw this.createError(McpErrorCode.ToolNotFound, `Unknown tool: ${params.name}`);
}
try {
// 如果工具可能长时间运行且客户端支持 Tasks,创建异步任务
if (params.taskHint && this.clientCapabilities && tool.annotations?.timeoutHint && tool.annotations.timeoutHint > 5000) {
return await this.executeAsTask(tool, params.arguments);
}
const result = await tool.handler(params.arguments);
// 🆕 支持结构化输出(2025-06-18)
if (tool.outputSchema) {
return { output: result }; // 结构化输出
}
// 传统文本输出
return {
content: Array.isArray(result) ? result : [{ type: 'text', text: String(result) }],
isError: false
};
} catch (error) {
throw this.createError(
McpErrorCode.ToolExecutionError,
error instanceof Error ? error.message : 'Tool execution failed'
);
}
}
/**
* 🆕 2025-11-25:异步任务执行
*/
private async executeAsTask(tool: any, args: any): Promise<{ taskId: string }> {
const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const task: Task = {
id: taskId,
status: 'queued',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
this.tasks.set(taskId, task);
// 异步执行
setImmediate(async () => {
task.status = 'working';
task.updatedAt = new Date().toISOString();
try {
const result = await tool.handler(args);
task.status = 'completed';
task.result = result;
task.updatedAt = new Date().toISOString();
// 通知订阅者
this.notifyTaskUpdate(taskId);
} catch (error) {
task.status = 'failed';
task.error = {
code: McpErrorCode.ToolExecutionError,
message: error instanceof Error ? error.message : 'Execution failed'
};
task.updatedAt = new Date().toISOString();
this.notifyTaskUpdate(taskId);
}
});
return { taskId };
}
// ========== 通知方法(修正版) ==========
notifyToolsListChanged() {
// 🐛 原方案错误:缺少 jsonrpc 字段
this.transport.send({
jsonrpc: '2.0',
method: 'notifications/tools/list_changed',
params: {}
});
}
notifyResourcesListChanged() {
this.transport.send({
jsonrpc: '2.0',
method: 'notifications/resources/list_changed',
params: {}
});
}
private notifyTaskUpdate(taskId: string) {
const task = this.tasks.get(taskId);
if (!task) return;
this.transport.send({
jsonrpc: '2.0',
method: 'notifications/tasks/updated',
params: { task }
});
}
// ========== 辅助方法 ==========
private createError(code: number, message: string) {
return { code, message };
}
private inferCapabilities(): McpCapabilities {
return {
tools: { listChanged: true },
resources: {
listChanged: true,
subscribe: true // 假设支持订阅
},
prompts: { listChanged: true }
};
}
async start() {
await this.transport.connect();
this.transport.onMessage((msg) => this.protocol.handleMessage(msg));
console.error(`✅ MCP Server "${this.options.name}" v${this.options.version} started`);
}
}
3.4 协议处理器
// src/server/ProtocolHandler.ts
import { JsonRpcRequest, JsonRpcResponse, JsonRpcNotification } from '../types';
import { McpServer } from './McpServer';
import { EventEmitter } from 'events';
export class ProtocolHandler extends EventEmitter {
private handlers = new Map<string, Function>();
private server: McpServer;
constructor(server: McpServer) {
super();
this.server = server;
}
on(method: string, handler: Function) {
this.handlers.set(method, handler);
}
async handleMessage(message: string) {
let requestId: string | number | undefined;
try {
const data = JSON.parse(message);
requestId = data.id;
// 验证 JSON-RPC 格式
if (data.jsonrpc !== '2.0') {
throw this.createError(-32600, 'Invalid JSON-RPC version');
}
// 区分请求和通知
if (data.id === undefined) {
// 通知(Notification):无需回复
await this.handleNotification(data as JsonRpcNotification);
} else {
// 请求(Request):必须回复
const response = await this.handleRequest(data as JsonRpcRequest);
this.server['transport'].send(JSON.stringify(response));
}
} catch (error) {
// 🐛 原方案错误:错误时无法获取 id,现已修正
const errorResponse: JsonRpcResponse = {
jsonrpc: '2.0',
id: requestId, // 现在可以正确获取
error: {
code: error.code || -32603,
message: error.message || 'Internal error'
}
};
this.server['transport'].send(JSON.stringify(errorResponse));
}
}
private async handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
const handler = this.handlers.get(request.method);
if (!handler) {
throw this.createError(-32601, `Method not found: ${request.method}`);
}
const result = await handler(request.params);
return {
jsonrpc: '2.0',
id: request.id,
result: result || {}
};
}
private async handleNotification(notification: JsonRpcNotification) {
// 处理客户端发来的通知(如取消请求)
if (notification.method === 'notifications/cancelled') {
// 处理取消逻辑
this.emit('cancelled', notification.params);
}
// 其他通知...
}
private createError(code: number, message: string) {
const error = new Error(message) as any;
error.code = code;
return error;
}
}
3.5 传输层实现(Streamable HTTP 支持)
// src/server/Transport.ts
import { EventEmitter } from 'events';
import * as readline from 'readline';
import * as http from 'http';
import * as url from 'url';
export abstract class Transport extends EventEmitter {
abstract connect(): Promise<void>;
abstract send(message: object): void;
abstract onMessage(callback: (message: string) => void): void;
abstract close(): Promise<void>;
}
/**
* Standard I/O 传输(本地进程通信)
*/
export class StdioTransport extends Transport {
private messageCallback?: (message: string) => void;
async connect(): Promise<void> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
rl.on('line', (line) => {
if (this.messageCallback && line.trim()) {
this.messageCallback(line);
}
});
// 处理优雅关闭
process.on('SIGINT', () => this.close());
process.on('SIGTERM', () => this.close());
}
onMessage(callback: (message: string) => void) {
this.messageCallback = callback;
}
send(message: object) {
const json = JSON.stringify(message);
process.stdout.write(json + '\n');
}
async close() {
process.exit(0);
}
}
/**
* 🆕 Streamable HTTP 传输(2025-11-25 标准)
* 替代旧版 SSE,支持双向流式通信
*/
export class StreamableHTTPTransport extends Transport {
private server?: http.Server;
private sessions = new Map<string, http.ServerResponse>();
private messageCallback?: (message: string) => void;
private port: number;
constructor(port: number) {
super();
this.port = port;
}
async connect(): Promise<void> {
this.server = http.createServer((req, res) => this.handleRequest(req, res));
await new Promise<void>((resolve) => {
this.server!.listen(this.port, () => {
console.error(`🌐 Streamable HTTP server listening on port ${this.port}`);
resolve();
});
});
}
private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse) {
const parsedUrl = url.parse(req.url!, true);
const sessionId = parsedUrl.query.sessionId as string || 'default';
// CORS 支持(2025-11-25 要求)
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// GET 请求:建立 SSE 流(服务器→客户端)
if (req.method === 'GET' && parsedUrl.pathname === '/mcp') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Mcp-Session-Id': sessionId
});
this.sessions.set(sessionId, res);
// 发送会话 ID
res.write(`data: ${JSON.stringify({ type: 'session', id: sessionId })}\n\n`);
req.on('close', () => {
this.sessions.delete(sessionId);
});
}
// POST 请求:接收客户端消息(客户端→服务器)
else if (req.method === 'POST' && parsedUrl.pathname === '/mcp') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
if (this.messageCallback) {
this.messageCallback(body);
}
res.writeHead(202); // Accepted
res.end();
});
}
else {
res.writeHead(404);
res.end();
}
}
onMessage(callback: (message: string) => void) {
this.messageCallback = callback;
}
send(message: object) {
const data = JSON.stringify(message);
// 广播到所有会话(或根据 message 中的 sessionId 定向发送)
for (const [id, res] of this.sessions) {
res.write(`data: ${data}\n\n`);
}
}
async close(): Promise<void> {
for (const res of this.sessions.values()) {
res.end();
}
this.sessions.clear();
return new Promise((resolve) => {
this.server?.close(() => resolve());
});
}
}
四、使用示例
4.1 基础服务器(stdio 模式)
// examples/basic-stdio.ts
import { McpServer } from '../src';
import { z } from 'zod'; // 🆕 使用 Zod 进行运行时验证
const server = new McpServer({
name: 'weather-server',
version: '1.0.0',
transport: 'stdio',
capabilities: {
tools: { listChanged: true },
resources: { listChanged: true, subscribe: true }
}
});
// 使用 Zod 定义工具(类型安全 + 运行时验证)
server.registerTool(
{
name: 'get_weather',
description: '获取指定城市的当前天气',
inputSchema: {
type: 'object',
properties: {
location: {
type: 'string',
description: '城市名称,如"北京"'
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: '温度单位'
}
},
required: ['location']
},
// 🆕 2025-06-18:结构化输出
outputSchema: {
type: 'object',
properties: {
temperature: { type: 'number' },
condition: { type: 'string' },
humidity: { type: 'number' }
}
},
annotations: {
title: '天气查询',
readOnlyHint: true, // 只读操作
idempotentHint: true, // 幂等(可安全重试)
timeoutHint: 3000 // 3秒超时建议
}
},
async (args: { location: string; unit?: string }) => {
// 模拟 API 调用
const weather = {
temperature: 25,
condition: '晴朗',
humidity: 60
};
// 返回结构化数据(根据 outputSchema)
return weather;
}
);
// 注册资源(带模板)
server.registerResourceTemplate(
'weather://{city}/current',
{
name: '城市天气数据',
description: '指定城市的实时天气',
mimeType: 'application/json'
},
async (params: { city: string }) => {
return {
contents: [{
uri: `weather://${params.city}/current`,
mimeType: 'application/json',
text: JSON.stringify({ temp: 25, condition: 'sunny' })
}]
};
}
);
await server.start();
4.2 Streamable HTTP 服务器(远程部署)
// examples/http-server.ts
import { McpServer } from '../src';
const server = new McpServer({
name: 'remote-api-server',
version: '2.0.0',
transport: 'http',
port: 3000,
capabilities: {
tools: { listChanged: true },
// 🆕 2025-11-25:声明支持的任务能力
tasks: { supported: true },
// 🆕 2025-11-25:诱导能力(OAuth 登录)
elicitation: { url: {} }
}
});
// 长时间运行任务示例
server.registerTool(
{
name: 'generate_report',
description: '生成数据分析报告(可能需要几分钟)',
inputSchema: {
type: 'object',
properties: {
dataSource: { type: 'string' },
analysisType: { type: 'string' }
}
},
annotations: {
timeoutHint: 120000, // 2分钟
memoryHint: 512 // 预估 512MB 内存
}
},
async (args) => {
// 实际实现会在后台运行
return { reportUrl: 'https://...' };
}
);
await server.start();
4.3 装饰器语法(高级封装)
// examples/decorator-pattern.ts
import { McpServer, ToolDefinition } from '../src';
import 'reflect-metadata';
const TOOL_METADATA_KEY = Symbol('tool');
// 改进的装饰器:自动收集元数据
function Tool(def: ToolDefinition) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 保存工具定义到类的元数据
const existing = Reflect.getMetadata(TOOL_METADATA_KEY, target.constructor) || [];
existing.push({
name: def.name,
definition: def,
handler: descriptor.value.bind(target)
});
Reflect.defineMetadata(TOOL_METADATA_KEY, existing, target.constructor);
return descriptor;
};
}
class DataAnalysisTools {
@Tool({
name: 'analyze_csv',
description: '分析 CSV 文件并返回统计信息',
inputSchema: {
type: 'object',
properties: {
filePath: { type: 'string' },
columns: { type: 'array', items: { type: 'string' } }
},
required: ['filePath']
}
})
async analyzeCSV(args: { filePath: string; columns?: string[] }) {
// 实现...
return { rowCount: 1000, columns: args.columns || [] };
}
@Tool({
name: 'create_chart',
description: '根据数据创建可视化图表',
inputSchema: {
type: 'object',
properties: {
data: { type: 'object' },
chartType: { enum: ['bar', 'line', 'pie'] }
}
}
})
async createChart(args: { data: any; chartType: string }) {
// 实现...
return { chartUrl: 'https://...' };
}
}
// 自动注册所有装饰过的工具
const server = new McpServer({
name: 'analysis-server',
version: '1.0.0',
transport: 'stdio'
});
const toolsInstance = new DataAnalysisTools();
const toolMetadata = Reflect.getMetadata(TOOL_METADATA_KEY, DataAnalysisTools) || [];
for (const tool of toolMetadata) {
server.registerTool(tool.definition, tool.handler);
}
await server.start();
五、高级特性实现
5.1 异步任务系统(2025-11-25 实验性)
5.2 资源订阅与实时通知
// 资源订阅实现示例
class ResourcesModule {
private subscriptions = new Map<string, Set<string>>(); // uri -> clientIds
private watchers = new Map<string, any>(); // uri -> fs.Watcher
async subscribe(uri: string, clientId: string) {
if (!this.subscriptions.has(uri)) {
this.subscriptions.set(uri, new Set());
// 启动文件监听(示例)
this.watchers.set(uri, fs.watch(uri, () => {
this.notifyUpdate(uri);
}));
}
this.subscriptions.get(uri)!.add(clientId);
}
private notifyUpdate(uri: string) {
const clients = this.subscriptions.get(uri);
if (!clients) return;
// 发送通知给所有订阅者
for (const clientId of clients) {
this.server.notifyResourceUpdated(uri, clientId);
}
}
}
5.3 URL 诱导(安全外跳)
2025-11-25 引入的 Elicitation 能力允许服务器安全地引导用户到外部 URL(如 OAuth 授权):
// 诱导用户进行 OAuth 登录
server.registerTool(
{
name: 'connect_github',
description: '连接 GitHub 账号',
inputSchema: { type: 'object', properties: {} }
},
async () => {
// 当需要授权时,返回诱导错误
throw {
code: -32042, // URLElicitationRequired
message: '需要 GitHub 授权',
data: {
elicitationId: 'auth_123',
url: 'https://github.com/login/oauth/authorize?client_id=...',
title: '授权访问 GitHub',
description: '请点击链接完成 GitHub 授权'
}
};
}
);
六、安全与最佳实践(增强版)
以下是基于 2025-11-25 规范的详细实践:
6.1 输入验证策略
// src/utils/validator.ts
import { z } from 'zod';
import { ToolDefinition } from '../types';
export function createValidator(schema: any) {
return z.object(schema);
}
// 工具参数自动验证中间件
export function withValidation(
tool: ToolDefinition,
handler: Function
) {
const validator = z.object(tool.inputSchema.properties as any);
return async (args: any) => {
// 1. 类型验证
const result = validator.safeParse(args);
if (!result.success) {
throw {
code: -32602, // InvalidParams
message: `参数验证失败: ${result.error.message}`
};
}
// 2. 注入检查(防止命令注入)
if (JSON.stringify(args).match(/[;&|`$]/)) {
throw { code: -32602, message: '检测到潜在的危险字符' };
}
return handler(result.data);
};
}
6.2 权限控制矩阵
| 操作类型 | 风险等级 | 控制措施 |
|---|---|---|
| 文件读取 | 🟡 中 | 限制在指定根目录(Roots)内 |
| 文件写入 | 🔴 高 | 需要用户确认,记录审计日志 |
| 网络请求 | 🔴 高 | 域名白名单,速率限制 |
| 代码执行 | ⚫ 极高 | 沙箱环境,完全禁止或人工审核 |
| 数据库查询 | 🟡 中 | 只读账号,SQL 注入检查 |
6.3 OAuth2 集成(2025-11-25)
// 服务器能力声明
const capabilities = {
oauth: {
providerMetadata: 'https://auth.example.com/.well-known/oauth-authorization-server'
}
};
// 动态客户端注册流程
server.on('oauth/register', async (params) => {
// 实现动态客户端注册(RFC 7591)
return {
client_id: 'generated_client_id',
client_secret: 'generated_secret',
redirect_uris: params.redirect_uris
};
});
七、NPM 包发布结构(优化版)
{
"name": "@yourorg/mcp-server-wrapper",
"version": "1.0.0",
"description": "现代化的 MCP 服务器封装 SDK,支持 2025-11-25 规范",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./server": {
"import": "./dist/server/index.js",
"types": "./dist/server/index.d.ts"
},
"./transports": {
"import": "./dist/transports/index.js",
"types": "./dist/transports/index.d.ts"
}
},
"scripts": {
"build": "tsc --project tsconfig.build.json",
"dev": "tsx watch src/index.ts",
"test": "vitest",
"lint": "eslint src/**/*.ts",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"zod": "^3.25.0",
"events": "^3.3.0"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsx": "^4.0.0",
"typescript": "^5.3.0",
"vitest": "^1.0.0"
},
"engines": {
"node": ">=18.0.0"
},
"keywords": [
"mcp",
"model-context-protocol",
"ai",
"llm",
"anthropic",
"claude"
],
"license": "MIT"
}
更多推荐



所有评论(0)