Skip to main content
Do this when a remote MCP server rejects a static token and expects an OAuth authorization-code flow. Use Pass secrets to an MCP server instead when the provider issues a long-lived API key you can paste. AgentArea acts as the OAuth client. It discovers the authorization server from the MCP endpoint, registers itself where the provider allows it, runs PKCE, and stores the resulting token as an auth config linked to the instance. The agent never sees the token. Providers split into two cases, and which one you are in decides whether there is anything to prepare: those that implement Dynamic Client Registration (RFC 7591) let AgentArea register itself, and those that do not — Google and GitHub among them — require an OAuth app you register with the provider and supply per connection. Ask the preflight endpoint rather than guessing.

Prerequisites

  • A url-type MCP instance that already exists. OAuth connect resolves the remote URL from the instance’s parent spec and returns 400 if there is none — create the instance first with Connect a remote MCP server.
  • The provider’s MCP endpoint must publish protected-resource metadata (RFC 9728) so the authorization server can be discovered.
  • Redis reachable from the API. The in-flight flow state lives there between the two requests.
  • A browser. The authorization step requires a human at a consent screen.

Steps

1

Ask what the provider needs

status decides the next step:Pass server_id instead of instance_id to ask about a catalog spec before any connection exists, or catalog_item_id to ask about a catalog API connection; connected is then always false. The same endpoints serve both kinds of connection.Every outcome is a 200 — the endpoint answers a question about capability rather than failing. It runs live discovery against the MCP URL on each call, so expect a round trip to the provider.
2

Start the flow

This returns JSON. It does not redirect. Open authorize_url in a browser yourself.Behind that single call AgentArea discovers the authorization server from the MCP URL, picks the OAuth client — the operator’s app when one serves the provider, else a client registered via Dynamic Client Registration — generates a PKCE S256 pair, persists the client credentials on an auth config, and stores the flow state in Redis keyed by state. The state holds a reference to those credentials, never a copy of the client secret.The offered scopes come from the resource’s own metadata (RFC 9728) where it publishes them, since that is the only place a provider states what the MCP endpoint itself accepts. A sign-in asks for all of them unless one of these narrows it, first match wins:
  • scopes in the request body: the person’s pick, which must be a non-empty subset of the offered ones (anything else is a 422). The connect panel shows the offered scopes as checkboxes under “Access”.
  • On a reconnect, the scopes the connection’s current credential asked for, minus any the server no longer offers.
  • metadata["agentarea:oauth_scopes"] on the catalog spec. Gmail’s server offers twenty scopes, permanent deletion among them, while its tools need one, so its entry declares "agentarea:oauth_scopes": ["https://www.googleapis.com/auth/gmail.modify"]. A declared scope the server does not offer is refused with a 502 rather than sent to a consent screen that would reject it.
GET /oauth/preflight returns both lists: scopes (offered) and default_scopes (what a sign-in without scopes asks for).offline_access is added only when the authorization server advertises it — asking a provider for a scope it never claimed risks invalid_scope on the consent screen, which costs the whole authorization rather than just its refresh token.A provider that needs more than OAuth 2.1 declares it on the server’s catalog spec, under metadata["agentarea:oauth_authorize_params"], and those parameters are added to the authorize URL. Google ignores offline_access and issues a refresh token only for access_type=offline on a fresh consent, so its catalog entries declare:
A spec without the key gets no extra parameters — a hand-made Google spec then connects, but without a refresh token, and needs reconnecting once the access token expires. The key may not replace a parameter the flow sets itself (client_id, redirect_uri, state, scope, resource, PKCE); such a spec is refused with a 502.return_to is where the browser lands afterwards. It is validated against an allowed base, so an arbitrary URL is rejected.
4

Connect a provider without dynamic registration

When preflight returned oauth_app_required — or you would rather sign in through your own app than the operator’s — register an OAuth app with the provider yourself, using the redirect_uri preflight returned ($AGENTAREA_URL/v1/oauth/callback, the one callback for every connection). Then pass its credentials with the authorize call:
The credentials belong to the connection, not to the deployment: each workspace authorizes through its own app, and the client secret is stored encrypted under the connection’s auth config.Either credential can instead reference an existing workspace secret, which is what to use when you rotate the app without re-authorizing every connection:
Exactly one source per credential. Sending both a value and a secret id for the same credential, or neither, is rejected with 422 rather than one silently winning. Referenced secrets must be user-owned workspace secrets — a secret already owned by another entity is refused.

Verify

Confirm the instance now carries an auth config and that a call through the governed proxy succeeds with an injected token.
Then exercise the proxy, which is what injects the token:
A JSON-RPC result rather than a 401 proves the stored token is being used. Your own Authorization header is stripped before the request goes upstream, so this only succeeds if the OAuth credential was stored correctly.

Troubleshooting

The instance is docker or command , not url . OAuth connect only applies to remote servers; a managed workload takes its credential from the environment instead.
The MCP endpoint does not publish protected-resource metadata, or the authorization server metadata document is unreachable. Confirm the provider documents OAuth for MCP; if it uses a plain API key, use Pass secrets to an MCP server .
The authorization server has no registration endpoint, or registration failed, so there is no client for AgentArea to authorize as. The response names the issuer in detail.issuer . Register an OAuth app with that provider and retry with credential_mode: custom .
A remote server that lists its tools without a token verifies as reachable and stores its tool list while nobody is authorized; the tool calls are what 401. Check auth_config_id on the instance — null means no token is stored, regardless of verification.status .
Flow state is held in Redis with a bounded lifetime and is consumed on first use. A stale browser tab, a second attempt at the same authorize_url , or a Redis restart between the two steps all produce this. Start again from /authorize .
It is validated against an allowed frontend base to stop the callback becoming an open redirect. Use your deployment’s configured frontend origin.
The stored refresh token is used to mint new access tokens, and some providers rotate refresh tokens on each use. If a refresh fails the connection needs re-authorizing — re-run /authorize for the same instance.
The server answered 403 with error="insufficient_scope" (RFC 6750): the token is valid but was granted too little, usually because a scope was unticked at sign-in. The message names the missing scopes, and the verification records them under detail.required_scopes. Reconnect and tick them; refreshing the token cannot add a scope.
Each successful flow creates a new auth config named mcp-oauth-{first 8 chars of instance id} and repoints auth_config_id . Older configs are left behind; the instance uses only the one it points at.

Connect a remote MCP server

Point AgentArea at an MCP server somebody else operates, test the endpoint before saving it

Pass secrets to an MCP server

Declare which inputs are credentials with env_schema, supply their values on the instance

MCP

What the Model Context Protocol gives an agent, and how AgentArea hosts MCP servers
Last modified on November 2, 2026