Skip to main content

MCP protocol flows

This page describes what an MCP client and the gateway at POST /gateway/mcp send each other. Connect clients covers the setup for each client. This page is for building a client or reading a trace.

We checked the discovery responses and the method handling in Call tools on an instance. We have not run the sign-in steps by hand for this page.

Find the authorization server​

  1. The client calls POST /gateway/mcp with no token. Every method needs a token, initialize included. The answer is HTTP 401.

    HTTP/1.1 401
    WWW-Authenticate: Bearer resource_metadata="https://your-environment.example/.well-known/oauth-protected-resource"

    {"jsonrpc":"2.0","id":1,"error":{"code":-32001,"message":"Authentication required"}}
  2. The client fetches the protected resource metadata (RFC 9728).

    {
    "resource": "https://your-environment.example/gateway/mcp",
    "authorization_servers": ["https://your-environment.example"],
    "bearer_methods_supported": ["header"],
    "scopes_supported": ["openid", "profile", "email", "mcp:tools", "mcp:resources", "read", "orderbook:read"]
    }

    scopes_supported lists the scopes in the cimd entry of the pbac.operator.controls.public_client_policy row (GatewayDiscoveryController.java). A connector or managed resource that is exposed over MCP adds its scopes there. A REST resource with no tool route adds none. With the resources from Register a managed resource, our instance listed orderbook:read from the MCP resource and no orders scope from the REST one. It also listed the scopes of the connectors installed on it.

  3. The client fetches /.well-known/oauth-authorization-server (RFC 8414) on the same origin. It names the endpoints, code_challenge_methods_supported: ["S256"], and registration_endpoint.

Sign in​

  1. The client registers itself at /register (RFC 7591), or uses a client id metadata document. The metadata sets client_id_metadata_document_supported to true.
  2. The client sends the user to /authorize with PKCE and code_challenge_method=S256. The server refuses plain. The resource parameter is <environment URL>/gateway/mcp, which the server maps to the type urn:mcp:server.
  3. The client exchanges the code at /token with the code verifier and the same resource.

Call tools​

MethodWhat the gateway does
initializeChecks only that a token is present. Answers with protocolVersion, capabilities: {"tools": {"listChanged": false}} and serverInfo.
tools/listLists every tool of every enabled resource, plus the two policyarc__ tools. It does not filter the list by the token's scopes. For each MCP resource it fetches the live list, which calls /introspect.
tools/callRuns the full gateway check for that one tool. See How the managed gateway works.
Any other methodJSON-RPC error -32601, Method not found.

On tools/call, an invalid or expired token is HTTP 401 with WWW-Authenticate: Bearer resource_metadata="...", error="invalid_token", so the client can refresh. A policy denial is a normal tool result with "isError": true and the text HTTP 403: Access denied.

A connected client is not told when the tool list changes, because listChanged is false. Reconnect it to see new tools.

Connect another account​

We read this section in the code and did not run it. The bodies, the -32042 error and the /gateway/link refusal below are what the code builds, not responses we saw.

Some tools forward the user's own token from another identity provider, or a credential the user enters. If the user has not connected that account, or the stored token has expired and cannot be refreshed, the gateway returns a link instead of failing.

On /gateway/<name>/... the answer is HTTP 401 with this body.

{"error": "idp_link_required", "idp": "gitlab", "link_url": "https://your-environment.example/gateway/link?ls=..."}

On /gateway/mcp the answer depends on what the client declared in initialize.

  • A client that declared elicitation.url gets the JSON-RPC error -32042. Its data.elicitations[0] holds mode: "url", elicitationId, url and message.
  • Any other client gets a tool result with "isError": true, the link in the text, and _meta.idp_link_required with idp and link_url.

The link opens GET /gateway/link on the environment. The browser's PolicyArc session must belong to the same user the link was made for. With no session, the user signs in first. A session for a different user is refused with HTTP 403. After the user connects, the client retries the tool call.

For a tool that needs a credential the user enters, the error is user_secret_required, _meta holds user_secret_required, and the link opens the form at /gateway/connect.