Use capa from an agent
Your Worker calls a capability through a service binding. An agent can call the same capability through capa-gateway, one MCP server for every capability.
The agent never sees an API key. It writes a short piece of code, and the gateway runs that code in a sandbox that can reach only capa bindings.
How it works
Section titled “How it works”The gateway gives the agent two tools.
| Tool | What it does |
|---|---|
search | Finds operations across all capabilities. It returns the method to call, the HTTP verb, the path, and the risk. |
execute | Runs an async JavaScript function body in a sandbox. The code calls capabilities on the capa object. |
An execute call looks like this:
const me = await capa.github.user.list();const repo = await capa.github.repos.retrieve("acoyfellow", "capa");return { login: me.result.login, stars: repo.result.stargazers_count };Every call returns { result, evidence }, the same shape your Worker gets.
What keeps the keys safe
Section titled “What keeps the keys safe”| Part | Cloudflare product | What it does |
|---|---|---|
| Sandbox | Worker Loader | Runs the agent’s code with fetch() and sockets turned off. The only way out is the capa object. |
| Capabilities | Service bindings | The sandbox calls capa-github and other capability Workers. It never holds their keys. |
| Vault | Durable Objects | One vault for each user. It holds that user’s provider keys and write settings. |
| Vault encryption key | Secrets Store | Encrypts every key in the vault. |
| Sign-in | Cloudflare Access | Identifies the user. MCP clients sign in with OAuth 2.1, and the user approves each new client by name. |
The agent cannot turn on writes. Only the user can, on the Connect page.
Connect an MCP client
Section titled “Connect an MCP client”Add the gateway URL to your client. The client opens a browser window. Sign in, then select Approve on the page that names the client. The client gets its own token.
In Claude, open Settings, then Connectors, then Add custom connector. Enter:
https://<your-gateway>/mcp{ "mcpServers": { "capa": { "url": "https://<your-gateway>/mcp" } }}Clients that only speak stdio can use mcp-remote:
npx mcp-remote https://<your-gateway>/mcpConnect your providers
Section titled “Connect your providers”Open https://<your-gateway>/connect and sign in. For each provider you can:
- Connect. GitHub opens an OAuth sign-in. For other providers, paste an API key.
- Manage, then Allow writes or Block writes. Writes are blocked by default, and the card shows Read only. A blocked agent gets a clear error that tells it to ask you.
- Manage, then Disconnect. The gateway deletes the key. For OAuth providers it also revokes the token at the provider.
The same page lists every MCP client you signed in. Select Revoke to stop a client. Its next call returns 401.
Deploy your own gateway
Section titled “Deploy your own gateway”The gateway lives in gateway/. It deploys with cf from one cloudflare.config.ts that declares every binding:
import { bindings, defineConfig, exports } from "cf/config";import capabilities from "./capabilities.gen.json" with { type: "json" };
const capabilityBindings = Object.fromEntries( capabilities.map(({ binding, worker, entrypoint }) => [binding, bindings.worker({ worker, exportName: entrypoint })]),);
export default defineConfig({ worker: { name: "capa-gateway", entrypoint: "src/index.ts", env: { LOADER: bindings.workerLoader(), VAULT: bindings.durableObject({ worker: "capa-gateway", exportName: "Vault" }), VAULT_KEY: bindings.secretsStoreSecret({ storeId: "<store-id>", secretName: "capa_gateway_vault_key" }), OAUTH_KV: bindings.kv({ id: "<kv-namespace-id>" }), ...capabilityBindings, ACCESS_CLIENT_SECRET: bindings.secret(), GITHUB_CLIENT_SECRET: bindings.secret(), COOKIE_SECRET: bindings.secret(), }, exports: { Vault: exports.durableObject({ storage: "sqlite" }), CapaBridge: exports.worker(), McpApi: exports.worker(), }, },});npm run index writes capabilities.gen.json from the capabilities/ folder. The gateway binds every capability on that list, and the gateway code reads the same list. To add a capability, regenerate the list and deploy.
The full file also sets compatibilityDate, the Access issuer and client ID, and the GitHub client ID.
- Deploy every capability to the same account. The gateway binds them by Worker name. They need no API key secret, because each user’s key comes from their vault.
- Create a Secrets Store secret for the vault key, and a KV namespace for OAuth state.
- Create a Cloudflare Access for SaaS application (OIDC). Set the redirect URL to
https://<your-gateway>/callback. - Optional: create a GitHub OAuth App. Set the callback to
https://<your-gateway>/connect/github/callback. - Put the three secrets in a JSON file, deploy, then delete the file:
cd gatewaynpm installnpm run indexcf deploy --secrets-file ./secrets.jsonbindings.secret() makes each secret required. A new Worker must get them with --secrets-file on its first deploy.
Limits today
Section titled “Limits today”- Every capability must be deployed in the same account as the gateway before the gateway deploys.
- Only GitHub connects with OAuth. Other providers use a pasted API key.
- Writes are allowed or blocked per provider. There is no approval for each call yet.
- Access controls who can sign in. The sample policy allows a fixed list of emails.