What you are setting up
Your agent runs on an AgentCore Runtime. Your MCP server runs on another one, reachable only through the AgentCore invocation endpoint rather than the open internet. The TrueFoundry gateway sits between them: it authenticates the agent, decides whether that agent may use this tool, and supplies the credential the MCP runtime requires.Mapping an AgentCore agent to a TrueFoundry agent
Three identities are in play. Keeping them distinct is what makes the governance in this guide possible.agent-as issuer) as an identity
provider in TrueFoundry with name claim cid, then register the agent with an
identity-provider-backed identity mapping cid = agent-service to demo-agent. A token arriving with
that cid now resolves to a named agent, and the gateway applies that agent’s policy.
One Okta service application can back several runtimes — useful when they are instances of the same logical
agent. Give agents that need different tool access different service applications, since access is granted
against the resolved agent.
Okta registrations you need
Three custom authorization servers, one per audience. The audience is what lets each hop confirm a token was minted for it, rather than merely being valid somewhere.agent-as and mcp-as, and grant it in each server’s access policy. The examples
below use tool.invoke; substitute the scopes your own MCP server expects. A scope is mandatory on every
exchange request, and OIDC scopes are refused, so there has to be an API scope to ask for.
Choosing between the two upstream modes
Both modes give you the same governance at the gateway — the agent is named, policy is enforced, and the call is audited. They differ in what reaches your MCP server.IAM SigV4 — agent identity stops at the gateway
The gateway signs its call to your MCP runtime with AWS SigV4, using a dedicated IAM principal. The runtime authorizes that principal; no token travels with the request, so the MCP server sees no user and no agent. Everything you want to control still happens — one hop earlier, at the gateway.Set it up
Create login-as and agent-as, and the applications
tool.invoke scope and an access
policy to each, then create chat-client and agent-service. On agent-service, enable the
Token Exchange grant under Advanced → non-interactive grants and turn Require DPoP off.Register login-as as a trusted server on agent-as
agent-as → Trusted servers → add login-as, then add a token-exchange rule letting agent-service
exchange a login-as token for one audienced to api://agent-api, granting tool.invoke. Set the user
condition so the signed-in user matches.Create the AgentCore Identity credential provider
agent-as — the exchange target. Nothing here names login-as: the subject issuer is
established by the trusted-server relationship in Okta and by the agent runtime’s authorizer.Create the two runtimes
Have the agent exchange the token and call the gateway
agent_token to the gateway as the bearer. Do not call GetWorkloadAccessTokenForJWT yourself —
AgentCore has already done that step, and calling it again from inside the runtime fails.Create the IAM principal the gateway signs with
InvokeAgentRuntime will not cover.Register everything in TrueFoundry
agent-as issuer) as an identity provider with name claim cid; register the agent with an
identity-provider-backed identity mapping cid = agent-service; add the MCP server and make the agent an
MCP Server User on it.What your MCP server receives
NoAuthorization header, and no claims — the request is authorized as an AWS principal. Your tool code
should handle that explicitly rather than falling back to a default user, so an unauthenticated call never
quietly looks like an authenticated one:
Okta OBO — user identity reaches the tool
Steps 1 and 2 are unchanged. Instead of signing with SigV4, the gateway performs a second exchange — turning the agent’s token into one audienced to your MCP runtime that still names the user.Set it up
Everything from IAM SigV4 still applies. This is what you add.Create mcp-as and the exchange client
mcp-as authorization server with audience api://mcp-api and the tool.invoke scope. Create
tfy-mcp-exchange as an OIDC service application with the Token Exchange grant enabled and DPoP off.Extend the trust chain and add the exchange rule
mcp-as → Trusted servers → add agent-as. Then add a token-exchange rule permitting
tfy-mcp-exchange to exchange an agent-as token for one audienced to api://mcp-api, granting
tool.invoke.Add the user claims you want downstream
mcp-as → Claims, add email as an expression (user.email) included in the access token, with
the condition set to any scope. A claim tied to the email or profile scope never appears, because the
exchange requests only tool.invoke — the token validates and the claim is simply absent.Give the MCP runtime a JWT authorizer and the header allowlist
Switch the gateway's upstream auth
What your MCP server receives
sub in the validated token, never from a tool argument. Tool arguments are produced
by the model, so a user parameter can be steered by prompt injection; a token cannot.
cid in the
final token names tfy-mcp-exchange rather than your agent. The user survives the hop; the agent was
resolved at the gateway and is recorded there. If your MCP server itself must make agent-aware decisions,
have Okta emit an actor claim (act) or a custom claim carrying the agent id.What you can govern
Because the agent resolves to a named principal at the gateway, policy lives there rather than in agent code — and applies identically in both modes.Verifying what actually arrived
Do not assume identity propagated — assert it. Add a diagnostic tool that returns what your MCP server genuinely sees, and keep it in place:sub, aud, cid and scopes, with tokens redacted.
Log the exchange request before you make it: a failure logged only on success produces silence exactly when
you need the detail.
Troubleshooting
When an exchange fails, read Okta’s System Log first. AgentCore and the gateway surface only a generic HTTP error; the log records the precise reason underoutcome.reason and echoes what was actually sent,
which also confirms whether your change took effect.