Skip to content
Frezz Tech logo with a futuristic AI robot and cloud circuitry, representing artificial intelligence, Microsoft Copilot, Azure AI, and cloud innovation. Frezz Tech logo with a futuristic AI robot and cloud circuitry, representing artificial intelligence, Microsoft Copilot, Azure AI, and cloud innovation. Frezz Tech

Real-world AI, Copilot and Azure architecture

Frezz Tech logo with a futuristic AI robot and cloud circuitry, representing artificial intelligence, Microsoft Copilot, Azure AI, and cloud innovation. Frezz Tech logo with a futuristic AI robot and cloud circuitry, representing artificial intelligence, Microsoft Copilot, Azure AI, and cloud innovation. Frezz Tech

Real-world AI, Copilot and Azure architecture

  • Home
  • Blog
  • About
  • Home
  • Blog
  • About
Close

Search

  • Home
  • Blog
  • About
Frezz Tech logo with a futuristic AI robot and cloud circuitry, representing artificial intelligence, Microsoft Copilot, Azure AI, and cloud innovation. Frezz Tech logo with a futuristic AI robot and cloud circuitry, representing artificial intelligence, Microsoft Copilot, Azure AI, and cloud innovation. Frezz Tech

Real-world AI, Copilot and Azure architecture

Frezz Tech logo with a futuristic AI robot and cloud circuitry, representing artificial intelligence, Microsoft Copilot, Azure AI, and cloud innovation. Frezz Tech logo with a futuristic AI robot and cloud circuitry, representing artificial intelligence, Microsoft Copilot, Azure AI, and cloud innovation. Frezz Tech

Real-world AI, Copilot and Azure architecture

  • Home
  • Blog
  • About
  • Home
  • Blog
  • About
Close

Search

  • Home
  • Blog
  • About
An agent calls through a gateway to an MCP server with the same user token and the server still sees callerType user
Azure API ManagementMicrosoft FoundrySecurity & Identity

Behind the Gateway: What Your MCP Server Still Knows About the Caller in Microsoft Foundry

10.11.2026 11 Min Read
0

The whoami server with OAuth identity passthrough, now routed through the Foundry AI gateway

Last verified against Microsoft Learn on 9 November 2026. The AI gateway for MCP tools is in preview, so check the linked docs before you rely on a detail.

  • The token arrives unchanged: Behind the gateway the whoami answer is the same as before. callerType: user, a delegated scope and no app role. The gateway does not touch the user token.
  • The agent is visible, but only in a header: Foundry sends the agent name, the agent version and an agent identity ID in the header x-ms-foundry-baggage. The token is the same for two agents. The header is not signed, so it is a hint and not proof.
  • The gateway enforces rules and owns no authority: It rejected a call without a token with 401 and it limited calls per agent with 429. The server never saw those requests. The gateway log does not name an agent or a user.

The problem in plain language

In my last post a Foundry agent called my own MCP server with the token of the signed in user. The server saw a person. Readers pointed out something I had only touched on: A signed in user is not the same as an agent identity and both are not the same as a mandate. The user may be allowed to do a task. That does not mean that this agent may do it for this user right now.

Foundry has a place where organizations want to answer such questions: the AI gateway. It puts Azure API Management in front of your MCP tools and applies policies. So this post asks what a gateway in front of the server changes. Three questions:

  1. Does the user token still arrive unchanged?
  2. Can I limit calls per user and not only per IP address like in the docs example?
  3. Can the gateway or the server tell two agents apart when they use the same connection?
Diagram with a Foundry agent, an API Management MCP server with validate-jwt, a correlation header and a rate limit, the container app and two logs
One call goes from the agent through the gateway to the server. The gateway checks the token, sets a correlation ID and counts

What the docs say

The docs describe the AI gateway for MCP tools in Govern MCP tools by using an AI gateway and the setup in Enable AI Gateway in the Foundry portal. These points matter for what follows:

Statement in the docsWhat I found
Only new MCP tools without managed OAuth are routed through the gatewayCustom OAuth identity passthrough is on the supported list. I created the tool after enabling the gateway
Existing projects must be enabled manuallyMy project showed Disabled until I selected Add project to gateway
Policies can only be applied in the Azure portalTrue, the Foundry portal has no policy editor
The rate limit example keys the counter on the IP addressI used the user and then the agent as the key
AI gateways do not log tool tracesI did not check this
Deleting the gateway in Foundry is not enough, delete the APIM instanceSame for me, see the cleanup at the end

Step 1: Enable the gateway

