Learn how to integrate MCP servers into your agent with the right authentication approach for your use case.
Prerequisite: Before proceeding, we recommend reviewing the Authentication and Security guide to understand the Gateway’s authentication architecture, including inbound/outbound authentication and access control concepts.
When building an AI agent that uses MCP tools, authentication can be confusing. This guide helps you understand which token(s) to pass and when, based on your specific use case.
Scenario A: Agent Acts on YOUR Behalf (Developer/Service Mode)
Your agent performs actions using your organization’s credentials - like a service account that does work on behalf of your company.Examples:
An internal support bot that queries your company’s knowledge base
A code assistant that uses a shared GitHub service account
A data analysis agent that accesses shared analytics databases
What tokens to use:
Token Type
What to Use
Why
Gateway Token
Virtual Account token
Identifies your service/application
MCP Server Auth
Shared credentials (pre-configured)
All requests use the same service account
Key point: You pass ONE token (Virtual Account). The MCP server’s shared credentials are configured in the UI, not in your code.
import osfrom fastmcp import Clientfrom fastmcp.client.transports import StreamableHttpTransport# Only pass the Virtual Account token# MCP server auth is pre-configured with shared credentials in the UIVA_TOKEN = os.environ["VIRTUAL_ACCOUNT_TOKEN"]async def call_tool_as_service(): transport = StreamableHttpTransport( url="https://<gateway-url>/mcp/<server-name>/server", headers={"Authorization": f"Bearer {VA_TOKEN}"} ) async with Client(transport) as client: # All users of your agent get the same level of access result = await client.call_tool("query_knowledge_base", {"question": "..."}) return result
With this approach, all requests from your agent have the same permissions. User A and User B get identical access to the downstream service.
Scenario B: Agent Acts on USER'S Behalf (Per-User Mode)
Your agent performs actions using the end user’s credentials - the agent accesses each user’s own data and respects their permissions.Examples:
A productivity agent that accesses the user’s own Gmail, Slack, or Calendar
A development assistant that accesses the user’s GitHub repositories
A CRM agent that sees only what the logged-in user can see
What tokens to use:
Token Type
What to Use
Why
Gateway Token
User’s token (TFY PAT or IdP JWT)
Identifies which user is making the request
MCP Server Auth
Per-user OAuth (managed by TrueFoundry)
Each user’s own credentials
Key point: You pass the USER’s token. TrueFoundry looks up and injects the OAuth token for that specific user.
from fastmcp import Clientfrom fastmcp.client.transports import StreamableHttpTransportasync def call_tool_as_user(user_token: str): # Pass the USER's token (not a shared service token) # TrueFoundry looks up the OAuth token for THIS user transport = StreamableHttpTransport( url="https://<gateway-url>/mcp/<server-name>/server", headers={"Authorization": f"Bearer {user_token}"} ) async with Client(transport) as client: # User A sees their emails, User B sees their emails result = await client.call_tool("search_emails", {"query": "..."}) return result
With this approach, User A and User B get different data based on their own OAuth authorizations. Each user must complete OAuth consent once.
Scenario C: Hybrid - Some Tools Shared, Some Per-User
Your agent uses some tools with shared credentials and other tools with per-user credentials.Examples:
An assistant that searches the web (shared API key) and sends emails on user’s behalf (per-user OAuth)
A dev tool that queries documentation (shared) but accesses user’s GitHub (per-user)
What tokens to use:
Token Type
What to Use
Why
Gateway Token
User’s token
Identifies the caller
MCP Server Auth
Mixed - configured per MCP server
Gateway handles it automatically
Key point: You pass ONE Gateway token. The Gateway routes to different MCP servers, each with its own auth model configured.
from fastmcp import Clientfrom fastmcp.client.transports import StreamableHttpTransportasync def call_mixed_tools(user_token: str): # Pass ONE token - the Gateway handles the rest transport = StreamableHttpTransport( url="https://<gateway-url>/mcp/<virtual-mcp-name>/server", headers={"Authorization": f"Bearer {user_token}"} ) async with Client(transport) as client: # Web search uses shared API key (configured on MCP server) await client.call_tool("web_search", {"query": "..."}) # Gmail uses user's OAuth token (user must have completed OAuth) await client.call_tool("send_email", {"to": "...", "body": "..."})
The Gateway abstracts away the complexity. You don’t need to pass different tokens for different MCP servers.
Follow this high-level path to integrate the MCP Gateway into your agent:
Choose your scenario — Use the Quick Decision Guide and Quick Start scenarios above to decide whether your agent acts on behalf of users or a shared service, and which tokens you need.
Meet the prerequisites — Ensure you have access, a registered MCP server, and the right token type (see Prerequisites below).
Get your MCP server URL — Copy the URL for your MCP server from the TrueFoundry UI (e.g. https://gateway.truefoundry.ai/mcp/sentry/server).
Connect from your agent — Use the Gateway token in the Authorization header and call the MCP server URL with your chosen client (e.g. fastmcp). See Connecting to an MCP Server and the scenario examples above.
Once you have the URL and token, the rest is standard MCP tool calls; the Gateway handles auth to the downstream MCP server.
import asynciofrom fastmcp import Clientfrom fastmcp.client.transports import StreamableHttpTransportasync def call_mcp_tool(): # MCP server URL from TrueFoundry UI mcp_url = "https://<gateway-url>/mcp/<server-name>/server" # Your authentication token (see scenarios above) auth_token = "your-token" transport = StreamableHttpTransport( url=mcp_url, headers={"Authorization": f"Bearer {auth_token}"} ) async with Client(transport) as client: # List available tools tools = await client.list_tools() print(f"Available tools: {[t.name for t in tools]}") # Call a specific tool result = await client.call_tool("tool_name", {"arg1": "value1"}) print(f"Result: {result}")asyncio.run(call_mcp_tool())
Virtual Accounts cannot have per-user OAuth tokens. If your MCP server requires OAuth for user-specific data, use a user token (TFY PAT or IdP JWT) instead.
When an MCP server is configured with OAuth2 and the user hasn’t completed the OAuth flow, the Gateway returns a 401 HTTP error whose JSON body contains authorization_urls the user must visit.Handling OAuth Errors in Code:
import httpxfrom fastmcp import Clientfrom fastmcp.client.transports import StreamableHttpTransportdef _make_httpx_client(**kwargs) -> httpx.AsyncClient: """Factory that creates an httpx client which reads error response bodies. The MCP library uses streaming requests, so response bodies are not automatically read. This hook ensures 4xx/5xx bodies are loaded before raise_for_status() discards them. """ async def _read_error_body(response: httpx.Response): if response.status_code >= 400: await response.aread() hooks = kwargs.pop("event_hooks", {}) hooks.setdefault("response", []).append(_read_error_body) return httpx.AsyncClient(event_hooks=hooks, **kwargs)async def call_mcp_with_oauth( mcp_url: str, user_token: str, tool_name: str, tool_args: dict): """Call an MCP tool, handling OAuth authorization if needed.""" transport = StreamableHttpTransport( url=mcp_url, headers={"Authorization": f"Bearer {user_token}"}, httpx_client_factory=_make_httpx_client, ) try: async with Client(transport) as client: result = await client.call_tool(tool_name, tool_args) return {"success": True, "result": result} except httpx.HTTPStatusError as e: if e.response.status_code == 401: body = e.response.json() if body.get("error", {}).get("type") == "McpAuthRequiredError": return { "success": False, "auth_required": True, "authorization_urls": body.get("authorization_urls", {}), "server_names": body.get("server_names", {}), "message": body.get("message", ""), } raise# Example usageasync def main(): result = await call_mcp_with_oauth( mcp_url="https://<gateway-url>/mcp/<server-name>/server", user_token="user-tfy-token-or-idp-jwt", tool_name="get_repositories", tool_args={} ) if result.get("auth_required"): print(result["message"]) for server, url in result["authorization_urls"].items(): print(f" Authorize {server}: {url}") # After user completes OAuth, retry the request else: print(f"Success: {result['result']}")
OAuth errors are returned as HTTP 401 responses with a JSON body containing error.type set to "McpAuthRequiredError". The authorization_urls field is a dictionary keyed by server name, and each value is the URL the user must visit to complete the OAuth consent flow.
After OAuth completion: Once the user completes the OAuth consent flow, TrueFoundry stores their token. Future requests automatically include the OAuth credentials—no code changes needed.
A common question: “My agent uses Gmail (OAuth) and a web search API (header-based). Do I need to handle them differently?”Answer: No. The Gateway handles this for you.