Call agents over MCP

MCP client OAuth

Authorize an MCP client through a person's sign-in.

For: application developers.

Authorize an MCP client through a person's sign-in.

Connect a client with OAuth

The edge is each tenant's OAuth 2.1 authorization server for MCP clients, as the MCP authorization specification describes it. It has no sign-in and no passwords of its own: you sign in at the console, exactly as for any page there. The metadata names the edge by --public-edge-url, or by its advertised address without it, and the console by --public-url, or by management's advertised address: give clients that same edge address, since they check that the metadata names the URL they were given.

  1. The client calls /mcp/<tenant>, gets the 401, reads the tenant's protected resource metadata (RFC 9728) and from it the authorization server's metadata at https://<edge>/.well-known/oauth-authorization-server/oauth/<tenant> (RFC 8414).
  2. It identifies itself: by the https URL of its client metadata document, when the tenant allows that client (below), or by registering (RFC 7591) with a redirect address on your own computer, such as http://127.0.0.1:<port>/callback.
  3. It opens your browser at the authorization endpoint with a PKCE challenge (S256 only). The edge checks the client and its redirect address before you see anything, then sends you to the console's Connect an MCP client page; if you are not signed in, the console signs you in first, through the cluster's identity provider or as your cluster allows.
  4. The page names the client, where its answer goes (a warning says so when that is an address on your own computer, where any program could listen), the tenant, and what the client gets. Approve sends your browser back to the client with a code; Deny sends it back with access_denied.
  5. The client redeems the code with its PKCE verifier and gets an access token for 10 minutes, and not a moment past its expiry (an edge whose clock runs ahead refuses it that much early), and a refresh token. Each refresh gives a new refresh token; an old one used again revokes the grant, since one of its holders is not the client, and so does a code used twice.
    When that revocation cannot be written at that moment, the answer is 503 with server_error, never one that says the tokens are revoked, and the request may be sent again; the revocation endpoint answers the same, and also when it cannot read the grant a token names at that moment.

While the edge cannot read a token's grant, its person or the tenant's client policy (its database away a moment), a call with that token is answered 503 with Retry-After, not 401: the token is not refused for it, and the client need not authorize again. No answer carries an internal error's own words; the edge logs them.

A client of the official SDKs needs nothing else. With the Python SDK, for example:

oauth_client.py
import ssl, httpx2
from mcp import Client
from mcp.client.auth import OAuthClientProvider
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientMetadata

auth = OAuthClientProvider(
    "https://edge.example.com:7443/mcp/acme",
    OAuthClientMetadata(
        client_name="My desk tool", redirect_uris=["http://127.0.0.1:53682/callback"], token_endpoint_auth_method="none"
    ),
    storage,
    open_browser,
    wait_for_callback,  # your token store, and your browser and callback handlers
)
http = httpx2.AsyncClient(auth=auth, verify=ssl.create_default_context(cafile="ca.crt"))
async with Client(streamable_http_client("https://edge.example.com:7443/mcp/acme", http_client=http)) as c:
    tools = await c.list_tools()

What the client does is yours: tools/list lists the agents and flows you may start (an agent user sees only the agents its grant names), a call starts a job as you, within your budgets, quotas and the tenant's tool policy, and approvals and questions are answered as you, only where you may. The audit log names you as the starter and the client in the entry's via, such as mcp-client:clients.example.com/oauth/client.json#mg_3f9c....

The tokens are for this tenant's MCP endpoint alone: not the runtime API, not the console, not another tenant. They are the edge's own (an HMAC under a key of the tenant's, derived from the cluster's master key), so every edge of the cluster checks them and nothing outside the cluster can.

See and revoke your clients

Settings › MCP clients in the console lists the clients you approved, where each answers, when you approved it and last used it, and when it ends (90 days after you approve it at most). Revoke ends a grant: its access and refresh tokens stop working within seconds on every edge, and a call of the client's that waits for its job, or a subscriptions/listen stream it holds open, ends within seconds too, answering that access ended (the job itself goes on, as a job started in the console does when its tab closes).

What acts later for a job the client started (the re-check of an approval that waited long, who may read its spend refusals, Ask's hand-over, the setup assistant) acts through the grant as it stands then, with your membership in the tenant and never a cluster role you hold: after a revocation, such an approval's yes is refused. A listen stream also ends when the access token it was opened with expires, and the client listens again with a fresh one. Agent users list and revoke the clients they approved the same way. On the command line:

Terminal
oz0 mcp clients list --tenant acme
oz0 mcp clients revoke mg_3f9c0a...

The tenant's admins (who hold keys.manage) list everyone's grants with --all and revoke any of them, without seeing when others last used their clients. A list shows the newest 500 grants and says when there are more; oz0 mcp clients revoke --client <client ID> reaches every grant of that client however many there are.

A person's grants also end with them: removing their account revokes their grants in every tenant, and removing their role in a tenant revokes their grants there, so someone given the same name later inherits none of the clients. A client may revoke its own tokens itself at the revocation endpoint (RFC 7009).

Who may connect

Each tenant has an allowlist of clients, a governance setting (mcp_clients). By default only clients that register with a loopback redirect address may connect: programs on the person's own computer, such as an IDE or a command-line assistant. To let in a client that identifies itself with a client metadata document, list its host or its document's exact URL; to let in a native app with a redirect scheme of its own, list the scheme:

Terminal
oz0 mcp clients policy --tenant acme
oz0 mcp clients policy --tenant acme --allow clients.example.com --reason "the company's IDE plugin"
oz0 mcp clients policy --tenant acme --allow scheme:com.example.desk --reason "the desktop app"
oz0 mcp clients policy --tenant acme --loopback off --reason "named clients only"

A host may be a wildcard, *.example.com, for the hosts under a domain (not the domain itself). A wildcard over a public suffix, a name many owners' domains share such as *.co.uk or a hosting service's *.github.io (by the public suffix list the server ships with), is refused: name the client's own domain (*.example.co.uk) or its host.

A wildcard reaches only the hosts its domain's owner holds, never one under a public suffix beneath the domain: *.amazonaws.com does not cover a bucket under s3.amazonaws.com. A list saved before this check keeps such an entry readable, and it allows nothing until you remove it.

Allowing more is a loosening: it takes a reason, and waits for another admin when the tenant has four eyes on. Narrowing applies at once: it revokes the grants of the clients the list no longer allows, so widening it again later does not bring them back, and every edge refuses those clients' tokens and calls within seconds. The edge fetches a client metadata document only for a listed host or URL, only over https from a public address, without following redirects, at most 5 KiB within 5 seconds, and keeps it for a few minutes (see outbound connections).

A document must name itself as its client_id, carry no client secret, and list its redirect addresses, which must be loopback, https or a listed scheme. A registration is never stored: its client ID is the registration itself, sealed by the edge, and registrations from one address are limited to a few a minute.

Copyright © 2026