MCP协议2026-07-28版实现无状态化,Cloudflare随之废弃有状态的McpAgent,推荐免费计划的无状态处理器。MCP Server可在免费Worker上运行。
本周,Model Context Protocol 发布了 2026-07-28 修订版,将"能否免费运行一个 MCP server"这个问题从需要权衡妥协,变成了一个明确的肯定——只有一个可量化的限制条件。此次修订的核心变化是协议层彻底走向无状态:会话(session)、Mcp-Session-Id 请求头,以及初始化握手(initialize handshake)全部被移除。发布公告直接陈述了这一变更的后果:
任何请求现在都可以落在任意一个服务器实例上,只要它们位于普通的轮询负载均衡器后面,无需共享存储。
就在一天前(比发布早一天),Cloudflare 自己的文档也发生了变更。McpAgent 类——此前因为传输层需要在某处保存会话状态,所以是所有 MCP server 的官方路径——现在已被标记为废弃并冻结功能,新的服务器推荐使用无状态请求处理器作为替代方案。
这两项改动从不同方向封闭了同一个缺口。免费版的 Worker 一直是小型 MCP server 的天然归属,只有一个问题:传输层需要状态,而状态意味着需要 Durable Object 和粘性实例。现在协议本身承诺了每个请求都是自包含的。剩下的就是免费版唯一的硬上限——每次请求 10 ms CPU 时间——以及一个真实的服务器能否在这个限制内运行,这不是靠争论能回答的问题,而是一个需要测量的问题。于是我们在本站上搭建了一个,测量了它。
旧的生命周期在每次连接建立时都要进行一次 initialize 往返,生成一个 session id,并要求客户端在每次调用时携带它。这些全部被删除了。版本号、客户端身份和能力现在通过每个请求的 _meta 字段传递,而服务器必须实现的唯一一件事是 server/discover,它向任何请求者报告其支持的版本和能力。
作为交换,传输层变得更加严格。每个 POST 请求必须携带一个与请求体内部版本号匹配的 MCP-Protocol-Version 请求头,加上一个 Mcp-Method 请求头来镜像 JSON-RPC 方法——工具调用时还需要 Mcp-Name——这样负载均衡器和网关可以在不解析请求体的情况下进行路由。不匹配则返回硬性的 400 错误。批处理(batching)保持被移除状态,ping 被完全删除,未知方法现在返回 HTTP 404,2024 年代的 HTTP+SSE 传输方式被正式归类为废弃。一个从不推送消息的服务器可以对每个请求都用普通 JSON 响应,并用 405 拒绝流式路径。
对于只读的服务器来说,协议表面积所剩无几:server/discover、tools/list、tools/call,以及请求头的规范。这就是完整列表。
这个博客已经发布了一个 agent 界面——每篇文章的 Markdown 镜像和一个 posts.json feed。MCP server 就是这两个文件,背后对应两个工具:list_articles 返回 feed,get_article 返回文章的完整 Markdown。它运行在 https://301.sh/mcp,与网站内容协商共用同一个 Worker,零依赖——修订版之后,剩余的协议表面积足够短,可以手写出来。Cloudflare 为同样的需求提供了 SDK 路由(createMcpHandler 加上协议 SDK),当你需要 resources、prompts 或 elicitation 时,那是正确的答案;但对于只有两个工具的只读服务器则不需要。
简化的调度逻辑:
switch (message.method) {
case 'server/discover':
return reply(id, { resultType: 'complete', supportedVersions: ['2026-07-28'],
capabilities: { tools: {} }, ttlMs: 3_600_000, cacheScope: 'public' });
case 'tools/list':
return reply(id, { resultType: 'complete', tools: TOOLS, ttlMs: 3_600_000,
cacheScope: 'public' });
case 'tools/call': {
// Mcp-Name header must equal params.name, or 400 + HeaderMismatch.
const mirror = await env.ASSETS.fetch(new URL(`/${slug}.md`, origin));
return reply(id, { resultType: 'complete',
content: [{ type: 'text', text: await mirror.text() }], isError: false });
}
default:
return fail(id, 404, -32601, `Method not found: ${message.method}`);
}
其中没有任何计算操作。每个答案都是通过 assets binding 获取的已部署静态资源,而这一选择正是测量结果所奖励的。
Cloudflare 对这个限制的定义是大多数人会跳过的那部分:
CPU 时间衡量的是你的 Worker 代码执行过程中 CPU 花费的时间。等待网络请求(如 fetch() 调用、KV 读取或数据库查询)不计入 CPU 时间。
所以 10 ms 预算花在了解析、验证和组装 JSON 上——而不是在获取文章正文的网络请求上。我们向生产端点发送了 40 个请求,读取了 Workers Logs 记录的每次调用的 cpuTime:
最坏情况花费了 10 ms 预算中的 2 ms,而最慢的响应——服务站内最长文章时的 88 ms 墙钟时间——几乎全部花在资产获取上,计量器对此不予计入。一个提供服务的工具只用了免费上限的五分之一,而且还有余量;免费版另一个数字——每天 100,000 次请求——对于一个 agent 在每次对话中只会调用几次的端点来说,完全是不同量级的问题。
上限是真实存在的;它只是存在于传输层之外的别处。当我们审查本站的 agent 界面时,测量了一个考虑作为服务暴露的模板引擎,它以八倍的系数突破了这 10 ms。这就是真实的分界线:一个提供字节的服务工具花费一到两毫秒;一个执行计算的工——解析文档、渲染模板、大规模哈希——几乎立刻耗尽预算。Cloudflare 确实允许偶尔的超限("每个 isolate 都有一些内置的灵活性"),但持续超出限制的 Worker 会被终止并返回错误 1102,而且突发放宽不是一种套餐方案。
这个提升是有价格标签的,根据本站的内部规则,它应该放在限制旁边:每月 5 美元的 Workers Paid 计划将每次请求上限提高到默认 30 秒(可配置至 5 分钟),并且每月包含 3000 万 CPU 毫秒和 1000 万次请求。如果你的工具需要计算,那是需要对比的数字,而不是免费版的 10 ms。
端点已经在线,一个请求就能展示新协议的全貌:
curl https://301.sh/mcp -X POST \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2026-07-28' \
-H 'Mcp-Method: server/discover' \
-d '{"jsonrpc":"2.0","id":1,"method":"server/discover",
"params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}'
无需打开会话、无需保存状态、账单上没有 Durable Object。如果你的网站已经发布了 Markdown 镜像或 feed——在完成 agent 就绪审查后我们的网站做到了——从"静态文件"到"MCP server"的距离就是在一个你可能已经在运行的 Worker 上加一条路由。协议规范终于匹配了小型只读服务器一直想要成为的样子:一个回答关于你已经构建好的内容的问题的普通 HTTP 端点。免费版可以轻松承载它。那些需要计算的工具有从来就不可能免费,现在你知道了决定性的数字。