# How the MCP OAuth Flow Works for Authenticating External Servers in Kimi Code

> Understand the MCP OAuth flow in Kimi Code. Learn how it securely authenticates external servers using temporary localhost servers and persistent disk storage for efficient token management.

- Repository: [Moonshot AI/kimi-code](https://github.com/MoonshotAI/kimi-code)
- Tags: deep-dive
- Published: 2026-08-16

---

**The MCP OAuth flow in Kimi Code is a per-process orchestration system that uses temporary localhost callback servers and persistent disk storage to acquire and reuse access tokens for external MCP servers without requiring permanent public endpoints.**

The **Model-Context-Protocol (MCP)** OAuth flow enables Kimi Code agents to securely authenticate with external HTTP-based MCP servers. Implemented in the `@moonshot-ai/agent-core` package, this system handles the complete Device-Code authorization lifecycle—from initiating authentication to persisting tokens for future sessions. This guide explains the architecture, implementation details, and practical usage based on the Kimi Code source code.

## MCP OAuth Flow Architecture Overview

The authentication system centers on `McpOAuthService`, a singleton-style orchestrator that manages one `McpOAuthClientProvider` per server. This design ensures serialized OAuth interactions and prevents race conditions when multiple tools request authentication simultaneously.

The flow consists of six coordinated stages: provider retrieval, authorization initiation, user interaction, flow completion, token reuse, and cleanup. Each stage is implemented with specific error handling and state validation to protect against CSRF attacks and mismatched redirect URIs.

## Stage 1: Provider Retrieval and Caching

The `McpOAuthService.getProvider(serverName, serverUrl)` method returns a cached provider or creates a new one. In [`packages/agent-core/src/mcp/oauth/service.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/packages/agent-core/src/mcp/oauth/service.ts) lines 79-92, this method:

- Generates a unique key from `serverName` and `serverUrl`
- Retrieves an existing `McpOAuthClientProvider` from the internal `Map` if present
- Creates and caches a new provider if none exists
- Configures the provider to persist credentials to `<KIMI_HOME>/credentials/mcp/`

```typescript
// From service.ts:79-92
const key = `${serverName}::${serverUrl}`;
let provider = this.providers.get(key);
if (!provider) {
  provider = new McpOAuthClientProvider({
    serverName,
    serverUrl,
    storagePath: path.join(this.kimiHome, 'credentials', 'mcp'),
  });
  this.providers.set(key, provider);
}
return provider;

```

The provider handles all server-specific operations: token storage, Dynamic Client Registration (DCR), and URL generation for the OAuth flow.

## Stage 2: Beginning Authorization with Temporary Callback Server

The `McpOAuthService.beginAuthorization()` method in [`service.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/service.ts) lines 106-150 drives the SDK's `auth()` orchestrator. This is the critical setup phase:

1. **Short-circuit check**: If valid tokens already exist, throws `AlreadyAuthorizedError`
2. **Callback server startup**: Calls `startCallbackServer()` to create a temporary local HTTP listener
3. **Provider configuration**: Sets the provider's redirect URL to the callback server address
4. **Stale registration cleanup**: Invalidates any existing DCR registration to prevent redirect URI mismatches
5. **SDK invocation**: Calls `auth()` with `authorize: true` to obtain the authorization URL

The method returns an object containing `authorizationUrl`, `complete` callback, and `cancel` callback:

```typescript
// Conceptual structure based on service.ts:106-150
async beginAuthorization(serverName, serverUrl) {
  const provider = this.getProvider(serverName, serverUrl);
  
  // Check existing tokens
  if (await provider.hasValidTokens()) {
    throw new AlreadyAuthorizedError(serverName);
  }

  // Start temporary callback server
  const callbackServer = await startCallbackServer(0); // Random available port
  
  // Configure provider and clean stale registrations
  provider.setRedirectUrl(callbackServer.getUrl());
  await provider.invalidateRegistration();

  // Drive SDK auth to get authorization URL
  const result = await auth(provider, { authorize: true });
  // result.status === 'REDIRECT' → extract authorizationUrl
  
  return {
    authorizationUrl: result.authorizationUrl,
    complete: () => this.completeAuthorization(callbackServer, provider),
    cancel: () => this.cancelAuthorization(callbackServer, provider),
  };
}

```

The temporary callback server eliminates the need for a permanent public endpoint, making this suitable for local development and desktop applications.

## Stage 3: User Authentication Flow

Once `beginAuthorization()` returns, the caller—typically the synthetic tool `mcp__<server>__authenticate`—presents the `authorizationUrl` to the user. Implementation options include:

- Opening the system default browser automatically
- Displaying the URL in the IDE interface for manual navigation
- Rendering the URL in a terminal or notification

The user authenticates with the external OAuth provider (e.g., GitHub, Google, custom provider), which then redirects to the localhost callback server with `code` and `state` parameters.

## Stage 4: Completing the Flow and Token Storage

The `complete()` callback returned by `beginAuthorization()` handles the final stage. In [`service.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/service.ts) lines 169-199:

- Waits for the callback server to receive the redirect via `callbackServer.waitForCode()`
- Validates the `state` parameter against the expected value to prevent CSRF attacks
- Calls `auth()` again with the received authorization code
- On success, stores tokens to disk via the provider
- Tears down the callback server
- Notifies registered `McpOAuthCredentialsCoordinator` instances of credential changes

```typescript
// From service.ts:169-199
async complete() {
  const callbackResult = await callbackServer.waitForCode();
  
  // CSRF protection
  if (callbackResult.state !== expectedState) {
    throw new Error('CSRF state mismatch in OAuth callback');
  }

  // Exchange code for tokens
  const authResult = await auth(provider, {
    authorize: false,
    authorizationCode: callbackResult.code,
  });

  // Persist and notify
  await provider.saveTokens(authResult.tokens);
  this.coordinators.forEach(c => c.onCredentialsChanged(serverName, serverUrl));
  
  callbackServer.close();
  return authResult;
}

```

Token storage uses a simple JSON-file store in [`src/mcp/oauth/store.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/mcp/oauth/store.ts), with credentials organized by server identity.

## Stage 5: Token Reuse for Subsequent Connections

After initial authorization, `McpOAuthService.hasTokens(serverName, serverUrl)` (lines 95-98) checks for stored credentials. When tokens exist:

- The OAuth flow is bypassed entirely
- Stored tokens are attached directly to the HTTP transport
- Connection latency is reduced by eliminating the authorization round-trip

```typescript
// From service.ts:95-98
async hasTokens(serverName: string, serverUrl: string): Promise<boolean> {
  const provider = this.getProvider(serverName, serverUrl);
  return provider.hasValidTokens();
}

```

This design optimizes for the common case where users authenticate once and use the server repeatedly across sessions.

## Stage 6: Cancellation and Cleanup

The `cancel()` callback closes the callback server and resets provider state. For explicit token clearing, `McpOAuthService.invalidate()` accepts a `scope` parameter controlling deletion granularity:

- `'all'` — Removes all credentials including DCR registration
- `'tokens'` — Removes only access and refresh tokens
- `'discovery'` — Removes only server metadata discovery data

```typescript
// From service.ts:120-128
async invalidate(serverName, serverUrl, scope) {
  const provider = this.getProvider(serverName, serverUrl);
  await provider.invalidate(scope);
  this.coordinators.forEach(c => c.onCredentialsChanged(serverName, serverUrl));
}

```

## Implementation Example

Here's a complete working example using the `McpOAuthService` API:

```typescript
import { McpOAuthService } from '@moonshot-ai/agent-core/src/mcp/oauth';

// Initialize with default KIMI home directory
const oauthService = new McpOAuthService();

async function authenticateMcpServer() {
  try {
    const { authorizationUrl, complete, cancel } = await oauthService.beginAuthorization(
      'github-mcp',                    // Human-readable server name
      'https://api.github.com/mcp',   // MCP server base URL
    );

    console.log('Please authenticate:', authorizationUrl.toString());
    
    // Optional: open browser automatically
    // await open(authorizationUrl.toString());

    // Wait for user to complete flow (5 minute timeout)
    await complete({ timeoutMs: 5 * 60_000 });
    
    console.log('Authentication successful—tokens stored.');
  } catch (error) {
    if (error.name === 'AlreadyAuthorizedError') {
      console.log('Server already has valid credentials.');
    } else {
      console.error('Authentication failed:', error.message);
      await oauthService.invalidate('github-mcp', 'https://api.github.com/mcp', 'tokens');
    }
  }
}

```

## Security Guarantees and Error Handling

The Kimi Code MCP OAuth implementation provides several security properties:

- **CSRF protection**: State parameter validation in the callback handler
- **Redirect URI integrity**: Stale registration invalidation prevents URI mismatch errors
- **No token exposure**: Tokens never transit through Kimi Code servers; all exchanges are direct between client and MCP server
- **Process isolation**: One provider per server prevents cross-request token contamination

Errors are wrapped with contextual messages at each stage, making debugging straightforward. Common failure modes include network timeouts, invalid client credentials, and user cancellation.

## Key Source Files

| Package | File Path | Purpose |
|---------|-----------|---------|
| `agent-core` | [`src/mcp/oauth/service.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/mcp/oauth/service.ts) | Core orchestrator: provider management, flow coordination, callbacks |
| `agent-core` | [`src/mcp/oauth/provider.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/mcp/oauth/provider.ts) | `McpOAuthClientProvider`: token storage, DCR, URL generation |
| `agent-core` | [`src/mcp/oauth/callback-server.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/mcp/oauth/callback-server.ts) | Temporary localhost HTTP server for OAuth redirects |
| `agent-core` | [`src/mcp/oauth/store.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/mcp/oauth/store.ts) | JSON-file persistence for MCP credentials |
| `protocol` | [`src/rest/oauth.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/rest/oauth.ts) | REST API definitions for OAuth flow endpoints |
| `node-sdk` | [`src/v2/global-mcp.ts`](https://github.com/MoonshotAI/kimi-code/blob/main/src/v2/global-mcp.ts) | SDK exposure and engine integration |

## Summary

- **Single provider per server**: `McpOAuthService` caches one `McpOAuthClientProvider` per `serverName`/`serverUrl` pair, ensuring serialized OAuth interactions
- **Temporary callback architecture**: The `startCallbackServer()` approach eliminates the need for permanent public endpoints, enabling desktop-first authentication
- **Automatic token reuse**: `hasTokens()` checks allow subsequent connections to bypass OAuth entirely, attaching stored credentials directly to HTTP transports
- **Granular invalidation**: The `invalidate()` method with `scope` parameter supports fine-grained credential cleanup
- **CSRF-protected completion**: The `complete()` callback validates state parameters before exchanging authorization codes

## Frequently Asked Questions

### What happens if I call `beginAuthorization` when tokens already exist?

The method throws `AlreadyAuthorizedError` immediately after checking `provider.hasValidTokens()`. This prevents unnecessary re-authentication and potential user confusion. To force re-authentication, call `invalidate(serverName, serverUrl, 'tokens')` first.

### How long do tokens persist across Kimi Code sessions?

Tokens are stored on disk at `<KIMI_HOME>/credentials/mcp/` and persist indefinitely until explicitly invalidated or revoked by the external OAuth provider. The `McpOAuthClientProvider` handles token refresh automatically when access tokens expire.

### Can multiple MCP servers authenticate simultaneously?

Yes—each server has its own isolated `McpOAuthClientProvider` instance. However, within a single server, the per-process provider cache ensures that only one OAuth flow runs at a time, preventing race conditions in the stateful Device-Code flow.

### What port does the temporary callback server use?

The callback server binds to port `0`, which allows the operating system to assign an available ephemeral port. The actual URL (typically `http://localhost:<random-port>/callback`) is retrieved via `callbackServer.getUrl()` and registered as the OAuth redirect URI.