Setting up the agent
The demo needs an AI client with two things wired up: a model reached through Cloudflare AI Gateway, and the five MCP servers reached through the MCP server portal. These are the two places the controls live.
- LLM / model traffic goes to
, the AI Gateway's own custom domain which is behind Cloudflare Access. On a custom domain, AI Gateway accepts a valid Access JWT as the request credential, so the client sends no gateway token and no API key at all. - MCP / tool traffic goes to the MCP portal at
, which fronts all the deployed MCP servers.
Access secures both traffic paths and therefore every request is attributed to the person who made it.
Sign in as the demo user
Every demo is run as Alice Watson — alice.watson@company.com,
password Savetheinternet!1. When the agent connects to the portal you will be sent
through Cloudflare Access and then FlareID.
admin@company.com is the system administrator, and it exists only for the
/admin page on each app.
First: authenticate each MCP server (one browser login each)
The script registers five MCP servers with Access and creates an Access application for each, but it cannot perform the upstream OAuth login those servers require. Until it is done each server sits in Waiting and the portal has nothing to offer your agent.
- In the Cloudflare dashboard, go to Zero Trust → Access controls → MCP Portals
→ MCP servers. All five (
hr,crm,work,wiki,fin) should be listed, each showing Waiting. - Click on the three dots next to each server → Authenticate server.
- Sign in as
nikita.chapman@company.com(passwordSavetheinternet!1). Cloudflare fetches the server's tools and the status becomes Ready. - Repeat for
crm,work,finandwiki.
admin@company.com exists in FlareID but is not an employee in any of the
apps, and every MCP server maps the Access identity to an employee record before it will
issue a token.
Setting up the clients
Two clients are documented below and you only need one. Both reach the same two endpoints, so every demo script works with either.
- opencode — runs on your machine. Native tool calls, so a step is usually one call and a short answer. This is what the walkthrough was written and timed against, and the one to reach for if you want the tightest demo.
- Cloudflare OS — runs in the browser, nothing to install. Its agent works by writing code against its bindings, so expect more on screen per step. What you get for that: every model call routes through the AI Gateway, rather than only the leg a desktop client chooses to send.
Client 1 — opencode
opencode runs on your machine, and that machine has to be enrolled in the Cloudflare One
client and authenticated as alice.watson@company.com. Not optional, and not
just for tidiness — it is what makes the configuration below work at all.
The device session is the credential. The Access application in front of
is configured to accept it, which is why the provider config has no API key in it: nothing in a file,
nothing in an environment variable, nothing on disk. Without the client, every model call is turned
away by Access before it reaches the gateway.
Signed in as the wrong person is worse than not signed in, because it looks like it works: the requests succeed and every one of them is attributed to whoever that is. The walkthrough's argument is that the agent acts with Alice's identity, so check the client's account before you present, not after.
The same session is what makes step 1 happen: the
browser-side DLP policy and the desktop notification explaining the block both come from this client.
On a machine without it, cloudflared can fetch a short-lived Access token instead, and
opencode can run that for you — but you lose step 1, so for a demo it is not a real
substitute.
One deploy-time caveat, because it fails the same way as a missing client. Accepting a device
session needs an account-wide Cloudflare One Client Authentication duration to exist first, and
wire-access.sh degrades rather than failing if it cannot set one — it prints
"Without client-session auth: clients authenticate with 'cloudflared access login' instead".
If you saw that, the flag is not on the application and no device session will authenticate, however
correctly the client is signed in. Turn it on under Zero Trust → Access controls →
Access settings and re-run.
Cloudflare OS needs none of this: it authenticates in the browser, so a signed-in Access session in any tab is enough.
Then two things to configure: a provider against AI Gateway's OpenAI-compatible endpoint, and an MCP server against the portal. Both are below, but the configuration shape changed between opencode 1.x and 2.x — so check which one you have before copying anything:
opencode --version
opencode 1.x1.18.x and earlier · top-level permission map
In ~/.config/opencode/opencode.json.
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"cf-ai-demo": {
"npm": "@ai-sdk/openai-compatible",
"name": "Cloudflare AI Gateway (demo)",
"options": {
"baseURL": "https://aig./compat"
},
"models": {
"workers-ai/@cf/google/gemma-4-26b-a4b-it": {
"name": "Google Gemma 4 (Workers AI)"
}
}
}
},
"mcp": {
"servers": {
"ai-demo": {
"type": "remote",
"url": "https://mcp./mcp",
"codemode": false
}
}
},
"permission": {
"execute": "deny",
"bash": "deny",
"edit": "deny",
"read": "deny",
"glob": "deny",
"grep": "deny",
"webfetch": "deny",
"websearch": "deny",
"subagent": "deny",
"skill": "deny"
}
}
opencode 2.xagents with a permissions array
In ~/.config/opencode/opencode.json.
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"cf-ai-demo": {
"npm": "@ai-sdk/openai-compatible",
"name": "Cloudflare AI Gateway (demo)",
"options": {
"baseURL": "https://aig./compat"
},
"models": {
"workers-ai/@cf/google/gemma-4-26b-a4b-it": {
"name": "Google Gemma 4 (Workers AI)"
}
}
}
},
"mcp": {
"servers": {
"ai-demo": {
"type": "remote",
"url": "https://mcp./mcp",
"codemode": false
}
}
},
"default_agent": "employee",
"agents": {
"employee": {
"description": "An ordinary employee's assistant - company tools only",
"mode": "primary",
"steps": 10,
"system": "You are the assistant of an employee at this company. Answer using only the company tools available to you. You have no filesystem, no shell and no access to the public internet. Call the tool that matches the question directly - do not search for tools and do not write code. If the tools cannot answer the question, say so plainly rather than guessing.",
"permissions": [
{ "action": "execute", "resource": "*", "effect": "deny" },
{ "action": "shell", "resource": "*", "effect": "deny" },
{ "action": "edit", "resource": "*", "effect": "deny" },
{ "action": "read", "resource": "*", "effect": "deny" },
{ "action": "glob", "resource": "*", "effect": "deny" },
{ "action": "grep", "resource": "*", "effect": "deny" },
{ "action": "webfetch", "resource": "*", "effect": "deny" },
{ "action": "websearch", "resource": "*", "effect": "deny" },
{ "action": "subagent", "resource": "*", "effect": "deny" },
{ "action": "skill", "resource": "*", "effect": "deny" }
]
}
}
}
Client 2 — Cloudflare OS
./deploy.sh deploys
Cloudflare OS
at , and the five apps carry a Company AI tile in their launcher
pointing at it. Set DEPLOY_CLOUDFLARE_OS="false" to skip it, in which case the tile is
left out rather than linking somewhere that does not resolve.
Worth knowing why you might prefer it: every model call it makes routes through the AI Gateway named in its config, so the DLP profiles and guardrails apply to every agent turn rather than only the leg a desktop client chooses to send, and it authenticates users through Access, so the agent runs as the signed-in person.
It needs three things done once per person, in the browser, and no API can do any of them — a model and a connector both belong to a user account and are configured over that user's own session:
- Add the model. Settings → Choose your model → Add new model →
Other Cloudflare Workers AI…, then the bare id
@cf/google/gemma-4-26b-a4b-it. No key is requested: gateway mode authenticates on the Workers AI binding. The id takes noworkers-ai/prefix here — that belongs to other clients' naming. - Connect the portal account. On
/gatekeepers, add the portal. It runs an OAuth flow against Access. This connects the account and grants no servers — see the next step, which is not on that page. - Connect the portal in one pass. Open a chat, click the + in the
message composer (the control labelled "Files, connections and skills"), then
Add a new connection. Choose MCP Server, paste the portal
endpoint —
with/mcpon the end — then pick All tools and confirm. One grant, every server behind the portal, including any added later.
MCP Server is the one to use. It takes a pasted endpoint and will grant all of it at once, which is why the step above is a single pass.
Company AI Portal — the connector named after your portal — is
the administrator-configured one, and its resource dialog grants one upstream server per
connection: it builds exactly one resource URL per grant
(<endpoint>#server=work) and refuses to build one without a single server
chosen. Five servers means five passes. Both reach the same portal with the same Access login and
the same per-user identity, so for a demo the first is simply faster.
What you give up is narrower than it sounds. The portal connector carries the
vetted trust tier, the only one on which an approved write may auto-apply; a
pasted endpoint is byo. readOnlyHint is honoured on both, so reads still
run without an approval prompt either way, and nothing in this demo wants writes auto-applied.
And a whole-portal grant does not hand Alice more than she should have: the portal only lists the servers her Access session entitles her to, so Ledger never appears and step 8's "the finance tools are not even listed" still holds. Worth confirming on the day by noting what the picker shows.
Skip that and the agent meets the portal for the first time mid-demo. It has no connection, so it
reasons about that at length, calls listConnectableResources, and asks you to set one up
— interesting once, and a poor way to open a walkthrough whose argument depends on the tools
being unremarkable.
Running without the protection layer first
If the suite was deployed with DEPLOY_PROTECTION=false there is no portal and no AI
Gateway yet. Point the client at the MCP servers individually and at the model provider directly:
{
"mcp": {
"servers": {
"workweek": { "type": "remote", "url": "https://hr-mcp./mcp" },
"pipeline": { "type": "remote", "url": "https://crm-mcp./mcp" },
"relay": { "type": "remote", "url": "https://work-mcp./mcp" },
"nexus": { "type": "remote", "url": "https://wiki-mcp./mcp" },
"ledger": { "type": "remote", "url": "https://fin-mcp./mcp" }
}
}
}
Each server runs its own OAuth 2.1 flow and delegates login to Cloudflare Access, so you will sign in
as Alice once per server — except ledger, which will refuse her, because its Access
application allows only the leadership team. Every demo script works in this mode
— that is the "before" half of each one.
The portal namespaces every tool with its server id, so list_employees becomes
hr_list_employees, get_pipeline_summary becomes
crm_get_pipeline_summary, and so on with work_, wiki_ and
fin_. The
demo scripts name the underlying tool; your transcript will show the prefixed one.
Check it works
Before running any script, ask the agent something harmless that proves both legs are live:
Who am I, and which tools do you have available?
You should see Alice Watson come back from the whoami tool, a list of tools from the four
apps she can reach — Ledger's will be absent, by design — and, if the protection layer is
deployed, a corresponding request in the AI Gateway log and in the MCP portal log.