MCP流式HTTP服务器模板
这是什么?
用于构建MCP服务器的模板。克隆它,剥离你不需要的东西,连接你的API客户端,定义工具。它的设计是可读的,易于构建。
附带双运行时支持(来自同一代码库的Node.js和Cloudflare Workers)、五种身份验证策略、加密令牌存储以及最新MCP规范支持的几乎所有内容。
什么是MCP?
模型上下文协议是JSON-RPC 2.0有线协议,其中服务器公开类型化功能(操作工具、数据资源、模板提示),客户端(IDE、代理、聊天应用程序)根据LLM决策调用它们。
双方都不实现对方的逻辑:服务器对哪个LLM使用它们一无所知,客户端对工具的内部工作方式一无所知。这种解耦解决了N×M积分问题。一个服务器为任何兼容的客户端提供服务,一个客户端消耗任何兼容的服务器。
支持什么?
| 特性 | Node.js | Workers | 注释 |
|---|---|---|---|
| 工具(列表、调用) | ✅ | ✅ | 核心能力,包括运行时 |
| 资源(列表、阅读、模板) | ✅ | ✅ | 静态和动态资源 |
| 提示(列表,获取) | ✅ | ✅ | 基于模板的提示生成 |
| 进度通知 | ✅ | ✅ | 长时间运行的工具反馈 |
| 取消 | ✅ | ✅ | 基于信号的中止 |
| 分页 | ✅ | ✅ | 基于光标的大型列表 |
| 日志记录 | ✅ | ✅ | 服务器→客户端日志消息 |
| 采样(服务器→客户端LLM) | ✅ | ❌ | 需要持久的SSE流 |
| 启发(用户输入) | ✅ | ❌ | 需要持久的SSE流 |
| 根目录(文件系统访问) | ✅ | ❌ | 需要客户端能力检查 |
支持的协议版本: 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05.
入门
首先,生成一个加密密钥(两个运行时都需要这个密钥):
openssl rand -base64 32 | tr -d '=' | tr '+/' '-_'Node.js
bun install
cp .env.example .env # Configure PROVIDER_*, AUTH_*, OAUTH_* vars
# Set RS_TOKENS_ENC_KEY with generated key
bun dev # MCP: localhost:3000/mcp, OAuth: localhost:3001Cloudflare员工
bun install
wrangler kv:namespace create TOKENS # Note the ID
# Update wrangler.toml with KV namespace ID
wrangler secret put PROVIDER_CLIENT_ID
wrangler secret put PROVIDER_CLIENT_SECRET
wrangler secret put RS_TOKENS_ENC_KEY # Paste generated key
wrangler dev # Local: localhost:8787/mcp
wrangler deploy # Production: your-worker.workers.dev/mcp服务器端点
| 端点 | 方法 | 目的 |
|---|---|---|
/mcp | POST、GET、DELETE | MCP协议(JSON-RPC) |
/health | GET | 健康检查+准备就绪 |
/.well-known/oauth-authorization-server | GET | OAuth AS元数据 |
/.well-known/oauth-protected-resource | GET | 受保护的资源元数据 |
/authorize | GET | 启动OAuth流 |
/oauth/callback | GET | 提供程序重定向目标 |
/token | POST | 令牌交换 |
/register | POST | 动态客户端注册 |
/revoke | POST | 令牌撤销 |
发现端点也可在 /mcp/.well-known/* 前缀。
Node.js vs Cloudflare Workers
该模板从同一代码库生成两个运行时。以下是你需要知道的:
Node.js(HONO+@HONO/noe-srver)
- 条目:
src/index.ts - 传输:SDK
StreamableHTTPServerTransport - 会议:
MemorySessionStore(默认)或SqliteSessionStore为了坚持 - 完整的MCP功能,包括双向请求(采样、启发、根)
- 地方发展:
bun dev
Cloudflare员工
- 条目:
src/worker.ts - 传输:自定义JSON-RPC调度器(
shared/mcp/dispatcher.ts) - 会议:
KvSessionStore具有内存回退功能(在请求之间持续存在) - 请求→仅回复;没有服务器发起的消息
- 部署:
wrangler deploy
共享代码 住在 src/shared/ (工具、存储接口、OAuth流、实用程序)。运行时特定的适配器 src/adapters/http-hono/ 和 src/adapters/http-workers/.
何时使用哪个:
- Node.js:本地开发,完整的MCP功能,自托管服务器
- 工人:生产部署、全球优势、简单的工具包装
授权
命名约定(重要!)
使用 **通用的 PROVIDER_* 名字**,而不是特定于服务的名称。这使模板在所有MCP服务器上保持可移植性和配置一致性。
| ✅ 正确 | ❌ 错了 |
|---|---|
PROVIDER_CLIENT_ID | SPOTIFY_CLIENT_ID, LINEAR_CLIENT_ID |
PROVIDER_CLIENT_SECRET | SPOTIFY_CLIENT_SECRET, GMAIL_SECRET |
PROVIDER_ACCOUNTS_URL | SPOTIFY_ACCOUNTS_URL |
PROVIDER_API_URL | LINEAR_API_URL, GITHUB_API_URL |
为什么?
- 相同的环境变量名称适用于所有服务器(Spotify、Linear、Gmail等)
- 部署脚本不需要特定于服务的逻辑
.env.example和wrangler.toml保持通用模板- 更容易审计安全性(检查一种模式)
示例 .env:
# Generic provider config — same vars for any OAuth provider
PROVIDER_CLIENT_ID=your-client-id
PROVIDER_CLIENT_SECRET=your-client-secret
PROVIDER_ACCOUNTS_URL=https://accounts.spotify.com # or github.com, etc.
PROVIDER_API_URL=https://api.spotify.com # optional, for API calls例外情况: 如果服务器同时集成多个提供程序(罕见),请在前面加上提供程序名称: GITHUB_CLIENT_ID, GITLAB_CLIENT_ID。单一提供商服务器应始终使用 PROVIDER_*.
认证策略
五种身份验证策略,通过配置 AUTH_STRATEGY env 是:
| 策略 | 标题 | 用例 |
|---|---|---|
oauth | Authorization: Bearer | 使用RS令牌的完整OAuth 2.1 PKCE流→ 提供者令牌映射 |
bearer | Authorization: Bearer | 静态令牌来自 BEARER_TOKEN env |
api_key | X-Api-Key: (可配置) | 静态密钥来自 API_KEY env |
custom | 多个标题 | 来自的自定义标题 CUSTOM_HEADERS env |
none | -- | 无身份验证 |
OAuth流(策略=OAuth):
- 客户端通过以下方式发现AS元数据
/.well-known/oauth-authorization-server - 客户端启动PKCE流→
/authorize→ 提供商登录 - 提供商回调→ 服务器发出RS令牌(访问+刷新)
- 客户端发送RS令牌→ 服务器映射到提供者令牌→ 工具使用提供程序API执行
令牌存储 (RS令牌→ 提供者令牌映射):
FileTokenStore--Node.js,基于文件,可选加密MemoryTokenStore--两个运行时都在带TTL的内存中KvTokenStore--工人,Cloudflare KV,可选加密- 所有支持AES-256-GCM加密
RS_TOKENS_ENC_KEY
会话
会话支持多租户操作。一个服务器实例可以为多个处于隔离状态的用户提供服务。两个运行时都使用 SessionStore 为了这个。
什么会议给你:
- API密钥→ 会话绑定(谁拥有此连接)
- 每个API密钥的会话限制(默认值:5,LRU逐出)
- 对每个请求进行会话验证(无效/过期会话为404)
- 每个会话的协议版本跟踪
- 服务器→客户端请求路由(采样/启发需要知道哪个客户端)
哪些会话不会给你(这取决于代理):
- 对话记忆(“回复该电子邮件”)
- 工作流状态(草稿延续,最后一个问题ID)
- 工具调用之间的上下文传递
存储实施:
| 存储 | 运行时 | 后端 | 持久性 |
|---|---|---|---|
MemorySessionStore | 两者 | 内存映射 | 进程生存期 |
SqliteSessionStore | Node.js | 通过Drizzle实现SQLite | 磁盘 |
KvSessionStore | 工人 | Cloudflare KV | 全球 |
会话生命周期(根据MCP规范):
- 客户端发送
initialize请求没有Mcp-Session-Id头球 - 服务器通过以下方式创建会话
SessionStore.create(sessionId, apiKey),在响应标头中返回会话ID - 客户端发送
initialized通知与Mcp-Session-Id→ 服务器将会话标记为已初始化 - 所有后续请求必须包括
Mcp-Session-Id(400错误请求,如果丢失) - 服务器在每个请求上验证会话是否存在(如果无效/过期,则为404 Not Found)
- TTL(默认值:24小时)或客户端发送DELETE请求后会话过期
API密钥解析 (用于会话绑定):
X-Api-Key或X-Auth-Token头(直接API密钥身份验证)- 持有者代币来自
Authorization标头(OAuth RS令牌) - 静态
API_KEY从配置(回退) "public"(未经身份验证)
多租户模式:
User A (api_key_1) ──┐
│
User B (api_key_2) ──┼──▶ Single MCP Server ──▶ Provider API
│ (sessions isolate users)
User C (api_key_3) ──┘添加工具
地点: src/shared/tools/
图案: 模式→ 元数据→ 处理器→ 注册
// 1. Define input schema with Zod
export const myToolInputSchema = z.object({
query: z.string().describe('Search query'),
});
// 2. Create tool with defineTool()
export const myTool = defineTool({
name: 'my_tool',
title: 'My Tool',
description: 'What it does',
inputSchema: myToolInputSchema,
outputSchema: { result: z.string() }, // optional
annotations: {
readOnlyHint: true,
destructiveHint: false,
},
handler: async (args, context) => {
// 3. Implement handler
return {
content: [{ type: 'text', text: args.query }],
structuredContent: { result: args.query }, // required if outputSchema defined
};
},
});
// 4. Add to sharedTools array in registry.ts
export const sharedTools: RegisteredTool[] = [
asRegisteredTool(healthTool),
asRegisteredTool(echoTool),
asRegisteredTool(myTool), // ← add your tool here
];注释 控制客户端如何显示/调用: readOnlyHint, destructiveHint, idempotentHint, openWorldHint.
服务: 对于复杂的集成,请将业务逻辑放入 src/shared/services/.提取时:处理程序超过30行,多个工具共享逻辑,或外部API需要速率限制/重试。简单的工具可以保持逻辑内联。
已知限制
Node.js运行时 --完整的MCP支持,包括服务器→通过SDK的客户端请求(采样、启发、根) StreamableHTTPServerTransport.会话通过以下方式持续 MemorySessionStore (默认)或 SqliteSessionStore 用于磁盘持久性。
Cloudflare Workers运行时 --请求→仅响应模式。会话通过以下方式持续 KvSessionStore 跨请求,但传输状态是无状态的(没有SSE流)。服务器→客户端请求(采样、启发、根)不可用,因为它们需要一个主动的SSE流,而Workers无法维护。将Workers用于简单的工具服务器;要获得完整的MCP功能,请使用Node.js或实现持久对象。
许可证
麻省理工学院
