返回市场
麦速适配器

麦速适配器

作者:Moe0310 星标更新:2025-08-14

项目介绍

使用Express服务器的MCP中间件适配器

检查清单:

  • Express中间件集成,支持SSE
  • WebSocket集成支持(即将推出,但SSE已经运行良好)
  • 支持TypeScript的工具实现
  • 基于头部的授权支持
  • 不同端点上的多个MCP客户端
  • 提示支持(即将推出,因为这有点多余)

为什么

  • 如果您有100个人通过Playwright、Brave等直接在单个服务器上使用npx打开MCP,这可能会消耗大量内存并导致性能瓶颈。您无法单独扩展或缩小您的MCP客户端(如果它们都很轻量级,您可以将它们分组在一起),从托管聊天的主要LLM服务中分离出来。
  • 默认部署、更新和维护许多MCP服务器的方式很麻烦,这个适配器试图简化它。

安装

npm install mcp-express-adapter@latest
# 或
yarn add mcp-express-adapter@latest
# 或
pnpm add mcp-express-adapter@latest

注意:如果您从私有仓库安装,则需要对GitHub Packages进行身份验证。更多详情请参阅GitHub Packages文档

开始使用

在Express服务器上创建一个MCP客户端(示例)

// examples/with-express/src/super-simple.ts
import express from 'express'
import cors from 'cors'
import { MCPClient, mcpTool } from 'mcp-express-adapter'
import { z } from 'zod'

// 创建Express应用
const app = express()
app.use(cors())

// 定义一个超级简单的天气工具
const weatherTool = mcpTool({
  name: 'get_weather',
  description: '获取某个地点的天气',
  // 定义输入模式
  schema: z.object({
    location: z.string().describe('要获取天气的城市'),
  }),
  // 简单字符串响应不需要输出模式
  handler: async (args) => {
    // 只返回一个字符串 - mcpTool处理格式化
    return `Weather for ${args.location}: ☀️ 晴朗,72°F`
  },
})

// 创建MCP客户端
const mcpClient = new MCPClient({
  endpoint: '/mcp',
  tools: [weatherTool],
  serverName: 'demo-server',
  serverVersion: '1.0.0',
})

// 挂载MCP路由器
app.use('/mcp', mcpClient.middleware())

// 应用其他路由的JSON解析器
app.use(express.json())

// 启动服务器
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
  console.log(`✨ 超级简单的MCP服务器正在运行!`)
  console.log(`🔗 连接到:http://localhost:${PORT}/mcp/sse`)
})

在终端确保服务器正在运行:

已创建具有以下配置的MCP客户端:
- 端点:/mcp
- 服务器:my-mcp-server v1.0.0
- 工具:get_weather, calculator, generate_list, greeting
MCP服务器正在端口3000上运行
连接到:http://localhost:3000/mcp/sse
调试模式:启用将显示调试日志,要禁用设置NODE_ENV=production

现在您可以在Claude桌面测试MCP服务器

  • 设置 > 开发者 > 编辑配置文件为:
{
  "mcpServers": {
    "localMcpServer": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-express-adapter",
        "--host",
        "http://localhost:3000/mcp/sse"
      ]
    }
  }
}

然后重启Claude桌面,您将在几秒钟内看到MCP工具 图片 图片

同一Express服务器上的多个MCP客户端。

// examples/with-express/src/multiple-mcp-clients.ts
import express from 'express'
import cors from 'cors'
import { MCPClient, mcpTool } from 'mcp-express-adapter'
import { z } from 'zod'

// 创建Express应用
const app = express()
app.use(cors())

// 使用mcpTool辅助定义天气工具
const weatherTool = mcpTool({
  name: 'get_weather',
  description: '获取某个地点的当前天气',
  schema: z.object({
    location: z.string().describe('要获取天气的地点'),
  }),
  // 您可以定义类型安全的输出模式...
  outputSchema: z.object({
    farenheight: z.number().describe('华氏温度'),
    celsius: z.number().describe('摄氏温度'),
  }),
  handler: async (args) => {
    return {
      farenheight: 72,
      celsius: 22,
    }
  },
})

// 使用mcpTool辅助定义计算器工具
const calculatorTool = mcpTool({
  name: 'calculate',
  description: '计算数学表达式的结果',
  schema: z.object({
    expression: z.string().describe('要评估的数学表达式'),
  }),
  handler: async (args) => {
    return `结果:${eval(args.expression)}`
  },
})

// 使用mcpTool辅助定义时间工具
const timeTool = mcpTool({
  name: 'get_time',
  description: '获取当前时间,可选地为特定时区',
  schema: z.object({
    timezone: z
      .string()
      .optional()
      .describe('要获取时间的时区(可选)'),
  }),
  handler: async (args) => {
    return `当前时间${args.timezone ? `在${args.timezone}` : ''}:${new Date().toLocaleString()}`
  },
})

// 创建带有天气工具的第一个MCP客户端
const weatherClient = new MCPClient({
  endpoint: '/weather-mcp',
  tools: [weatherTool],
  serverName: 'weather-mcp-server',
  serverVersion: '1.0.0',
})

