fastmcp4-cf
A Python MCP server on Cloudflare implementing the stateless MCP 2026-07-28 protocol, enabling load-balanced and scalable tool execution without session affinity.
README
fastmcp4-cf
A Python MCP server on Cloudflare — FastMCP 4 speaking the MCP 2026-07-28 stateless protocol, on Cloudflare Containers behind a Worker.
Clone it, add tools, deploy.
Why this exists
MCP 2026-07-28 is the largest revision since the protocol launched. It removes
protocol-level sessions: no initialize handshake, no Mcp-Session-Id. Every
request carries its own protocol version and client identity in a _meta
envelope, so any instance can answer any request — which is what makes
round-robin load balancing and scale-to-zero possible at all.
This is a working place to watch that happen:
whoamireports which container answered, and who the server thinks you are. Call it repeatedly — the instance changes mid-conversation, nothing pinned.delete_notesis a guard tool: the multi-round-trip replacement forctx.elicit(), which now raises on modern connections. It also demonstrates per-caller authorization.probe.shspeaks the raw protocol over curl, so you can watch a tool call succeed with no handshake — impossible under 2025-11-25.- CI boots the real container behind the real Worker and asserts all of it.
Tracks a beta.
fastmcp==4.0.0b1is the newest FastMCP 4 (no RC yet; stable is still 3.4.x) and pullspydantic 2.14.0a1. Pin your versions.
Why Containers, not Workers
Workers run JS/TS on V8 isolates; Python there goes through Pyodide, which takes
only pure-Python and PyEmscripten wheels — and FastMCP's tree includes
pydantic-core, which is Rust. So Python MCP on Cloudflare means Containers,
which run an ordinary linux/amd64 image with FastMCP unmodified.
The tradeoff: containers are regional, started on demand in the nearest region — not per-PoP like Workers. Closer to Cloud Run than to edge. If you want true edge, you want TypeScript and Cloudflare's Agents SDK, not this.
Shape
request → src/index.ts (Worker) → Dockerfile → server.py (FastMCP 4)
routes + passes secrets in
Containers are not standalone: a Worker receives every request and picks which instance handles it.
| file | role |
|---|---|
server.py |
the MCP server — put your tools here |
Dockerfile |
linux/amd64 image, required by Containers |
src/index.ts |
the Worker: routing, secrets, protocol-era handling |
wrangler.jsonc |
container, Durable Object binding, migration |
probe.sh |
smoke tool — raw 2026-07-28 over curl |
AUTH.md |
read before exposing this — authn/authz |
NOTES.md |
gotchas that cost real time to find |
Prerequisites
- Docker running — wrangler builds the image locally
- Workers Paid plan — Containers are not on the free tier
- Node 20+
Run locally
No Cloudflare account needed — the container runs in local Docker.
npm install
cp .dev.vars.example .dev.vars
python -c "import secrets; print(secrets.token_hex(32))" # paste into .dev.vars
npm run dev # ready on :8787
Then, in another shell:
./probe.sh http://localhost:8787 server/discover
./probe.sh http://localhost:8787 tools/call whoami
.dev.vars is local-dev config and deliberately holds no auth settings — you
cannot mint a Cloudflare Access token on localhost, so adding them makes every
local request 401. Deploy-time values live in .deploy.vars instead.
server/discover replaced the initialize handshake — it returns
supportedVersions, resultType, instructions, and cache hints. Call
whoami a few times and watch instance change: several containers answering
one client, no session between them.
Faster loop: no Docker, no Worker
wrangler dev rebuilds the image on every start, which is slow when you are
just iterating on tool code. To run the MCP server on its own — no container,
no Worker, no load balancing, but the same protocol:
python -m venv .venv && . .venv/bin/activate
pip install --pre -r requirements.txt
REQUEST_STATE_KEY=$(python -c "import secrets; print(secrets.token_hex(32))") \
PORT=9000 python server.py
./probe.sh http://localhost:9000 tools/call whoami
Use wrangler dev when you care about the Worker, the routing, or anything
Cloudflare-shaped; use this when you are writing tools.
The server asking a question back
delete_notes returns a question instead of blocking on one:
# Round 1 — the server asks. Note resultType: "input_required".
./probe.sh http://localhost:8787 tools/call delete_notes '{"folder":"notes"}'
# Round 2 — retry the SAME call with the answer and the state.
STATE=$(./probe.sh http://localhost:8787 tools/call delete_notes '{"folder":"notes"}' \
| python3 -c "import json,sys; print(json.load(sys.stdin)['result']['requestState'])")
./probe.sh http://localhost:8787 tools/call delete_notes '{"folder":"notes"}' \
'{"confirm":{"action":"accept","content":{"value":true}}}' "$STATE"
The tool body runs from the top on both rounds — locals don't survive.
ctx.input_responses is None the first time, populated the second; anything
else that must carry over goes in request_state.
Deploy
npx wrangler login
npx wrangler secret put REQUEST_STATE_KEY # 32+ bytes of randomness
npm run deploy
First provisioning takes a few minutes. Then:
./probe.sh https://fastmcp4-cf.<your-subdomain>.workers.dev tools/call whoami
REQUEST_STATE_KEY seals the multi-round-trip request_state. Every instance
needs the same value — see NOTES.md for why the default breaks
under a load balancer. The server refuses to boot without it.
Adding your own tools
@mcp.tool
def my_tool(arg: str) -> str:
"""Docstring becomes the tool description the model reads."""
return f"got {arg}"
Two constants are duplicated by necessity — keep them in sync:
- port
8080:Dockerfile(EXPOSE),src/index.ts(defaultPort),server.py - instance count:
src/index.ts(INSTANCES) andwrangler.jsonc(max_instances)
server.py runs with stateless_http=True, which is what lets both
protocol eras be load-balanced with no affinity. Turn it off and clients on the
old protocol break intermittently — see NOTES.md.
Authentication
Off by default — as cloned, anyone with the URL can call every tool, and the server says so on boot.
Cloudflare Access is wired up and needs no code: Access runs the OAuth flow
at the edge and forwards a signed JWT, which server.py verifies.
npx wrangler secret put ACCESS_TEAM_DOMAIN # yourteam.cloudflareaccess.com
npx wrangler secret put ACCESS_AUD # the application's AUD tag
npx wrangler secret put ADMIN_EMAILS # who may call gated tools
npm run deploy
Verified end to end on a plain workers.dev hostname — no custom domain needed.
Two dashboard settings will block you if missed; both are in
AUTH.md, along with per-caller tool visibility, service tokens
for CI, and the rules worth treating as non-negotiable.
Any other provider — Google, Auth0, WorkOS, Keycloak — swaps into build_auth().
Teardown
wrangler delete removes the Worker but leaves the container application and
the pushed image, which keeps costing storage:
npx wrangler delete
npx wrangler containers list && npx wrangler containers delete <ID>
npx wrangler containers images list && npx wrangler containers images delete <image>
Delete the Access application separately in the Zero Trust dashboard.
probe.sh
A smoke tool, not a client library. It sends the required headers and _meta
envelope and supports multi-round-trip retries. Its requestState escaping
handles quotes only. For real client work use the
MCP Inspector.
License
MIT — see LICENSE.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。