Skip to main content

On behalf of

On-behalf-of (OBO) carries the user identity from the caller all the way to the downstream MCP tools your agent invokes. Catalyst mints the delegated token for you; your agent's only job is to send the caller's token on the outbound call, which the identity-aware HTTP client does for you. For where this fits, see the Enterprise identity overview and the trust model in Security.

How it works​

When your agent calls an MCP tool, the request passes through the Catalyst MCP proxy. For an agent running under enterprise identity, the proxy mints a delegated token that asserts the original user's identity, using Catalyst's delegation grant (the RFC 8693 token-exchange standard). It attaches that token to the forwarded call.

Send the caller's identity​

An outbound call is a separate request from the one that invoked your agent, so the caller's token has to ride on it. Build your MCP client on the identity-aware HTTP client from diagrid.identity.http and that happens for you:

from mcp.client.streamable_http import streamable_http_client
from diagrid.identity.http import AsyncClient

# A drop-in httpx2.AsyncClient that attaches the calling user's token.
client = AsyncClient()

async with streamable_http_client(mcp_url, http_client=client) as (read, write, _):
...

AsyncClient takes every httpx2.AsyncClient keyword argument and returns a plain client, so it drops into code that already builds one. Use Client for synchronous calls. If you own a client you cannot replace, attach the event hook instead:

import httpx2
from diagrid.identity.http import attach_identity_headers_async

client = httpx2.AsyncClient(event_hooks={"request": [attach_identity_headers_async]})

The token is read when each request is sent, not when the client is built, so a single long-lived client is safe under concurrency — every request carries its own caller's identity. When there is no inbound user — a scheduled, pub/sub, or cron trigger — the header is omitted and the call goes out unauthenticated.

Sending the token is necessary but not sufficient — the receiving server has to ask for the identity before Catalyst will pass it on. That is the next section.

Send it only where it belongs

The client attaches the user's token to every request it makes, and drops it if a redirect leaves the origin you addressed. Use it for the Catalyst MCP proxy and sub-agents. Call third-party APIs with a plain httpx2 client, so the user's token is never sent to them.

What the downstream server sees​

One setting decides this: whether the MCP server requires a user identity.

The serverWhat its tools receive
Requires a user identityA delegated token naming the end user, in the X-Diagrid-User-Token header
Does not (the default)No user identity — Catalyst removes the user's token before forwarding

There is no third state. A server that does not require a user identity never receives one, even when the call that triggered it was made by a person.

This is separate from how Catalyst authenticates itself to your MCP server — the OAuth 2.0 client credentials, stored secret, or SPIFFE JWT you configure on the MCP server. That credential travels in its own header and is unchanged by any of this. See Authentication for MCP servers for how it is configured.

Read the delegated token​

Your MCP server receives the delegated token in X-Diagrid-User-Token. It names two parties:

ClaimWho it identifies
subThe end user the call acts for
act.subThe agent acting on their behalf

It is a standard OIDC-discoverable RS256 JWT, so any JWT library validates it — the mechanics are the same as for the SPIFFE JWT, and Verify the SPIFFE JWT on your server walks through discovery, the JWKS endpoint, and key rotation.

Validate it as a second, separate token rather than folding it into that check. They arrive in different headers and answer different questions:

Headersub identifies
SPIFFE JWTThe header you configured, usually AuthorizationThe calling workload — proves the request came from Catalyst
Delegated user tokenX-Diagrid-User-TokenThe end user — tells you whose permissions to apply

Authenticate the caller with the first, then authorize the work with the second.

What's next​