OAuth 2.1 Implementation Summary
Overview
NCP now supports OAuth 2.1 with PKCE authentication for remote MCP servers, following the MCP specification 2025-03-26. This enables secure, standards-based authentication with automatic token management.
Implementation Details
Core Components
1. MCPOAuthProvider (src/auth/mcp-oauth-provider.ts)
- Implements
OAuthClientProviderinterface from@modelcontextprotocol/sdk - Handles complete OAuth 2.1 flow with PKCE
- Manages token storage, refresh, and lifecycle
- Supports browser-based and headless authorization flows
Key Methods:
redirectUrl: Local callback URL for OAuth redirect (http://localhost:{port}/callback)clientMetadata: OAuth client metadata (name, redirect URI, scopes)state(): Generates/returns OAuth state parameter for CSRF protectionclientInformation(): Loads registered client info (ID, secret)saveClientInformation(): Stores dynamically registered clienttokens(): Retrieves stored OAuth tokenssaveTokens(): Persists access/refresh tokensredirectToAuthorization(): Opens browser or prints URL for authorizationsaveCodeVerifier()/codeVerifier(): PKCE code verifier managementinvalidateCredentials(): Clears stored credentials (all/client/tokens/verifier)
OAuth Flow:
- Protected Resource Discovery - Discovers MCP server's OAuth metadata (RFC 9728)
- Authorization Server Discovery - Finds token/authorization endpoints
- Dynamic Registration - Auto-registers client if no clientId provided (RFC 7591)
- PKCE Authorization - Generates code challenge, opens browser for consent
- Token Exchange - Exchanges authorization code for access token (with PKCE verifier)
- Token Refresh - Automatically refreshes tokens when expired
2. Transport Factory Integration (src/orchestrator/services/transport-factory.ts)
- Added
getOAuthProvider()method to create/cache OAuth providers - Modified
createStreamableHTTPTransport()to passauthProviderto SDK - Provider caching prevents duplicate authorization flows
Key Changes:
typescript
// Cache OAuth providers by server URL
private oauthProviders: Map<string, MCPOAuthProvider> = new Map();
getOAuthProvider(config: MCPConfig): MCPOAuthProvider {
const key = config.url || config.name;
let provider = this.oauthProviders.get(key);
if (!provider) {
provider = createMCPOAuthProvider({
serverUrl: config.url!,
clientName: 'NCP - MCP Aggregator',
scopes: config.auth?.oauth21?.scopes,
callbackPort: config.auth?.oauth21?.callbackPort,
clientId: config.auth?.oauth21?.clientId,
clientSecret: config.auth?.oauth21?.clientSecret,
});
this.oauthProviders.set(key, provider);
}
return provider;
}3. Connection Types (src/orchestrator/types/connection.ts)
- Added
OAuth21Authtype for OAuth configuration - Extended
MCPAuthunion to includeoauthtype
Type Definitions:
typescript
export type OAuth21Auth = {
type: 'oauth';
oauth21?: {
scopes?: string[];
callbackPort?: number;
clientId?: string;
clientSecret?: string;
};
};
export type MCPAuth = BearerAuth | BasicAuth | ApiKeyAuth | OAuth21Auth;Token Storage
Tokens are stored in ~/.ncp/auth/ with per-server isolation:
- File format:
{serverKey}.json - Server key: SHA256 hash of server URL
- Stored data:
clientInfo: Registered client ID/secrettokens: Access token, refresh token, expirycodeVerifier: PKCE verifier (temporary, during auth flow)
Security:
- Tokens stored locally (not in config.json)
- Each server has separate token storage
- Automatic cleanup of invalid credentials
Browser Authorization Flow
- NCP starts local callback server on configured port (default: 9876)
- Opens browser to authorization URL (or prints URL if headless)
- User grants permissions on OAuth server
- OAuth server redirects to
http://localhost:9876/callback?code=...&state=... - NCP receives callback, exchanges code for tokens (with PKCE)
- Callback server shuts down, returns success page
- Tokens saved to
~/.ncp/auth/for future use
Headless Fallback:
- If browser fails to open, prints authorization URL
- User manually visits URL, grants permissions
- Copies authorization code from redirect URL
- Pastes code into NCP prompt
- NCP exchanges code for tokens
Configuration
Basic OAuth 2.1 (Dynamic Registration)
json
{
"mcpServers": {
"my-oauth-mcp": {
"url": "https://mcp.example.com/api",
"auth": {
"type": "oauth",
"oauth21": {
"scopes": ["read", "write"]
}
}
}
}
}Pre-registered Client
json
{
"mcpServers": {
"my-oauth-mcp": {
"url": "https://mcp.example.com/api",
"auth": {
"type": "oauth",
"oauth21": {
"scopes": ["read", "write"],
"callbackPort": 8080,
"clientId": "my-pre-registered-client-id",
"clientSecret": "my-client-secret"
}
}
}
}
}Configuration Options
scopes: Array of requested scopes (e.g.,["read", "write"])callbackPort: Local port for OAuth callback (default: 9876)clientId: Pre-registered client ID (optional - uses dynamic registration if omitted)clientSecret: Pre-registered client secret (optional)
Testing
Unit Tests (tests/integration/oauth-integration.test.ts)
- ✅ OAuth provider creation with default/custom config
- ✅ Interface compliance (
OAuthClientProvider) - ✅ Transport factory provider caching
- ✅ Multiple servers get different providers
- ✅ Static auth types don't create OAuth providers
All 8 tests passing
Integration Testing
bash
npm run build
npm test -- tests/integration/oauth-integration.test.tsSDK Compatibility
Uses official @modelcontextprotocol/sdk OAuth infrastructure:
OAuthClientProviderinterface fromsdk/client/auth.jsStreamableHTTPClientTransportwithauthProvideroptionOAuthTokens,OAuthClientInformation,OAuthMetadatatypes- Built-in discovery, registration, and token exchange functions
No custom OAuth implementation - fully leverages SDK's battle-tested OAuth stack.
Security Features
- PKCE Required - OAuth 2.1 mandates PKCE (Proof Key for Code Exchange)
- State Parameter - CSRF protection via state validation
- Token Isolation - Per-server token storage prevents cross-contamination
- Secure Storage - Tokens stored in
~/.ncp/auth/(not in config) - Automatic Refresh - Expired tokens refreshed transparently
- Credential Invalidation - Can clear all/client/tokens/verifier on demand
Future Enhancements
Potential improvements for future releases:
- [ ] Token expiry tracking (store
issued_attimestamp with tokens) - [ ] Revocation endpoint support (RFC 7009)
- [ ] Device flow for headless environments (RFC 8628)
- [ ] Refresh token rotation
- [ ] Multiple callback ports (auto-find available port)
- [ ] OAuth server health checks
References
- MCP Specification 2025-03-26
- OAuth 2.1 Draft
- RFC 7636 - PKCE
- RFC 7591 - Dynamic Client Registration
- RFC 9728 - Protected Resource Metadata
- @modelcontextprotocol/sdk Documentation
Files Changed
New Files
src/auth/mcp-oauth-provider.ts- OAuth 2.1 provider implementationtests/integration/oauth-integration.test.ts- OAuth integration testsdocs/OAUTH-IMPLEMENTATION.md- This document
Modified Files
src/orchestrator/services/transport-factory.ts- Added OAuth provider integrationsrc/orchestrator/types/connection.ts- Added OAuth21Auth typeREADME.md- Added OAuth 2.1 documentationCHANGELOG.md- Documented OAuth 2.1 feature
Build & Test Results
✅ TypeScript compilation successful
✅ All 8 OAuth integration tests passing
✅ No breaking changes to existing functionality
✅ Transport factory properly routes OAuth configs
✅ Provider caching prevents duplicate auth flowsImplementation complete and tested. Ready for use with OAuth 2.1-enabled MCP servers.