How Continue's Authentication and OAuth Work: MCP Integration Explained
Continue.dev implements Model-Context-Protocol (MCP) OAuth to authenticate against remote MCP servers, wrapping the standard OAuth 2.0 authorization-code grant with PKCE-style state handling in the MCPConnectionOauthProvider class to support VS Code, JetBrains, and other IDE environments.
Continue.dev enables secure connections to remote Model-Context-Protocol (MCP) servers through a comprehensive OAuth 2.0 implementation. The authentication system, centered in core/context/mcp/MCPOauth.ts, coordinates redirect URL handling, state management, and token persistence across different IDE platforms. This guide examines the exact mechanisms powering Continue's authentication and OAuth flow, from initial redirect to token storage.
Redirect URL Handling
The MCPConnectionOauthProvider class dynamically determines the appropriate callback URL based on IDE capabilities. This ensures compatibility with both local development environments and cloud-based IDEs like VS Code for Web.
// core/context/mcp/MCPOauth.ts#L86-L99
private async _initializeRedirectUrl(): Promise<void> {
if (this.ide.getExternalUri) {
const localUri = `http://localhost:${PORT}`;
const externalUri = await this.ide.getExternalUri(localUri);
this._redirectUrl = externalUri; // IDE‑provided public URL
}
// otherwise keep default localhost URL
}
The constructor initializes a default http://localhost:3000 endpoint before asynchronously calling _initializeRedirectUrl(). When the IDE exposes getExternalUri, the system replaces the localhost address with a publicly reachable tunnel URL required for web-based authentication flows. The redirectUrl getter guarantees synchronous access to a valid URL even before async initialization completes.
Building OAuth Client Metadata
The provider constructs dynamic client metadata that incorporates PKCE-style state parameters to prevent CSRF attacks and support multiple simultaneous authentication flows.
// core/context/mcp/MCPOauth.ts#L19-L34
get clientMetadata() {
const state = authenticatingContexts.get(this.oauthServerUrl)?.state;
const redirectUri = state
? this.getRedirectUrlWithState(state)
: this.redirectUrl;
return {
redirect_uris: [redirectUri],
token_endpoint_auth_method: "none",
grant_types: ["authorization_code", "refresh_token"],
response_types: ["code"],
client_name: "Continue Dev, Inc",
client_uri: "https://continue.dev",
};
}
When a state value exists in the authenticatingContexts map, the system appends it to the redirect URI as a query parameter. This metadata object is passed to the MCP SDK's auth helper, which registers or retrieves a temporary OAuth client on the target MCP server.
Initiating the Authorization Flow
The performAuth function orchestrates the complete login sequence, generating unique flow identifiers and mapping them to server URLs for later callback validation.
// core/context/mcp/MCPOauth.ts#L43-L56
export async function performAuth(serverId: string, url: string, ide: IDE) {
const authProvider = new MCPConnectionOauthProvider(url, ide);
await authProvider.ensureRedirectUrl();
const state = uuidv4(); // unique flow identifier
authenticatingContexts.set(url, { serverId, ide, state });
stateToServerUrl.set(state, url); // map state → server
return await auth(authProvider, { serverUrl: url });
}
This function performs four critical operations:
- Instantiates
MCPConnectionOauthProviderfor the target server - Guarantees redirect URL readiness via
ensureRedirectUrl() - Generates a UUID
stateand stores it in two maps:authenticatingContexts(holding IDE, server ID, and state) andstateToServerUrl(enabling state-to-URL resolution) - Invokes the SDK's
authfunction to open the provider's authorization endpoint in the user's browser
Local Callback Server and Token Exchange
When using localhost redirects, the system spawns a temporary HTTP server to capture the OAuth callback and exchange the authorization code for tokens.
// core/context/mcp/MCPOauth.ts#L33-L65
const createServerForOAuth = () =>
http.createServer((req, res) => {
const parsedUrl = url.parse(req.url!, true);
const code = parsedUrl.query["code"] as string;
const state = parsedUrl.query["state"] as string | undefined;
void handleMCPOauthCode(code, state);
// respond with a tiny “Authentication Complete” page
});
The server listens on port 3000, extracts the code and optional state from query parameters, and delegates processing to handleMCPOauthCode. This handler validates the state against the stateToServerUrl map, falls back to the sole active context if state is missing (legacy browser support), and completes the token exchange:
// core/context/mcp/MCPOauth.ts#L20-L27 (inside handleMCPOauthCode)
const authProvider = new MCPConnectionOauthProvider(serverUrl, ide);
await authProvider.ensureRedirectUrl();
const authStatus = await auth(authProvider, {
serverUrl,
authorizationCode,
});
if (authStatus === "AUTHORIZED") {
const { MCPManagerSingleton } = await import("./MCPManagerSingleton");
await MCPManagerSingleton.getInstance().refreshConnection(serverId);
}
The auth helper from @modelcontextprotocol/sdk/client/auth performs the actual code-to-token exchange. Upon successful authorization, the system immediately refreshes the MCP connection through MCPManagerSingleton to activate the new credentials.
Token Storage and Retrieval
Continue persists OAuth credentials using the GlobalContext key-value store, enabling token reuse across IDE sessions without repeated logins.
// core/context/mcp/MCPOauth.ts#L36‑L55 (private helpers)
private _getOauthStorage<K extends MCPOauthStorageKey>(key: K) { … }
private _updateOauthStorage<K extends MCPOauthStorageKey>(key: K, value: MCPOauthStorage[K]) { … }
private _clearOauthStorage() { … }
The provider implements three storage operations:
- Saving:
saveClientInformation,saveTokens, andsaveCodeVerifierpersist data via_updateOauthStorage - Retrieval:
clientInformation()andtokens()fetch and validate stored values using Zod schemas from@modelcontextprotocol/sdk/shared/auth - Convenience access: The
getOauthTokenfunction extracts the access token for immediate API usage
// core/context/mcp/MCPOauth.ts#L33-L37
export async function getOauthToken(mcpServerUrl: string, ide: IDE) {
const authProvider = new MCPConnectionOauthProvider(mcpServerUrl, ide);
const tokens = await authProvider.tokens();
return tokens?.access_token;
}
Revoking Authentication
To force re-authentication or remove credentials, the system provides removeMCPAuth, which clears all persisted OAuth data for a specific server.
// core/context/mcp/MCPOauth.ts#L46-L49
export function removeMCPAuth(url: string, ide: IDE) {
const authProvider = new MCPConnectionOauthProvider(url, ide);
authProvider.clear(); // clears the persisted storage for that server
}
This operation wipes client information, access tokens, refresh tokens, and the code verifier from GlobalContext, ensuring the next request triggers a fresh OAuth flow.
Summary
- Continue's authentication and OAuth implementation leverages the Model-Context-Protocol SDK to provide standardized OAuth 2.0 flows for MCP servers.
- The
MCPConnectionOauthProviderclass incore/context/mcp/MCPOauth.tshandles dynamic redirect URL detection, supporting both localhost and IDE-provided tunnel URLs. - PKCE-style state management using
authenticatingContextsandstateToServerUrlmaps enables secure, concurrent authentication flows to multiple servers. - A temporary HTTP server on port 3000 captures authorization codes, exchanges them for tokens via the SDK's
authhelper, and immediately refreshes the MCP connection. - All credentials persist in
GlobalContextwith Zod schema validation, exposinggetOauthTokenfor retrieval andremoveMCPAuthfor credential revocation.
Frequently Asked Questions
How does Continue handle OAuth redirects in cloud-based IDEs like VS Code for Web?
Continue detects IDE capabilities through the getExternalUri method. When available (as in VS Code for Web), the system replaces the default localhost:3000 redirect with a publicly reachable tunnel URL provided by the IDE. This ensures OAuth callbacks can reach the authentication handler even when the IDE runs in a browser environment.
What happens if the OAuth state parameter is missing during the callback?
The handleMCPOauthCode function includes fallback logic for legacy browsers or providers that omit the state parameter. If state is undefined, the system checks authenticatingContexts for a single active flow. When exactly one authentication context exists, it proceeds with that context; otherwise, it logs an error and aborts the exchange.
Where are OAuth tokens stored in Continue, and are they secure?
Tokens persist in the GlobalContext key-value store, which uses the IDE's native storage mechanisms (encrypted where supported by the host application). The implementation uses Zod schemas from @modelcontextprotocol/sdk/shared/auth to validate token structure on retrieval, ensuring data integrity across sessions.
Can users revoke MCP server authentication without restarting Continue?
Yes. Calling removeMCPAuth(url, ide) immediately clears all cached OAuth data—including client information, access tokens, and refresh tokens—for the specified server URL. This forces a fresh authentication flow on the next request without requiring an IDE restart.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →