Concepts
Seven nouns cover everything OpZero does. Read this once and the tool reference stops looking like a list of eighty unrelated verbs.
Projects
A project is the unit of ownership: a name, a hosting target, a subdomain, and a history. Everything you deploy lands in one. Deploying with a name that already exists redeploys that project in place; passing a new name creates a separate one.
Each project gets a URL of the form https://your-name.opzero.sh. On Pro and Team plans you can point a custom domain at it with set_custom_domain.
Deleting is a soft delete by default: the live runtime goes offline immediately and its slot frees up, but the record survives a seven-day grace period, so redeploying the same name brings it back. A hard delete skips the grace period and cannot be undone.
Deployments
Every publish creates a deployment: an immutable snapshot of the files, with a status and a build log. Projects accumulate them, and you can move between them freely.
update_deploymentmerges a partial file map into the latest deployment — the right tool for changing one file without resubmitting the site.redeployrebuilds from stored source, androllback_deploymentrepublishes an older snapshot as a new deployment.get_deployment_detailsreturns the full deployed file contents, so a conversation that has lost the source can recover it.
The app runtime: AI and storage bindings
A deployed React app is static by default. Switch on bindings and it becomes a real application with a backend it did not have to write.
- AI binding.
window.claude.complete()andoz.aireach real server-side inference through the Vercel AI Gateway. The model allowlist, the max output tokens, and a per-IP rate limit are set server-side, and the system prompt is pinned where the browser cannot override it. No key ever reaches the client. - Storage binding. Two halves of one switch:
oz.storageis key-value records,oz.filesis bytes with a media type under a path. Both are scoped per end user, with a project-wide shared scope alongside.
Turn them on at deploy time with ai: true / storage: true on deploy_react_artifact, or change them later on a running app with configure_bindings — no code redeploy needed. Bindings require the default Cloudflare target.
The storage_* tools read and write that same file store from the conversation, so you can seed an app with data or inspect what it has written. A file written with public: true is served credential-free at https://your-app.opzero.sh/__oz/files/<path> — the usual way to give a page an image to link to.
Assistants
An assistant is your own agent, shipped: a model, a pinned system prompt, and a list of MCP servers whose tools it may call. It runs the agent loop server-side and answers over a chat endpoint.
Two things surprise people. First, mcp_serversonly points at servers that already exist — it connects, it does not create. Second, an assistant has no host of its own: it is served from one of your deployed apps’ subdomains at /__oz/assistant/<slug>/chat, reached from a page via oz.assistant(slug). Deploying an assistant before you have any app succeeds, but the chat URL stays a placeholder until an app exists.
To test one without any of that, use chat_with_assistant. It invokes the assistant server-side and returns the full reply plus a trace of each tool-use round.
Hosted MCP servers
You can deploy your own MCP server to OpZero. Hand deploy_mcp_server TypeScript written against @opzero/mcp-runtime, or Python written against opzero_mcp, and OpZero builds it, deploys it to a Cloudflare Worker, assigns a stable endpoint, and verifies it actually speaks MCP before calling the deploy good.
Start from a scaffold rather than a blank file — get_mcp_server_template has eight:
minimal a working server with one tool
fetch-api calling an external HTTP API
oauth1-api an API behind OAuth 1.0a
storage per-user ctx.storage keyed on ctx.user
files per-user ctx.files, plus publishing to a public URL
widget an MCP Apps view authored as its own .html file
sync a scheduled() handler on a cron, with ranged rollups
python the minimal server, in PythonThree auth modes decide who may call it:
oauth— validates per-user tokens through MCPAuthKit and scopes access to you, with audience-bound tokens other users cannot reuse.token— one shared bearer token, returned exactly once at first mint and replaceable withrotate_mcp_server_token.public— unauthenticated. Shared-scope storage writes are off by default here, since any anonymous caller could otherwise mutate shared state.
Secrets go in with set_mcp_server_secret and arrive as ctx.env.KEY. A deployed server cannot wake itself, so unattended work runs off the schedule cron, which invokes the server’s exported scheduled() handler at a minimum interval of five minutes.
Gateways
A gateway is one MCP endpoint fronting several backends — your hosted servers, external MCP servers, and assistants — behind an exposure policy. Every account has an auto-created default gateway that tracks all your deployed servers; create your own for per-app profiles that expose only what one app should see. Endpoints look like gw.opzero.sh/g/<slug>/mcp.
The exposure mode is the decision that matters, and getting it wrong fails quietly:
search— a small, fixed set of meta-tools (asearch_toolsplus invoke pair) whose shape never changes, with live discovery at call time. Recommended, and required for clients like Claude.ai that snapshottools/list.inline— every backend tool merged intotools/list, namespaced<ns>_<tool>. Only safe for clients that re-list each session with a small, fixed toolset. The surface changes whenever backends do, which strands a snapshot cache.pinned— chosen tools inline, meta-tools for the rest.auto— resolves tosearchwhenevertrack_all_deployedis on, otherwise inline until the tool count passesinline_tool_limit.
inline gateway whose backends changed after Claude cached the tool list. Switch it to search with update_gateway.External connections
A connection is an OAuth grant to a third-party MCP server — Notion, Linear, Sentry, Neon, Vercel, or any server publishing RFC 9728 metadata — stored encrypted on your account and refreshed automatically. create_connection returns a one-time authorize URL to open in a browser.
Connections are never ambient. Nothing can use one until you grant it explicitly to a named workload with grant_connection: a gateway backend, an assistant, or a deployed MCP server. That is the consent boundary — a server you deployed can reach the connections you gave it and no others.
Granting also requires your MCP identity to be linked to your account via link_identity. Without the link, the platform can only match your token to your account by email, and an unverified email is not a strong enough claim to release someone’s Notion credential against.
Prefer a connection over pasting a bearer token into add_gateway_backend. Third-party OAuth tokens are short-lived; a pasted one typically stops working within the hour, while a connection refreshes itself.
Plans and limits
How many hosted MCP servers, assistants, and gateways you can keep at once depends on your plan, as do custom domains and monthly deploy volume. Updating or redeploying something you already have never counts against a cap — only creating a new one does. Ask for get_system_status to see where you stand, or check pricing.