返回市场
MCP代理封装器

MCP代理封装器

作者:mcp-plugins4 星标更新:2025-08-27

项目介绍

技术文档摘要

MCP Proxy Wrapper

一个轻量级、强大的包装器,用于Model Context Protocol (MCP)服务器,提供全面的钩子系统,可以在不更改现有服务器代码的情况下拦截、监控和修改工具调用。

npm 版本 许可证:MIT TypeScript

🚀 功能

  • 🔧 零改动包装:无需更改代码即可包装现有的MCP服务器
  • 🪝 强大的钩子系统:在工具调用前后执行自定义逻辑
  • 🔌 插件架构:可扩展的插件系统,实现可复用的功能
  • 🔄 参数与结果修改:实时转换输入和输出
  • ⚡ 短路能力:使用自定义响应跳过工具执行
  • 🧠 智能插件包含:LLM摘要和聊天记忆插件
  • 📊 全面日志记录:内置监控和调试支持
  • 🧪 完全测试:通过真实MCP客户端-服务器验证的100%测试覆盖率
  • 📘 TypeScript优先:完整的TypeScript支持,确保类型安全
  • 🌐 通用兼容性:适用于任何版本1.6.0及以上的MCP SDK服务器

📦 安装

npm install mcp-proxy-wrapper

🎯 快速开始

基本用法

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { wrapWithProxy } from 'mcp-proxy-wrapper';
import { z } from 'zod';

// 创建你的现有MCP服务器
const server = new McpServer({
  name: '我的服务器',
  version: '1.0.0'
});

// 使用代理功能包装它
const proxiedServer = await wrapWithProxy(server, {
  hooks: {
    // 监控所有工具调用
    beforeToolCall: async (context) => {
      console.log(`🔧 调用工具: ${context.toolName}`);
      console.log(`📝 参数:`, context.args);
    },
    
    // 处理结果
    afterToolCall: async (context, result) => {
      console.log(`✅ 工具完成: ${context.toolName}`);
      return result; // 不变地传递
    }
  },
  debug: true // 启用详细日志
});

// 正常注册工具
proxiedServer.tool('greet', { name: z.string() }, async (args) => {
  return {
    content: [{ type: 'text', text: `你好,${args.name}!` }]
  };
});

🔌 插件系统

MCP Proxy Wrapper 包含一个强大的插件架构,允许您创建可复用、组合的功能。

使用内置插件

import { LLMSummarizationPlugin, ChatMemoryPlugin } from 'mcp-proxy-wrapper';

const summarizationPlugin = new LLMSummarizationPlugin();
const memoryPlugin = new ChatMemoryPlugin();

const proxiedServer = await wrapWithProxy(server, {
  plugins: [
    summarizationPlugin,
    memoryPlugin
  ]
});

LLM 摘要插件

自动使用AI对长工具响应进行摘要:

import { LLMSummarizationPlugin } from 'mcp-proxy-wrapper';

const plugin = new LLMSummarizationPlugin();
plugin.updateConfig({
  options: {
    provider: 'openai', // 或者'mock'用于测试
    openaiApiKey: process.env.OPENAI_API_KEY,
    model: 'gpt-4o-mini',
    minContentLength: 500,
    summarizeTools: ['research', 'analyze', 'fetch-data'],
    saveOriginal: true // 存储原始响应以供检索
  }
});

const proxiedServer = await wrapWithProxy(server, {
  plugins: [plugin]
});

// 工具响应会自动被摘要
const result = await client.callTool({
  name: 'research',
  arguments: { topic: '人工智能' }
});

console.log(result._meta.summarized); // true
console.log(result._meta.originalLength); // 2000
console.log(result._meta.summaryLength); // 200
console.log(result.content[0].text); // "摘要:..."

聊天记忆插件

为保存的工具响应提供对话接口:

import { ChatMemoryPlugin } from 'mcp-proxy-wrapper';

const memoryPlugin = new ChatMemoryPlugin();
memoryPlugin.updateConfig({
  options: {
    provider: 'openai',
    openaiApiKey: process.env.OPENAI_API_KEY,
    saveResponses: true,
    enableChat: true,
    maxEntries: 1000
  }
});

const proxiedServer = await wrapWithProxy(server, {
  plugins: [memoryPlugin]
});

// 工具响应会自动保存
await client.callTool({
  name: 'research',
  arguments: { topic: '气候变化', userId: 'user123' }
});

// 与保存的数据对话
const sessionId = await memoryPlugin.startChatSession('user123');
const response = await memoryPlugin.chatWithMemory(
  sessionId,
  "我关于气候变化研究了什么?",
  'user123'
);
console.log(response); // 基于保存的研究的AI响应