In the Foundry portal go to Manage, AI Gateway, Add AI Gateway. Select the Foundry resource, choose Create new and give the gateway a name. Create new provisions a Basic v2 instance of API Management. The docs say that typically takes 5 to 10 minutes. For me the instance was ready after a few minutes.

The AI Gateway page in the Foundry portal with a gateway and a project whose status is Enabled
The gateway page after I added the project

Key insight: The instance can be ready and the project still shows Disabled. The docs mention it in the last step of the setup: New projects are enabled by default and existing projects must be enabled manually. I selected Add project to gateway and the status changed to Enabled. If you skip this, you create your tool and it silently goes past the gateway.

Step 2: Create the tool after the gateway

Create the MCP tool the same way as in the last post: Build, Tools, Connect a tool, Custom, Model Context Protocol, OAuth Identity Passthrough. The form is the same. You still enter the address of your own server. After saving, the details page shows a banner “AI Gateway enabled” and the endpoint is the address of the gateway.

Tool details page with the banner AI Gateway enabled, the gateway endpoint and the checkbox Governed with AI Gateway
The endpoint points to the gateway. The checkbox Governed with AI Gateway is set and I could not clear it

Two small differences to the docs: They show an endpoint like https://<apim>.azure-api.net/mcp/.... Mine looked like https://<apim>.azure-api.net/tool-<project>-<app>-<suffix>. And the redirect URL started with global.consent... just like in the last post.

In API Management Foundry created an MCP server for the tool. It shows up in the list under MCP Servers and not as a new API. That is where the policies of the tool live.

The MCP Servers page of the API Management instance with the server that Foundry created for the tool
The name combines the project and the container app

The consent flow was the same as before: two Entra dialogs and the Foundry confirmation page, then the tool approval.

Question 1: What arrives at the server

The answer of whoami through the gateway:

{
  "callerType": "user",
  "objectId": "56d16eb0...",
  "clientApp": "382dfc6a...",
  "delegatedScopes": ["Mcp.Tools.Access"],
  "audience": "api://8530123d...",
  "expiresAt": "2026-11-09T21:42:03+00:00"
}

Nothing changed. The server validates signature, issuer, audience and expiry like before and sees the same user.

For this post I extended the audit line of the demo server. It now also lists the names of all request headers. Values stay out, because a header can carry anything.

whoami called callerType=user objectId=56d16eb0... clientApp=382dfc6a... scopes=Mcp.Tools.Access roles=-
headers=accept,authorization,baggage,client-ip,client-request-id,content-type,disguised-host,host,
max-forwards,mcp-method,mcp-name,mcp-protocol-version,traceparent,user-agent,x-envoy-expected-rq-timeout-ms,
x-forwarded-for,x-k8se-app-name,x-ms-containerapp-name,x-ms-foundry-baggage,x-ms-useragent,x-original-url,
x-request-id,... correlationId=-

(shortened, the full list has 38 names)

Sorted by origin, as far as the names tell me:

  • Looks like an App Service style front end: client-ip, client-request-id, disguised-host, max-forwards, was-default-hostname, x-appservice-proto, x-arr-log-id, x-arr-ssl, x-azure-ja4-fingerprint, x-site-deployment-id, x-waws-unencoded-url. My guess is that these come from the API Management gateway.
  • Container Apps: x-envoy-*, x-k8se-*, x-ms-containerapp-*, x-forwarded-*, x-request-id, x-original-url
  • Foundry and MCP: x-ms-foundry-baggage, x-ms-useragent, baggage, traceparent, mcp-method, mcp-name, mcp-protocol-version

Key insight: I cannot say which of these headers the gateway adds. The checkbox Governed with AI Gateway cannot be cleared, so I had no run without the gateway to compare against. The names show what arrives. They do not show who put it there.

Question 3 first: Who is calling

I created a second agent that uses the same connection. Both agents called whoami several times. The token is identical for both: same user, same client app, same scope. But one header differs:

x-ms-foundry-baggage:
foundry.project.id=<account>%40<project>%40AML,
foundry.account.id=<guid>,
foundry.caller.identity_type=agent,
foundry.caller.agentic_identity.id=<guid>,
foundry.caller.agent.name=whoami-mcp-gateway-agent,
foundry.caller.agent.version=2
Agent 1Agent 2
foundry.caller.agent.namewhoami-mcp-gateway-agentwhoami-mcp-gateway-agent2
foundry.caller.agentic_identity.id293f39ef...9e2a6028...
foundry.caller.agent.version28
Token: user, client app, scopeidenticalidentical