// 创建带有计算器工具的第二个MCP客户端
const calculatorClient = new MCPClient({
  endpoint: '/calculator-mcp',
  tools: [calculatorTool],
  serverName: 'calculator-mcp-server',
  serverVersion: '1.0.0',
})

// 创建带有时间工具的第三个MCP客户端
const timeClient = new MCPClient({
  endpoint: '/time-mcp',
  tools: [timeTool],
  serverName: 'time-mcp-server',
  serverVersion: '1.0.0',
})

// 在全局JSON解析器之前挂载MCP路由器
app.use('/weather-mcp', weatherClient.middleware())
app.use('/calculator-mcp', calculatorClient.middleware())
app.use('/time-mcp', timeClient.middleware())

// 在代理路由之后应用全局JSON解析器
app.use(express.json())

// 启动服务器
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000
app.listen(PORT, () => {
  console.log(`多个MCP服务器正在端口${PORT}上运行`)
  console.log(`天气MCP:http://localhost:${PORT}/weather-mcp/sse`)
  console.log(`计算器MCP:http://localhost:${PORT}/calculator-mcp/sse`)
  console.log(`时间MCP:http://localhost:${PORT}/time-mcp/sse`)
})

与Langchain + Langgraph一起使用

// examples/with-langchain/src/index.ts
import { MultiServerMCPClient } from '@langchain/mcp-adapters'
import { ChatAnthropic } from '@langchain/anthropic'
import { createReactAgent } from '@langchain/langgraph/prebuilt' // 错误
import dotenv from 'dotenv'

dotenv.config()

async function runLangchainMcpExample() {
  console.log('正在使用MCP适配器初始化LangChain...')

  const model = new ChatAnthropic({
    model: 'claude-3-5-sonnet-20240620',
    temperature: 0,
    anthropicApiKey: process.env.ANTHROPIC_API_KEY,
  })

  // 仅保留mcpServers映射的构造函数
  const mcpClient = new MultiServerMCPClient({
    googleMapsServer: {
      // 直接映射服务器
      transport: 'sse',
      url: 'http://localhost:3000/mcp/sse',
      useNodeEventSource: true,
      reconnect: {
        enabled: true,
        maxAttempts: 3,
        delayMs: 1000,
      },
    },
  })

  console.log('正在通过express适配器从MCP服务器加载工具...')
  // 保持getTools调用带选项
  const tools = (await Promise.race([
    mcpClient.getTools(),
    new Promise((_, reject) =>
      setTimeout(
        () =>
          reject(new Error('超时:未能在15秒内加载工具')),
        15000,
      ),
    ),
  ])) as Awaited<ReturnType<typeof mcpClient.getTools>>

  if (tools.length === 0) {
    console.error('未加载任何工具...')
    await mcpClient.close()
    return
  }

  console.log(
    `加载了${tools.length}个工具:`,
    tools.map((t) => t.name).join(', '),
  )

  const agent = await createReactAgent({
    llm: model,
    tools,
  })

  const messages = [
    {
      role: 'system',
      content:
        '您是一个有用的助手。使用工具来回答用户的问题。',
    },
    {
      role: 'user',
      content: `旧金山现在的天气如何?`,
    },
  ]
  let inputs = { messages }
  // console.log(`现在一切都好,开始流式传输!: `, inputs);
  // await new Promise((resolve) => setImmediate(resolve));
  const eventStream = await agent.streamEvents(inputs, {
    version: 'v2',
    //  signal: localController.signal, // <--- 必须传递localController!
  })

  // --- 调用保持不变 ---
  for await (const event of eventStream) {
    if (event.event === 'on_chat_model_stream') {
      console.log('聊天模型流')
      console.log(event.data.chunk.content[0]?.text)
    } else if (event.event === 'on_tool_start') {
      console.log('工具开始')
      console.log(JSON.stringify(event, null, 2))
    } else if (event.event === 'on_tool_end') {
      console.log('工具结束')
      console.log(JSON.stringify(event, null, 2))
    }
  }

  // console.log("\n关闭MCP客户端连接...");
  // await mcpClient.close();
  // console.log("MCP客户端已关闭。");
  // throw new Error("测试错误");
}

runLangchainMcpExample()

示例

带类型安全工具的基本示例

这里是一个完整的示例,使用mcpTool辅助创建带Zod模式的类型安全MCP工具:

// examples/with-express/src/index.ts
import express from 'express'
import cors from 'cors'
import { MCPClient, mcpTool } from 'mcp-express-adapter'
import { z } from 'zod'
import dotenv from 'dotenv'

// 加载环境变量
dotenv.config()

// 创建Express应用
const app = express()
app.use(cors())

