- What it ships — durable per-instance state (SQLite), the AI SDK tool-calling loop,
AIChatAgent+ theuseAgentChatReact hook, hibernation/wake, scheduling, WebSockets, and a built-in MCP client (this.addMcpServer,this.mcp.getAITools()). - What it doesn’t ship — a way for that edge agent to act as a real account: sign up for a SaaS tool, hold a funded card, or spend within a budget you control, per end-user.
How the pairing works
- Cloudflare Agents ships a first-class MCP client: call
this.addMcpServer(name, url, …)and every tool from that server becomes available to the agent;this.mcp.getAITools()hands them straight tostreamText/generateText. - Naive mints per-user MCP sessions — short-lived, revocable SSE endpoints whose tool list is the fused native + third-party toolset, already filtered by that user’s Account Kit.
- So the integration is: per agent instance, mint a Naive session →
addMcpServerwith its SSE URL + scoped bearer → every tool call runs as that user, gated server-side.
Tested against: Naive API v2 (hosted MCP server over SSE, per-user sessions) and
@usenaive-sdk/server 0.x for the one-time control-plane script; Cloudflare agents
0.12.x, @cloudflare/ai-chat 0.7.x, workers-ai-provider (current), and the AI SDK
(ai v5+: streamText, convertToModelMessages, stepCountIs,
toUIMessageStreamResponse). Wrangler with nodejs_compat.Version assumptions: the Agents AIChatAgent requires the new_sqlite_classes migration in
wrangler.jsonc for message persistence. Naive’s hosted MCP server speaks SSE, so this
guide pins transport: { type: "sse" } (Cloudflare’s default "auto" also negotiates it).
Pin your versions and set the model to one your account can access.Prerequisites
- A Naive API key (
nv_sk_...) — get one from the dashboard. - A Cloudflare account with Workers + the
wranglerCLI (npm create cloudflare@latest). - A model — this guide uses the Workers AI binding (no extra key); swap any AI SDK
provider (
@ai-sdk/openai, etc.) if you prefer. - Node ≥ 20 for the control-plane script.
Minimal viable integration
The shortest path to a Cloudflare Agent that can actually transact: define a policy and provision a user (control plane, once), wire the Worker, then connect the per-user Naive session inside the agent.Define the policy, then provision a user
An Account Kit is the spend/capability policy. Here a tenant
user gets a card (capped at $500, approval required), the vault, and an allowlist of apps.
Everything the agent does is bounded by this kit — server-side. These are one-time
control-plane calls (run anywhere with Node — they don’t belong in the edge runtime):
Configure the Worker
Register the agent as a Durable Object with the SQLite migration
AIChatAgent needs, bind
Workers AI, and pass the user id through vars (the API key is a secret):Connect the session — and let the agent transact
In the agent, mint a per-user session and connect it as an MCP server. The
bearer lives only in the request headers (never the URL) and expires. Ask the agent to “Connect my GitHub, then issue a $50 virtual card called ‘Ads budget’.”
It discovers GitHub (
AIChatAgent waits for
MCP connections before each turn, so this.mcp.getAITools() returns the full toolset:naive_connections_connect), returns a connect link for Alice to
authorize, and attempts the card (naive_cards_create) — a real card on Alice’s account,
capped by her kit. The loop is Cloudflare’s; the real-world actions are Naive’s.Extension: human-in-the-loop spend
Because the kit setcards.requiresApproval: true, the agent cannot silently spend. The
enforcement is server-side, so it holds no matter what the edge runtime does — the tool
result comes back as a frozen approval (HTTP 202) instead of a live card:
202 response uses action; approval records from approvals.list() or
approvals.get() use action_type.
Resolve it out of band — and on approval, Naive replays the frozen action server-side. A
small control-plane handler (or the dashboard / CLI) does it:
pending → executed / failed / denied).
Approvals are only enforced for agent (API-key / MCP) calls on real tenant users. A human
acting in your dashboard, and agent calls on the operator’s own default user, bypass the
gate — so end-user agents stay governed while your own automation isn’t slowed down.
Going multi-tenant: one agent per end-user
The realistic shape: each Durable Object instance is one of your end-users. Route by your user id, store the matching Naive user id in agent state, and mint the session for that user:- Refresh on expiry — when a session lapses,
removeMcpServer("naive")thenaddMcpServerwith a freshly minted session. - Revoke on logout — call
DELETE /v1/users/:id/sessions/:id(orclient.sessions.revoke(id)) to kill the endpoint immediately, independent of the DO.
What stays enforced
No matter where the agent runs, the policy is enforced where it matters — on Naive’s servers, not in your prompt or your Worker config:- Identity — every action runs as a specific tenant user, fully isolated from your other users.
- Capability bounds — the Account Kit decides which
primitives and which apps the agent can touch (
allowlist/blocklist/ per-tool). - Scoped spend — virtual cards are capped per card and per user; the model can’t raise its own limit.
- Human-in-the-loop — sensitive actions (cards, domains, KYC, formation, connecting an app) freeze as approvals until a human says yes.
- Revocable — the session is short-lived and killable on demand; revoke and the agent loses real-world reach instantly, mid-flight.
Next steps
- Sessions — per-user MCP sessions for edge / untrusted runtimes
- MCP server — the hosted Naive MCP endpoint and its tools
- Account Kits — author spend/capability policy
- Approvals — the human-in-the-loop lifecycle
- SDK overview — the full Naive client surface for the control plane