So the server can tell the agents apart, but not through the token. Foundry sends the information in a header together with x-ms-useragent: AzureAIFoundryAgentRuntime/.... That points to the Foundry agent runtime as the source and not to the gateway. I did not verify it.

Three panels: the token names a user, the header names an agent and the gateway log names neither
Three places and what each one tells you

Key insight: The header is not signed and it is not bound to the token. Whoever holds the token can send any value. My server can use it to log and to attribute calls. It must not use it to grant access. For cost control or fairness a hint is enough. For authority it is not.

To read these values I added a setting to the demo server: AUDIT_HEADER_VALUES takes a list of header names whose values appear in the audit line. It is off by default. Credentials like Authorization are never logged even if you list them. I keep it off because the baggage contains the project and in my case a foundry.account.id that was identical to my subscription ID.

The gateway log does not help here. API Management writes a row per request to Log Analytics when you enable the diagnostic setting (category ApiManagementGatewayLogs, with the destination table set to resource specific). The columns are CorrelationId, ResponseCode, CallerIpAddress, BackendUrl, LastErrorSource and about 50 more. UserId and ApimSubscriptionId were empty. CallerIpAddress is a changing address of the Foundry runtime. RequestHeaders is empty unless you configure header logging. Nothing in the log names the agent or the user.

Connecting the two logs

The docs show a policy that sets a correlation ID. I added it to the policy of the MCP server.

<set-header name="X-Correlation-Id" exists-action="override">
  <value>@(context.RequestId.ToString())</value>
</set-header>

The audit line of my server now shows correlationId=9b4e4722-362f-46ec-93f4-1171a1051ca3 and the same value is the column CorrelationId of the gateway row. The server logged the call 8 to 27 milliseconds after the gateway row in the calls I compared. With that ID you can follow one call through both logs.

It also showed a gap: One call that my server logged at 20:29:02 UTC has no row in the gateway log. I do not know why. The correlation ID makes such a gap visible. I would not rely on the gateway log alone as the complete record.

Question 2: Limit per user and per agent

The docs example keys the rate limit on context.Request.IpAddress. Behind Foundry that is an address of the runtime, so it limits everybody together. I keyed the counter first on the object ID of the user (oid) from the token. That needs validate-jwt first, because AsJwt() only parses the token and does not check the signature. Without the check a client could invent its own key.

Then I keyed it on the agent name from the baggage header. These are the three elements I added after the cors block that Foundry created:

<set-header name="X-Correlation-Id" exists-action="override">
  <value>@(context.RequestId.ToString())</value>
</set-header>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized">
  <openid-config url="https://login.microsoftonline.com/<TENANT_ID>/v2.0/.well-known/openid-configuration" />
  <audiences>
    <audience>api://<API_APP_ID></audience>
  </audiences>
  <issuers>
    <issuer>https://sts.windows.net/<TENANT_ID>/</issuer>
    <issuer>https://login.microsoftonline.com/<TENANT_ID>/v2.0</issuer>
  </issuers>
</validate-jwt>
<rate-limit-by-key calls="4" renewal-period="60"
  counter-key="@{ var b = context.Request.Headers.GetValueOrDefault("x-ms-foundry-baggage", ""); var m = System.Text.RegularExpressions.Regex.Match(b, @"foundry\.caller\.agent\.name=([^,]+)"); return m.Success ? m.Groups[1].Value : "unknown"; }" />

For a limit per user use this counter key instead: context.Request.Headers.GetValueOrDefault("Authorization","").AsJwt()?.Claims.GetValueOrDefault("oid", "none"). The portal accepted the double quotes inside the attribute, even though that is not well formed XML.

Policy editor with the elements set-header, validate-jwt and rate-limit-by-key after the cors block
The last line is cut off in the editor. The full version is in the code block above

The result in Foundry is a full error message and not a general one like the 403 in the last post:

Foundry playground with the error 429 Too Many Requests and the response body Rate limit is exceeded
Foundry shows the status code and the body of the gateway, including the wait time

The gateway log shows the same event and the server never saw the call:

Log Analytics rows with 200 and 429 responses and the last error source rate-limit-by-key
Times are UTC. Agent 2 ran into the limit at 20:30:44. Agent 1 got through at 20:31:00 while agent 2 was still inside its window

That last detail is the answer to the per agent question. Both agents have the same user and the same token, but each had its own counter.

Some numbers did not match what I configured. The limit counts HTTP requests. One whoami call is two POST requests in the gateway log and a conversation can start with one more pair. I set calls="4" and still six to seven requests passed before the first 429. I do not know how API Management counts here, so I would not promise an exact number of tool calls per minute.