创建自定义插件

import { BasePlugin, PluginContext, ToolCallResult } from 'mcp-proxy-wrapper';

class MyCustomPlugin extends BasePlugin {
  name = 'my-custom-plugin';
  version = '1.0.0';
  
  async afterToolCall(context: PluginContext, result: ToolCallResult): Promise<ToolCallResult> {
    // 添加自定义元数据
    return {
      ...result,
      result: {
        ...result.result,
        _meta: {
          ...result.result._meta,
          processedBy: this.name,
          customField: '自定义值'
        }
      }
    };
  }
}

const proxiedServer = await wrapWithProxy(server, {
  plugins: [new MyCustomPlugin()]
});

插件配置

const plugin = new LLMSummarizationPlugin();

// 运行时配置更新
plugin.updateConfig({
  enabled: true,
  priority: 10,
  options: {
    minContentLength: 200,
    provider: 'openai'
  },
  includeTools: ['research', 'analyze'], // 只有这些工具
  excludeTools: ['chat'], // 跳过这些工具
  debug: true
});

高级钩子示例

1. 参数修改

const proxiedServer = wrapWithProxy(server, {
  hooks: {
    beforeToolCall: async (context) => {
      // 为所有工具调用添加时间戳
      context.args.timestamp = new Date().toISOString();
      
      // 清理用户输入
      if (context.args.message) {
        context.args.message = context.args.message.trim();
      }
    }
  }
});

2. 结果增强

const proxiedServer = wrapWithProxy(server, {
  hooks: {
    afterToolCall: async (context, result) => {
      // 为所有响应添加元数据
      if (result.result.content) {
        result.result._meta = {
          toolName: context.toolName,
          processedAt: new Date().toISOString(),
          version: '1.0.0'
        };
      }
      return result;
    }
  }
});

3. 访问控制与短路

const proxiedServer = wrapWithProxy(server, {
  hooks: {
    beforeToolCall: async (context) => {
      // 阻止某些工具
      if (context.toolName === 'delete' && !context.args.adminKey) {
        return {
          result: {
            content: [{ type: 'text', text: '访问被拒绝:需要管理员密钥' }],
            isError: true
          }
        };
      }
      
      // 速率限制
      if (await isRateLimited(context.args.userId)) {
        return {
          result: {
            content: [{ type: 'text', text: '超过速率限制。稍后再试。' }],
            isError: true
          }
        };
      }
    }
  }
});

4. 错误处理与监控

const proxiedServer = wrapWithProxy(server, {
  hooks: {
    beforeToolCall: async (context) => {
      // 日志到监控服务
      await analytics.track('tool_call_started', {
        tool: context.toolName,
        userId: context.args.userId,
        timestamp: Date.now()
      });
    },
    
    afterToolCall: async (context, result) => {
      // 处理错误
      if (result.result.isError) {
        await errorLogger.log({
          tool: context.toolName,
          error: result.result.content[0].text,
          context: context.args
        });
      }
      
      return result;
    }
  }
});

📚 核心概念

钩子系统

代理包装器提供了两个主要钩子:

  • beforeToolCall:在原始工具函数之前执行

    • 可以修改参数
    • 可以通过返回结果来短路执行
    • 适合验证、认证、日志记录
  • afterToolCall:在原始工具函数之后执行

    • 可以修改结果
    • 必须返回一个 ToolCallResult
    • 适合后处理、缓存、分析

上下文对象

每个钩子都会收到一个 ToolCallContext

interface ToolCallContext {
  toolName: string;              // 正在调用的工具名称
  args: Record<string, any>;     // 工具参数(可变)
  metadata?: Record<string, any>; // 额外的上下文数据
}

结果对象

afterToolCall 钩子使用 ToolCallResult

interface ToolCallResult {
  result: any;                   // 工具的返回值
  metadata?: Record<string, any>; // 额外的结果元数据
}

🔧 API 参考

wrapWithProxy(server, options)

用代理功能包装一个MCP服务器实例。

参数:

  • server (McpServer):要包装的MCP服务器
  • options (ProxyWrapperOptions):配置选项

返回: Promise<McpServer> - 一个新的具有代理功能的MCP服务器实例

ProxyWrapperOptions

interface ProxyWrapperOptions {
  hooks?: ProxyHooks;              // 钩子函数
  plugins?: ProxyPlugin[];         // 插件实例
  pluginConfig?: Record<string, any>; // 全局插件配置
  metadata?: Record<string, any>;  // 全局元数据
  debug?: boolean;                 // 启用调试日志
}

ProxyHooks

