MCP代理包装器

一个轻量级且功能强大的MCP服务器包装器,提供全面的钩子系统,用于拦截、监控和修改工具调用,而无需更改现有服务器代码。

  • by mcp-plugins
  • TypeScript
  • 本地部署
  • 5 星标
  • 更新于
在 GitHub 查看 mcp-plugins/mcp-proxy-wrapper

使用说明

MCP代理包装器

一个轻量级、功能强大的模型上下文协议(MCP)服务器包装器,提供了一个全面的钩子系统,用于拦截、监视和修改工具调用,而无需更改现有的服务器代码。

🚀 特性

  • 🔧 零修改包装:包装现有的MCP服务器,而不更改其代码
  • 🪝 强大的挂钩系统:在工具调用前后执行自定义逻辑
  • 🔌 插件架构:用于可重用功能的可扩展插件系统
  • 🔄 参数和结果修改:动态转换输入和输出
  • ⚡ 短路能力:使用自定义响应跳过工具执行
  • 🧠 包括智能插件:LLM摘要和聊天记忆插件
  • 📊 综合录井:内置监控和调试支持
  • 🧪 经过全面测试:通过真实的MCP客户端-服务器验证实现100%的测试覆盖率
  • 📘 TypeScript优先:完全支持TypeScript,具有完全的类型安全性
  • 🌐 通用兼容性:适用于任何MCP SDK v1.6.0+服务器

📦 安装

npm install mcp-proxy-wrapper

🎯 快速开始

基本用法

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

// Create your existing MCP server
const server = new McpServer({
  name: 'My Server',
  version: '1.0.0'
});

// Wrap it with proxy functionality
const proxiedServer = await wrapWithProxy(server, {
  hooks: {
    // Monitor all tool calls
    beforeToolCall: async (context) => {
      console.log(`🔧 Calling tool: ${context.toolName}`);
      console.log(`📝 Arguments:`, context.args);
    },
    
    // Process results
    afterToolCall: async (context, result) => {
      console.log(`✅ Tool completed: ${context.toolName}`);
      return result; // Pass through unchanged
    }
  },
  debug: true // Enable detailed logging
});

// Register tools normally
proxiedServer.tool('greet', { name: z.string() }, async (args) => {
  return {
    content: [{ type: 'text', text: `Hello, ${args.name}!` }]
  };
});

🔌 插件系统

MCP代理包装器包含一个强大的插件架构,允许您创建可重用、可组合的功能。

使用内置插件

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', // or 'mock' for testing
    openaiApiKey: process.env.OPENAI_API_KEY,
    model: 'gpt-4o-mini',
    minContentLength: 500,
    summarizeTools: ['research', 'analyze', 'fetch-data'],
    saveOriginal: true // Store original responses for retrieval
  }
});

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

// Tool responses are automatically summarized
const result = await client.callTool({
  name: 'research',
  arguments: { topic: 'artificial intelligence' }
});

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

聊天记忆插件

为保存的工具响应提供对话界面:

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]
});

// Tool responses are automatically saved
await client.callTool({
  name: 'research',
  arguments: { topic: 'climate change', userId: 'user123' }
});

// Chat with your saved data
const sessionId = await memoryPlugin.startChatSession('user123');
const response = await memoryPlugin.chatWithMemory(
  sessionId,
  "What did I research about climate change?",
  'user123'
);
console.log(response); // AI response based on saved research

创建自定义插件

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> {
    // Add custom metadata
    return {
      ...result,
      result: {
        ...result.result,
        _meta: {
          ...result.result._meta,
          processedBy: this.name,
          customField: 'custom value'
        }
      }
    };
  }
}

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

插件配置

const plugin = new LLMSummarizationPlugin();

// Runtime configuration updates
plugin.updateConfig({
  enabled: true,
  priority: 10,
  options: {
    minContentLength: 200,
    provider: 'openai'
  },
  includeTools: ['research', 'analyze'], // Only these tools
  excludeTools: ['chat'], // Skip these tools
  debug: true
});

高级钩子示例

1.参数修改

const proxiedServer = wrapWithProxy(server, {
  hooks: {
    beforeToolCall: async (context) => {
      // Add timestamp to all tool calls
      context.args.timestamp = new Date().toISOString();
      
      // Sanitize user input
      if (context.args.message) {
        context.args.message = context.args.message.trim();
      }
    }
  }
});

2.成果提升

