Connect a User to an MCP Server
A user MCP client lets each signed-in user authenticate directly with an external Model Context Protocol server using their own credentials. Tokens and headers are stored at the USER override level, so each user has an isolated set of credentials that are not visible to other users or administrators.
Use a user client when the MCP server needs to identify the human caller (per-user consent, per-user data, audit trails). For one set of credentials shared across the application, see Register a Shared App MCP Client instead.
Two ways to authenticate a user
How you authenticate depends on what the MCP server expects:
- Per-user headers — each user registers with their own authentication header (a bearer token, API key, or similar). This is the simplest path; use it when you already have a per-user token or the server authenticates with a static header. See Authenticate with per-user headers.
- OAuth 2.0 PKCE flow — each user signs in through the MCP server's OAuth provider and C3 exchanges an authorization code for a token. Use this when the server requires OAuth. See Authenticate with OAuth 2.0 PKCE.
Both store the resulting credentials on GenaiCore.Mcp.Client.User.Config at the USER override level, and both let the same user list and call tools afterward.
Authenticate with per-user headers
When the MCP server authenticates with a static header — for example an Authorization bearer token or an X-Api-Key — each user supplies their own header at registration time. The header is stored as a secret at the USER override level, so one user's credentials are never visible to another.
The first registration for a given client name also creates the base config (url) at the APP level. An administrator typically registers the client once; from then on each user calls register again with their own headers to store their personal credentials.
Call GenaiCore.Mcp.Client.User's register method with the name, URL, and a GenaiCore.Mcp.Client.User.RegisterSpec that carries the headers map.
spec = c3.GenaiCore.Mcp.Client.User.RegisterSpec(
headers={"Authorization": "C3Bearer <your-token>"},
failIfExists=False,
)
client = c3.GenaiCore.Mcp.Client.User.register(
name="example-user-mcp",
url="https://mcp.example.com/server",
spec=spec,
)Any header scheme the server expects works — for example an API key instead of a bearer token:
spec = c3.GenaiCore.Mcp.Client.User.RegisterSpec(
headers={"X-Api-Key": "<your-key>"},
failIfExists=False,
)Set failIfExists=False to update the URL or your headers if the client already exists; set it to true to error out instead. See Registration semantics.
Once registered, the user can list and call tools immediately — each call uses that user's stored headers:
tools = client.listTools()
result = client.callTool(toolName="mock_add", args={"a": 10, "b": 20})For listing tools, calling tools, persisting tool definitions, and using tools from an agent, see Use Tools, Prompts, and Resources from an MCP Client.
Authenticate with OAuth 2.0 PKCE
When the MCP server requires OAuth, each user signs in through the server's OAuth provider and C3 runs the authorization code flow with PKCE on their behalf.
Before you begin
For the OAuth path you need:
- The MCP server's base URL.
- An OAuth redirect URI registered with the MCP server's authorization provider. The same redirect URI is used when beginning and completing the flow.
- One of the following for the authorization server:
- A reachable well-known discovery URL or RFC 9728 resource metadata on the MCP server (C3 will auto-discover the endpoints), or
- The pre-built OAuth.AuthorizationServer object you want to use.
- Optional: a static OAuth
clientId. If you omit it and the authorization server supports RFC 7591, C3 performs dynamic client registration on your behalf. - Optional: an OAuth
scopestring (space-delimited).
Two ways to drive the flow
C3 ships two entry points to the same underlying flow:
- Server-side API path: call the typed methods on GenaiCore.Mcp.Client.User directly. Pick this when your server controls the redirect (for example, a CLI or service).
- Browser-side helper path: call GenaiCore.Mcp.OAuthUi.Helper, which packages the three steps as plain JSON-returning methods designed for a browser UI. Pick this when the user signs in through a web page.
Both paths read and write the same GenaiCore.Mcp.Client.User.Config and produce the same end state.
Use the server-side API path
Step 1: Register the user client
Call GenaiCore.Mcp.Client.User's register method with the name, URL, and a GenaiCore.Mcp.Client.User.RegisterSpec containing the OAuth authorization server (and optionally clientId and scope).
spec = c3.GenaiCore.Mcp.Client.User.RegisterSpec(
authorizationServer=auth_server_serialized,
clientId="<optional-static-client-id>",
scope="mcp:read mcp:write",
failIfExists=True,
)
client = c3.GenaiCore.Mcp.Client.User.register(
name="example-user-mcp",
url="https://mcp.example.com/server",
spec=spec,
)Step 2: Begin PKCE authorization
Call beginPkceAuthorization to create a code verifier, challenge, state, and authorization URL.
start = client.beginPkceAuthorization(
redirectUri="https://your-app.example.com/oauth/callback",
scope="mcp:read mcp:write",
)The returned GenaiCore.Mcp.Client.PkceAuthorizationStart carries the values the caller must store before redirecting:
authorizationUrl: open this URL in the user's browser.codeVerifier: store securely (for example, in session storage). The token exchange in step 3 needs it.state: store securely. After the redirect, verify that thestatereturned by the OAuth provider matches this value before proceeding.
Step 3: Complete PKCE authorization
After the user signs in and the OAuth provider redirects back with a code and state, verify the state matches the value from step 2. Then call completePkceAuthorization with an OAuth.Request.Token containing the stored code verifier, the returned code, and the redirect URI.
request = c3.OAuth.Request.Token(
code=callback_code,
codeVerifier=stored_code_verifier,
redirectUri="https://your-app.example.com/oauth/callback",
)
client.completePkceAuthorization(request)The client exchanges the code for an access token and writes the authentication headers to the user's USER-scoped config.
Use the browser-side helper path
GenaiCore.Mcp.OAuthUi.Helper wraps the same three steps in methods that return JSON, suitable for calling from a browser-based UI.
Step 1: Register the client
result = c3.GenaiCore.Mcp.OAuthUi.Helper.registerClient(
name="example-user-mcp",
mcpUrl="https://mcp.example.com/server",
redirectUri="https://your-app.example.com/oauth/callback",
clientId="",
scope="mcp:read mcp:write",
)The helper auto-discovers the OAuth authorization server endpoints from the MCP URL. The returned JSON includes mcpClientName, mcpUrl, redirectUri, and the clientId (after dynamic registration if you did not supply one).
Step 2: Begin authorization
start = c3.GenaiCore.Mcp.OAuthUi.Helper.beginForClient(
mcpClientName="example-user-mcp",
redirectUri="https://your-app.example.com/oauth/callback",
mcpUrl="",
scope="",
)The returned JSON includes authorizationUrl, codeVerifier, state, mcpClientName, and mcpUrl.
beginForClient also auto-registers the client on the fly if it does not exist and mcpUrl is supplied. This is convenient when one UI screen drives the entire first-time setup.
Step 3: Complete authorization
After the redirect, verify the state and call complete:
result = c3.GenaiCore.Mcp.OAuthUi.Helper.complete(
mcpClientName="example-user-mcp",
codeVerifier=stored_code_verifier,
code=callback_code,
redirectUri="https://your-app.example.com/oauth/callback",
)The returned JSON contains mcpClientName and status (the string "authenticated") after the token exchange succeeds.
Discover the authorization server
If you do not want to construct an OAuth.AuthorizationServer yourself, call discoverAuthorizationServer on the client. It tries well-known discovery URLs and the optional RFC 9728 WWW-Authenticate resource metadata URL until OAuth.AuthorizationServer's importFromDiscoveryUrl succeeds.
metadata = client.discoverAuthorizationServer(
resourceServerUrl="https://mcp.example.com/server",
name="example-auth-server",
failIfNotDiscovered=True,
)The returned JSON includes name, discoveryUrl, tokenEndpoint, authorizationEndpoint, and optional revocationEndpoint and registrationEndpoint fields.
Error handling
The complete method raises clear errors when something goes wrong:
ValueError:mcpClientNameis empty after trimming; the client's authorization server or required endpoints are not configured; orcode,codeVerifier, orredirectUriis empty.RuntimeError: the token exchange itself fails, or the provider's response cannot be applied (for example, missingaccessToken).
The PKCE error path was hardened in 8.11 so that downstream consumers see actionable failures rather than silent fallbacks to the wrong authorization server.
Where tokens live
The user's authentication headers are stored on GenaiCore.Mcp.Client.User.Config, which is configured minOverride="USER", maxOverride="USER" with the headers field annotated @config(secret=true). Each user has an isolated set of credentials that are not visible to other users or administrators. This applies whether the headers come from per-user header registration or from the OAuth token exchange.
Registration semantics
GenaiCore.Mcp.Client.RegisterSpec carries a failIfExists flag. Set it to true to make registration error out if a client with the same name already exists; otherwise the call upserts.