// 使用增强的mcpTool辅助定义天气工具
const weatherTool = mcpTool({
  name: 'get_weather',
  description: '获取某个地点的当前天气',
  schema: z.object({
    location: z.string().describe('要获取天气的地点'),
  }),
  // 定义输出模式
  outputSchema: z
    .object({
      temperature: z.number().describe('当前温度,单位为°F'),
      condition: z.string().describe('天气状况(例如,晴朗,下雨)'),
      humidity: z.number().describe('湿度百分比'),
      location: z.string().describe('此天气信息对应的地点'),
    })
    .describe('请求地点的天气信息'),
  // 简单返回数据 - mcpTool处理MCP格式化
  handler: async (args) => {
    console.log(`[WeatherTool] 被调用,地点:${args.location}`)
    // 返回一个匹配我们输出模式的对象
    return {
      temperature: 72,
      condition: '晴朗',
      humidity: 45,
      location: args.location,
    }
  },
})

// 添加一个计算器工具,简单数字输出
const calculatorTool = mcpTool({
  name: 'calculator',
  description: '计算两个数字之和',
  schema: z.object({
    a: z.number().describe('第一个数字'),
    b: z.number().describe('第二个数字'),
  }),
  // 输出只是一个数字
  outputSchema: z.number().describe('两个输入数字之和'),
  // 简单返回总和 - 不需要为MCP格式化
  handler: async (args) => {
    console.log(`[CalculatorTool] 被调用,参数:${args.a}, ${args.b}`)
    return args.a + args.b
  },
})

// 添加一个返回数组的工具
const listTool = mcpTool({
  name: 'generate_list',
  description: '根据类别生成项目列表',
  schema: z.object({
    category: z
      .string()
      .describe('要生成项目的类别(例如,水果,颜色)'),
    count: z
      .number()
      .optional()
      .describe('要生成的项目数量(默认:3)'),
  }),
  // 输出是一串字符串
  outputSchema: z
    .array(z.string())
    .describe('生成的类别中的项目列表'),
  handler: async (args) => {
    const count = args.count || 3
    console.log(
      `[ListTool] 正在生成${count}个${args.category}类别的项目`,
    )

    // 根据类别生成样本数据
    const items: Record<string, string[]> = {
      fruits: ['苹果', '香蕉', '橙子', '葡萄', '草莓'],
      colors: ['红色', '蓝色', '绿色', '黄色', '紫色'],
      animals: ['狗', '猫', '大象', '老虎', '企鹅'],
    }

    const categoryItems = items[args.category.toLowerCase()] || [
      '项目1',
      '项目2',
      '项目3',
      '项目4',
      '项目5',
    ]
    return categoryItems.slice(0, count)
  },
})

// 添加一个不指定outputSchema的工具(期望返回字符串)
const greetingTool = mcpTool({
  name: 'greeting',
  description: '获取个性化的问候语',
  schema: z.object({
    name: z.string().describe('要问候的名字'),
    formal: z.boolean().optional().describe('是否使用正式语言'),
  }),
  // 不需要outputSchema,只需返回一个字符串
  handler: async (args) => {
    const greeting = args.formal
      ? `您好,${args.name}。我如何为您效劳?`
      : `嘿,${args.name}!你好吗?`

    console.log(
      `[GreetingTool] 为${args.name}生成问候语(正式:${args.formal || false})`,
    )
    return greeting
  },
})

// 添加一个检查认证的受保护工具
const protectedTool = mcpTool({
  name: 'get_passcode',
  description: '获取用户的通行码',
  schema: z.object({
    name: z.string().describe('用户的姓名'),
  }),
  // 在处理器中实现认证检查
  handler: async (args, context) => {
    console.log(`[ProtectedTool] 被调用,姓名:${args.name}`)

    // 检查授权头部
    const authHeader = context?.headers?.authorization || ''
    console.log(context)
    console.log(`[ProtectedTool] 认证头部:${authHeader}`)

    // 检查匹配“000000”的bearer token
    const validToken = 'Bearer 000000'
    if (!authHeader || authHeader !== validToken) {
      // 返回未经授权访问的错误
      throw new Error('未经授权:无效或缺少认证令牌')
    }

    // 如果已授权,返回受保护的数据
    return `受保护的数据ID:${args.name}`
  },
})

// 如果为true将显示调试日志,要禁用设置NODE_ENV=production
const debugMode = process.env.NODE_ENV === 'development'

// 创建MCP客户端
const mcpClient = new MCPClient({
  endpoint: '/mcp',
  tools: [weatherTool, calculatorTool, listTool, greetingTool, protectedTool],
  serverName: 'my-mcp-server',
  serverVersion: '1.0.0',
  debug: debugMode, // 只有当传递--debug标志时才启用调试日志
})

// 显示客户端元数据
const metadata = mcpClient.getMetadata()
console.log('已创建具有以下配置的MCP客户端:')
console.log(`- 端点:${metadata.endpoint}`)
console.log(`- 服务器:${metadata.serverName} v${metadata.serverVersion}`)
console.log(`- 工具:${metadata.tools.map((tool) => tool.name).join(', ')}`)

// 挂载MCP路由器
app.use('/mcp', mcpClient.middleware())

// 应用其他路由的JSON解析器
app.use(express.json