feat(mcp): OAuth authorization for HTTP MCP servers - #199
Merged
Merged
Conversation
Migrate the test ServerHandler::call_tool to the new CallToolResponse return type (wrap CallToolResult via From). No behavioural change to the client; this only prepares for enabling rmcp's OAuth auth feature.
Enable rmcp's 'auth' feature and introduce the reusable pieces the MCP authorization flow needs on top of the transport: - FileCredentialStore: a rmcp CredentialStore that persists one server's OAuth tokens to a JSON file (atomic write, 0600 on unix) so an interactive login survives restarts. - OAuthAuthorizer trait + AuthorizationOutcome: the embedder-provided seam that presents the authorization URL to the user and returns the redirect callback params. Unit-tested the store (round-trip, dir creation, idempotent clear, permission bits).
connect_http is now reactive: - with stored credentials, connect authorized non-interactively (rmcp refreshes the token transparently); - otherwise try unauthenticated and, on the server's 401 challenge, surface a typed AuthorizationRequired (server name + WWW-Authenticate) so embedders can offer an Authenticate action rather than a bare retry. authenticate_http_server drives the interactive login through an OAuthAuthorizer (discovery -> client selection -> browser consent -> code exchange) and persists tokens into the given CredentialStore, so later connects reuse them silently. Adds an integration test (401 -> AuthorizationRequired) and a guard that authenticating a stdio server fails without network access.
- ConfigToolRegistry now passes a persistent per-server credential store (<config_dir>/mcp-oauth/<server>.json) when connecting HTTP servers, so a token obtained interactively is reused silently on later agent runs. - tools::mcp gains authenticate_mcp_server / forget_mcp_oauth_tokens / has_mcp_oauth_tokens and the OAuth store path helpers. - New CLI: 'code-assistant mcp-login <server>' runs the browser OAuth flow via a loopback callback authorizer (RFC 8252) and 'mcp-logout <server>' forgets the tokens. Mirrors Cline's Authenticate button.
- discover_tools_with_credentials reuses stored tokens, so a configuration UI can list the tools of an authenticated HTTP server; core exposes discover_server_tools which attaches the per-server credential store. - Move the loopback OAuth authorizer into code_assistant_core (tools::mcp_auth::LoopbackAuthorizer + login_mcp_server) so both the CLI and the settings UI drive the same flow; the mcp-login command is now a thin wrapper. - Re-export AuthorizationRequired through core for the UI to detect.
The settings section now discovers tools with the stored token (discover_server_tools), and when an HTTP server needs OAuth it shows a NeedsAuth state with an 'Authenticate' button instead of a generic failure. Clicking it runs the loopback OAuth login on a background runtime (browser + redirect), then re-discovers so the server's tools appear once authenticated. Adds an Authenticating progress state.
The per-run tool registry is cached by a fingerprint of the config files (tools.json, mcp-servers.json, .mcp.json). Completing an interactive login writes only a token file under mcp-oauth/, so the fingerprint was unchanged and the cached pre-auth (tool-less) registry kept being reused until restart — the agent never saw a freshly authenticated server. Include a fingerprint of the persisted OAuth token files (names, sizes, mtimes) so a login invalidates the cache and the next run rebuilds, reconnecting the now-authorized server so its tools appear. Tested.
Replace the connect/connect_with_credentials and discover_tools/discover_tools_with_credentials pairs with single functions that take the credentials argument. The parallel 'call with None' wrappers hid the decision: they left the non-pooled register_mcp_tools silently on the credential-less path and turned the old discover_tools into dead code once discover_server_tools existed. Now every call site states its intent — the pooled ConnectionProvider and tool discovery thread the per-server store; register_mcp_tools and tests pass None explicitly (stdio / static-header / no-auth), documented as such. No behaviour change; the compiler verified every caller.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds OAuth authorization support for HTTP (streamable) MCP servers — the "Authenticate" flow that servers behind SSO require. Previously we only supported static
Authorizationheaders, so such servers could not be connected at all.Implements MCP's authorization spec (OAuth 2.1 authorization-code + PKCE, discovery via RFC 9728 → RFC 8414, dynamic client registration) on top of
rmcp's auth machinery, mirroring what Cline's "Authenticate" button does.What's included
rmcp2.1 → 3.3 and itsauthfeature enabled. Only break was the testServerHandler::call_toolreturn type (CallToolResult→CallToolResponse).401challenge either reuse a stored token (non-interactive, transparent refresh) or surface a typedAuthorizationRequiredso callers can offer an Authenticate action.authenticate_http_server) driven by anOAuthAuthorizerseam; tokens persist via aFileCredentialStore(<config_dir>/mcp-oauth/<server>.json, atomic write,0600).ConfigToolRegistrypasses a per-server credential store, so an authenticated server is reused silently by agent runs and by tool discovery.code-assistant mcp-login <server>(loopback callback per RFC 8252 + browser) andmcp-logout <server>.Testing
cargo check --workspace --tests,cargo fmt --check,cargo clippy(touched crates) green.401 → AuthorizationRequireddetection, stdio-rejects-auth guard, loopback bind, registry rebuild on token change.mcp_clientandcode_assistant_coresuites pass.Notes / possible follow-ups
AuthorizationRequestoptions if a server requires them.