MCP 包装器
MCP 封装器 这是一个代理MCP服务器,它封装了现有的SSE MCP服务器,以选择性地仅暴露您所需的工具。
概述
在使用多个MCP服务器时,您可能不需要所有可用的工具,或者只想使用特定的工具。MCP Wrapper允许您在这些情况下过滤并自定义来自上游MCP服务器的工具。
Claude ↔️ MCP Wrapper ↔️ Upstream SSE MCP Server主要特点
- ✅ 选择性工具暴露仅向Claude提供您想要的工具
- 🔒 表示“锁”或“安全”的意思。 访问控制增强敏感工具的安全性
- 🔐 OAuth 2.1 支持连接到需要OAuth认证的MCP服务器(例如,Asana MCP)
- 📝(一个待办事项或笔记的符号,可理解为“待办事项”或“笔记”) 日志记录与监控追踪工具使用模式
- 🛠️(扳手) 定制化修改工具的元数据和行为
- 🔄 旋转(循环、重复) 多重认证模式选择无认证、基于令牌或OAuth
安装
git clone https://github.com/amondnet/please-mcp.git
cd please-mcp
bun install
bun run build快速入门:Asana MCP 服务器(OAuth)
要连接到需要OAuth认证的服务器,如Asana MCP服务器:
- 构建项目:
bun run build- 配置MCP设置 (
.mcp.json或者claude_desktop_config.json):
{
"mcpServers": {
"asana": {
"command": "node",
"args": ["/absolute/path/to/please-mcp/dist/index.js"],
"env": {
"UPSTREAM_URL": "https://mcp.asana.com/sse",
"AUTH_MODE": "oauth",
"ALLOWED_TOOLS": "asana_get_task,asana_get_tasks,asana_create_task,asana_update_task,asana_search_tasks",
"DEBUG": "true"
}
}
}
}- 启动MCP客户端 - 将打开浏览器窗口以进行Asana认证
📖 有关OAuth设置的详细信息: OAUTH_GUIDE.md 翻译为中文是:“OAUTH 指南.md”
使用方法
1. 基本设置
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
// Connect to upstream MCP server
const upstreamClient = new Client({
name: 'wrapper-client',
version: '1.0.0'
}, {
capabilities: {}
})
const sseTransport = new SSEClientTransport(
new URL('http://localhost:3000/sse')
)
await upstreamClient.connect(sseTransport)
// Create wrapper server
const server = new Server({
name: 'mcp-wrapper',
version: '1.0.0'
}, {
capabilities: {
tools: {}
}
})2. 工具过滤
// List of allowed tools
const ALLOWED_TOOLS = [
'search_web',
'fetch_url',
'read_file'
]
server.setRequestHandler('tools/list', async () => {
const upstream = await upstreamClient.listTools()
return {
tools: upstream.tools.filter(tool =>
ALLOWED_TOOLS.includes(tool.name)
)
}
})3. 工具调用代理
server.setRequestHandler('tools/call', async (request) => {
const toolName = request.params.name
// Check if tool is allowed
if (!ALLOWED_TOOLS.includes(toolName)) {
throw new Error(`Tool '${toolName}' is not allowed`)
}
// Forward request to upstream server
return await upstreamClient.callTool(request.params)
})4. 启动服务器
const transport = new StdioServerTransport()
await server.connect(transport)Claude 桌面配置
claude_desktop_config.json:
{
"mcpServers": {
"filtered-mcp": {
"command": "node",
"args": ["/path/to/mcp-wrapper.js"],
"env": {
"UPSTREAM_URL": "http://localhost:3000/sse"
}
}
}
}高级功能
修改工具元数据
server.setRequestHandler('tools/list', async () => {
const upstream = await upstreamClient.listTools()
return {
tools: upstream.tools
.filter(tool => ALLOWED_TOOLS.includes(tool.name))
.map(tool => ({
...tool,
description: `[Filtered] ${tool.description}`,
// Modify parameter schema
inputSchema: {
...tool.inputSchema,
required: [...(tool.inputSchema.required || []), 'source']
}
}))
}
})日志记录和监控
server.setRequestHandler('tools/call', async (request) => {
const startTime = Date.now()
console.log(`[${new Date().toISOString()}] Tool called: ${request.params.name}`)
console.log(`Arguments:`, JSON.stringify(request.params.arguments, null, 2))
try {
const result = await upstreamClient.callTool(request.params)
const duration = Date.now() - startTime
console.log(`[Success] Completed in ${duration}ms`)
return result
}
catch (error) {
console.error(`[Error] ${error.message}`)
throw error
}
})访问控制和验证
server.setRequestHandler('tools/call', async (request) => {
const { name, arguments: args } = request.params
// Additional validation for sensitive tools
if (name === 'delete_file') {
if (!args.path.startsWith('/safe/directory/')) {
throw new Error('Access denied: Path outside allowed directory')
}
}
// Rate limiting
if (shouldRateLimit(name)) {
throw new Error('Rate limit exceeded')
}
return await upstreamClient.callTool(request.params)
})多个上游服务器集成
const clients = {
asana: await createClient('http://localhost:3000/sse'),
github: await createClient('http://localhost:3001/sse'),
slack: await createClient('http://localhost:3002/sse')
}
server.setRequestHandler('tools/list', async () => {
const allTools = []
for (const [source, client] of Object.entries(clients)) {
const { tools } = await client.listTools()
allTools.push(...tools.map(tool => ({
...tool,
name: `${source}_${tool.name}` // Add namespace
})))
}
return { tools: allTools }
})
server.setRequestHandler('tools/call', async (request) => {
const [source, ...toolNameParts] = request.params.name.split('_')
const toolName = toolNameParts.join('_')
const client = clients[source]
if (!client) {
throw new Error(`Unknown source: ${source}`)
}
return await client.callTool({
...request.params,
name: toolName
})
})完整示例
#!/usr/bin/env node
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
const UPSTREAM_URL = process.env.UPSTREAM_URL || 'http://localhost:3000/sse'
const ALLOWED_TOOLS = (process.env.ALLOWED_TOOLS || '').split(',').filter(Boolean)
async function main() {
// Setup upstream client
const upstreamClient = new Client({
name: 'mcp-wrapper-client',
version: '1.0.0'
}, {
capabilities: {}
})
const sseTransport = new SSEClientTransport(new URL(UPSTREAM_URL))
await upstreamClient.connect(sseTransport)
// Setup wrapper server
const server = new Server({
name: 'mcp-wrapper',
version: '1.0.0'
}, {
capabilities: {
tools: {}
}
})
// List tools
server.setRequestHandler('tools/list', async () => {
const upstream = await upstreamClient.listTools()
let tools = upstream.tools
// Filter if configured
if (ALLOWED_TOOLS.length > 0) {
tools = tools.filter(tool => ALLOWED_TOOLS.includes(tool.name))
}
return { tools }
})
// Call tools
server.setRequestHandler('tools/call', async (request) => {
const { name, arguments: args } = request.params
// Check allowlist
if (ALLOWED_TOOLS.length > 0 && !ALLOWED_TOOLS.includes(name)) {
throw new Error(`Tool '${name}' is not allowed`)
}
// Logging
console.error(`[MCP Wrapper] Calling tool: ${name}`)
try {
const result = await upstreamClient.callTool(request.params)
console.error(`[MCP Wrapper] Tool '${name}' succeeded`)
return result
}
catch (error) {
console.error(`[MCP Wrapper] Tool '${name}' failed:`, error.message)
throw error
}
})
// Start stdio transport
const transport = new StdioServerTransport()
await server.connect(transport)
console.error('MCP Wrapper started')
}
main().catch(console.error)环境变量
| 变量 | 描述 | 默认值 | 示例 |
|---|---|---|---|
UPSTREAM_URL 上游SSE MCP服务器URL http://localhost:3000/sse | http://api.example.com/mcp | ||
ALLOWED_TOOLS | 允许的工具列表(逗号分隔) | 全部允许 | search_web,fetch_url,read_file |
用例
1. 增强的安全性
限制执行敏感操作的工具的访问权限,并维护审计日志。
2. 性能优化
过滤掉不常用的工具,以高效利用Claude的上下文窗口。
3. 团队专属定制
为不同的团队或项目提供不同的工具集。
4. 统一界面
通过单一统一接口呈现多个MCP服务器。
故障排除
无法连接到上游服务器
# Check if server is running
curl http://localhost:3000/sse
# Check firewall settings
# Verify URL is correct工具未显示
# Check ALLOWED_TOOLS environment variable
echo $ALLOWED_TOOLS
# Check logs
# Check MCP logs in Claude Desktop's developer tools权限错误
# Check script execution permissions
chmod +x mcp-wrapper.js
# Check Node.js version (18+ required)
node --version许可证
麻省理工学院(MIT)
贡献
欢迎提出问题和提交拉取请求(PRs)!
