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.
Copy
Ask AI
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.
Copy
Ask AI
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.
Copy
Ask AI
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 an error with an authorization URL.Handling OAuth Errors in Code:
Copy
Ask AI
import refrom fastmcp import Clientfrom fastmcp.client.transports import StreamableHttpTransportdef extract_auth_urls(exception: Exception) -> list[str]: """Extract OAuth authorization URLs from MCP Gateway errors.""" current = exception while current is not None: try: data = str(current.error.data) if "Please visit:" in data: match = re.search(r"Please visit:\s*(.+)$", data) if match: return [url.strip() for url in match.group(1).split(" , ")] except AttributeError: pass current = current.__cause__ return []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}"} ) try: async with Client(transport) as client: result = await client.call_tool(tool_name, tool_args) return {"success": True, "result": result} except Exception as e: auth_urls = extract_auth_urls(e) if auth_urls: return { "success": False, "auth_required": True, "authorization_urls": auth_urls, "message": "User needs to complete OAuth authorization" } 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"): for url in result["authorization_urls"]: print(f"Please authorize access: {url}") # After user completes OAuth, retry the request else: print(f"Success: {result['result']}")
OAuth errors occur during connection, not when calling individual tools. The Gateway checks for valid OAuth tokens during the initial handshake.
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.