MCP Middleware Adapter for Express Servers
Okay, here's a breakdown of how to run multiple Minecraft Protocol (MCP) clients on a NodeJS Express server, acting as an adapter or middleware. This is a complex topic, so I'll break it down into key concepts and provide code snippets to illustrate the ideas. **Core Concepts** 1. **Minecraft Protocol (MCP) Clients:** These are libraries (like `minecraft-protocol` or `prismarine-proxy`) that handle the low-level communication with Minecraft servers. You'll need to choose one and understand its API. 2. **NodeJS Express Server:** This provides the HTTP endpoint(s) that your users/applications will interact with. It acts as the intermediary between the outside world and your Minecraft clients. 3. **Client Management:** You need a way to keep track of each MCP client instance. A simple object or array can work, but for more complex scenarios, consider a more robust data structure. 4. **Proxying/Adapting:** The Express server will receive requests, determine which MCP client should handle the request, and then forward the request (or a modified version of it) to the appropriate Minecraft server. The response from the Minecraft server is then sent back to the original requester. 5. **Authentication/Authorization:** Crucially important. You need to ensure that only authorized users can control specific Minecraft clients. This might involve API keys, user accounts, or other authentication mechanisms. 6. **Error Handling:** Robust error handling is essential. Minecraft servers can be unreliable, and network issues can occur. Your Express server needs to gracefully handle these errors and provide informative feedback to the user. **Example Structure (Conceptual)** ``` / ├── server.js // Main Express server file ├── mcp_manager.js // Manages MCP client instances ├── routes/ │ └── minecraft.js // Routes for interacting with Minecraft clients └── config.js // Configuration settings ``` **1. Project Setup** ```bash mkdir minecraft-proxy cd minecraft-proxy npm init -y npm install express minecraft-protocol // Or prismarine-proxy, etc. ``` **2. `config.js` (Configuration)** ```javascript module.exports = { minecraftServers: [ { id: 'server1', host: 'minecraft.example.com', port: 25565 }, { id: 'server2', host: 'another.minecraft.net', port: 25565 } ], apiKeys: { 'user1': 'some-secret-key', 'user2': 'another-secret-key' } }; ``` **3. `mcp_manager.js` (MCP Client Management)** ```javascript const mc = require('minecraft-protocol'); const config = require('./config'); class MCPManager { constructor() { this.clients = {}; // Store MCP client instances } createClient(serverId, username, password) { const serverConfig = config.minecraftServers.find(s => s.id === serverId); if (!serverConfig) { throw new Error(`Server with ID ${serverId} not found.`); } const client = mc.createClient({ host: serverConfig.host, port: serverConfig.port, username: username, password: password // Handle authentication appropriately }); this.clients[serverId] = client; client.on('connect', () => { console.log(`Connected to ${serverId}`); }); client.on('disconnect', (packet) => { console.log(`Disconnected from ${serverId}: ${packet.reason}`); delete this.clients[serverId]; // Remove client on disconnect }); client.on('error', (err) => { console.error(`Error on ${serverId}: ${err}`); delete this.clients[serverId]; // Remove client on error }); return client; } getClient(serverId) { return this.clients[serverId]; } removeClient(serverId) { if (this.clients[serverId]) { this.clients[serverId].end(); // Disconnect the client delete this.clients[serverId]; } } } module.exports = new MCPManager(); ``` **4. `routes/minecraft.js` (Express Routes)** ```javascript const express = require('express'); const router = express.Router(); const mcpManager = require('../mcp_manager'); const config = require('../config'); // Middleware to authenticate API key const authenticate = (req, res, next) => { const apiKey = req.headers['x-api-key']; const userId = req.query.userId; // Or get from body, params, etc. if (!apiKey || !userId || config.apiKeys[userId] !== apiKey) { return res.status(401).json({ error: 'Unauthorized' }); } req.userId = userId; // Store user ID for later use next(); }; // Route to create a Minecraft client router.post('/client/:serverId', authenticate, (req, res) => { const serverId = req.params.serverId; const username = req.body.username; const password = req.body.password; try { const client = mcpManager.createClient(serverId, username, password); res.status(201).json({ message: `Client created for ${serverId}` }); } catch (error) { console.error(error); res.status(500).json({ error: error.message }); } }); // Route to send a chat message router.post('/client/:serverId/chat', authenticate, (req, res) => { const serverId = req.params.serverId; const message = req.body.message; const client = mcpManager.getClient(serverId); if (!client) { return res.status(404).json({ error: `Client for ${serverId} not found` }); } client.write('chat', { message: message }); res.json({ message: 'Chat message sent' }); }); // Route to disconnect a client router.delete('/client/:serverId', authenticate, (req, res) => { const serverId = req.params.serverId; mcpManager.removeClient(serverId); res.json({ message: `Client for ${serverId} disconnected` }); }); module.exports = router; ``` **5. `server.js` (Main Express Server)** ```javascript const express = require('express'); const app = express(); const minecraftRoutes = require('./routes/minecraft'); const port = 3000; app.use(express.json()); // Parse JSON request bodies app.use('/minecraft', minecraftRoutes); app.listen(port, () => { console.log(`Server listening on port ${port}`); }); ``` **Explanation and Key Points** * **`config.js`:** Stores configuration data like Minecraft server details and API keys. **Never hardcode sensitive information directly into your code.** * **`mcp_manager.js`:** * Manages the creation, retrieval, and removal of MCP client instances. * Uses a `clients` object to store the clients, keyed by `serverId`. * Handles connection, disconnection, and error events for each client. This is crucial for maintaining a stable system. * **`routes/minecraft.js`:** * Defines the Express routes for interacting with the Minecraft clients. * **`authenticate` middleware:** This is a *critical* security measure. It checks for a valid API key before allowing access to the routes. Adapt this to your authentication needs. * `/client/:serverId`: Creates a new MCP client for the specified server. * `/client/:serverId/chat`: Sends a chat message to the specified server. * `/client/:serverId`: Disconnects the client for the specified server. * **`server.js`:** * Sets up the Express server and mounts the `minecraftRoutes`. * Includes `express.json()` middleware to parse JSON request bodies. **How to Run** 1. Save all the files. 2. Open a terminal in the project directory. 3. Run `node server.js`. **Example API Usage (using `curl`)** * **Create a client:** ```bash curl -X POST -H "Content-Type: application/json" -H "X-API-Key: some-secret-key" -d '{"username": "MyBot", "password": "MyPassword"}' "http://localhost:3000/minecraft/client/server1?userId=user1" ``` * **Send a chat message:** ```bash curl -X POST -H "Content-Type: application/json" -H "X-API-Key: some-secret-key" -d '{"message": "Hello, world!"}' "http://localhost:3000/minecraft/client/server1/chat?userId=user1" ``` * **Disconnect a client:** ```bash curl -X DELETE -H "X-API-Key: some-secret-key" "http://localhost:3000/minecraft/client/server1?userId=user1" ``` **Important Considerations and Enhancements** * **Error Handling:** The example includes basic error handling, but you should add more robust error handling throughout the code. Use `try...catch` blocks, log errors, and provide informative error messages to the user. * **Authentication:** The API key authentication is a simple example. For production environments, use a more secure authentication method like JWT (JSON Web Tokens) or OAuth 2.0. * **Rate Limiting:** Implement rate limiting to prevent abuse of your API. This can be done using middleware like `express-rate-limit`. * **Logging:** Use a logging library (like `winston` or `morgan`) to log requests, errors, and other important events. This will help you debug and monitor your application. * **Scalability:** For high-traffic applications, consider using a message queue (like RabbitMQ or Kafka) to handle requests asynchronously. This will prevent your Express server from becoming overloaded. You could also use a load balancer to distribute traffic across multiple instances of your Express server. * **Minecraft Protocol Library:** The example uses `minecraft-protocol`. You can also use `prismarine-proxy` or other libraries. Choose the library that best suits your needs. `prismarine-proxy` is often used for more advanced proxying scenarios. * **Data Validation:** Validate the data that you receive from the user (e.g., username, password, message). This will help prevent errors and security vulnerabilities. Use a library like `joi` or `express-validator`. * **Minecraft Server Version Compatibility:** Minecraft protocols change with each version. Make sure your chosen library supports the Minecraft server versions you want to connect to. You might need to handle different protocol versions. * **Proxying vs. Adapting:** * **Proxying:** Simply forwards the raw Minecraft protocol packets between the client and the server. This is more efficient but requires more knowledge of the Minecraft protocol. `prismarine-proxy` is well-suited for this. * **Adapting:** Translates the requests into a different format (e.g., HTTP) and then converts them back into Minecraft protocol packets. This is easier to implement but can be less efficient. The example above is more of an adapter. * **Command Handling:** Instead of just sending chat messages, you can implement a more sophisticated command handling system. This would allow users to execute commands on the Minecraft server through your API. * **WebSockets:** Consider using WebSockets for real-time communication between the client and the Minecraft server. This would allow you to send updates to the client as they happen (e.g., player positions, chat messages). **Example using `prismarine-proxy` (Conceptual)** ```javascript const prismarineProxy = require('prismarine-proxy'); const express = require('express'); const app = express(); app.get('/proxy/:host/:port', (req, res) => { const host = req.params.host; const port = parseInt(req.params.port); prismarineProxy.createProxy({ host: host, port: port, // Options for the proxy (e.g., custom login handling) }); res.send(`Proxy created for ${host}:${port}`); }); app.listen(3000, () => { console.log('Proxy server listening on port 3000'); }); ``` This `prismarine-proxy` example is *very* basic. You'd need to add authentication, client management, and more sophisticated handling of the proxy events. `prismarine-proxy` gives you more control over the raw Minecraft protocol packets. **In summary, building a robust Minecraft proxy/adapter requires careful planning, security considerations, and a good understanding of the Minecraft protocol. Start with a simple example and gradually add features as needed.**
Moe03
README
用于 Express 服务器的 MCP 中间件适配器
-
一个轻量级的适配器,用于使用 Express.js 创建 MCP (模型上下文协议) 服务器。
-
由 https://tixaeagents.ai 赞助,可在几秒钟内创建文本/语音 AI 代理,与 MCP 服务器兼容。
清单:
- [x] Express 中间件集成 SSE 支持
- [ ] Websocket 集成支持(即将推出,但 SSE 工作良好)
- [x] 具有 TypeScript 支持的工具实现
- [x] 基于标头的授权支持
- [x] 不同端点上的多个 MCP 客户端
- [ ] Prompts 支持(即将推出,因为它有点不必要)
为什么
- 您可以从托管聊天的主要 LLM 服务中分别向上或向下扩展您的 MCP 客户端(如果它们都是轻量级的,则可以将一些客户端组合在一起),如果您有 100 个人直接在单个服务器上使用 npx 打开 playwright、brave 等 MCP,这很容易占用大量内存并导致性能瓶颈。
- 部署、更新和维护许多 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
schema: z.object({
location: z.string().describe('要获取天气的城市'),
}),
// 简单字符串响应不需要输出 schema
handler: async (args) => {
// 只需返回一个字符串 - mcpTool 会处理格式化
return `Weather for ${args.location}: ☀️ Sunny and 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(`✨ 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 服务器
- settings > developer > edit config file to this:
{
"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('要获取天气的地点'),
}),
// 您可以定义类型安全的输出 schema..
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 `Result: ${eval(args.expression)}`
},
})
// 使用 mcpTool 助手定义时间工具
const timeTool = mcpTool({
name: 'get_time',
description: '获取当前时间,可以选择特定时区',
schema: z.object({
timezone: z
.string()
.optional()
.describe('要获取时间的时区(可选)'),
}),
handler: async (args) => {
return `Current time${args.timezone ? ` in ${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(`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 schema 的类型安全 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('要获取天气的地点'),
}),
// 定义输出 schema
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] Called with location: ${args.location}`)
// 返回与我们的输出 schema 匹配的对象
return {
temperature: 72,
condition: 'Sunny',
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] Called with: ${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] Generating ${count} items for category: ${args.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)
},
})
// 添加一个未指定 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
? `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
},
})
// 添加一个受保护的工具,该工具检查身份验证
const protectedTool = mcpTool({
name: 'get_passcode',
description: '获取用户的密码',
schema: z.object({
name: z.string().describe('用户的名字'),
}),
// 在处理程序中实现身份验证检查
handler: async (args, context) => {
console.log(`[ProtectedTool] Called with name: ${args.name}`)
// 检查授权标头
const authHeader = context?.headers?.authorization || ''
console.log(context)
console.log(`[ProtectedTool] Auth header: ${authHeader}`)
// 检查与“000000”匹配的 bearer 令牌
const validToken = 'Bearer 000000'
if (!authHeader || authHeader !== validToken) {
// 返回未经授权的访问错误
throw new Error('Unauthorized: Invalid or missing authentication token')
}
// 如果已授权,则返回受保护的数据
return `Protected data for 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 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(', ')}`)
// 挂载 MCP 路由器
app.use('/mcp', mcpClient.middleware())
// 为其他路由应用 JSON 解析器
app.use(express.json())
app.get('/', (req, res) => {
res.send(`Hello World MCP Express Adapter.`)
})
// 启动服务器
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000
app.listen(PORT, () => {
const baseUrl = `http://localhost:${PORT}`
// 使用助手方法获取 SSE 端点 URL
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'
/**
* 示例 1:具有复杂数据输出 schema 的工具
*
* 当您的工具返回需要强类型检查的结构化数据时,请使用此方法。
*/
const weatherTool = mcpTool({
name: 'get_weather',
description: '获取某个地点的当前天气',
schema: z.object({
location: z.string().describe('要获取天气的地点'),
}),
// 定义结构化数据的输出 schema
outputSchema: z
.object({
temperature: z.number().describe('当前温度(°F)'),
condition: z.string().describe('天气状况(例如,晴朗、多雨)'),
humidity: z.number().describe('湿度百分比'),
location: z.string().describe('此天气对应的地点'),
})
.describe('请求地点的天气信息'),
handler: async (args) => {
// args.location 完全类型化为字符串
return {
temperature: 72,
condition: 'Sunny',
humidity: 45,
location: args.location,
}
},
})
/**
* 示例 2:没有简单字符串响应输出 schema 的工具
*
* 当您的工具返回不需要复杂结构或验证的简单文本响应时,请使用此方法。
*/
const greetingTool = mcpTool({
name: 'greeting',
description: '获取个性化问候语',
schema: z.object({
name: z.string().describe('要问候的名字'),
formal: z.boolean().optional().describe('是否使用正式语言'),
}),
// 简单字符串响应不需要 outputSchema
handler: async (args) => {
// 当未提供 outputSchema 时,您必须返回一个字符串
return args.formal
? `Good day, ${args.name}. How may I be of service?`
: `Hey ${args.name}! How's it going?`
},
})
// 非类型安全工具:
// javascript 就绪
const nonTypesafeTool = {
name: 'search_web',
description: '在网上搜索信息',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索查询' },
limit: {
type: 'number',
description: '要返回的最大结果数',
},
},
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 // MCP 端点的基本路径
tools: ToolImpl[] // 工具实现数组
serverName?: string // 可选服务器名称(默认值:'mcp-server')
serverVersion?: string // 可选服务器版本(默认值:'1.0.0')
}
工具实现
interface ToolImpl<T = any> {
name: string // 工具名称
description: string // 工具描述
inputSchema: {
// 工具输入的 JSON Schema
type: 'object'
properties: Record<string, any>
required?: string[]
}
handler: (
args: T,
context?: {
headers?: Record<string, string> // 可在此处访问请求标头
[key: string]: any
},
) => Promise<{
content: Array<
| { type: string; text?: string }
| { type: string; data?: string; mimeType?: string }
>
isError?: boolean
}>
}
工具中的标头访问
您可以通过 context.headers 对象访问工具处理程序函数中的请求标头。这对于实现身份验证、传递自定义元数据或其他基于标头的逻辑非常有用。
客户端发送的标头(例如,使用带有 --header 或 --headers 标志的 mcp-express-adapter CLI)可在 context 中使用。
示例:通过 CLI 传递授权标头
要调用需要 Authorization: Bearer <token> 标头的受保护工具,您可以像这样使用 CLI 适配器:
# 使用 --header
npx mcp-express-adapter@latest --host http://localhost:3000/mcp/sse --header "Authorization: Bearer 000000"
# 使用 --headers(如果传递多个)
npx mcp-express-adapter@latest --host http://localhost:3000/mcp/sse --headers "Authorization: Bearer 000000, X-Custom: my-value"
示例:工具读取授权标头
这是 protectedTool 示例(来自 examples/with-express/src/index.ts),演示了如何从 context 读取 Authorization 标头:
// 受保护的工具示例,带有身份验证
const protectedTool = mcpTool({
name: 'get_passcode', // 或 'protected_data',具体取决于您的示例版本
description: '获取受保护的数据(需要身份验证)',
schema: z.object({
// ... 输入 schema 属性
name: z.string().describe('用户的名字'), // 示例属性
}),
handler: async (args, context) => {
// 从 context 对象访问标头
// Node.js 自动将标头名称转换为小写
const authHeader = context?.headers?.authorization || ''
console.log(`[ProtectedTool] Auth header received: ${authHeader}`)
// 验证令牌(例如,检查特定的 Bearer 令牌)
const validToken = 'Bearer 000000'
if (authHeader !== validToken) {
throw new Error('Unauthorized: Invalid or missing authentication token')
}
// 如果已授权,则继续执行工具逻辑
console.log(`[ProtectedTool] Authorized access for user: ${args.name}`)
return `Protected passcode for ${args.name}: 123456` // 返回受保护的数据
},
})
高级示例
这是一个更高级的示例,具有多个工具和端点:
import express from 'express'
import cors from 'cors'
import { MCPClient } from 'mcp-express-adapter'
const app = express()
app.use(cors())
// 定义多个工具
const weatherTool = {
name: 'get_weather',
description: '获取某个地点的当前天气',
inputSchema: {
type: 'object',
properties: {
location: { type: 'string', description: '地点' },
},
required: ['location'],
},
handler: async (args) => ({
content: [
{ type: 'text', text: `Weather for ${args.location}: Sunny, 72°F` },
],
isError: false,
}),
}
const searchTool = {
name: 'search_web',
description: '在网上搜索信息',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索查询' },
},
required: ['query'],
},
handler: async (args) => ({
content: [
{
type: 'text',
text: `Search results for "${args.query}": Results here...`,
},
],
isError: false,
}),
}
// 在不同的端点上设置多个 MCP 客户端
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], // 此代理只有天气工具
serverName: 'mcp-server-agent-2',
serverVersion: '1.0.0',
})
// 在全局 JSON 解析器之前挂载代理路由器
app.use('/agent-1', agent1.middleware())
app.use('/agent-2', agent2.middleware())
// 在代理路由之后应用全局 JSON 解析器
app.use(express.json())
// 启动服务器
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 测试您的 MCP 服务器以连接到 SSE 端点:
curl -N http://localhost:3000/mcp/sse
或者使用 MCP 命令行客户端:
# 基本用法
npx mcp-express-adapter --host http://localhost:3000/mcp/sse
# 带有单个标头
npx mcp-express-adapter --host http://localhost:3000/mcp/sse --header "Authorization: Bearer token123"
# 带有多个标头(选项 1:重复 --header)
npx mcp-express-adapter --host http://localhost:3000/mcp/sse --header "Authorization: Bearer token123" --header "X-Custom: Value"
# 带有多个标头(选项 2:逗号分隔列表)
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
推荐服务器
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
MCP Package Docs Server
促进大型语言模型高效访问和获取 Go、Python 和 NPM 包的结构化文档,通过多语言支持和性能优化来增强软件开发。
Claude Code MCP
一个实现了 Claude Code 作为模型上下文协议(Model Context Protocol, MCP)服务器的方案,它可以通过标准化的 MCP 接口来使用 Claude 的软件工程能力(代码生成、编辑、审查和文件操作)。
@kazuph/mcp-taskmanager
用于任务管理的模型上下文协议服务器。它允许 Claude Desktop(或任何 MCP 客户端)在基于队列的系统中管理和执行任务。
mermaid-mcp-server
一个模型上下文协议 (MCP) 服务器,用于将 Mermaid 图表转换为 PNG 图像。
Jira-Context-MCP
MCP 服务器向 AI 编码助手(如 Cursor)提供 Jira 工单信息。
Linear MCP Server
一个模型上下文协议(Model Context Protocol)服务器,它与 Linear 的问题跟踪系统集成,允许大型语言模型(LLM)通过自然语言交互来创建、更新、搜索和评论 Linear 问题。
Sequential Thinking MCP Server
这个服务器通过将复杂问题分解为顺序步骤来促进结构化的问题解决,支持修订,并通过完整的 MCP 集成来实现多条解决方案路径。
Curri MCP Server
通过管理文本笔记、提供笔记创建工具以及使用结构化提示生成摘要,从而实现与 Curri API 的交互。