C3 AI Documentation Home

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.

Python
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:

Python
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:

Python
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 scope string (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).

Python
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.

Python
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 the state returned 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.

Python
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

Python
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

Python
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:

Python
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.

Python
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: mcpClientName is empty after trimming; the client's authorization server or required endpoints are not configured; or code, codeVerifier, or redirectUri is empty.
  • RuntimeError: the token exchange itself fails, or the provider's response cannot be applied (for example, missing accessToken).

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.

See also

Was this page helpful?