Key insight: There was a second 429 that did not come from the gateway. In one long conversation Foundry stopped with “Your requests to gpt-5-mini … have exceeded token rate limit”. That is the token limit of the model deployment. Each turn sends the whole history again and my tokens per answer grew from 933 to 5259. If you test a tool limit, start a new conversation for each run.

What the gateway enforces and what it does not

A request without a token never reaches the server. I sent a POST without Authorization straight to the gateway address.

GatewayServer
Status401401
Body{ "statusCode": 401, "message": "Unauthorized" }{"error": "invalid_token", "error_description": "Authentication required"}
WWW-AuthenticatemissingBearer error="invalid_token" ... resource_metadata="https://.../.well-known/oauth-protected-resource/mcp"
A gateway log row with the response code 401 and the last error source validate-jwt
The reason is TokenNotPresent. There was no backend call

The gateway checked the token before the server saw anything. That is useful: It takes load off the server and it enforces a rule in one place. But note what it checked: signature, issuer and audience of a token. It does not know who may do what. It cannot tell whether this agent may perform this task for this user. The header with the agent name is unsigned, so even the key of my per agent limit is only as trustworthy as the sender.

So the three layers do different jobs. The token says which user and which client. The header says which agent, as a hint. The gateway applies rules to what it can see. None of them express a mandate: “this agent may do this task for this user”. That stays an open question and it is not something a rate limit solves.

What I did not test

  • A run without the gateway: I could not switch it off for the project, so I do not know which headers the gateway adds.
  • How API Management counts: Six to seven requests passed with calls="4". I did not test a condition on the header mcp-method.
  • A token with the wrong audience: I only tested a request without a token.
  • A second user: That each user has an own counter follows from the key. I did not see it with a second account.
  • Forging the agent header: That a client can set it follows from the design. I did not test it.
  • Tool traces: The docs say the gateway does not log them. I did not check.
  • Models through the gateway: I only looked at the MCP tool.
  • Managed OAuth and other tool types: The docs say they are not routed.

Cost and cleanup

API Management costs money until you delete the instance. Disabling the project or deleting the gateway in the Foundry portal does not delete it. The docs say to delete the instance with the same name as the gateway in the Azure portal. A deleted instance stays soft deleted for a while and az apim deletedservice purge removes it for good. My instance ran for about five hours. My teardown script now deletes the resource group, both app registrations and the soft deleted instances and ends with a check that lists what is left. The Log Analytics workspace for the gateway logs was in the same resource group.

The demo repo

The code for this post is in the demo repo under the tag post-4. It has the extended audit line with the header names and the optional AUDIT_HEADER_VALUES, 29 unit tests and the policies in deploy/gateway/. The setup and the teardown are in deploy/.

Conclusion

Behind the AI gateway the server sees the same user as before. The gateway enforces simple rules at one place and shows its decision to the agent in full. For agents there is a hint in a Foundry header and nothing signed. If you need to know who may do what, the gateway is a good place to enforce a decision. It is not the place that owns it.

Tags:

agentsai-gatewayazure-api-managementcontainer-appsidentity-passthroughMCPmicrosoft-entramicrosoft-foundryOAuthrate-limiting
Author

Alexander Dierkes

Follow Me
Other Articles
A user consents, a Foundry connection carries the user token to an MCP server and the server sees callerType user
Previous

From Workload to Person: OAuth Identity Passthrough for Your Own MCP Server in Microsoft Foundry

No Comment! Be the first one.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Latest Posts

  • Behind the Gateway: What Your MCP Server Still Knows About the Caller in Microsoft Foundry
  • From Workload to Person: OAuth Identity Passthrough for Your Own MCP Server in Microsoft Foundry
  • Hosting Your Own MCP Server for Microsoft Foundry on Azure Container Apps
  • Which Agent, on Whose Behalf? Authenticating MCP Tools in Microsoft Foundry
  • Which Identity Does Your Foundry Agent Really Use?

agents ai-gateway AI Agents Authentication Azure azure-api-management Azure Container Apps Azure OpenAI container-apps Foundry Local Governance identity-passthrough Identity Passthrough Local AI MCP microsoft-entra microsoft-foundry Microsoft Entra Microsoft Foundry OAuth rate-limiting

Resources

About | Imprint

Disclaimer

Opinions expressed here are my own and may not reflect those of others. Unless I'm quoting someone, they're just my own views.

© 2026 Frezz Tech - All rights reserved by Alexander Dierkes
Independent technology blog. Not affiliated with Microsoft.