Description
There's a type mismatch between oauth-callback's storage format and the MCP SDK's storage format. The mcp-client-gen package uses MCP SDK's types (OAuthTokens, OAuthClientInformationFull) while oauth-callback uses different types (Tokens, ClientInfo). This forces developers to write adapter layers to bridge the two systems, adding unnecessary complexity and potential for errors.
Current Problem
When integrating oauth-callback with MCP SDK, developers encounter type incompatibilities:
// MCP SDK types (from @modelcontextprotocol/sdk)
interface OAuthTokens {
accessToken: string;
refreshToken?: string;
expiresIn?: number;
tokenType?: string;
scope?: string;
}
interface OAuthClientInformationFull {
clientId: string;
clientSecret?: string;
authorizationEndpoint: string;
tokenEndpoint: string;
scopes?: string[];
}
// oauth-callback types
interface Tokens {
access_token: string;
refresh_token?: string;
expires_at?: string; // Note: different from MCP's expiresIn
token_type?: string;
scope?: string;
}
interface ClientInfo {
client_id: string; // Note: snake_case vs camelCase
client_secret?: string;
// Different structure...
}
This mismatch forces developers to write error-prone adapter code:
// ❌ Current situation - manual adapter required
function adaptTokens(mcpTokens: OAuthTokens): Tokens {
return {
access_token: mcpTokens.accessToken,
refresh_token: mcpTokens.refreshToken,
expires_at: mcpTokens.expiresIn
? new Date(Date.now() + mcpTokens.expiresIn * 1000).toISOString()
: undefined,
token_type: mcpTokens.tokenType,
scope: mcpTokens.scope
};
}
// Every integration needs this boilerplate!
What needs to be done
- Create adapter utilities that convert between oauth-callback and MCP SDK types
- Provide storage adapters that wrap oauth-callback stores for MCP SDK compatibility
- Add bidirectional conversion functions for tokens and client information
- Document integration patterns with MCP SDK
Why this matters
This is a high-priority issue because:
- MCP ecosystem integration: MCP (Model Context Protocol) is becoming a standard for AI tool integration
- Developer friction: Every MCP integration requires writing the same adapter code
- Error-prone: Manual conversion between formats leads to bugs
- Maintenance burden: Changes in either library break integrations
- Growing adoption: More developers are building MCP servers and need OAuth
Without built-in adapters:
- Developers waste time writing boilerplate
- Integration errors from incorrect type mapping
- Inconsistent implementations across projects
- Barrier to MCP SDK adoption
Implementation considerations
⚠️ Note: This feature requires critical thinking during implementation. Consider:
-
Peer dependency vs built-in: Should MCP SDK be a peer dependency or should adapters work without it installed?
-
Type safety: How do we maintain type safety when MCP SDK types might not be available?
-
Alternative approach: Should oauth-callback adopt MCP SDK's type format as the standard instead?
-
Versioning: How do we handle different versions of MCP SDK with potentially different types?
-
Scope: Should we also provide adapters for other popular OAuth libraries (Passport.js, node-oauth2-server)?
Suggested implementation
Option 1: Dedicated adapter module
// src/adapters/mcp.ts
import type { Tokens, TokenStore, ClientInfo } from '../types';
// Re-declare MCP types to avoid hard dependency
interface MCPOAuthTokens {
accessToken: string;
refreshToken?: string;
expiresIn?: number;
tokenType?: string;
scope?: string;
}
interface MCPOAuthClientInfo {
clientId: string;
clientSecret?: string;
authorizationEndpoint: string;
tokenEndpoint: string;
scopes?: string[];
}
interface MCPTokenStore {
get(key: string): Promise<MCPOAuthTokens | null>;
set(key: string, tokens: MCPOAuthTokens): Promise<void>;
delete(key: string): Promise<void>;
clear(): Promise<void>;
}
/**
* Converts MCP SDK tokens to oauth-callback format
* @example
* ```typescript
* import { fromMCPTokens } from 'oauth-callback/adapters/mcp';
*
* const mcpTokens = await mcpStore.get('key');
* const tokens = fromMCPTokens(mcpTokens);
* ```
*/
export function fromMCPTokens(mcp: MCPOAuthTokens): Tokens {
return {
access_token: mcp.accessToken,
refresh_token: mcp.refreshToken,
expires_at: mcp.expiresIn
? new Date(Date.now() + mcp.expiresIn * 1000).toISOString()
: undefined,
token_type: mcp.tokenType || 'Bearer',
scope: mcp.scope
};
}
/**
* Converts oauth-callback tokens to MCP SDK format
*/
export function toMCPTokens(tokens: Tokens): MCPOAuthTokens {
const expiresIn = tokens.expires_at
? Math.floor((new Date(tokens.expires_at).getTime() - Date.now()) / 1000)
: undefined;
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: expiresIn && expiresIn > 0 ? expiresIn : undefined,
tokenType: tokens.token_type,
scope: tokens.scope
};
}
/**
* Converts MCP client info to oauth-callback format
*/
export function fromMCPClientInfo(mcp: MCPOAuthClientInfo): ClientInfo {
return {
client_id: mcp.clientId,
client_secret: mcp.clientSecret,
// Map other fields as needed
};
}
/**
* Converts oauth-callback client info to MCP format
*/
export function toMCPClientInfo(
info: ClientInfo,
endpoints: { authorizationEndpoint: string; tokenEndpoint: string }
): MCPOAuthClientInfo {
return {
clientId: info.client_id,
clientSecret: info.client_secret,
authorizationEndpoint: endpoints.authorizationEndpoint,
tokenEndpoint: endpoints.tokenEndpoint,
scopes: info.scope?.split(' ')
};
}
/**
* Wraps an oauth-callback TokenStore to be compatible with MCP SDK
* @example
* ```typescript
* import { fileStore } from 'oauth-callback';
* import { createMCPStore } from 'oauth-callback/adapters/mcp';
*
* const store = fileStore();
* const mcpStore = createMCPStore(store);
*
* // Now use mcpStore with MCP SDK
* const transport = new StreamableHTTPClientTransport(url, {
* authProvider: {
* getTokens: () => mcpStore.get('default'),
* setTokens: (tokens) => mcpStore.set('default', tokens)
* }
* });
* ```
*/
export function createMCPStore(store: TokenStore): MCPTokenStore {
return {
async get(key: string): Promise<MCPOAuthTokens | null> {
const tokens = await store.get(key);
return tokens ? toMCPTokens(tokens) : null;
},
async set(key: string, mcpTokens: MCPOAuthTokens): Promise<void> {
await store.set(key, fromMCPTokens(mcpTokens));
},
async delete(key: string): Promise<void> {
await store.delete(key);
},
async clear(): Promise<void> {
await store.clear();
}
};
}
/**
* Creates an oauth-callback compatible store from MCP store
*/
export function fromMCPStore(mcpStore: MCPTokenStore): TokenStore {
return {
async get(key: string): Promise<Tokens | null> {
const mcpTokens = await mcpStore.get(key);
return mcpTokens ? fromMCPTokens(mcpTokens) : null;
},
async set(key: string, tokens: Tokens): Promise<void> {
await mcpStore.set(key, toMCPTokens(tokens));
},
async delete(key: string): Promise<void> {
await mcpStore.delete(key);
},
async clear(): Promise<void> {
await mcpStore.clear();
}
};
}
Option 2: Enhanced browserAuth with MCP compatibility
// src/auth/browser-auth.ts
export interface BrowserAuthOptions {
// ... existing options
/**
* Enable MCP SDK compatibility mode
* Automatically converts between token formats
*/
mcpCompatibility?: boolean;
}
export function browserAuth(options: BrowserAuthOptions = {}) {
if (options.mcpCompatibility) {
// Return MCP-compatible provider
return createMCPAuthProvider(options);
}
// Standard implementation
return createStandardAuthProvider(options);
}
function createMCPAuthProvider(options: BrowserAuthOptions) {
const baseProvider = createStandardAuthProvider(options);
// Wrap with format conversion
return {
async getTokens(): Promise<MCPOAuthTokens | null> {
const tokens = await baseProvider.getTokens();
return tokens ? toMCPTokens(tokens) : null;
},
async setTokens(mcpTokens: MCPOAuthTokens): Promise<void> {
await baseProvider.setTokens(fromMCPTokens(mcpTokens));
},
// ... other methods
};
}
Option 3: Export compatibility layer
// src/index.ts
export * as MCP from './adapters/mcp';
// Usage:
import { MCP } from 'oauth-callback';
const mcpStore = MCP.createMCPStore(fileStore());
const tokens = MCP.fromMCPTokens(mcpTokens);
Usage examples
// Example 1: Using oauth-callback with MCP SDK
import { fileStore } from 'oauth-callback';
import { createMCPStore } from 'oauth-callback/adapters/mcp';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
const store = createMCPStore(fileStore());
const authProvider = {
getTokens: () => store.get('mcp-server'),
setTokens: (tokens) => store.set('mcp-server', tokens)
};
// Example 2: Converting existing MCP tokens
import { fromMCPTokens, toMCPTokens } from 'oauth-callback/adapters/mcp';
const mcpTokens = await mcpClient.getTokens();
const oauthTokens = fromMCPTokens(mcpTokens);
// Use with oauth-callback
await myStore.set('key', oauthTokens);
// Example 3: Bidirectional compatibility
const adapter = createBidirectionalAdapter(oauthStore, mcpStore);
await adapter.sync(); // Sync tokens between systems
Testing requirements
- Round-trip conversion: Verify tokens survive conversion in both directions
- Edge cases: Handle missing fields, expired tokens, null values
- Type safety: Ensure TypeScript catches type mismatches
- Integration tests: Test with actual MCP SDK if available
- Performance: Verify adapters don't add significant overhead
Documentation needs
- Add "MCP Integration" section to README
- Provide complete examples for common MCP scenarios
- Document field mapping between formats
- Migration guide for existing MCP integrations
Skills required
- TypeScript (type conversion and adapters)
- OAuth 2.0 token formats
- Adapter pattern implementation
- API design (maintaining compatibility)
- MCP SDK knowledge (helpful but not required)
Difficulty
Easy - This is primarily about creating type conversions and adapter wrappers. Great for someone who wants to improve integration capabilities!
Priority
HIGH - This directly impacts anyone trying to use oauth-callback with MCP SDK, which is increasingly common for AI tool development.
Related
Description
There's a type mismatch between oauth-callback's storage format and the MCP SDK's storage format. The
mcp-client-genpackage uses MCP SDK's types (OAuthTokens,OAuthClientInformationFull) while oauth-callback uses different types (Tokens,ClientInfo). This forces developers to write adapter layers to bridge the two systems, adding unnecessary complexity and potential for errors.Current Problem
When integrating oauth-callback with MCP SDK, developers encounter type incompatibilities:
This mismatch forces developers to write error-prone adapter code:
What needs to be done
Why this matters
This is a high-priority issue because:
Without built-in adapters:
Implementation considerations
Peer dependency vs built-in: Should MCP SDK be a peer dependency or should adapters work without it installed?
Type safety: How do we maintain type safety when MCP SDK types might not be available?
Alternative approach: Should oauth-callback adopt MCP SDK's type format as the standard instead?
Versioning: How do we handle different versions of MCP SDK with potentially different types?
Scope: Should we also provide adapters for other popular OAuth libraries (Passport.js, node-oauth2-server)?
Suggested implementation
Option 1: Dedicated adapter module
Option 2: Enhanced browserAuth with MCP compatibility
Option 3: Export compatibility layer
Usage examples
Testing requirements
Documentation needs
Skills required
Difficulty
Easy - This is primarily about creating type conversions and adapter wrappers. Great for someone who wants to improve integration capabilities!
Priority
HIGH - This directly impacts anyone trying to use oauth-callback with MCP SDK, which is increasingly common for AI tool development.
Related