const proxiedServer = wrapWithProxy(server, {
  hooks: {
    afterToolCall: async (context, result) => {
      // Add metadata to all responses
      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) => {
      // Block certain tools
      if (context.toolName === 'delete' && !context.args.adminKey) {
        return {
          result: {
            content: [{ type: 'text', text: 'Access denied: Admin key required' }],
            isError: true
          }
        };
      }
      
      // Rate limiting
      if (await isRateLimited(context.args.userId)) {
        return {
          result: {
            content: [{ type: 'text', text: 'Rate limit exceeded. Try again later.' }],
            isError: true
          }
        };
      }
    }
  }
});

4.错误处理和监控

const proxiedServer = wrapWithProxy(server, {
  hooks: {
    beforeToolCall: async (context) => {
      // Log to monitoring service
      await analytics.track('tool_call_started', {
        tool: context.toolName,
        userId: context.args.userId,
        timestamp: Date.now()
      });
    },
    
    afterToolCall: async (context, result) => {
      // Handle errors
      if (result.result.isError) {
        await errorLogger.log({
          tool: context.toolName,
          error: result.result.content[0].text,
          context: context.args
        });
      }
      
      return result;
    }
  }
});

📚 核心概念

钉钩系统

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

  • beforeToolCall:在原始工具功能之前执行

    • 可以修改参数
    • 可以通过返回结果来缩短执行时间
    • 非常适合验证、身份验证和日志记录
  • afterToolCall:在原始工具功能之后执行

    • 可以修改结果
    • 必须返回a ToolCallResult
    • 非常适合后处理、缓存和分析

上下文对象

每个钩子都有一个 ToolCallContext 与:

interface ToolCallContext {
  toolName: string;              // Name of the tool being called
  args: Record<string, any>;     // Tool arguments (mutable)
  metadata?: Record<string, any>; // Additional context data
}

结果对象

这 afterToolCall 钩子与 ToolCallResult:

interface ToolCallResult {
  result: any;                   // The tool's return value
  metadata?: Record<string, any>; // Additional result metadata
}

🔧 api参考

wrapWithProxy(server, options)

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

参数:

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

退货: Promise<McpServer> -具有代理功能的新MCP服务器实例

ProxyWrapper选项

interface ProxyWrapperOptions {
  hooks?: ProxyHooks;              // Hook functions
  plugins?: ProxyPlugin[];         // Plugin instances
  pluginConfig?: Record<string, any>; // Global plugin configuration
  metadata?: Record<string, any>;  // Global metadata
  debug?: boolean;                 // Enable debug logging
}

ProxyHooks

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

🧪 测试

MCP代理包装器包括使用真实MCP客户端-服务器通信的全面测试:

# Run all tests
npm test

# Run with coverage
npm run test:coverage

# Run specific test suites
npm test -- --testNamePattern="Comprehensive Tests"
npm test -- --testNamePattern="Edge Cases"
npm test -- --testNamePattern="Protocol Compliance"

测试覆盖率

  • ✅ 65+综合测试 涵盖所有功能
  • ✅ 真正的MCP客户端-服务器通信 使用内存传输
  • ✅ 插件系统验证 集成测试
  • ✅ 边缘案例 包括并发性、大数据、Unicode处理
  • ✅ 协议遵从 验证
  • ✅ 错误场景 压力测试
  • ✅ TypeScript和JavaScript 兼容性

🔄 迁移和兼容性

MCP SDK兼容性

  • 支持:MCP SDK v1.6.0及更高版本
  • 已测试:完全通过MCP SDK v1.12.1验证
  • 备注:需要Zod模式才能正确传递参数

升级您的服务器

代理包装器被设计为一个即插即用的替代品:

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

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

🛠 用例

1.身份验证和授权

const authProxy = wrapWithProxy(server, {
  hooks: {
    beforeToolCall: async (context) => {
      if (!await validateApiKey(context.args.apiKey)) {
        return { result: { content: [{ type: 'text', text: 'Invalid API key' }], 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: 'Rate limit exceeded' }], 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.人工智能增强

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
      }
    })
  ]
});

// Long research responses are automatically summarized
// All responses are saved for conversational querying

🤝 贡献

我们欢迎捐款!请查看我们的 贡献指南 了解详情。

开发设置

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

📄 许可证

MIT许可证-请参阅 许可证 文件以获取详细信息。

🔗 链接


Built with ❤️ for the MCP ecosystem
Created by Dennison Bertram