Skip to main content
You run agents on AWS Bedrock AgentCore and MCP servers on AgentCore too. This guide puts the TrueFoundry gateway between them, so you control which agent may use which tool — and choose whether the end user’s identity reaches the tool at all.

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. The agent never holds a credential for the MCP runtime. It presents a token describing itself, and the gateway is the only place an upstream credential lives.
One inbound token, two upstream modes. The agent sends the gateway the same thing either way: its own agent token, obtained by exchanging the user’s login token. What differs is only how the gateway authenticates upstream to your MCP runtime — and therefore what your MCP server can see.

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. The link between the last two is a claim mapping. Register Okta (the 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.
Do not use the org authorization server. A token exchange requires the authorization server that issued the subject token to be registered as a trusted server on the server performing the exchange. Okta’s org authorization server cannot be named in that relationship — it is not selectable — and cannot carry a custom audience. Every hop that takes part in an exchange, including the sign-in hop, needs its own custom authorization server.
Applications, and the scope they trade in: Define a real API scope on 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.
Turn DPoP off on the service applications. AgentCore Identity and the gateway both authenticate to Okta with a client secret and do not generate DPoP proofs. If Require Demonstrating Proof of Possession is enabled, every token request from that client fails.

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. Why you may end up with two MCP runtimes. An AgentCore Runtime accepts exactly one inbound auth mode — IAM or JWT, never both. If you want the same tools reachable under both modes, deploy the same MCP image twice and register each runtime as its own MCP server. The tool code is identical; only the runtime’s inbound configuration differs.

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.
IAM SigV4 flow: the user signs in at login-as, the agent exchanges that token at agent-as so it carries the user in sub and the agent in cid, the TrueFoundry gateway resolves the agent and enforces policy, then signs its upstream call with AWS SigV4 — so neither the user nor the agent travels past the gateway and the MCP server sees only an AWS principal.IAM SigV4 flow: the user signs in at login-as, the agent exchanges that token at agent-as so it carries the user in sub and the agent in cid, the TrueFoundry gateway resolves the agent and enforces policy, then signs its upstream call with AWS SigV4 — so neither the user nor the agent travels past the gateway and the MCP server sees only an AWS principal.

Set it up

1

Create login-as and agent-as, and the applications

Create the two custom authorization servers with their audiences, add the 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.
2

Register login-as as a trusted server on agent-as

On agent-asTrusted 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.
3

Create the AgentCore Identity credential provider

It points at 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.
4

Create the two runtimes

The agent runtime validates the login token and forwards the bearer. The MCP runtime takes no authorizer at all — omitting it is what selects IAM/SigV4 inbound.
5

Have the agent exchange the token and call the gateway

Send 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.
6

Create the IAM principal the gateway signs with

A dedicated IAM user whose only permission is invoking that one runtime. Grant both actions — the gateway passes a caller id, and invoking on behalf of a named user is a separate action that a policy with only InvokeAgentRuntime will not cover.
Use a long-lived access key: the gateway’s SigV4 support does not take a session token, so SSO or STS credentials cannot be used here.
7

Register everything in TrueFoundry

Add Okta (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.
Use the bare runtime ARN in the URL. The endpoint is selected by the ?qualifier= query parameter, so the ARN embedded in the path must be the runtime ARN. The AWS console shows the endpoint ARN (…/runtime-endpoint/DEFAULT) prominently, and pasting that produces a failure that looks like a permissions problem. The region must also match in three places: the URL, the ARN, and the gateway’s SigV4 region field.

What your MCP server receives

No Authorization 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:
Return only data that is safe for any caller the gateway has authorized. Per-user decisions are not possible here — that is what Okta OBO adds.

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.
Okta OBO flow: the first two hops are unchanged, then the TrueFoundry gateway performs a second token exchange at mcp-as as tfy-mcp-exchange, producing a token audienced to api://mcp-api that still names the user, which the MCP runtime validates and uses to scope its answer.Okta OBO flow: the first two hops are unchanged, then the TrueFoundry gateway performs a second token exchange at mcp-as as tfy-mcp-exchange, producing a token audienced to api://mcp-api that still names the user, which the MCP runtime validates and uses to scope its answer.

Set it up

Everything from IAM SigV4 still applies. This is what you add.
1

Create mcp-as and the exchange client

Add the 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.
2

Extend the trust chain and add the exchange rule

On mcp-asTrusted 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.
3

Add the user claims you want downstream

On mcp-asClaims, 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.
4

Give the MCP runtime a JWT authorizer and the header allowlist

The allowlist is essential. AgentCore strips inbound headers by default, even when its authorizer has just validated the token — without it your MCP server receives no token at all, and Okta OBO behaves exactly like IAM SigV4 while looking like a failed exchange.
5

Switch the gateway's upstream auth

Register the second MCP server against the JWT runtime. No agent code changes — the agent sends the same token it sent under IAM SigV4.

What your MCP server receives

Derive the caller from 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.
Verify the signature unless a validating layer guarantees it. Behind an AgentCore runtime with a JWT authorizer, the signature, issuer, audience and scopes have already been checked before your code runs, so decoding the payload to read claims is enough. If your MCP server is reachable without such a layer, decoding without verifying accepts any self-signed JWT — a complete authentication bypass. Verify against the issuer’s JWKS in that case.
Where the agent’s name lives. The second exchange is performed by the gateway’s client, so 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. Changing mode is gateway configuration, not an agent change: moving a server from SigV4 to on-behalf-of touches the server registration and the MCP runtime’s inbound auth, and the agent keeps sending the same token.

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:
Ask your agent “who am I?” against each server. Under IAM SigV4 the answer names no user — that is correct, not a failure. Under Okta OBO it names the signed-in person. Seeing the two side by side is the clearest evidence that delegation is real. It also pays to log identity at every hop you own — the client, the agent’s entry point, each exchange (request and result), and the MCP server — recording 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 under outcome.reason and echoes what was actually sent, which also confirms whether your change took effect.
A habit worth keeping. If a change that should have altered the failure produces a byte-identical error, stop forming new hypotheses and first prove your change reached the component under test — a cached upstream credential or a runtime still on its previous version will absorb every fix you try.