OpenAI Apps MCP by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill 'OpenAI Apps MCP'状态 : 生产就绪 最后更新 : 2026-01-21 依赖项 : cloudflare-worker-base, hono-routing (可选) 最新版本 : @modelcontextprotocol/sdk@1.25.3, hono@4.11.3, zod@4.3.5, wrangler@4.58.0
在 Cloudflare Workers 上使用 MCP(模型上下文协议) 服务器构建 ChatGPT 应用。通过自定义工具和交互式小部件(在 iframe 中渲染的 HTML/JS UI)扩展 ChatGPT。
架构 : ChatGPT → MCP 端点 (JSON-RPC 2.0) → 工具处理器 → 小部件资源 (HTML)
状态 : 应用已向商业/企业/教育版用户开放(2025年11月13日 GA)。MCP 应用扩展 (SEP-1865) 于 2025年11月21日正式确定。
npm create cloudflare@latest my-openai-app -- --type hello-world --ts --git --deploy false
cd my-openai-app
npm install @modelcontextprotocol/sdk@1.25.3 hono@4.11.3 zod@4.3.5
npm install -D @cloudflare/vite-plugin@1.17.1 vite@7.2.4
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
{
"name": "my-openai-app",
"main": "dist/index.js",
"compatibility_flags": ["nodejs_compat"], // MCP SDK 必需
"assets": {
"directory": "dist/client",
"binding": "ASSETS" // 必须与 TypeScript 匹配
}
}
src/index.ts)import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
const app = new Hono<{ Bindings: { ASSETS: Fetcher } }>();
// 关键:必须允许 chatgpt.com
app.use('/mcp/*', cors({ origin: 'https://chatgpt.com' }));
const mcpServer = new Server(
{ name: 'my-app', version: '1.0.0' },
{ capabilities: { tools: {}, resources: {} } }
);
// 工具注册
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'hello',
description: '当用户想要查看问候语时使用此工具',
inputSchema: {
type: 'object',
properties: { name: { type: 'string' } },
required: ['name']
},
annotations: {
openai: { outputTemplate: 'ui://widget/hello.html' } // 小部件 URI
}
}]
}));
// 工具执行
mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'hello') {
const { name } = request.params.arguments as { name: string };
return {
content: [{ type: 'text', text: `Hello, ${name}!` }],
_meta: { initialData: { name } } // 传递给小部件
};
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
app.post('/mcp', async (c) => {
const body = await c.req.json();
const response = await mcpServer.handleRequest(body);
return c.json(response);
});
app.get('/widgets/*', async (c) => c.env.ASSETS.fetch(c.req.raw));
export default app;
src/widgets/hello.html)<!DOCTYPE html>
<html>
<head>
<style>
body { margin: 0; padding: 20px; font-family: system-ui; }
</style>
</head>
<body>
<div id="greeting">加载中...</div>
<script>
if (window.openai && window.openai.getInitialData) {
const data = window.openai.getInitialData();
document.getElementById('greeting').textContent = `Hello, ${data.name}! 👋`;
}
</script>
</body>
</html>
npm run build
npx wrangler deploy
npx @modelcontextprotocol/inspector https://my-app.workers.dev/mcp
CORS : 必须在 /mcp/* 路由上允许 https://chatgpt.com 小部件 URI : 必须使用 ui://widget/ 前缀(例如,ui://widget/map.html)MIME 类型 : HTML 资源必须为 text/html+skybridge 小部件数据 : 通过 _meta.initialData 传递(通过 window.openai.getInitialData() 访问)工具描述 : 面向操作("当用户想要...时使用此工具")ASSETS 绑定 : 从 ASSETS 提供小部件,而不是打包在 worker 代码中 SSE : 每 30 秒发送一次心跳(Workers 上超时为 100 秒)
此技能可预防 14 个已记录的问题:
错误 : Access to fetch blocked by CORS policy 修复 : app.use('/mcp/*', cors({ origin: 'https://chatgpt.com' }))
错误 : 小部件 URL 返回 404 (Not Found) 修复 : 使用 ui://widget/ 前缀(而不是 resource:// 或 /widgets/)
annotations: { openai: { outputTemplate: 'ui://widget/map.html' } }
错误 : 显示 HTML 源代码而不是渲染后的小部件 修复 : MIME 类型必须为 text/html+skybridge(而不是 text/html)
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [{ uri: 'ui://widget/map.html', mimeType: 'text/html+skybridge' }]
}));
错误 : TypeError: Cannot read property 'fetch' of undefined 修复 : wrangler.jsonc 中的绑定名称必须与 TypeScript 匹配
{ "assets": { "binding": "ASSETS" } } // wrangler.jsonc
type Bindings = { ASSETS: Fetcher }; // index.ts
错误 : SSE 流意外关闭 修复 : 每 30 秒发送一次心跳(Workers 在 100 秒无活动后超时)
const heartbeat = setInterval(async () => {
await stream.writeSSE({ data: JSON.stringify({ type: 'heartbeat' }), event: 'ping' });
}, 30000);
错误 : 工具已注册但从未出现在推荐中 修复 : 使用面向操作的描述
// ✅ 良好:'当用户想要在地图上查看位置时使用此工具'
// ❌ 不佳:'显示地图'
错误 : window.openai.getInitialData() 返回 undefined 修复 : 通过 _meta.initialData 传递数据
return {
content: [{ type: 'text', text: '这是您的地图' }],
_meta: { initialData: { location: 'SF', zoom: 12 } }
};
错误 : Refused to load script (CSP directive) 修复 : 使用内联脚本或同源脚本。第三方 CDN 被阻止。
<!-- ✅ 有效 --> <script>console.log('ok');</script>
<!-- ❌ 被阻止 --> <script src="https://cdn.example.com/lib.js"></script>
错误 : No response is returned from route handler (Next.js App Router) 来源 : GitHub Issue #1369 受影响版本 : v1.25.0 至 v1.25.2 修复版本 : v1.25.3 原因 : Hono(MCP SDK 依赖项)覆盖了 global.Response,破坏了扩展它的框架(Next.js, Remix, SvelteKit)。NextResponse instanceof 检查失败。预防措施 :
升级到 v1.25.3+(推荐)
修复前 : 使用 webStandardStreamableHTTPServerTransport 替代
或者 : 在与 Next.js/Remix/SvelteKit 应用不同的端口上运行 MCP 服务器
// ✅ v1.25.3+ - 已修复 const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, });
// ✅ v1.25.0-1.25.2 - 变通方案 import { webStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/index.js'; const transport = webStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, });
错误 : EvalError: Code generation from strings disallowed 来源 : GitHub Issue #689 原因 : 内部 AJV v6 验证器在边缘平台上使用了被禁止的 API 预防措施 : 避免在边缘平台(Cloudflare Workers, Vercel Edge, Deno Deploy)上使用 elicitInput()
变通方案 :
// ❌ 不要在 Cloudflare Workers 上使用
const userInput = await server.elicitInput({
prompt: "What is your name?",
schema: { type: "string" }
});
// ✅ 改用工具参数
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name } = request.params.arguments as { name: string };
// 用户通过工具调用提供,而不是引导
});
状态 : 需要 MCP SDK v2 来正确修复。跟踪 PR #844。
错误 : 400: No transport found for sessionId 来源 : GitHub Issue #273 原因 : SSEServerTransport 依赖于内存中的会话存储。在无服务器环境(AWS Lambda, Cloudflare Workers)中,初始的 GET /sse 请求可能由实例 A 处理,但后续的 POST /messages 请求却落在实例 B 上,而实例 B 缺少内存中的状态。预防措施 : 对于无服务器部署,使用 Streamable HTTP 传输(在 v1.24.0 中添加)而不是 SSE 解决方案 : 对于有状态的 SSE,部署到非无服务器环境(VPS, 长期运行的容器)
官方状态 : 通过引入 Streamable HTTP (v1.24+) 修复 - 现在是无服务器的 推荐标准。
来源 : Cloudflare 远程 MCP 服务器文档 原因 : OAuth 提供程序严格验证重定向 URL。本地主机和生产环境有不同的 URL,因此它们需要单独的 OAuth 客户端注册。预防措施 :
# 开发 OAuth 应用
Callback URL: http://localhost:8788/callback
# 生产 OAuth 应用
Callback URL: https://my-mcp-server.workers.dev/callback
额外要求 :
COOKIE_ENCRYPTION_KEY 环境变量: openssl rand -hex 32来源 : OpenAI Apps SDK - ChatGPT UI 原因 : 小部件状态仅持久化到与单个对话消息关联的一个小部件实例。当用户通过主聊天编辑器而不是小部件控件提交时,状态会被重置。预防措施 : 为获得最佳性能,保持状态负载在 4k 令牌 以下
// ✅ 良好 - 轻量级状态
window.openai.setWidgetState({ selectedId: "item-123", view: "grid" });
// ❌ 不佳 - 将导致性能问题
window.openai.setWidgetState({
items: largeArray, // 不要存储完整数据集
history: conversationLog, // 不要存储对话历史
cache: expensiveComputation // 不要缓存大型结果
});
最佳实践 :
来源 : OpenAI Apps SDK - ChatGPT UI 原因 : 通过 window.openai.callTool() 发起工具调用的组件需要该工具在 MCP 服务器上标记为"可由组件发起"。没有此标志,调用会静默失败。预防措施 : 在注解中将工具标记为 widgetCallable: true
// MCP 服务器 - 将工具标记为可由小部件调用
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'update_item',
description: '更新项目',
inputSchema: { /* ... */ },
annotations: {
openai: {
outputTemplate: 'ui://widget/item.html',
// ✅ 小部件发起调用所必需
widgetCallable: true
}
}
}]
}));
// 小部件 - 现在允许调用工具
window.openai.callTool({
name: 'update_item',
arguments: { id: itemId, status: 'completed' }
});
window.openai.uploadFile() 仅支持 3 种图像格式:image/png、image/jpeg 和 image/webp。其他格式会静默失败。
// ✅ 支持
window.openai.uploadFile({ accept: 'image/png,image/jpeg,image/webp' });
// ❌ 不支持(静默失败)
window.openai.uploadFile({ accept: 'application/pdf' });
window.openai.uploadFile({ accept: 'text/csv' });
其他文件类型的替代方案 :
工具调用超过"几百毫秒"会导致 ChatGPT 中的 UI 卡顿。官方文档建议对后端进行性能分析,并对慢速操作实施缓存。
性能目标 :
优化策略 :
// 1. 缓存昂贵的计算
const cache = new Map();
if (cache.has(key)) return cache.get(key);
const result = await expensiveOperation();
cache.set(key, result);
// 2. 使用 KV/D1 存储预计算数据
const cached = await env.KV.get(`result:${id}`);
if (cached) return JSON.parse(cached);
// 3. 对大型数据集进行分页
return {
content: [{ type: 'text', text: '前 20 条结果...' }],
_meta: { hasMore: true, nextPage: 2 }
};
// 4. 将慢速工作移至异步任务
// 立即返回,通过后续更新
破坏性变更 从 @modelcontextprotocol/sdk@1.24.x → 1.25.x:
setRequestHandler 现在是类型安全的 - 不正确的模式会抛出类型错误新功能 :
迁移 : 如果使用宽松的类型导入,请更新为特定的模式导入:
// ❌ 旧版(在 1.25.0 中移除)
import { Tools } from '@modelcontextprotocol/sdk/types.js';
// ✅ 新版 (1.25.1+)
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
破坏性变更 从 zod@3.x → 4.x:
.default() 现在期望输入类型(而不是输出类型)。对于旧行为,使用 .prefault()。error.issues(不是 error.errors).merge() 和 .superRefine() 已弃用性能 : 字符串解析快 14 倍,数组快 7 倍,对象快 6.5 倍
迁移 : 更新验证代码:
// Zod 4.x
try {
const validated = schema.parse(data);
} catch (error) {
if (error instanceof z.ZodError) {
return { content: [{ type: 'text', text: error.issues.map(e => e.message).join(', ') }] };
}
}
{
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.3",
"hono": "^4.11.3",
"zod": "^4.3.5"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.17.1",
"@cloudflare/workers-types": "^4.20260103.0",
"vite": "^7.2.4",
"wrangler": "^4.54.0"
}
}
开源示例 : https://github.com/jezweb/chatgpt-app-sdk(作品集轮播小部件)
window.openai.toolOutput → React 轮播/src/lib/mcp/server.ts - 完整的 MCP 处理器/src/server/tools/portfolio.ts - 带有小部件注解的工具/src/widgets/PortfolioWidget.tsx - 数据访问模式Cloudflare 一键部署 : 使用预构建模板和自动配置的 CI/CD 将 MCP 服务器部署到 Cloudflare Workers。包含 OAuth 包装器和 Python 支持。
Skybridge (社区): 专注于 React 的框架,支持小部件的 HMR 和增强的 MCP 服务器助手。非官方但积极维护。
注意 : 社区框架不受官方支持。请自行斟酌使用
每周安装次数
–
代码仓库
GitHub 星标数
643
首次出现
–
安全审计
Status : Production Ready Last Updated : 2026-01-21 Dependencies : cloudflare-worker-base, hono-routing (optional) Latest Versions : @modelcontextprotocol/sdk@1.25.3, hono@4.11.3, zod@4.3.5, wrangler@4.58.0
Build ChatGPT Apps using MCP (Model Context Protocol) servers on Cloudflare Workers. Extends ChatGPT with custom tools and interactive widgets (HTML/JS UI rendered in iframe).
Architecture : ChatGPT → MCP endpoint (JSON-RPC 2.0) → Tool handlers → Widget resources (HTML)
Status : Apps available to Business/Enterprise/Edu (GA Nov 13, 2025). MCP Apps Extension (SEP-1865) formalized Nov 21, 2025.
npm create cloudflare@latest my-openai-app -- --type hello-world --ts --git --deploy false
cd my-openai-app
npm install @modelcontextprotocol/sdk@1.25.3 hono@4.11.3 zod@4.3.5
npm install -D @cloudflare/vite-plugin@1.17.1 vite@7.2.4
{
"name": "my-openai-app",
"main": "dist/index.js",
"compatibility_flags": ["nodejs_compat"], // Required for MCP SDK
"assets": {
"directory": "dist/client",
"binding": "ASSETS" // Must match TypeScript
}
}
src/index.ts)import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
const app = new Hono<{ Bindings: { ASSETS: Fetcher } }>();
// CRITICAL: Must allow chatgpt.com
app.use('/mcp/*', cors({ origin: 'https://chatgpt.com' }));
const mcpServer = new Server(
{ name: 'my-app', version: '1.0.0' },
{ capabilities: { tools: {}, resources: {} } }
);
// Tool registration
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'hello',
description: 'Use this when user wants to see a greeting',
inputSchema: {
type: 'object',
properties: { name: { type: 'string' } },
required: ['name']
},
annotations: {
openai: { outputTemplate: 'ui://widget/hello.html' } // Widget URI
}
}]
}));
// Tool execution
mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'hello') {
const { name } = request.params.arguments as { name: string };
return {
content: [{ type: 'text', text: `Hello, ${name}!` }],
_meta: { initialData: { name } } // Passed to widget
};
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
app.post('/mcp', async (c) => {
const body = await c.req.json();
const response = await mcpServer.handleRequest(body);
return c.json(response);
});
app.get('/widgets/*', async (c) => c.env.ASSETS.fetch(c.req.raw));
export default app;
src/widgets/hello.html)<!DOCTYPE html>
<html>
<head>
<style>
body { margin: 0; padding: 20px; font-family: system-ui; }
</style>
</head>
<body>
<div id="greeting">Loading...</div>
<script>
if (window.openai && window.openai.getInitialData) {
const data = window.openai.getInitialData();
document.getElementById('greeting').textContent = `Hello, ${data.name}! 👋`;
}
</script>
</body>
</html>
npm run build
npx wrangler deploy
npx @modelcontextprotocol/inspector https://my-app.workers.dev/mcp
CORS : Must allow https://chatgpt.com on /mcp/* routes Widget URI : Must use ui://widget/ prefix (e.g., ui://widget/map.html) MIME Type : Must be text/html+skybridge for HTML resources Widget Data : Pass via _meta.initialData (accessed via window.openai.getInitialData()) Tool Descriptions : Action-oriented ("Use this when user wants to...") ASSETS Binding : Serve widgets from ASSETS, not bundled in worker code SSE : Send heartbeat every 30s (100s timeout on Workers)
This skill prevents 14 documented issues:
Error : Access to fetch blocked by CORS policy Fix : app.use('/mcp/*', cors({ origin: 'https://chatgpt.com' }))
Error : 404 (Not Found) for widget URL Fix : Use ui://widget/ prefix (not resource:// or /widgets/)
annotations: { openai: { outputTemplate: 'ui://widget/map.html' } }
Error : HTML source code visible instead of rendered widget Fix : MIME type must be text/html+skybridge (not text/html)
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [{ uri: 'ui://widget/map.html', mimeType: 'text/html+skybridge' }]
}));
Error : TypeError: Cannot read property 'fetch' of undefined Fix : Binding name in wrangler.jsonc must match TypeScript
{ "assets": { "binding": "ASSETS" } } // wrangler.jsonc
type Bindings = { ASSETS: Fetcher }; // index.ts
Error : SSE stream closes unexpectedly Fix : Send heartbeat every 30s (Workers timeout at 100s inactivity)
const heartbeat = setInterval(async () => {
await stream.writeSSE({ data: JSON.stringify({ type: 'heartbeat' }), event: 'ping' });
}, 30000);
Error : Tool registered but never appears in suggestions Fix : Use action-oriented descriptions
// ✅ Good: 'Use this when user wants to see a location on a map'
// ❌ Bad: 'Shows a map'
Error : window.openai.getInitialData() returns undefined Fix : Pass data via _meta.initialData
return {
content: [{ type: 'text', text: 'Here is your map' }],
_meta: { initialData: { location: 'SF', zoom: 12 } }
};
Error : Refused to load script (CSP directive) Fix : Use inline scripts or same-origin scripts. Third-party CDNs blocked.
<!-- ✅ Works --> <script>console.log('ok');</script>
<!-- ❌ Blocked --> <script src="https://cdn.example.com/lib.js"></script>
Error : No response is returned from route handler (Next.js App Router) Source : GitHub Issue #1369 Affected Versions : v1.25.0 to v1.25.2 Fixed In : v1.25.3 Why It Happens : Hono (MCP SDK dependency) overwrites global.Response, breaking frameworks that extend it (Next.js, Remix, SvelteKit). NextResponse instanceof check fails. Prevention :
Upgrade to v1.25.3+ (recommended)
Before fix : Use webStandardStreamableHTTPServerTransport instead
Or : Run MCP server on separate port from Next.js/Remix/SvelteKit app
// ✅ v1.25.3+ - Fixed const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, });
// ✅ v1.25.0-1.25.2 - Workaround import { webStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/index.js'; const transport = webStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, });
Error : EvalError: Code generation from strings disallowed Source : GitHub Issue #689 Why It Happens : Internal AJV v6 validator uses prohibited APIs on edge platforms Prevention : Avoid elicitInput() on edge platforms (Cloudflare Workers, Vercel Edge, Deno Deploy)
Workaround :
// ❌ Don't use on Cloudflare Workers
const userInput = await server.elicitInput({
prompt: "What is your name?",
schema: { type: "string" }
});
// ✅ Use tool parameters instead
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name } = request.params.arguments as { name: string };
// User provides via tool call, not elicitation
});
Status : Requires MCP SDK v2 to fix properly. Track PR #844.
Error : 400: No transport found for sessionId Source : GitHub Issue #273 Why It Happens : SSEServerTransport relies on in-memory session storage. In serverless environments (AWS Lambda, Cloudflare Workers), the initial GET /sse request may be handled by Instance A, but subsequent POST /messages requests land on Instance B, which lacks the in-memory state. Prevention : Use Streamable HTTP transport (added in v1.24.0) instead of SSE for serverless deployments Solution : For stateful SSE, deploy to non-serverless environments (VPS, long-running containers)
Official Status : Fixed by introducing Streamable HTTP (v1.24+) - now the recommended standard for serverless.
Source : Cloudflare Remote MCP Server Docs Why It Happens : OAuth providers validate redirect URLs strictly. Localhost and production have different URLs, so they need separate OAuth client registrations. Prevention :
# Development OAuth App
Callback URL: http://localhost:8788/callback
# Production OAuth App
Callback URL: https://my-mcp-server.workers.dev/callback
Additional Requirements :
COOKIE_ENCRYPTION_KEY env var: openssl rand -hex 32Source : OpenAI Apps SDK - ChatGPT UI Why It Happens : Widget state persists only to a single widget instance tied to one conversation message. State is reset when users submit via the main chat composer instead of widget controls. Prevention : Keep state payloads under 4k tokens for optimal performance
// ✅ Good - Lightweight state
window.openai.setWidgetState({ selectedId: "item-123", view: "grid" });
// ❌ Bad - Will cause performance issues
window.openai.setWidgetState({
items: largeArray, // Don't store full datasets
history: conversationLog, // Don't store conversation history
cache: expensiveComputation // Don't cache large results
});
Best Practice :
Source : OpenAI Apps SDK - ChatGPT UI Why It Happens : Components initiating tool calls via window.openai.callTool() require the tool marked as "able to be initiated by the component" on the MCP server. Without this flag, calls fail silently. Prevention : Mark tools as widgetCallable: true in annotations
// MCP Server - Mark tool as widget-callable
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'update_item',
description: 'Update an item',
inputSchema: { /* ... */ },
annotations: {
openai: {
outputTemplate: 'ui://widget/item.html',
// ✅ Required for widget-initiated calls
widgetCallable: true
}
}
}]
}));
// Widget - Now allowed to call tool
window.openai.callTool({
name: 'update_item',
arguments: { id: itemId, status: 'completed' }
});
Source : OpenAI Apps SDK - ChatGPT UI
window.openai.uploadFile() only supports 3 image formats: image/png, image/jpeg, and image/webp. Other formats fail silently.
// ✅ Supported
window.openai.uploadFile({ accept: 'image/png,image/jpeg,image/webp' });
// ❌ Not supported (fails silently)
window.openai.uploadFile({ accept: 'application/pdf' });
window.openai.uploadFile({ accept: 'text/csv' });
Alternative for Other File Types :
Source : OpenAI Apps SDK - Troubleshooting
Tool calls exceeding "a few hundred milliseconds" cause UI sluggishness in ChatGPT. Official docs recommend profiling backends and implementing caching for slow operations.
Performance Targets :
Optimization Strategies :
// 1. Cache expensive computations
const cache = new Map();
if (cache.has(key)) return cache.get(key);
const result = await expensiveOperation();
cache.set(key, result);
// 2. Use KV/D1 for pre-computed data
const cached = await env.KV.get(`result:${id}`);
if (cached) return JSON.parse(cached);
// 3. Paginate large datasets
return {
content: [{ type: 'text', text: 'First 20 results...' }],
_meta: { hasMore: true, nextPage: 2 }
};
// 4. Move slow work to async tasks
// Return immediately, update via follow-up
Breaking Changes from @modelcontextprotocol/sdk@1.24.x → 1.25.x:
setRequestHandler is now typesafe - incorrect schemas throw type errorsNew Features :
Migration : If using loose type imports, update to specific schema imports:
// ❌ Old (removed in 1.25.0)
import { Tools } from '@modelcontextprotocol/sdk/types.js';
// ✅ New (1.25.1+)
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
Breaking Changes from zod@3.x → 4.x:
.default() now expects input type (not output type). Use .prefault() for old behavior.error.issues (not error.errors).merge() and .superRefine() deprecatedPerformance : 14x faster string parsing, 7x faster arrays, 6.5x faster objects
Migration : Update validation code:
// Zod 4.x
try {
const validated = schema.parse(data);
} catch (error) {
if (error instanceof z.ZodError) {
return { content: [{ type: 'text', text: error.issues.map(e => e.message).join(', ') }] };
}
}
{
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.3",
"hono": "^4.11.3",
"zod": "^4.3.5"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.17.1",
"@cloudflare/workers-types": "^4.20260103.0",
"vite": "^7.2.4",
"wrangler": "^4.54.0"
}
}
Open Source Example : https://github.com/jezweb/chatgpt-app-sdk (portfolio carousel widget)
window.openai.toolOutput → React carousel/src/lib/mcp/server.ts - Complete MCP handler/src/server/tools/portfolio.ts - Tool with widget annotations/src/widgets/PortfolioWidget.tsx - Data access patternCloudflare One-Click Deploy : Deploy MCP servers to Cloudflare Workers with pre-built templates and auto-configured CI/CD. Includes OAuth wrapper and Python support.
Skybridge (Community): React-focused framework with HMR support for widgets and enhanced MCP server helpers. Unofficial but actively maintained.
Note : Community frameworks are not officially supported. Use at your own discretion
Weekly Installs
–
Repository
GitHub Stars
643
First Seen
–
Security Audits
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
109,600 周安装