cloudflare-mcp-server by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill cloudflare-mcp-server使用 TypeScript 在 Cloudflare Workers 上构建和部署 模型上下文协议 (MCP) 服务器。
状态 : 生产就绪 ✅ 最后更新 : 2026-01-21 最新版本 : @modelcontextprotocol/sdk@1.25.3, @cloudflare/workers-oauth-provider@0.2.2, agents@0.3.6
近期更新 (2025) :
本技能教你如何在 Cloudflare 上构建 远程 MCP 服务器 —— 唯一官方支持远程 MCP 的平台。
适用场景 : 避免 24+ 个常见的 MCP + Cloudflare 错误 (尤其是 URL 路径不匹配 —— 这是导致失败的首要原因)
从 Cloudflare 官方模板开始:
npm create cloudflare@latest -- my-mcp-server \
--template=cloudflare/ai/demos/remote-mcp-authless
cd my-mcp-server && npm install && npm run dev
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
根据认证需求选择模板:
remote-mcp-authless - 无认证 (推荐给大多数情况)remote-mcp-github-oauth - GitHub OAuthremote-mcp-google-oauth - Google OAuthremote-mcp-auth0 / remote-mcp-authkit - 企业级 SSOmcp-server-bearer-auth - 自定义认证生产环境示例 : https://github.com/cloudflare/mcp-server-cloudflare (包含真实集成的 15 个服务器)
# 1. 从模板创建
npm create cloudflare@latest -- my-mcp --template=cloudflare/ai/demos/remote-mcp-authless
cd my-mcp && npm install && npm run dev
# 2. 部署
npx wrangler deploy
# 注意输出的 URL: https://my-mcp.YOUR_ACCOUNT.workers.dev
# 3. 测试 (防止 80% 的错误!)
curl https://my-mcp.YOUR_ACCOUNT.workers.dev/sse
# 预期结果: {"name":"My MCP Server","version":"1.0.0","transports":["/sse","/mcp"]}
# 得到 404? 请查看下面的 "HTTP 传输基础"
# 4. 配置客户端 (~/.config/claude/claude_desktop_config.json)
{
"mcpServers": {
"my-mcp": {
"url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse" // 必须与 curl URL 匹配!
}
}
}
# 5. 重启 Claude Desktop (配置仅在启动时加载)
部署后检查清单:
MCP 服务器连接失败的首要原因是 URL 路径配置错误。
当你在特定路径上提供 MCP 服务器时,客户端 URL 必须完全匹配。
示例 1: 在/sse 路径提供服务
// src/index.ts
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const { pathname } = new URL(request.url);
if (pathname.startsWith("/sse")) {
return MyMCP.serveSSE("/sse").fetch(request, env, ctx); // ← 基础路径是 "/sse"
}
return new Response("Not Found", { status: 404 });
}
};
客户端配置必须包含/sse:
{
"mcpServers": {
"my-mcp": {
"url": "https://my-mcp.workers.dev/sse" // ✅ 正确
}
}
}
❌ 错误的客户端配置 :
"url": "https://my-mcp.workers.dev" // 缺少 /sse → 404
"url": "https://my-mcp.workers.dev/" // 缺少 /sse → 404
"url": "http://localhost:8788" // 部署后错误
示例 2: 在/ (根路径) 提供服务
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
return MyMCP.serveSSE("/").fetch(request, env, ctx); // ← 基础路径是 "/"
}
};
客户端配置 :
{
"mcpServers": {
"my-mcp": {
"url": "https://my-mcp.workers.dev" // ✅ 正确 (没有 /sse)
}
}
}
当你调用serveSSE("/sse") 时,MCP 工具在以下路径提供服务:
https://my-mcp.workers.dev/sse/tools/list
https://my-mcp.workers.dev/sse/tools/call
https://my-mcp.workers.dev/sse/resources/list
当你调用serveSSE("/") 时,MCP 工具在以下路径提供服务:
https://my-mcp.workers.dev/tools/list
https://my-mcp.workers.dev/tools/call
https://my-mcp.workers.dev/resources/list
基础路径会自动添加到所有 MCP 端点之前。
1. 客户端连接到: https://my-mcp.workers.dev/sse
↓
2. Worker 接收请求: { url: "https://my-mcp.workers.dev/sse", ... }
↓
3. 你的 fetch 处理器: const { pathname } = new URL(request.url)
↓
4. pathname === "/sse" → 检查通过
↓
5. MyMCP.serveSSE("/sse").fetch() → MCP 服务器处理请求
↓
6. 工具调用路由到: /sse/tools/call
如果客户端连接到https://my-mcp.workers.dev (缺少 /sse):
pathname === "/" → 检查失败 → 404 Not Found
步骤 1: 部署你的 MCP 服务器
npx wrangler deploy
# 输出: Deployed to https://my-mcp.YOUR_ACCOUNT.workers.dev
步骤 2: 使用 curl 测试基础路径
# 如果在 /sse 路径提供服务,测试此 URL:
curl https://my-mcp.YOUR_ACCOUNT.workers.dev/sse
# 应该返回 MCP 服务器信息 (不是 404)
步骤 3: 使用你测试过的确切 URL 更新客户端配置
{
"mcpServers": {
"my-mcp": {
"url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse" // 匹配 curl URL
}
}
}
步骤 4: 重启 Claude Desktop
部署后,请验证:
curl https://worker.dev/sse 返回 MCP 服务器信息 (不是 404)workes.dev 而不是 workers.dev)https:// (而不是 http://)有两种传输方式可用:
SSE (服务器发送事件) - 传统方式,兼容性广
MyMCP.serveSSE("/sse").fetch(request, env, ctx)
可流式 HTTP - 2025 标准 (推荐),单一端点
MyMCP.serve("/mcp").fetch(request, env, ctx)
同时支持两者以获得最大兼容性:
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const { pathname } = new URL(request.url);
if (pathname.startsWith("/sse")) {
return MyMCP.serveSSE("/sse").fetch(request, env, ctx);
}
if (pathname.startsWith("/mcp")) {
return MyMCP.serve("/mcp").fetch(request, env, ctx);
}
return new Response("Not Found", { status: 404 });
}
};
关键 : 使用 pathname.startsWith() 来正确匹配路径!
MCP 服务器现在可以在工具执行期间请求用户输入:
// 在工具执行期间请求用户输入
const result = await this.elicit({
prompt: "输入你的 API 密钥:",
type: "password"
});
// 使用 Durable Objects 状态的交互式工作流
await this.state.storage.put("api_key", result);
使用场景 : 确认、表单、多步骤工作流 状态 : 在代理休眠期间保留
Agents SDK 将 MCP 模式转换为 TypeScript API:
// 旧方式: 直接工具调用
await server.callTool("get_user", { id: "123" });
// 新方式: 类型安全的生成 API
const user = await api.getUser("123");
优势 : 自动生成的文档注释、类型安全、代码补全
用于 MCP 客户端能力的新类:
import { MCPClientManager } from "agents/mcp";
const manager = new MCPClientManager(env);
await manager.connect("https://external-mcp.com/sse");
// 自动发现工具、资源、提示
// 处理重连、OAuth 流程、休眠
// 用于后台作业的任务队列
await this.queue.send({ task: "process_data", data });
// 电子邮件集成
async onEmail(message: Email) {
// 处理传入的电子邮件
const response = await this.generateReply(message);
await this.sendEmail(response);
}
单一端点替代了独立的连接/消息端点:
// 旧方式: 独立端点
/connect // 初始化连接
/message // 发送/接收消息
// 新方式: 单一可流式端点
/mcp // 所有通信通过 HTTP 流式传输
优势 : 简化的架构,更好的性能
CVE : GHSA-qgp8-v765-qxx9 严重性 : 严重 修复版本 : @cloudflare/workers-oauth-provider@0.0.5
问题 : OAuth 提供者库的早期版本存在严重漏洞,完全绕过了 PKCE 保护,可能允许攻击者拦截授权码。
所需操作 :
# 检查当前版本
npm list @cloudflare/workers-oauth-provider
# 如果版本 < 0.0.5,则更新
npm install @cloudflare/workers-oauth-provider@latest
最低安全版本 : @cloudflare/workers-oauth-provider@0.0.5 或更高版本
始终对 OAuth 令牌使用加密存储:
// ✅ 良好: workers-oauth-provider 自动处理加密
export default new OAuthProvider({
kv: (env) => env.OAUTH_KV, // 令牌加密存储
// ...
});
// ❌ 不良: 以明文存储令牌
await env.KV.put("access_token", token); // 安全风险!
用户作用域的 KV 键 防止用户间的数据泄露:
// ✅ 良好: 按用户 ID 命名空间
await env.KV.put(`user:${userId}:todos`, data);
// ❌ 不良: 全局命名空间
await env.KV.put(`todos`, data); // 所有用户可见的数据!
根据使用场景选择认证方式:
无认证 - 内部工具、开发 (模板: remote-mcp-authless)
Bearer 令牌 - 自定义认证 (模板: mcp-server-bearer-auth)
// 验证 Authorization: Bearer <token>
const token = request.headers.get("Authorization")?.replace("Bearer ", "");
if (!await validateToken(token, env)) {
return new Response("Unauthorized", { status: 401 });
}
OAuth 代理 - GitHub/Google (模板: remote-mcp-github-oauth)
import { OAuthProvider, GitHubHandler } from "@cloudflare/workers-oauth-provider";
export default new OAuthProvider({
authorizeEndpoint: "/authorize",
tokenEndpoint: "/token",
defaultHandler: new GitHubHandler({
clientId: (env) => env.GITHUB_CLIENT_ID,
clientSecret: (env) => env.GITHUB_CLIENT_SECRET,
scopes: ["repo", "user:email"]
}),
kv: (env) => env.OAUTH_KV,
apiHandlers: { "/sse": MyMCP.serveSSE("/sse") }
});
⚠️ 关键 : 所有 OAuth URL (url, authorizationUrl, tokenUrl) 必须使用 相同域名
remote-mcp-authkit)安全级别 : 无认证 (⚠️) < Bearer (✅) < OAuth 代理 (✅✅) < 远程 OAuth (✅✅✅)
McpAgent 扩展了 Durable Objects 以支持每个会话的状态:
// 存储 API
await this.state.storage.put("key", "value");
const value = await this.state.storage.get<string>("key");
// 必需的 wrangler.jsonc
{
"durable_objects": {
"bindings": [{ "name": "MY_MCP", "class_name": "MyMCP" }]
},
"migrations": [{ "tag": "v1", "new_classes": ["MyMCP"] }] // 首次部署时必需!
}
关键 : 首次部署时需要迁移
成本 : Durable Objects 现在包含在免费套餐中 (2025年)
重要 : McpAgent 对面向客户端的通信和内部通信使用不同的传输方式。
来源 : GitHub Issue #172
Client --- (SSE 或 HTTP) --> Worker --- (WebSocket) --> Durable Object
客户端 → Worker (外部):
/sse 端点/mcp 端点Worker → Durable Object (内部):
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const { pathname } = new URL(request.url);
// 客户端使用 SSE
if (pathname.startsWith("/sse")) {
// ✅ 客户端 → Worker: SSE
// ✅ Worker → DO: WebSocket (自动)
return MyMCP.serveSSE("/sse").fetch(request, env, ctx);
}
return new Response("Not Found", { status: 404 });
}
};
关键要点 : 你可以向客户端提供 SSE 服务,而无需担心内部的 WebSocket 要求。
所有 MCP 工具必须返回此确切格式:
this.server.tool(
"my_tool",
{ /* schema */ },
async (params) => {
// ✅ 正确: 返回包含 content 数组的对象
return {
content: [
{ type: "text", text: "你的结果在这里" }
]
};
// ❌ 错误: 原始字符串
return "你的结果在这里";
// ❌ 错误: 普通对象
return { result: "你的结果在这里" };
}
);
常见错误 : 返回原始字符串或普通对象,而不是正确的 MCP 内容格式。这会导致客户端解析错误。
根据认证用户动态添加工具:
export class MyMCP extends McpAgent<Env> {
async init() {
this.server = new McpServer({ name: "My MCP" });
// 所有用户的基础工具
this.server.tool("public_tool", { /* schema */ }, async (params) => {
// 对所有人可用
});
// 基于用户的条件性工具
const userId = this.props?.userId;
if (await this.isAdmin(userId)) {
this.server.tool("admin_tool", { /* schema */ }, async (params) => {
// 仅对管理员可用
});
}
// 高级功能
if (await this.isPremiumUser(userId)) {
this.server.tool("premium_feature", { /* schema */ }, async (params) => {
// 仅对高级用户可用
});
}
}
private async isAdmin(userId?: string): Promise<boolean> {
if (!userId) return false;
const userRole = await this.state.storage.get<string>(`user:${userId}:role`);
return userRole === "admin";
}
}
使用场景 :
async getCached<T>(key: string, ttlMs: number, fetchFn: () => Promise<T>): Promise<T> {
const cached = await this.state.storage.get<{ data: T, timestamp: number }>(key);
if (cached && Date.now() - cached.timestamp < ttlMs) {
return cached.data;
}
const data = await fetchFn();
await this.state.storage.put(key, { data, timestamp: Date.now() });
return data;
}
async rateLimit(key: string, maxRequests: number, windowMs: number): Promise<boolean> {
const requests = await this.state.storage.get<number[]>(`ratelimit:${key}`) || [];
const recentRequests = requests.filter(ts => Date.now() - ts < windowMs);
if (recentRequests.length >= maxRequests) return false;
recentRequests.push(Date.now());
await this.state.storage.put(`ratelimit:${key}`, recentRequests);
return true;
}
错误 : TypeError: Cannot read properties of undefined (reading 'serve')
原因 : 忘记导出 McpAgent 类
解决方案 :
export class MyMCP extends McpAgent { ... } // ✅ 必须导出
export default { fetch() { ... } }
错误 : 404 Not Found 或 Connection failed
原因 : serveSSE("/sse") 但客户端配置为 https://worker.dev (缺少 /sse)
解决方案 : 完全匹配基础路径
// 服务器在 /sse 路径提供服务
MyMCP.serveSSE("/sse").fetch(...)
// 客户端必须包含 /sse
{ "url": "https://worker.dev/sse" } // ✅ 正确
{ "url": "https://worker.dev" } // ❌ 错误 - 404
调试步骤 :
serveSSE("/sse") 对比 serveSSE("/")curl https://worker.dev/sse错误 : Connection failed: Unexpected response format
原因 : 客户端期望 SSE 但连接到 HTTP 端点 (或反之)
解决方案 : 匹配传输类型
// SSE 传输
MyMCP.serveSSE("/sse") // 客户端 URL: https://worker.dev/sse
// HTTP 传输
MyMCP.serve("/mcp") // 客户端 URL: https://worker.dev/mcp
最佳实践 : 同时支持两种传输 (参见传输选择指南)
错误 : /sse 和 /mcp 路由都失败或冲突
原因 : 路径匹配逻辑不正确
解决方案 : 正确使用 startsWith()
// ✅ 正确
if (pathname.startsWith("/sse")) {
return MyMCP.serveSSE("/sse").fetch(...);
}
if (pathname.startsWith("/mcp")) {
return MyMCP.serve("/mcp").fetch(...);
}
// ❌ 错误: 精确匹配会破坏子路径
if (pathname === "/sse") { // 破坏 /sse/tools/list
return MyMCP.serveSSE("/sse").fetch(...);
}
错误 : 开发环境工作正常,部署后失败
原因 : 客户端仍配置为 localhost URL
解决方案 : 部署后更新客户端配置
// 开发环境
{ "url": "http://localhost:8788/sse" }
// ⚠️ 必须在 npx wrangler deploy 后更新
{ "url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse" }
部署后检查清单 :
npx wrangler deploy 并记下输出 URL错误 : OAuth error: redirect_uri does not match
原因 : OAuth 重定向 URI 与部署的 URL 不匹配
解决方案 : 部署后更新所有 OAuth URL
{
"url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse",
"auth": {
"type": "oauth",
"authorizationUrl": "https://my-mcp.YOUR_ACCOUNT.workers.dev/authorize", // 必须匹配部署的域名
"tokenUrl": "https://my-mcp.YOUR_ACCOUNT.workers.dev/token"
}
}
关键 : 所有 URL 必须使用相同的协议和域名!
错误 : Access to fetch at '...' blocked by CORS policy 或 Method Not Allowed
原因 : 浏览器客户端为 CORS 预检发送 OPTIONS 请求,但服务器未处理它们
解决方案 : 添加 OPTIONS 处理器
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
// 处理 CORS 预检
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400"
}
});
}
// ... 你的 fetch 处理器的其余部分
}
};
何时需要 : 基于浏览器的 MCP 客户端 (如浏览器中的 MCP Inspector)
错误 : TypeError: Cannot read properties of undefined 或 JSON 解析中的 Unexpected token
原因 : 客户端发送格式错误的 JSON,服务器在解析前未验证
解决方案 : 在 try/catch 中包装请求处理
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
try {
// 你的 MCP 服务器逻辑
return await MyMCP.serveSSE("/sse").fetch(request, env, ctx);
} catch (error) {
console.error("请求处理错误:", error);
return new Response(
JSON.stringify({
error: "无效请求",
details: error.message
}),
{
status: 400,
headers: { "Content-Type": "application/json" }
}
);
}
}
};
错误 : TypeError: env.API_KEY is undefined 或静默失败 (工具返回空数据)
原因 : 必需的环境变量未配置或在运行时缺失
解决方案 : 添加启动验证
export class MyMCP extends McpAgent<Env> {
async init() {
// 验证必需的环境变量
if (!this.env.API_KEY) {
throw new Error("未配置 API_KEY 环境变量");
}
if (!this.env.DATABASE_URL) {
throw new Error("未配置 DATABASE_URL 环境变量");
}
// 继续工具注册
this.server.tool(...);
}
}
配置检查清单 :
.dev.vars (仅本地,gitignored)wrangler.jsonc 的 vars (公开) 或使用 wrangler secret (敏感)最佳实践 :
# .dev.vars (本地开发,gitignored)
API_KEY=dev-key-123
DATABASE_URL=http://localhost:3000
# wrangler.jsonc (公开配置)
{
"vars": {
"ENVIRONMENT": "production",
"LOG_LEVEL": "info"
}
}
# wrangler secret (生产环境密钥)
npx wrangler secret put API_KEY
npx wrangler secret put DATABASE_URL
错误 : TypeError: server.registerTool is not a function 或 this.server is undefined
原因 : 尝试将独立 SDK 模式与 McpAgent 类一起使用
解决方案 : 使用 McpAgent 的 this.server.tool() 模式
// ❌ 错误: 将独立 SDK 与 McpAgent 混合使用
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({ name: "My Server" });
server.registerTool(...); // 与 McpAgent 不兼容!
export class MyMCP extends McpAgent { /* 没有 server 属性 */ }
// ✅ 正确: McpAgent 模式
export class MyMCP extends McpAgent<Env> {
server = new McpServer({
name: "My MCP Server",
version: "1.0.0"
});
async init() {
this.server.tool("tool_name", ...); // 使用 this.server
}
}
关键区别 : McpAgent 提供 this.server 属性,独立 SDK 不提供。
错误 : 重连后工具调用失败,提示 "state not found"
原因 : 休眠时内存中的状态被清除
解决方案 : 使用 this.state.storage 替代实例属性
// ❌ 不要这样做: 休眠时丢失
this.userId = "123";
// ✅ 这样做: 通过休眠持久化
await this.state.storage.put("userId", "123");
错误 : TypeError: Cannot read properties of undefined (reading 'idFromName')
原因 : 忘记在 wrangler.jsonc 中添加 DO 绑定
解决方案 : 添加绑定 (参见有状态的 MCP 服务器部分)
{
"durable_objects": {
"bindings": [
{
"name": "MY_MCP",
"class_name": "MyMCP",
"script_name": "my-mcp-server"
}
]
}
}
错误 : Error: Durable Object class MyMCP has no migration defined
原因 : 首次 DO 部署需要迁移
解决方案 :
{
"migrations": [
{ "tag": "v1", "new_classes": ["MyMCP"] }
]
}
错误 : WebSocket 元数据在休眠唤醒时丢失
原因 : 未使用 serializeAttachment() 来保留连接元数据
解决方案 : 参见 WebSocket 休眠部分
安全风险 : 用户看不到他们正在授予的权限
原因 : 生产环境中 allowConsentScreen: false
解决方案 : 在生产环境中始终启用
export default new OAuthProvider({
allowConsentScreen: true, // ✅ 在生产环境中始终为 true
// ...
});
错误 : Error: JWT_SIGNING_KEY environment variable not set
原因 : OAuth 提供者需要签名密钥来生成令牌
解决方案 :
# 生成安全密钥
openssl rand -base64 32
# 添加到 wrangler secret
npx wrangler secret put JWT_SIGNING_KEY
错误 : ZodError: Invalid input type
原因 : 客户端发送字符串,模式期望数字 (或反之)
解决方案 : 使用 Zod 转换
// 接受字符串,转换为数字
param: z.string().transform(val => parseInt(val, 10))
// 或者: 接受两种类型
param: z.union([z.string(), z.number()]).transform(val =>
typeof val === "string" ? parseInt(val, 10) : val
)
错误 : 添加 /mcp 后 /sse 返回 404
原因 : 路径匹配不正确 (缺少 startsWith())
解决方案 : 正确使用 startsWith() 或精确匹配 (参见错误 #4)
错误 : OAuth 流程在本地开发中失败,或 Durable Objects 行为不同
原因 : Miniflare 不支持所有 DO 功能
解决方案 : 使用 npx wrangler dev --remote 以获得完整的 DO 支持
# 本地模拟 (更快但有限制)
npm run dev
# 远程 DOs (更慢但准确)
npx wrangler dev --remote
错误 : Claude Desktop 无法识别服务器
原因 : claude_desktop_config.json 中的 JSON 格式错误
解决方案 : 参见 "连接 Claude Desktop" 部分以获取正确格式
常见错误 :
// ❌ 错误: 缺少 "mcpServers" 包装器
{
"my-mcp": {
"url": "https://worker.dev/sse"
}
}
// ❌ 错误: 尾随逗号
{
"mcpServers": {
"my-mcp": {
"url": "https://worker.dev/sse", // ← 移除逗号
}
}
}
// ✅ 正确
{
"mcpServers": {
"my-mcp": {
"url": "https://worker.dev/sse"
}
}
}
问题 : 无法判断 Worker 是否正在运行或 URL 是否正确
影响 : 调试连接问题需要更长时间
解决方案 : 添加健康检查端点 (参见传输选择指南)
测试 :
curl https://my-mcp.workers.dev/health
# 应该返回: {"status":"ok","transports":{...}}
错误 : Access to fetch at '...' blocked by CORS policy
原因 : MCP 服务器未为跨域请求返回 CORS 头部
解决方案 : 向所有响应添加 CORS 头部
// 手动 CORS (如果不使用 OAuthProvider)
const corsHeaders = {
"Access-Control-Allow-Origin": "*", // 或特定来源
Build and deploy Model Context Protocol (MCP) servers on Cloudflare Workers with TypeScript.
Status : Production Ready ✅ Last Updated : 2026-01-21 Latest Versions : @modelcontextprotocol/sdk@1.25.3, @cloudflare/workers-oauth-provider@0.2.2, agents@0.3.6
Recent Updates (2025) :
This skill teaches you to build remote MCP servers on Cloudflare - the ONLY platform with official remote MCP support.
Use when : Avoiding 24+ common MCP + Cloudflare errors (especially URL path mismatches - the #1 failure cause)
Start with Cloudflare's official template:
npm create cloudflare@latest -- my-mcp-server \
--template=cloudflare/ai/demos/remote-mcp-authless
cd my-mcp-server && npm install && npm run dev
Choose template based on auth needs:
remote-mcp-authless - No auth (recommended for most)remote-mcp-github-oauth - GitHub OAuthremote-mcp-google-oauth - Google OAuthremote-mcp-auth0 / remote-mcp-authkit - Enterprise SSOmcp-server-bearer-auth - Custom authAll templates : https://github.com/cloudflare/ai/tree/main/demos
Production examples : https://github.com/cloudflare/mcp-server-cloudflare (15 servers with real integrations)
# 1. Create from template
npm create cloudflare@latest -- my-mcp --template=cloudflare/ai/demos/remote-mcp-authless
cd my-mcp && npm install && npm run dev
# 2. Deploy
npx wrangler deploy
# Note the output URL: https://my-mcp.YOUR_ACCOUNT.workers.dev
# 3. Test (PREVENTS 80% OF ERRORS!)
curl https://my-mcp.YOUR_ACCOUNT.workers.dev/sse
# Expected: {"name":"My MCP Server","version":"1.0.0","transports":["/sse","/mcp"]}
# Got 404? See "HTTP Transport Fundamentals" below
# 4. Configure client (~/.config/claude/claude_desktop_config.json)
{
"mcpServers": {
"my-mcp": {
"url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse" // Must match curl URL!
}
}
}
# 5. Restart Claude Desktop (config only loads at startup)
Post-Deployment Checklist:
The #1 reason MCP servers fail to connect is URL path configuration mistakes.
When you serve an MCP server at a specific path, the client URL must match exactly.
Example 1: Serving at/sse
// src/index.ts
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const { pathname } = new URL(request.url);
if (pathname.startsWith("/sse")) {
return MyMCP.serveSSE("/sse").fetch(request, env, ctx); // ← Base path is "/sse"
}
return new Response("Not Found", { status: 404 });
}
};
Client configuration MUST include/sse:
{
"mcpServers": {
"my-mcp": {
"url": "https://my-mcp.workers.dev/sse" // ✅ Correct
}
}
}
❌ WRONG client configurations :
"url": "https://my-mcp.workers.dev" // Missing /sse → 404
"url": "https://my-mcp.workers.dev/" // Missing /sse → 404
"url": "http://localhost:8788" // Wrong after deploy
Example 2: Serving at/ (root)
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
return MyMCP.serveSSE("/").fetch(request, env, ctx); // ← Base path is "/"
}
};
Client configuration :
{
"mcpServers": {
"my-mcp": {
"url": "https://my-mcp.workers.dev" // ✅ Correct (no /sse)
}
}
}
When you callserveSSE("/sse"), MCP tools are served at:
https://my-mcp.workers.dev/sse/tools/list
https://my-mcp.workers.dev/sse/tools/call
https://my-mcp.workers.dev/sse/resources/list
When you callserveSSE("/"), MCP tools are served at:
https://my-mcp.workers.dev/tools/list
https://my-mcp.workers.dev/tools/call
https://my-mcp.workers.dev/resources/list
The base path is prepended to all MCP endpoints automatically.
1. Client connects to: https://my-mcp.workers.dev/sse
↓
2. Worker receives request: { url: "https://my-mcp.workers.dev/sse", ... }
↓
3. Your fetch handler: const { pathname } = new URL(request.url)
↓
4. pathname === "/sse" → Check passes
↓
5. MyMCP.serveSSE("/sse").fetch() → MCP server handles request
↓
6. Tool calls routed to: /sse/tools/call
If client connects tohttps://my-mcp.workers.dev (missing /sse):
pathname === "/" → Check fails → 404 Not Found
Step 1: Deploy your MCP server
npx wrangler deploy
# Output: Deployed to https://my-mcp.YOUR_ACCOUNT.workers.dev
Step 2: Test the base path with curl
# If serving at /sse, test this URL:
curl https://my-mcp.YOUR_ACCOUNT.workers.dev/sse
# Should return MCP server info (not 404)
Step 3: Update client config with the EXACT URL you tested
{
"mcpServers": {
"my-mcp": {
"url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse" // Match curl URL
}
}
}
Step 4: Restart Claude Desktop
After deploying, verify:
curl https://worker.dev/sse returns MCP server info (not 404)workes.dev instead of workers.dev)https:// (not http://) for deployed WorkersTwo transports available:
SSE (Server-Sent Events) - Legacy, wide compatibility
MyMCP.serveSSE("/sse").fetch(request, env, ctx)
Streamable HTTP - 2025 standard (recommended), single endpoint
MyMCP.serve("/mcp").fetch(request, env, ctx)
Support both for maximum compatibility:
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const { pathname } = new URL(request.url);
if (pathname.startsWith("/sse")) {
return MyMCP.serveSSE("/sse").fetch(request, env, ctx);
}
if (pathname.startsWith("/mcp")) {
return MyMCP.serve("/mcp").fetch(request, env, ctx);
}
return new Response("Not Found", { status: 404 });
}
};
CRITICAL : Use pathname.startsWith() to match paths correctly!
MCP servers can now request user input during tool execution:
// Request user input during tool execution
const result = await this.elicit({
prompt: "Enter your API key:",
type: "password"
});
// Interactive workflows with Durable Objects state
await this.state.storage.put("api_key", result);
Use cases : Confirmations, forms, multi-step workflows State : Preserved during agent hibernation
Agents SDK converts MCP schema → TypeScript API:
// Old: Direct tool calls
await server.callTool("get_user", { id: "123" });
// New: Type-safe generated API
const user = await api.getUser("123");
Benefits : Auto-generated doc comments, type safety, code completion
New class for MCP client capabilities:
import { MCPClientManager } from "agents/mcp";
const manager = new MCPClientManager(env);
await manager.connect("https://external-mcp.com/sse");
// Auto-discovers tools, resources, prompts
// Handles reconnection, OAuth flow, hibernation
// Task queues for background jobs
await this.queue.send({ task: "process_data", data });
// Email integration
async onEmail(message: Email) {
// Process incoming email
const response = await this.generateReply(message);
await this.sendEmail(response);
}
Single endpoint replaces separate connection/messaging endpoints:
// Old: Separate endpoints
/connect // Initialize connection
/message // Send/receive messages
// New: Single streamable endpoint
/mcp // All communication via HTTP streaming
Benefit : Simplified architecture, better performance
CVE : GHSA-qgp8-v765-qxx9 Severity : Critical Fixed in : @cloudflare/workers-oauth-provider@0.0.5
Problem : Earlier versions of the OAuth provider library had a critical vulnerability that completely bypassed PKCE protection, potentially allowing attackers to intercept authorization codes.
Action Required :
# Check current version
npm list @cloudflare/workers-oauth-provider
# Update if < 0.0.5
npm install @cloudflare/workers-oauth-provider@latest
Minimum Safe Version : @cloudflare/workers-oauth-provider@0.0.5 or later
Always use encrypted storage for OAuth tokens:
// ✅ GOOD: workers-oauth-provider handles encryption automatically
export default new OAuthProvider({
kv: (env) => env.OAUTH_KV, // Tokens stored encrypted
// ...
});
// ❌ BAD: Storing tokens in plain text
await env.KV.put("access_token", token); // Security risk!
User-scoped KV keys prevent data leakage between users:
// ✅ GOOD: Namespace by user ID
await env.KV.put(`user:${userId}:todos`, data);
// ❌ BAD: Global namespace
await env.KV.put(`todos`, data); // Data visible to all users!
Choose auth based on use case:
No Auth - Internal tools, dev (Template: remote-mcp-authless)
Bearer Token - Custom auth (Template: mcp-server-bearer-auth)
// Validate Authorization: Bearer <token>
const token = request.headers.get("Authorization")?.replace("Bearer ", "");
if (!await validateToken(token, env)) {
return new Response("Unauthorized", { status: 401 });
}
OAuth Proxy - GitHub/Google (Template: remote-mcp-github-oauth)
import { OAuthProvider, GitHubHandler } from "@cloudflare/workers-oauth-provider";
export default new OAuthProvider({
authorizeEndpoint: "/authorize",
tokenEndpoint: "/token",
defaultHandler: new GitHubHandler({
clientId: (env) => env.GITHUB_CLIENT_ID,
clientSecret: (env) => env.GITHUB_CLIENT_SECRET,
scopes: ["repo", "user:email"]
}),
kv: (env) => env.OAUTH_KV,
apiHandlers: { "/sse": MyMCP.serveSSE("/sse") }
});
⚠️ CRITICAL : All OAuth URLs (url, authorizationUrl, tokenUrl) must use same domain
remote-mcp-authkit)Security levels : No Auth (⚠️) < Bearer (✅) < OAuth Proxy (✅✅) < Remote OAuth (✅✅✅)
McpAgent extends Durable Objects for per-session state:
// Storage API
await this.state.storage.put("key", "value");
const value = await this.state.storage.get<string>("key");
// Required wrangler.jsonc
{
"durable_objects": {
"bindings": [{ "name": "MY_MCP", "class_name": "MyMCP" }]
},
"migrations": [{ "tag": "v1", "new_classes": ["MyMCP"] }] // Required on first deploy!
}
Critical : Migrations required on first deployment
Cost : Durable Objects now included in free tier (2025)
Important : McpAgent uses different transports for client-facing vs internal communication.
Source : GitHub Issue #172
Client --- (SSE or HTTP) --> Worker --- (WebSocket) --> Durable Object
Client → Worker (External):
/sse endpoint/mcp endpointWorker → Durable Object (Internal):
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const { pathname } = new URL(request.url);
// Client uses SSE
if (pathname.startsWith("/sse")) {
// ✅ Client → Worker: SSE
// ✅ Worker → DO: WebSocket (automatic)
return MyMCP.serveSSE("/sse").fetch(request, env, ctx);
}
return new Response("Not Found", { status: 404 });
}
};
Key Takeaway : You can serve SSE to clients without worrying about the internal WebSocket requirement.
Source : Stytch Blog - Building MCP Server with OAuth
All MCP tools must return this exact format:
this.server.tool(
"my_tool",
{ /* schema */ },
async (params) => {
// ✅ CORRECT: Return object with content array
return {
content: [
{ type: "text", text: "Your result here" }
]
};
// ❌ WRONG: Raw string
return "Your result here";
// ❌ WRONG: Plain object
return { result: "Your result here" };
}
);
Common mistake : Returning raw strings or plain objects instead of proper MCP content format. This causes client parsing errors.
Source : Cloudflare Blog - Building AI Agents
Dynamically add tools based on authenticated user:
export class MyMCP extends McpAgent<Env> {
async init() {
this.server = new McpServer({ name: "My MCP" });
// Base tools for all users
this.server.tool("public_tool", { /* schema */ }, async (params) => {
// Available to everyone
});
// Conditional tools based on user
const userId = this.props?.userId;
if (await this.isAdmin(userId)) {
this.server.tool("admin_tool", { /* schema */ }, async (params) => {
// Only available to admins
});
}
// Premium features
if (await this.isPremiumUser(userId)) {
this.server.tool("premium_feature", { /* schema */ }, async (params) => {
// Only for premium users
});
}
}
private async isAdmin(userId?: string): Promise<boolean> {
if (!userId) return false;
const userRole = await this.state.storage.get<string>(`user:${userId}:role`);
return userRole === "admin";
}
}
Use cases :
async getCached<T>(key: string, ttlMs: number, fetchFn: () => Promise<T>): Promise<T> {
const cached = await this.state.storage.get<{ data: T, timestamp: number }>(key);
if (cached && Date.now() - cached.timestamp < ttlMs) {
return cached.data;
}
const data = await fetchFn();
await this.state.storage.put(key, { data, timestamp: Date.now() });
return data;
}
async rateLimit(key: string, maxRequests: number, windowMs: number): Promise<boolean> {
const requests = await this.state.storage.get<number[]>(`ratelimit:${key}`) || [];
const recentRequests = requests.filter(ts => Date.now() - ts < windowMs);
if (recentRequests.length >= maxRequests) return false;
recentRequests.push(Date.now());
await this.state.storage.put(`ratelimit:${key}`, recentRequests);
return true;
}
Error : TypeError: Cannot read properties of undefined (reading 'serve')
Cause : Forgot to export McpAgent class
Solution :
export class MyMCP extends McpAgent { ... } // ✅ Must export
export default { fetch() { ... } }
Error : 404 Not Found or Connection failed
Cause : serveSSE("/sse") but client configured with https://worker.dev (missing /sse)
Solution : Match base paths exactly
// Server serves at /sse
MyMCP.serveSSE("/sse").fetch(...)
// Client MUST include /sse
{ "url": "https://worker.dev/sse" } // ✅ Correct
{ "url": "https://worker.dev" } // ❌ Wrong - 404
Debug steps :
serveSSE("/sse") vs serveSSE("/")curl https://worker.dev/sseError : Connection failed: Unexpected response format
Cause : Client expects SSE but connects to HTTP endpoint (or vice versa)
Solution : Match transport types
// SSE transport
MyMCP.serveSSE("/sse") // Client URL: https://worker.dev/sse
// HTTP transport
MyMCP.serve("/mcp") // Client URL: https://worker.dev/mcp
Best practice : Support both transports (see Transport Selection Guide)
Error : Both /sse and /mcp routes fail or conflict
Cause : Incorrect path matching logic
Solution : Use startsWith() correctly
// ✅ CORRECT
if (pathname.startsWith("/sse")) {
return MyMCP.serveSSE("/sse").fetch(...);
}
if (pathname.startsWith("/mcp")) {
return MyMCP.serve("/mcp").fetch(...);
}
// ❌ WRONG: Exact match breaks sub-paths
if (pathname === "/sse") { // Breaks /sse/tools/list
return MyMCP.serveSSE("/sse").fetch(...);
}
Error : Works in dev, fails after deployment
Cause : Client still configured with localhost URL
Solution : Update client config after deployment
// Development
{ "url": "http://localhost:8788/sse" }
// ⚠️ MUST UPDATE after npx wrangler deploy
{ "url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse" }
Post-deployment checklist :
npx wrangler deploy and note output URLError : OAuth error: redirect_uri does not match
Cause : OAuth redirect URI doesn't match deployed URL
Solution : Update ALL OAuth URLs after deployment
{
"url": "https://my-mcp.YOUR_ACCOUNT.workers.dev/sse",
"auth": {
"type": "oauth",
"authorizationUrl": "https://my-mcp.YOUR_ACCOUNT.workers.dev/authorize", // Must match deployed domain
"tokenUrl": "https://my-mcp.YOUR_ACCOUNT.workers.dev/token"
}
}
CRITICAL : All URLs must use the same protocol and domain!
Error : Access to fetch at '...' blocked by CORS policy or Method Not Allowed
Cause : Browser clients send OPTIONS requests for CORS preflight, but server doesn't handle them
Solution : Add OPTIONS handler
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
// Handle CORS preflight
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400"
}
});
}
// ... rest of your fetch handler
}
};
When needed : Browser-based MCP clients (like MCP Inspector in browser)
Error : TypeError: Cannot read properties of undefined or Unexpected token in JSON parsing
Cause : Client sends malformed JSON, server doesn't validate before parsing
Solution : Wrap request handling in try/catch
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
try {
// Your MCP server logic
return await MyMCP.serveSSE("/sse").fetch(request, env, ctx);
} catch (error) {
console.error("Request handling error:", error);
return new Response(
JSON.stringify({
error: "Invalid request",
details: error.message
}),
{
status: 400,
headers: { "Content-Type": "application/json" }
}
);
}
}
};
Error : TypeError: env.API_KEY is undefined or silent failures (tools return empty data)
Cause : Required environment variables not configured or missing at runtime
Solution : Add startup validation
export class MyMCP extends McpAgent<Env> {
async init() {
// Validate required environment variables
if (!this.env.API_KEY) {
throw new Error("API_KEY environment variable not configured");
}
if (!this.env.DATABASE_URL) {
throw new Error("DATABASE_URL environment variable not configured");
}
// Continue with tool registration
this.server.tool(...);
}
}
Configuration checklist :
.dev.vars (local only, gitignored)wrangler.jsonc vars (public) or use wrangler secret (sensitive)Best practices :
# .dev.vars (local development, gitignored)
API_KEY=dev-key-123
DATABASE_URL=http://localhost:3000
# wrangler.jsonc (public config)
{
"vars": {
"ENVIRONMENT": "production",
"LOG_LEVEL": "info"
}
}
# wrangler secret (production secrets)
npx wrangler secret put API_KEY
npx wrangler secret put DATABASE_URL
Error : TypeError: server.registerTool is not a function or this.server is undefined
Cause : Trying to use standalone SDK patterns with McpAgent class
Solution : Use McpAgent's this.server.tool() pattern
// ❌ WRONG: Mixing standalone SDK with McpAgent
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({ name: "My Server" });
server.registerTool(...); // Not compatible with McpAgent!
export class MyMCP extends McpAgent { /* no server property */ }
// ✅ CORRECT: McpAgent pattern
export class MyMCP extends McpAgent<Env> {
server = new McpServer({
name: "My MCP Server",
version: "1.0.0"
});
async init() {
this.server.tool("tool_name", ...); // Use this.server
}
}
Key difference : McpAgent provides this.server property, standalone SDK doesn't.
Error : Tool calls fail after reconnect with "state not found"
Cause : In-memory state cleared on hibernation
Solution : Use this.state.storage instead of instance properties
// ❌ DON'T: Lost on hibernation
this.userId = "123";
// ✅ DO: Persists through hibernation
await this.state.storage.put("userId", "123");
Error : TypeError: Cannot read properties of undefined (reading 'idFromName')
Cause : Forgot DO binding in wrangler.jsonc
Solution : Add binding (see Stateful MCP Servers section)
{
"durable_objects": {
"bindings": [
{
"name": "MY_MCP",
"class_name": "MyMCP",
"script_name": "my-mcp-server"
}
]
}
}
Error : Error: Durable Object class MyMCP has no migration defined
Cause : First DO deployment requires migration
Solution :
{
"migrations": [
{ "tag": "v1", "new_classes": ["MyMCP"] }
]
}
Error : WebSocket metadata lost on hibernation wake
Cause : Not using serializeAttachment() to preserve connection metadata
Solution : See WebSocket Hibernation section
Security risk : Users don't see what permissions they're granting
Cause : allowConsentScreen: false in production
Solution : Always enable in production
export default new OAuthProvider({
allowConsentScreen: true, // ✅ Always true in production
// ...
});
Error : Error: JWT_SIGNING_KEY environment variable not set
Cause : OAuth Provider requires signing key for tokens
Solution :
# Generate secure key
openssl rand -base64 32
# Add to wrangler secret
npx wrangler secret put JWT_SIGNING_KEY
Error : ZodError: Invalid input type
Cause : Client sends string, schema expects number (or vice versa)
Solution : Use Zod transforms
// Accept string, convert to number
param: z.string().transform(val => parseInt(val, 10))
// Or: Accept both types
param: z.union([z.string(), z.number()]).transform(val =>
typeof val === "string" ? parseInt(val, 10) : val
)
Error : /sse returns 404 after adding /mcp
Cause : Incorrect path matching (missing startsWith())
Solution : Use startsWith() or exact matches correctly (see Error #4)
Error : OAuth flow fails in local dev, or Durable Objects behave differently
Cause : Miniflare doesn't support all DO features
Solution : Use npx wrangler dev --remote for full DO support
# Local simulation (faster but limited)
npm run dev
# Remote DOs (slower but accurate)
npx wrangler dev --remote
Error : Claude Desktop doesn't recognize server
Cause : Wrong JSON format in claude_desktop_config.json
Solution : See "Connect Claude Desktop" section for correct format
Common mistakes :
// ❌ WRONG: Missing "mcpServers" wrapper
{
"my-mcp": {
"url": "https://worker.dev/sse"
}
}
// ❌ WRONG: Trailing comma
{
"mcpServers": {
"my-mcp": {
"url": "https://worker.dev/sse", // ← Remove comma
}
}
}
// ✅ CORRECT
{
"mcpServers": {
"my-mcp": {
"url": "https://worker.dev/sse"
}
}
}
Issue : Can't tell if Worker is running or if URL is correct
Impact : Debugging connection issues takes longer
Solution : Add health check endpoint (see Transport Selection Guide)
Test :
curl https://my-mcp.workers.dev/health
# Should return: {"status":"ok","transports":{...}}
Error : Access to fetch at '...' blocked by CORS policy
Cause : MCP server doesn't return CORS headers for cross-origin requests
Solution : Add CORS headers to all responses
// Manual CORS (if not using OAuthProvider)
const corsHeaders = {
"Access-Control-Allow-Origin": "*", // Or specific origin
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization"
};
// Add to responses
return new Response(body, {
headers: {
...corsHeaders,
"Content-Type": "application/json"
}
});
Note : OAuthProvider handles CORS automatically!
Error : IoContext timed out due to inactivity, waitUntil tasks were cancelled
Source : GitHub Issue #640
Cause : When implementing MCP servers using McpAgent with custom Bearer authentication, the IoContext times out during the MCP protocol initialization handshake (before any tools are called).
Symptoms :
/mcp are canceledAffected Code Pattern :
// Custom Bearer auth without OAuthProvider wrapper
export default {
fetch: async (req, env, ctx) => {
const authHeader = req.headers.get("Authorization");
if (!authHeader?.startsWith("Bearer ")) {
return new Response("Unauthorized", { status: 401 });
}
if (url.pathname === "/sse") {
return MyMCP.serveSSE("/sse")(req, env, ctx); // ← Timeout here
}
return new Response("Not found", { status: 404 });
}
};
Root Cause Hypothesis :
OAuthProvider wrapper even for custom Bearer authCloudflareMCPServer instead of standard McpServerWorkaround : Use official templates with OAuthProvider pattern instead of custom Bearer auth:
// Use OAuthProvider wrapper (recommended)
import { OAuthProvider } from "@cloudflare/workers-oauth-provider";
export default new OAuthProvider({
authorizeEndpoint: "/authorize",
tokenEndpoint: "/token",
// ... OAuth config
apiHandlers: { "/sse": MyMCP.serveSSE("/sse") }
});
Status : Investigation ongoing (issue open as of 2026-01-21)
Error : Connection to remote MCP server fails when using OAuth (works locally but fails when deployed)
Source : GitHub Issue #444
Cause : When deploying MCP client from Cloudflare Agents repository to Workers, client fails to connect to MCP servers secured with OAuth.
Symptoms :
Troubleshooting Steps :
Verify OAuth tokens are handled correctly during remote connection attempts
// Check token is being passed to remote server
console.log("Connecting with token:", token ? "present" : "missing");
Check network permissions to access OAuth provider
// Ensure Worker can reach OAuth endpoints
const response = await fetch("https://oauth-provider.com/token");
Verify CORS configuration on OAuth provider
// OAuth provider must allow Worker origin
headers: {
"Access-Control-Allow-Origin": "https://your-worker.workers.dev",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization"
}
Check redirect URIs match deployed URLs
{
"url": "https://mcp.workers.dev/sse",
"auth": {
"authorizationUrl": "https://mcp.workers.dev/authorize", // Must match deployed domain
"tokenUrl": "https://mcp.workers.dev/token"
}
}
Deployment Checklist :
wrangler secret)Related : Issue #640 (both involve OAuth/auth in remote deployments)
# Local dev
npm run dev # Miniflare (fast)
npx wrangler dev --remote # Remote DOs (accurate)
# Test with MCP Inspector
npx @modelcontextprotocol/inspector@latest
# Open http://localhost:5173, enter http://localhost:8788/sse
# Deploy
npx wrangler login # First time only
npx wrangler deploy
# ⚠️ CRITICAL: Update client config with deployed URL!
# Monitor logs
npx wrangler tail
Package Versions : @modelcontextprotocol/sdk@1.25.3, @cloudflare/workers-oauth-provider@0.2.2, agents@0.3.6 Last Verified : 2026-01-21 Errors Prevented : 24 documented issues (100% prevention rate) Skill Version : 3.1.0 | Changes : Added IoContext timeout (#23), OAuth remote failures (#24), Security section (PKCE vulnerability), Architecture clarification (internal WebSocket), Tool return format pattern, Conditional tool registration
Weekly Installs
328
Repository
GitHub Stars
652
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
claude-code272
gemini-cli220
opencode219
cursor206
antigravity198
codex194
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
105,000 周安装