关于 MCP Middleware Adapter for Express Servers
这是一个用于通过 Express 中间件构建 MCP 端点的 TypeScript 适配器,适合托管多组 MCP 工具的团队。它支持 SSE、独立端点和基于请求头的授权。
主要功能
- 01Express 中间件与 SSE 端点
- 02在不同端点配置多个 MCP 客户端
- 03支持 TypeScript 的工具定义
- 04基于请求头的授权
适用场景
- 01通过 Express 应用提供 MCP 工具
- 02将不同工具组划分到多个端点
- 03向受保护工具传递授权请求头
MCP 相关
它在 Express 服务中创建 MCP 端点,通过 SSE 向 MCP 客户端提供配置的工具。多个客户端可挂载在不同端点。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
Express 服务的 MCP 中间件适配器
-
用于使用 Express.js 创建 MCP(Model Context Protocol) 服务的轻量级适配器。
-
由 https://tixaeagents.ai 赞助,可在数秒内创建兼容 MCP 服务的文本/语音 AI Agent。
清单:
- Express 中间件集成,支持 SSE
- Websocket 集成支持(即将推出,但 SSE 已运行良好)
- 支持 TypeScript 的工具实现
- 支持基于标头的授权
- 在不同端点上使用多个 MCP 客户端
- 支持提示词(即将推出,因为这有点没必要)
原因
- 如果你有 100 个人在单个服务器上直接通过 npx 打开 Playwright、Brave 等 MCP 客户端,那么内存很容易被大量消耗,性能也会遇到瓶颈;而你无法将 MCP 客户端与托管聊天的主大模型服务分开扩缩容(如果它们都很轻量,也无法将其中一些组合在一起)。
- 部署、更新和维护多个 MCP 服务的默认方式很麻烦,本项目旨在简化这一流程。
安装
npm install mcp-express-adapter@latest
# or
yarn add mcp-express-adapter@latest
# or
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'
// Create Express app
const app = express()
app.use(cors())
// Define a super simple weather tool
const weatherTool = mcpTool({
name: 'get_weather',
description: 'Get weather for a location',
// Define input schema
schema: z.object({
location: z.string().describe('The city to get weather for'),
}),
// No output schema needed for simple string responses
handler: async (args) => {
// Just return a string - mcpTool handles the formatting
return `Weather for ${args.location}: ☀️ Sunny and 72°F`
},
})
// Create MCP client
const mcpClient = new MCPClient({
endpoint: '/mcp',
tools: [weatherTool],
serverName: 'demo-server',
serverVersion: '1.0.0',
})
// Mount MCP router
app.use('/mcp', mcpClient.middleware())
// Apply JSON parser for other routes
app.use(express.json())
// Start the server
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
console.log(`✨ Super Simple MCP Server running!`)
console.log(`🔗 Connect at: http://localhost:${PORT}/mcp/sse`)
})
在终端中确认服务正在运行:
MCP Client created with the following configuration:
- Endpoint: /mcp
- Server: my-mcp-server v1.0.0
- Tools: get_weather, calculator, generate_list, greeting
MCP Server running on port 3000
Connect at: http://localhost:3000/mcp/sse
Debug mode: enabled will show debug logs, to disable set 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'
// Create Express app
const app = express()
app.use(cors())
// Define weather tool using mcpTool helper
const weatherTool = mcpTool({
name: 'get_weather',
description: 'Get the current weather for a location',
schema: z.object({
location: z.string().describe('The location to get weather for'),
}),
// you can define typesafe output schema..
outputSchema: z.object({
farenheight: z.number().describe('The temperature in farenheight'),
celsius: z.number().describe('The temperature in celsius'),
}),
handler: async (args) => {
return {
farenheight: 72,
celsius: 22,
}
},
})
// Define calculator tool using mcpTool helper
const calculatorTool = mcpTool({
name: 'calculate',
description: 'Calculate the result of a mathematical expression',
schema: z.object({
expression: z.string().describe('The mathematical expression to evaluate'),
}),
handler: async (args) => {
return `Result: ${eval(args.expression)}`
},
})
// Define time tool using mcpTool helper
const timeTool = mcpTool({
name: 'get_time',
description: 'Get the current time, optionally for a specific timezone',
schema: z.object({
timezone: z
.string()
.optional()
.describe('The timezone to get time for (optional)'),
}),
handler: async (args) => {
return `Current time${args.timezone ? ` in ${args.timezone}` : ''}: ${new Date().toLocaleString()}`
},
})
// Create first MCP client with weather tool
const weatherClient = new MCPClient({
endpoint: '/weather-mcp',
tools: [weatherTool],
serverName: 'weather-mcp-server',
serverVersion: '1.0.0',
})
// Create second MCP client with calculator tool
const calculatorClient = new MCPClient({
endpoint: '/calculator-mcp',
tools: [calculatorTool],
serverName: 'calculator-mcp-server',
serverVersion: '1.0.0',
})
// Create third MCP client with time tool
const timeClient = new MCPClient({
endpoint: '/time-mcp',
tools: [timeTool],
serverName: 'time-mcp-server',
serverVersion: '1.0.0',
})
// Mount MCP routers BEFORE global JSON parser
app.use('/weather-mcp', weatherClient.middleware())
app.use('/calculator-mcp', calculatorClient.middleware())
app.use('/time-mcp', timeClient.middleware())
// Apply global JSON parser AFTER agent routes
app.use(express.json())
// Start the server
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000
app.listen(PORT, () => {
console.log(`Multiple MCP Servers running on port ${PORT}`)
console.log(`Weather MCP: http://localhost:${PORT}/weather-mcp/sse`)
console.log(`Calculator MCP: http://localhost:${PORT}/calculator-mcp/sse`)
console.log(`Time MCP: http://localhost:${PORT}/time-mcp/sse`)
})
与 Langchain + Langgraph 搭配使用
- 借助 @langchain/mcp-adapters https://github.com/langchain-ai/langchainjs-mcp-adapters
// examples/with-langchain/src/index.ts
import { MultiServerMCPClient } from '@langchain/mcp-adapters'
import { ChatAnthropic } from '@langchain/anthropic'
import { createReactAgent } from '@langchain/langgraph/prebuilt' // Incorrect
import dotenv from 'dotenv'
dotenv.config()
async function runLangchainMcpExample() {
console.log('Initializing LangChain with MCP Adapters...')
const model = new ChatAnthropic({
model: 'claude-3-5-sonnet-20240620',
temperature: 0,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
})
// Keep constructor with only mcpServers map
const mcpClient = new MultiServerMCPClient({
googleMapsServer: {
// The server map directly
transport: 'sse',
url: 'http://localhost:3000/mcp/sse',
useNodeEventSource: true,
reconnect: {
enabled: true,
maxAttempts: 3,
delayMs: 1000,
},
},
})
console.log('Loading tools from MCP server via express adapter...')
// Keep getTools call with options
const tools = (await Promise.race([
mcpClient.getTools(),
new Promise((_, reject) =>
setTimeout(
() =>
reject(new Error('Timeout: Failed to load tools within 15 seconds')),
15000,
),
),
])) as Awaited<ReturnType<typeof mcpClient.getTools>>
if (tools.length === 0) {
console.error('No tools were loaded...')
await mcpClient.close()
return
}
console.log(
`Loaded ${tools.length} tools:`,
tools.map((t) => t.name).join(', '),
)
const agent = await createReactAgent({
llm: model,
tools,
})
const messages = [
{
role: 'system',
content:
'You are a helpful assistant. Use tools to answer user questions.',
},
{
role: 'user',
content: `What is the current weather in San Francisco?`,
},
]
let inputs = { messages }
// console.log(`ALL GOOD NOW TART EVEN STREAMM>>!: `, inputs);
// await new Promise((resolve) => setImmediate(resolve));
const eventStream = await agent.streamEvents(inputs, {
version: 'v2',
// signal: localController.signal, // <--- critical to pass localController!
})
// --- Invocation remains the same ---
for await (const event of eventStream) {
if (event.event === 'on_chat_model_stream') {
console.log('Chat model stream')
console.log(event.data.chunk.content[0]?.text)
} else if (event.event === 'on_tool_start') {
console.log('Tool start')
console.log(JSON.stringify(event, null, 2))
} else if (event.event === 'on_tool_end') {
console.log('Tool end')
console.log(JSON.stringify(event, null, 2))
}
}
// console.log("\nClosing MCP client connections...");
// await mcpClient.close();
// console.log("MCP client closed.");
// throw new Error("Test 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'
// Load environment variables
dotenv.config()
// Create Express app
const app = express()
app.use(cors())
// Define weather tool using the enhanced mcpTool helper
const weatherTool = mcpTool({
name: 'get_weather',
description: 'Get the current weather for a location',
schema: z.object({
location: z.string().describe('The location to get weather for'),
}),
// Define the output schema
outputSchema: z
.object({
temperature: z.number().describe('Current temperature in °F'),
condition: z.string().describe('Weather condition (e.g., Sunny, Rainy)'),
humidity: z.number().describe('Humidity percentage'),
location: z.string().describe('The location this weather is for'),
})
.describe('Weather information for the requested location'),
// Simply return the data - mcpTool handles the MCP formatting
handler: async (args) => {
console.log(`[WeatherTool] Called with location: ${args.location}`)
// Return an object matching our output schema
return {
temperature: 72,
condition: 'Sunny',
humidity: 45,
location: args.location,
}
},
})
// Add a calculator tool with a simple numeric output
const calculatorTool = mcpTool({
name: 'calculator',
description: 'Calculate the sum of two numbers',
schema: z.object({
a: z.number().describe('First number'),
b: z.number().describe('Second number'),
}),
// Output is just a number
outputSchema: z.number().describe('The sum of the two input numbers'),
// Simply return the sum - no need to format for MCP
handler: async (args) => {
console.log(`[CalculatorTool] Called with: ${args.a}, ${args.b}`)
return args.a + args.b
},
})
// Add a tool that returns an array
const listTool = mcpTool({
name: 'generate_list',
description: 'Generate a list of items based on a category',
schema: z.object({
category: z
.string()
.describe('Category to generate items for (e.g., fruits, colors)'),
count: z
.number()
.optional()
.describe('Number of items to generate (default: 3)'),
}),
// Output is an array of strings
outputSchema: z
.array(z.string())
.describe('List of generated items in the category'),
handler: async (args) => {
const count = args.count || 3
console.log(
`[ListTool] Generating ${count} items for category: ${args.category}`,
)
// Sample data based on category
const items: Record<string, string[]> = {
fruits: ['apple', 'banana', 'orange', 'grape', 'strawberry'],
colors: ['red', 'blue', 'green', 'yellow', 'purple'],
animals: ['dog', 'cat', 'elephant', 'tiger', 'penguin'],
}
const categoryItems = items[args.category.toLowerCase()] || [
'item1',
'item2',
'item3',
'item4',
'item5',
]
return categoryItems.slice(0, count)
},
})
// Add a tool that doesn't specify an outputSchema (will expect string return)
const greetingTool = mcpTool({
name: 'greeting',
description: 'Get a personalized greeting',
schema: z.object({
name: z.string().describe('The name to greet'),
formal: z.boolean().optional().describe('Whether to use formal language'),
}),
// No outputSchema needed, just return a string
handler: async (args) => {
const greeting = args.formal
? `Good day, ${args.name}. How may I be of service?`
: `Hey ${args.name}! How's it going?`
console.log(
`[GreetingTool] Generated greeting for ${args.name} (formal: ${args.formal || false})`,
)
return greeting
},
})
// Add a protected tool that checks for authentication
const protectedTool = mcpTool({
name: 'get_passcode',
description: 'Get the passcode for the user',
schema: z.object({
name: z.string().describe('The name of the user'),
}),
// Implement authentication check in the handler
handler: async (args, context) => {
console.log(`[ProtectedTool] Called with name: ${args.name}`)
// Check for authorization header
const authHeader = context?.headers?.authorization || ''
console.log(context)
console.log(`[ProtectedTool] Auth header: ${authHeader}`)
// Check for bearer token that matches "000000"
const validToken = 'Bearer 000000'
if (!authHeader || authHeader !== validToken) {
// Return error for unauthorized access
throw new Error('Unauthorized: Invalid or missing authentication token')
}
// If authorized, return the protected data
return `Protected data for ID: ${args.name}`
},
})
// if true will show debug logs, to disable set NODE_ENV=production
const debugMode = process.env.NODE_ENV === 'development'
// Create MCP client
const mcpClient = new MCPClient({
endpoint: '/mcp',
tools: [weatherTool, calculatorTool, listTool, greetingTool, protectedTool],
serverName: 'my-mcp-server',
serverVersion: '1.0.0',
debug: debugMode, // Enable debug logs only when --debug flag is passed
})
// Show metadata about the client
const metadata = mcpClient.getMetadata()
console.log('MCP Client created with the following configuration:')
console.log(`- Endpoint: ${metadata.endpoint}`)
console.log(`- Server: ${metadata.serverName} v${metadata.serverVersion}`)
console.log(`- Tools: ${metadata.tools.map((tool) => tool.name).join(', ')}`)
// Mount MCP router
app.use('/mcp', mcpClient.middleware())
// Apply JSON parser for other routes
app.use(express.json())
app.get('/', (req, res) => {
res.send(`Hello World MCP Express Adapter.`)
})
// Start the server
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000
app.listen(PORT, () => {
const baseUrl = `http://localhost:${PORT}`
// Get the SSE endpoint URL using the helper method
const sseEndpoint = mcpClient.getSSEEndpoint(baseUrl)
console.log(`MCP Client SSE Endpoint: ${sseEndpoint}`)
console.log(
`Debug mode: ${debugMode ? 'enabled will show debug logs, to disable set NODE_ENV=production' : 'disabled will not log anything.'}`,
)
})
你可以通过以下方式运行此示例:
# From the root of the repo
pnpm install
pnpm test-express
工具实现示例
以下是使用 mcpTool 辅助函数创建简单工具的方法:
// examples/with-express/src/tool-example.ts
import { mcpTool } from 'mcp-express-adapter'
import { z } from 'zod'
/**
* Example 1: Tool with an output schema for complex data
*
* Use this approach when your tool returns structured data that
* needs strong type checking.
*/
const weatherTool = mcpTool({
name: 'get_weather',
description: 'Get the current weather for a location',
schema: z.object({
location: z.string().describe('The location to get weather for'),
}),
// Define the output schema for structured data
outputSchema: z
.object({
temperature: z.number().describe('Current temperature in °F'),
condition: z.string().describe('Weather condition (e.g., Sunny, Rainy)'),
humidity: z.number().describe('Humidity percentage'),
location: z.string().describe('The location this weather is for'),
})
.describe('Weather information for the requested location'),
handler: async (args) => {
// args.location is fully typed as string
return {
temperature: 72,
condition: 'Sunny',
humidity: 45,
location: args.location,
}
},
})
/**
* Example 2: Tool without an output schema for simple string responses
*
* Use this approach when your tool returns simple text responses
* that don't need complex structure or validation.
*/
const greetingTool = mcpTool({
name: 'greeting',
description: 'Get a personalized greeting',
schema: z.object({
name: z.string().describe('The name to greet'),
formal: z.boolean().optional().describe('Whether to use formal language'),
}),
// No outputSchema needed for simple string responses
handler: async (args) => {
// When no outputSchema is provided, you must return a string
return args.formal
? `Good day, ${args.name}. How may I be of service?`
: `Hey ${args.name}! How's it going?`
},
})
// non typesafe tool:
// javascript ready
const nonTypesafeTool = {
name: 'search_web',
description: 'Search the web for information',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'The search query' },
limit: {
type: 'number',
description: 'Maximum number of results to return',
},
},
required: ['query'],
},
handler: async (args) => ({
content: [
{
type: 'text',
text: `Search results for "${args.query}": Results here...`,
},
],
isError: false,
}),
}
export { weatherTool, greetingTool, nonTypesafeTool }
API 参考
MCPClient
用于在 Express 服务上创建 MCP 端点的主类。
构造函数选项
interface MCPClientOptions {
endpoint: string // The base path for the MCP endpoints
tools: ToolImpl[] // Array of tool implementations
serverName?: string // Optional server name (default: 'mcp-server')
serverVersion?: string // Optional server version (default: '1.0.0')
}
工具实现
interface ToolImpl<T = any> {
name: string // Tool name
description: string // Tool description
inputSchema: {
// JSON Schema for the tool's input
type: 'object'
properties: Record<string, any>
required?: string[]
}
handler: (
args: T,
context?: {
headers?: Record<string, string> // Request headers accessible here
[key: string]: any
},
) => Promise<{
content: Array<
| { type: string; text?: string }
| { type: string; data?: string; mimeType?: string }
>
isError?: boolean
}>
}
在工具中访问标头
你可以通过 context.headers 对象,在工具的处理函数中访问请求标头。这对于实现身份验证、传递自定义元数据或处理其他基于标头的逻辑很有用。
客户端发送的标头(例如,使用 mcp-express-adapter CLI 的 --header 或 --headers 标志)可在 context 中使用。
示例:通过 CLI 传递 Authorization 标头
若要调用一个需要 Authorization: Bearer <token> 标头的受保护工具,可以像这样使用 CLI 适配器:
# Using --header
npx mcp-express-adapter@latest --host http://localhost:3000/mcp/sse --header "Authorization: Bearer 000000"
# Using --headers (if passing multiple)
npx mcp-express-adapter@latest --host http://localhost:3000/mcp/sse --headers "Authorization: Bearer 000000, X-Custom: my-value"
示例:读取 Authorization 标头的工具
以下是 protectedTool 示例(来自 examples/with-express/src/index.ts),演示如何从 context 读取 Authorization 标头:
// Protected tool example with authentication
const protectedTool = mcpTool({
name: 'get_passcode', // or 'protected_data' depending on your example version
description: 'Get protected data (requires authentication)',
schema: z.object({
// ... input schema properties
name: z.string().describe('The name of the user'), // Example property
}),
handler: async (args, context) => {
// Access the headers from the context object
// Node.js automatically lowercases header names
const authHeader = context?.headers?.authorization || ''
console.log(`[ProtectedTool] Auth header received: ${authHeader}`)
// Validate the token (e.g., check for a specific Bearer token)
const validToken = 'Bearer 000000'
if (authHeader !== validToken) {
throw new Error('Unauthorized: Invalid or missing authentication token')
}
// If authorized, proceed with tool logic
console.log(`[ProtectedTool] Authorized access for user: ${args.name}`)
return `Protected passcode for ${args.name}: 123456` // Return the protected data
},
})
进阶示例
以下是一个包含多个工具和端点的进阶示例:
import express from 'express'
import cors from 'cors'
import { MCPClient } from 'mcp-express-adapter'
const app = express()
app.use(cors())
// Define multiple tools
const weatherTool = {
name: 'get_weather',
description: 'Get the current weather for a location',
inputSchema: {
type: 'object',
properties: {
location: { type: 'string', description: 'The location' },
},
required: ['location'],
},
handler: async (args) => ({
content: [
{ type: 'text', text: `Weather for ${args.location}: Sunny, 72°F` },
],
isError: false,
}),
}
const searchTool = {
name: 'search_web',
description: 'Search the web for information',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'The search query' },
},
required: ['query'],
},
handler: async (args) => ({
content: [
{
type: 'text',
text: `Search results for "${args.query}": Results here...`,
},
],
isError: false,
}),
}
// Set up multiple MCP Clients on different endpoints
const agent1 = new MCPClient({
endpoint: '/agent-1',
tools: [weatherTool, searchTool],
serverName: 'mcp-server-agent-1',
serverVersion: '1.0.0',
})
const agent2 = new MCPClient({
endpoint: '/agent-2',
tools: [weatherTool], // This agent only has weather tool
serverName: 'mcp-server-agent-2',
serverVersion: '1.0.0',
})
// Mount agent routers BEFORE global JSON parser
app.use('/agent-1', agent1.middleware())
app.use('/agent-2', agent2.middleware())
// Apply global JSON parser AFTER agent routes
app.use(express.json())
// Start the server
const PORT = 3000
app.listen(PORT, () => {
console.log(`MCP Server running on port ${PORT}`)
console.log(`Agent 1: http://localhost:${PORT}/agent-1/sse`)
console.log(`Agent 2: http://localhost:${PORT}/agent-2/sse`)
})
测试 MCP 服务
你可以使用 curl 连接到 SSE 端点来测试 MCP 服务:
curl -N http://localhost:3000/mcp/sse
或者使用 MCP 命令行客户端:
# Basic usage
npx mcp-express-adapter --host http://localhost:3000/mcp/sse
# With a single header
npx mcp-express-adapter --host http://localhost:3000/mcp/sse --header "Authorization: Bearer token123"
# With multiple headers (option 1: repeating --header)
npx mcp-express-adapter --host http://localhost:3000/mcp/sse --header "Authorization: Bearer token123" --header "X-Custom: Value"
# With multiple headers (option 2: comma-separated list)
npx mcp-express-adapter --host http://localhost:3000/mcp/sse --headers "Authorization: Bearer token123, X-Custom: Value"
常见问题
- 确保在 app.use MCP 中间件之后应用 exress.json()
- Webscokets 尚未经过充分测试。
贡献者
- Moe03 - 主要贡献者和维护者
如何贡献
有兴趣参与贡献吗?请在 GitHub 仓库中提交功能请求或错误报告。
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。