interface ProxyHooks {
  beforeToolCall?: (context: ToolCallContext) => Promise<void | ToolCallResult>;
  afterToolCall?: (context: ToolCallContext, result: ToolCallResult) => Promise<ToolCallResult>;
}

🧪 测试

MCP Proxy Wrapper 包含全面的测试,使用真实的MCP客户端-服务器通信:

# 运行所有测试
npm test

# 运行带有覆盖率的测试
npm run test:coverage

# 运行特定的测试套件
npm test -- --testNamePattern="综合测试"
npm test -- --testNamePattern="边界情况"
npm test -- --testNamePattern="协议合规性"

测试覆盖率

  • 65个以上综合测试覆盖所有功能
  • 真实的MCP客户端-服务器通信使用InMemoryTransport
  • 插件系统验证通过集成测试
  • 边界情况包括并发、大数据、Unicode处理
  • 协议合规性验证
  • 错误场景和压力测试
  • TypeScript和JavaScript兼容性

🔄 迁移与兼容性

MCP SDK 兼容性

  • 支持:MCP SDK v1.6.0及以上版本
  • 已测试:完全验证至MCP SDK v1.12.1
  • 注意:需要Zod模式以正确传递参数

升级您的服务器

代理包装器设计为即插即用替换:

// 之前
const server = new McpServer(config);
server.tool('myTool', schema, handler);

// 之后
const server = new McpServer(config);
const proxiedServer = await wrapWithProxy(server, { 
  hooks: myHooks,
  plugins: [new LLMSummarizationPlugin()]
});
proxiedServer.tool('myTool', schema, handler); // 同样的API!

🛠 使用案例

1. 认证与授权

const authProxy = wrapWithProxy(server, {
  hooks: {
    beforeToolCall: async (context) => {
      if (!await validateApiKey(context.args.apiKey)) {
        return { result: { content: [{ type: 'text', text: '无效的API密钥' }], isError: true }};
      }
    }
  }
});

2. 速率限制

const rateLimitedProxy = wrapWithProxy(server, {
  hooks: {
    beforeToolCall: async (context) => {
      const userId = context.args.userId;
      if (await rateLimiter.isExceeded(userId)) {
        return { result: { content: [{ type: 'text', text: '超过速率限制' }], isError: true }};
      }
      await rateLimiter.increment(userId);
    }
  }
});

3. 缓存

const cachedProxy = wrapWithProxy(server, {
  hooks: {
    beforeToolCall: async (context) => {
      const cacheKey = `${context.toolName}:${JSON.stringify(context.args)}`;
      const cached = await cache.get(cacheKey);
      if (cached) {
        return { result: cached };
      }
    },
    afterToolCall: async (context, result) => {
      const cacheKey = `${context.toolName}:${JSON.stringify(context.args)}`;
      await cache.set(cacheKey, result.result, { ttl: 300 });
      return result;
    }
  }
});

4. 分析与监控

const monitoredProxy = await wrapWithProxy(server, {
  hooks: {
    beforeToolCall: async (context) => {
      await metrics.increment('tool_calls_total', { tool: context.toolName });
      context.startTime = Date.now();
    },
    afterToolCall: async (context, result) => {
      const duration = Date.now() - context.startTime;
      await metrics.histogram('tool_call_duration', duration, { tool: context.toolName });
      return result;
    }
  }
});

5. AI增强

import { LLMSummarizationPlugin, ChatMemoryPlugin } from 'mcp-proxy-wrapper';

const aiEnhancedProxy = await wrapWithProxy(server, {
  plugins: [
    new LLMSummarizationPlugin({
      options: {
        provider: 'openai',
        openaiApiKey: process.env.OPENAI_API_KEY,
        summarizeTools: ['research', 'analyze', 'fetch-data'],
        minContentLength: 500
      }
    }),
    new ChatMemoryPlugin({
      options: {
        provider: 'openai',
        openaiApiKey: process.env.OPENAI_API_KEY,
        saveResponses: true,
        enableChat: true
      }
    })
  ]
});

// 长研究响应会被自动摘要
// 所有响应都会被保存以便对话查询

🤝 贡献

我们欢迎贡献!请参阅我们的贡献指南了解详情。

开发设置

git clone https://github.com/crazyrabbitLTC/mcp-proxy-wrapper.git
cd mcp-proxy-wrapper
npm install
npm run build
npm test

📄 许可证

MIT 许可证 - 详情见LICENSE文件。

🔗 链接


<div align="center"> <strong>为MCP生态系统用心打造</strong><br> <em>由<a href="mailto:dennison@dennisonbertram.com">Dennison Bertram</a>创建</em> </div>