# How Continue's Authentication and OAuth Work: MCP Integration Explained

> Understand Continue's authentication and OAuth flow. Learn how MCP integrates with VS Code, JetBrains, and more for secure remote access. Get the details here.

- Repository: [Continue/continue](https://github.com/continuedev/continue)
- Tags: deep-dive
- Published: 2026-06-24

---

**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`](https://github.com/continuedev/continue/blob/main/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.

```typescript
// 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.

```typescript
// 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.

```typescript
// 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 `MCPConnectionOauthProvider` for the target server
- Guarantees redirect URL readiness via `ensureRedirectUrl()`
- Generates a UUID `state` and stores it in two maps: `authenticatingContexts` (holding IDE, server ID, and state) and `stateToServerUrl` (enabling state-to-URL resolution)
- Invokes the SDK's `auth` function 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.

```typescript
// 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:

```typescript
// 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.

```typescript
// 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`, and `saveCodeVerifier` persist data via `_updateOauthStorage`
- **Retrieval**: `clientInformation()` and `tokens()` fetch and validate stored values using Zod schemas from `@modelcontextprotocol/sdk/shared/auth`
- **Convenience access**: The `getOauthToken` function extracts the access token for immediate API usage

```typescript
// 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.

```typescript
// 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 `MCPConnectionOauthProvider` class in [`core/context/mcp/MCPOauth.ts`](https://github.com/continuedev/continue/blob/main/core/context/mcp/MCPOauth.ts) handles dynamic redirect URL detection, supporting both localhost and IDE-provided tunnel URLs.
- PKCE-style state management using `authenticatingContexts` and `stateToServerUrl` maps 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 `auth` helper, and immediately refreshes the MCP connection.
- All credentials persist in `GlobalContext` with Zod schema validation, exposing `getOauthToken` for retrieval and `removeMCPAuth` for 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.