Most Model Context Protocol servers ship without authentication. GitHub’s MCP server, Linear’s, Notion’s: they all assume you will handle auth somewhere else. That assumption creates a deployment problem when you want agents to call these tools in production.
AgentCore Gateway solves this by sitting between your agent orchestrator and the MCP servers, injecting OAuth tokens into tool calls without modifying either the agent code or the MCP server implementation. It is a reverse proxy that speaks JSON-RPC on both sides and performs token exchange in the middle.
The Authentication Gap in MCP
MCP defines how agents discover and invoke tools. It does not define how to authenticate those calls. The protocol assumes:
- Clients (Claude Desktop, VS Code) run on developer machines with local credentials
- Servers trust whoever connects to them
- Token management happens outside the protocol
This works for local development. It breaks when you deploy agents that need to call GitHub, Jira, or internal MCP servers on behalf of users or service accounts.
You have three options:
- Wait for every MCP server to implement OAuth (not happening)
- Fork each server and add auth logic (maintenance nightmare)
- Put an authenticating proxy in front of all servers (gateway pattern)
AgentCore Gateway implements option three.
Gateway Architecture
The gateway presents a single MCP endpoint to your agent orchestrator. Behind that endpoint, it routes tool calls to multiple upstream MCP servers and injects the appropriate OAuth tokens.
Request flow:
- Agent calls
github.create_issuevia the gateway’s unified MCP interface - Gateway checks if the session has a valid GitHub token
- If not, it redirects the user through Okta (or any OAuth provider) to authorize GitHub access
- Gateway stores the token and associates it with the session
- Gateway forwards the tool call to the real GitHub MCP server with the token in the Authorization header
- Response flows back through the gateway to the agent
The agent never sees the OAuth dance. The MCP server never knows a gateway exists. The gateway handles token refresh, session management, and routing.
Client ID Metadata Documents (CIMD)
The original MCP auth proposal used Dynamic Client Registration: let clients auto-register with your authorization server. Enterprises rejected this because it meant any client could register itself.
CIMD fixes this by making the client ID a URL that hosts a JSON metadata document. Instead of registering clients dynamically, you configure your OAuth provider to trust specific client URLs.
VS Code’s CIMD document lives at https://vscode.dev/.well-known/oauth-client:
{
"client_name": "Visual Studio Code",
"logo_uri": "https://code.visualstudio.com/assets/branding/code-stable.png",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"redirect_uris": ["vscode://vscode.github-authentication/did-authenticate"]
}
Your IdP (Okta, Auth0, Entra ID) fetches this document and validates the client’s identity before issuing tokens. No dynamic registration, no trust-on-first-use.
Token Injection and Session State
The gateway maintains a session store that maps agent sessions to OAuth tokens. When an agent makes parallel tool calls, all requests share the same session and token.
Session lifecycle:
- Initialization: Agent connects, gateway creates session ID
- First tool call: Gateway detects missing token, triggers OAuth flow
- Token storage: Gateway stores access token, refresh token, expiry
- Subsequent calls: Gateway injects stored token into Authorization header
- Token refresh: Gateway detects expiry, uses refresh token to get new access token
- Parallel calls: All requests in the same session use the same token, no race conditions
The gateway does not modify the JSON-RPC payload. It only adds HTTP headers before forwarding to the upstream MCP server.
Routing and Multi-Server Configuration
A single gateway instance can front multiple MCP servers. You configure routing rules that map tool namespaces to upstream servers:
| Tool Namespace | Upstream Server | OAuth Scope |
|---|---|---|
github.* | http://github-mcp:3000 | repo, read:org |
jira.* | http://jira-mcp:3001 | read:jira-work, write:jira-work |
notion.* | http://notion-mcp:3002 | read_content, update_content |
When the agent calls github.create_issue, the gateway:
- Parses the tool name to extract the
githubnamespace - Looks up the routing rule
- Checks if the session has a GitHub token with
reposcope - Forwards the request to
http://github-mcp:3000
If the agent calls jira.create_ticket in the same session, the gateway uses a different token with Jira scopes.
Failure Modes and Observability
When Okta is unreachable:
The gateway cannot issue new tokens but can continue serving requests with cached tokens until they expire. You configure the failure behavior:
- Fail closed: Reject all new sessions, serve existing sessions until tokens expire
- Fail open: Allow unauthenticated requests (dangerous, only for dev)
- Queue requests: Buffer tool calls and retry OAuth flow when Okta recovers
Most deployments fail closed.
When token refresh fails:
The gateway logs the failure and returns an error to the agent. The agent’s retry logic determines what happens next. If the agent has no retry logic, the tool call fails and the agent must handle the error in its orchestration loop.
Observability hooks:
- Emit metrics for token refresh attempts, failures, and latency
- Log all OAuth redirects with session ID and requested scopes
- Trace tool calls through the gateway with correlation IDs
- Export session count, active tokens, and cache hit rate
You need these metrics to detect token expiry storms, scope mismatches, and upstream MCP server failures.
Deployment Shape
The gateway runs as a sidecar or standalone service depending on your orchestration platform.
Kubernetes deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: agentcore-gateway
spec:
replicas: 3
template:
spec:
containers:
- name: gateway
image: agentcore-gateway:latest
env:
- name: OAUTH_PROVIDER
value: "okta"
- name: OKTA_DOMAIN
valueFrom:
secretKeyRef:
name: oauth-config
key: domain
- name: SESSION_STORE
value: "redis://redis:6379"
ports:
- containerPort: 8080
name: mcp
The session store must be shared across replicas. Redis or DynamoDB work. Do not use in-memory storage unless you run a single replica.
Security boundaries:
- Gateway to IdP: TLS with certificate pinning
- Gateway to MCP servers: mTLS if servers are internal, TLS + API key if third-party
- Agent to gateway: TLS + session cookie or bearer token
- Session store: encrypted at rest, access controlled by IAM role
When Token Scopes Conflict
An agent might call github.read_repo (needs repo scope) and github.read_org (needs read:org scope) in the same session. The gateway requests both scopes during the initial OAuth flow.
If the user denies read:org, the gateway stores a token with only repo scope. When the agent calls github.read_org, the gateway returns an authorization error. The agent must handle this by either:
- Prompting the user to grant additional scopes
- Falling back to a different tool
- Failing the task and logging the scope gap
The gateway does not retry the OAuth flow automatically. Scope expansion requires explicit user consent.
Technical Verdict
Use AgentCore Gateway when:
- You need to connect agents to multiple MCP servers that lack built-in auth
- Your security team requires centralized OAuth enforcement
- You want to avoid forking and maintaining custom MCP server code
- You already run a service mesh or API gateway and can add another proxy
Avoid it when:
- You control the MCP server code and can add auth directly
- Your agents run locally on developer machines with personal credentials
- You need sub-millisecond latency and cannot tolerate proxy overhead
- Your OAuth provider does not support CIMD or URL-based client IDs
The gateway trades latency (one extra hop) for operational simplicity (one auth config, many MCP servers). If you are deploying agents in production and need enterprise SSO, the trade is worth it.