kriasoft / kriasoft/oauth-callback
Add storage adapters for MCP SDK compatibility
- Dominant language
- TypeScript
- Stars
- 11
- Forks
- 4
- PR merge metrics
- No merged PRs in 30d
Description
## 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:
```typescript
// 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:
```typescript
// ❌ 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
1. **Create adapter utilities** that convert between oauth-callback and MCP SDK types
2. **Provide storage adapters** that wrap oauth-callback stores for MCP SDK compatibility
3. **Add bidirectional conversion** functions for tokens and client information
4. **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:
1. **Peer dependency vs built-in**: Should MCP SDK be a peer dependency or should adapters work without it installed?
2. **Type safety**: How do we maintain type safety when MCP SDK types might not be available?
3. **Alternative approach**: Should oauth-callback adopt MCP SDK's type format as the standard instead?
4. **Versioning**: How do we handle different versions of MCP SDK with potentially different types?
5. **Scope**: Should we also provide adapters for other popular OAuth libraries (Passport.js, node-oauth2-server)?
## Suggested implementation
### Option 1: Dedicated adapter module
```typescript
// 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;
set(key: string, tokens: MCPOAuthTokens): Promise;
delete(key: string): Promise;
clear(): Promise;
}
/**
* 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 {
const tokens = await store.get(key);
return tokens ? toMCPTokens(tokens) : null;
},
async set(key: string, mcpTokens: MCPOAuthTokens): Promise {
await store.set(key, fromMCPTokens(mcpTokens));
},
async delete(key: string): Promise {
await store.delete(key);
},
async clear(): Promise {
await store.clear();
}
};
}
/**
* Creates an oauth-callback compatible store from MCP store
*/
export function fromMCPStore(mcpStore: MCPTokenStore): TokenStore {
return {
async get(key: string): Promise {
const mcpTokens = await mcpStore.get(key);
return mcpTokens ? fromMCPTokens(mcpTokens) : null;
},
async set(key: string, tokens: Tokens): Promise {
await mcpStore.set(key, toMCPTokens(tokens));
},
async delete(key: string): Promise {
await mcpStore.delete(key);
},
async clear(): Promise {
await mcpStore.clear();
}
};
}
```
### Option 2: Enhanced browserAuth with MCP compatibility
```typescript
// 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 {
const tokens = await baseProvider.getTokens();
return tokens ? toMCPTokens(tokens) : null;
},
async setTokens(mcpTokens: MCPOAuthTokens): Promise {
await baseProvider.setTokens(fromMCPTokens(mcpTokens));
},
// ... other methods
};
}
```
### Option 3: Export compatibility layer
```typescript
// 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
```typescript
// 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
1. **Round-trip conversion**: Verify tokens survive conversion in both directions
2. **Edge cases**: Handle missing fields, expired tokens, null values
3. **Type safety**: Ensure TypeScript catches type mismatches
4. **Integration tests**: Test with actual MCP SDK if available
5. **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
- mcp-client-gen package that highlighted this issue
- MCP SDK: @modelcontextprotocol/sdk
- Issue #16 (missing type exports) - related type compatibility issue
Contributor guide
Research direction
Start by reading the existing token and client-information types, TokenStore implementation, package exports, and the proposed src/adapters/mcp.ts, src/auth/browser-auth.ts, and README locations. Resolve the adapter scope, MCP dependency and versioning approach before implementing. Done means documented bidirectional conversions and store compatibility with round-trip, edge-case, type-safety, and integration tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, authentication
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100