Authentication
Most remote MCP servers require authentication. MCPAdapter delegates auth to FastMCP, so any credential a fastmcp.Client accepts works: a static bearer token, a full OAuth 2.1 flow, or any httpx.Auth. Pass the credential on a prebuilt client and hand that client to the adapter. When one agent talks to several servers, use per-server authentication; in a deployment, use per-user authentication so each run reaches the server as the caller.
Note
The
langchain.mcpnamespace requireslangchain[mcp]>=1.4.0and is in beta. The API may change.
Bearer token
The simplest case: the server verifies a token you provisioned and adds it in the Authorization: Bearer <token> header. There is no discovery, browser, or refresh. Pass the token as the client’s auth:
from fastmcp.client import Client
from langchain.mcp import MCPAdapter
async def load_tools_with_bearer(url: str, token: str) -> list:
# `auth` accepts a bearer-token string, the literal "oauth" (full OAuth 2.1
# with dynamic client registration), or any `httpx.Auth`.
async with MCPAdapter(Client(url, auth=token)) as adapter:
return await adapter.list_tools()The auth argument accepts a bearer-token string, the literal "oauth", or any httpx.Auth. In an MCPConfig fleet, each server takes the same key.
OAuth authentication
For a server that issues its own credentials, pass the literal string "oauth". FastMCP runs the full OAuth 2.1 flow: discovery, dynamic client registration, the browser redirect, and the token exchange. Dynamic client registration means the client registers itself at runtime instead of you pre-provisioning a client ID:
async def load_tools_with_oauth(url: str) -> list:
# "oauth" runs discovery, dynamic client registration, the browser redirect,
# and the token exchange. Pass `OAuth(..., token_storage=...)` to persist
# tokens across runs instead of repeating the browser step each time.
async with MCPAdapter(Client(url, auth="oauth")) as adapter:
return await adapter.list_tools()By default tokens are held in memory, so each run repeats the browser step. Pass a prebuilt OAuth provider with a token store to persist them across runs:
from fastmcp.client import Client
from fastmcp.client.auth import OAuth
oauth = OAuth(mcp_url="https://example.com/mcp", token_storage=...)
client = Client("https://example.com/mcp", auth=oauth)FastMCP ships providers for common identity providers (Auth0, WorkOS, Okta, and more); the flow the client runs is identical across them. See OAuth authentication in the FastMCP documentation.
Per-server authentication
When one agent talks to several servers, each server may need its own credential. Give each its own connection with a ClientGroup, and set auth per client, so every server authenticates independently:
from fastmcp.client.group import ClientGroup
async def load_with_per_server_auth(
billing_url: str, docs_token: str, docs_url: str
) -> list:
# Each server carries its own credential. A `ClientGroup` keeps one
# connection per server, so each authenticates independently.
group = ClientGroup(
{
"billing": Client(billing_url, auth="oauth"),
"docs": Client(docs_url, auth=docs_token),
}
)
async with MCPAdapter(group) as adapter:
return await adapter.list_tools()Per-user authentication
In a deployment, each run should reach the MCP server as the user who initiated it, not with one shared credential. The pattern has two halves:
- Authenticate the caller at the LangGraph server. A custom auth handler resolves the incoming request to a user identity, which each run reads off its runtime.
- Mint or exchange a credential for that user. Inside the graph factory, read the user’s identity and build the MCP client with a per-user token, so the connection carries that user’s authorization.
from fastmcp.client import Client
from fastmcp.client.auth import BearerAuth
CONFIG = {
"mcpServers": {
"docs": { "url": "https://example.com/mcp" }
}
}
async def make_graph(runtime):
user = runtime.user.identity if runtime.user is not None else "anonymous"
auth = BearerAuth(token_for(user)) # exchange for a per-user token
async with MCPAdapter(Client(CONFIG, auth=auth)) as adapter:
tools = await adapter.list_tools()
return create_agent("claude-sonnet-5", tools)In production, token_for stands in for whatever the deployment already has: an OAuth gateway that exchanges the session for a per-user token, or a fastmcp.client.auth provider that runs the authorization-code flow per identity. Isolate any cached responses per user, keyed off the verified identity, so one user never sees another’s cached tool list.