mcp-oauth-provider 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/integration.test.js +0 -20
- package/dist/__tests__/integration.test.js.map +1 -1
- package/dist/client/config.js.map +1 -1
- package/dist/client/index.js +38 -35
- package/dist/client/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/integration.test.ts +0 -28
- package/src/client/config.ts +6 -11
- package/src/client/index.ts +37 -49
|
@@ -78,26 +78,6 @@ describe('MCPOAuthClientProvider Integration', ()=>{
|
|
|
78
78
|
expect(metadata.grant_types).toBeDefined(); // Should still have defaults
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
|
-
describe('authorization server metadata', ()=>{
|
|
82
|
-
test('should accept authorizationServerMetadata through constructor', ()=>{
|
|
83
|
-
const metadata = {
|
|
84
|
-
issuer: 'https://auth.example.com',
|
|
85
|
-
authorization_endpoint: 'https://auth.example.com/authorize',
|
|
86
|
-
token_endpoint: 'https://auth.example.com/token'
|
|
87
|
-
};
|
|
88
|
-
const provider = new MCPOAuthClientProvider({
|
|
89
|
-
...config,
|
|
90
|
-
authorizationServerMetadata: metadata
|
|
91
|
-
});
|
|
92
|
-
expect(provider.authorizationServerMetadata).toBeDefined();
|
|
93
|
-
expect(provider.authorizationServerMetadata?.issuer).toBe('https://auth.example.com');
|
|
94
|
-
expect(provider.authorizationServerMetadata?.token_endpoint).toBe('https://auth.example.com/token');
|
|
95
|
-
});
|
|
96
|
-
test('should be undefined when not provided', ()=>{
|
|
97
|
-
const provider = new MCPOAuthClientProvider(config);
|
|
98
|
-
expect(provider.authorizationServerMetadata).toBeUndefined();
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
81
|
describe('token management', ()=>{
|
|
102
82
|
test('should return undefined when no tokens exist', async ()=>{
|
|
103
83
|
const provider = new MCPOAuthClientProvider(config);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/__tests__/integration.test.ts"],"sourcesContent":["import { beforeEach, describe, expect, test } from 'bun:test';\nimport type { OAuthConfig, OAuthTokens } from '../client/config.js';\nimport { MCPOAuthClientProvider } from '../client/index.js';\nimport { MemoryStorage } from '../client/storage.js';\n\ndescribe('MCPOAuthClientProvider Integration', () => {\n let config: OAuthConfig;\n let storage: MemoryStorage;\n\n beforeEach(() => {\n storage = new MemoryStorage();\n config = {\n redirectUri: 'http://localhost:8080/callback',\n scope: 'openid profile email',\n storage,\n };\n });\n\n describe('initialization', () => {\n test('should create provider with minimal config', () => {\n const provider = new MCPOAuthClientProvider(config);\n\n expect(provider).toBeDefined();\n expect(provider.redirectUrl).toBe(config.redirectUri);\n });\n\n test('should generate session ID if not provided', () => {\n const provider1 = new MCPOAuthClientProvider(config);\n const provider2 = new MCPOAuthClientProvider(config);\n\n // Each provider should have its own session ID\n // We can't directly access sessionId, but we can verify they work independently\n expect(provider1).not.toBe(provider2);\n });\n\n test('should use provided session ID', () => {\n const sessionId = 'test-session-123';\n const provider = new MCPOAuthClientProvider({\n ...config,\n sessionId,\n });\n\n expect(provider).toBeDefined();\n });\n\n test('should use provided client credentials', async () => {\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientId: 'test-client-id',\n clientSecret: 'test-secret',\n });\n\n const clientInfo = await provider.clientInformation();\n\n expect(clientInfo).toEqual({\n client_id: 'test-client-id',\n client_secret: 'test-secret',\n });\n });\n });\n\n describe('OAuth state management', () => {\n test('should generate unique state values', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const state1 = await provider.state();\n const state2 = await provider.state();\n\n expect(state1).not.toBe(state2);\n expect(state1).toBeString();\n expect(state2).toBeString();\n });\n });\n\n describe('client metadata', () => {\n test('should provide default client metadata', () => {\n const provider = new MCPOAuthClientProvider(config);\n const metadata = provider.clientMetadata;\n\n expect(metadata).toHaveProperty('client_name');\n expect(metadata).toHaveProperty('grant_types');\n expect(metadata).toHaveProperty('response_types');\n expect(metadata.redirect_uris).toContain(config.redirectUri);\n });\n\n test('should merge custom metadata with defaults', () => {\n const customMetadata = {\n client_name: 'Custom OAuth Client',\n };\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientMetadata: customMetadata,\n });\n const metadata = provider.clientMetadata;\n\n expect(metadata.client_name).toBe('Custom OAuth Client');\n expect(metadata.grant_types).toBeDefined(); // Should still have defaults\n });\n });\n\n describe('authorization server metadata', () => {\n test('should accept authorizationServerMetadata through constructor', () => {\n const metadata = {\n issuer: 'https://auth.example.com',\n authorization_endpoint: 'https://auth.example.com/authorize',\n token_endpoint: 'https://auth.example.com/token',\n };\n const provider = new MCPOAuthClientProvider({\n ...config,\n authorizationServerMetadata: metadata,\n });\n\n expect(provider.authorizationServerMetadata).toBeDefined();\n expect(provider.authorizationServerMetadata?.issuer).toBe(\n 'https://auth.example.com'\n );\n expect(provider.authorizationServerMetadata?.token_endpoint).toBe(\n 'https://auth.example.com/token'\n );\n });\n\n test('should be undefined when not provided', () => {\n const provider = new MCPOAuthClientProvider(config);\n\n expect(provider.authorizationServerMetadata).toBeUndefined();\n });\n });\n\n describe('token management', () => {\n test('should return undefined when no tokens exist', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const tokens = await provider.tokens();\n\n expect(tokens).toBeUndefined();\n });\n\n test('should save and retrieve tokens', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const mockTokens = {\n access_token: 'test-access-token',\n token_type: 'Bearer' as const,\n expires_in: 3600,\n refresh_token: 'test-refresh-token',\n };\n\n await provider.saveTokens(mockTokens);\n const retrieved = await provider.tokens();\n\n expect(retrieved).toEqual(mockTokens);\n });\n\n test('should clear tokens', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const mockTokens = {\n access_token: 'test-access-token',\n token_type: 'Bearer' as const,\n };\n\n await provider.saveTokens(mockTokens);\n await provider.invalidateCredentials('tokens');\n const tokens = await provider.tokens();\n\n expect(tokens).toBeUndefined();\n });\n\n test('should initialize tokens from config and allow updates', async () => {\n const initialTokens = {\n access_token: 'initial-token',\n token_type: 'Bearer' as const,\n refresh_token: 'initial-refresh',\n };\n const provider = new MCPOAuthClientProvider({\n ...config,\n tokens: initialTokens,\n });\n\n // Should return initial tokens from config on first access\n const tokens1 = await provider.tokens();\n\n expect(tokens1).toEqual(initialTokens);\n\n // Should be able to update tokens\n const newTokens = {\n access_token: 'new-token',\n token_type: 'Bearer' as const,\n refresh_token: 'new-refresh',\n };\n\n await provider.saveTokens(newTokens);\n\n // Should return updated tokens (storage takes precedence after initialization)\n const tokens2 = await provider.tokens();\n\n expect(tokens2).toEqual(newTokens);\n expect(tokens2?.access_token).not.toBe(initialTokens.access_token);\n });\n });\n\n describe('client information management', () => {\n test('should return undefined when no client info exists and no static credentials', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const clientInfo = await provider.clientInformation();\n\n expect(clientInfo).toBeUndefined();\n });\n\n test('should save and retrieve client information from dynamic registration', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const clientInfo = {\n client_id: 'dynamic-client-id',\n client_secret: 'dynamic-secret',\n redirect_uris: [config.redirectUri],\n client_id_issued_at: Date.now(),\n };\n\n await provider.saveClientInformation(clientInfo);\n const retrieved = await provider.clientInformation();\n\n expect(retrieved).toMatchObject({\n client_id: clientInfo.client_id,\n client_secret: clientInfo.client_secret,\n });\n });\n\n test('should prefer static credentials over stored credentials', async () => {\n const staticCreds = {\n clientId: 'static-id',\n clientSecret: 'static-secret',\n };\n const provider = new MCPOAuthClientProvider({\n ...config,\n ...staticCreds,\n });\n\n // Save dynamic credentials\n await provider.saveClientInformation({\n client_id: 'dynamic-id',\n client_secret: 'dynamic-secret',\n redirect_uris: [config.redirectUri],\n });\n\n // Should return static credentials\n const clientInfo = await provider.clientInformation();\n\n expect(clientInfo?.client_id).toBe(staticCreds.clientId);\n });\n });\n\n describe('storage isolation', () => {\n test('should isolate tokens between different sessions', async () => {\n const provider1 = new MCPOAuthClientProvider({\n ...config,\n sessionId: 'session-1',\n });\n const provider2 = new MCPOAuthClientProvider({\n ...config,\n sessionId: 'session-2',\n });\n const tokens1 = {\n access_token: 'token-1',\n token_type: 'Bearer' as const,\n };\n const tokens2 = {\n access_token: 'token-2',\n token_type: 'Bearer' as const,\n };\n\n await provider1.saveTokens(tokens1);\n await provider2.saveTokens(tokens2);\n const retrieved1 = await provider1.tokens();\n const retrieved2 = await provider2.tokens();\n\n expect(retrieved1?.access_token).toBe('token-1');\n expect(retrieved2?.access_token).toBe('token-2');\n });\n });\n\n describe('automatic token refresh', () => {\n test('should automatically refresh expired tokens when metadata is available', async () => {\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientId: 'test-client',\n clientSecret: 'test-secret',\n });\n\n // Set authorization server metadata with token endpoint\n provider.authorizationServerMetadata = {\n issuer: 'https://auth.example.com',\n authorization_endpoint: 'https://auth.example.com/authorize',\n token_endpoint: 'https://auth.example.com/token',\n response_types_supported: ['code'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n };\n\n // Save expired tokens with refresh token\n const expiredTokens: OAuthTokens = {\n access_token: 'expired-token',\n token_type: 'Bearer',\n expires_in: 100, // Less than 5 minute buffer, will trigger refresh\n refresh_token: 'refresh-token-123',\n };\n\n await provider.saveTokens(expiredTokens);\n\n // Mock the refresh (we can't easily test the actual HTTP call in unit test)\n // The tokens() method should detect expired tokens and attempt refresh\n const tokens = await provider.tokens();\n\n // Should return the expired tokens (refresh will fail due to no network mock)\n // but the attempt to refresh should have been made\n expect(tokens).toBeDefined();\n expect(tokens?.access_token).toBe('expired-token');\n });\n\n test('should return tokens immediately if not expired', async () => {\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientId: 'test-client',\n clientSecret: 'test-secret',\n });\n\n // Save valid tokens that won't expire soon\n const validTokens: OAuthTokens = {\n access_token: 'valid-token',\n token_type: 'Bearer',\n expires_in: 3600, // 1 hour, well beyond 5 minute buffer\n refresh_token: 'refresh-token-123',\n };\n\n await provider.saveTokens(validTokens);\n\n // Should return tokens without attempting refresh\n const tokens = await provider.tokens();\n\n expect(tokens).toBeDefined();\n expect(tokens?.access_token).toBe('valid-token');\n });\n\n test('should not attempt refresh if no refresh token available', async () => {\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientId: 'test-client',\n clientSecret: 'test-secret',\n });\n\n provider.authorizationServerMetadata = {\n issuer: 'https://auth.example.com',\n authorization_endpoint: 'https://auth.example.com/authorize',\n token_endpoint: 'https://auth.example.com/token',\n response_types_supported: ['code'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n };\n\n // Save expired tokens WITHOUT refresh token\n const expiredTokens: OAuthTokens = {\n access_token: 'expired-token',\n token_type: 'Bearer',\n expires_in: 100, // Less than 5 minute buffer\n // No refresh_token\n };\n\n await provider.saveTokens(expiredTokens);\n\n // Should return expired tokens without attempting refresh\n const tokens = await provider.tokens();\n\n expect(tokens).toBeDefined();\n expect(tokens?.access_token).toBe('expired-token');\n });\n\n test('should not attempt refresh if no metadata available', async () => {\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientId: 'test-client',\n clientSecret: 'test-secret',\n });\n\n // No authorizationServerMetadata set\n\n // Save expired tokens with refresh token\n const expiredTokens: OAuthTokens = {\n access_token: 'expired-token',\n token_type: 'Bearer',\n expires_in: 100, // Less than 5 minute buffer\n refresh_token: 'refresh-token-123',\n };\n\n await provider.saveTokens(expiredTokens);\n\n // Should return expired tokens without attempting refresh (no metadata)\n const tokens = await provider.tokens();\n\n expect(tokens).toBeDefined();\n expect(tokens?.access_token).toBe('expired-token');\n });\n });\n});\n"],"names":["beforeEach","describe","expect","test","MCPOAuthClientProvider","MemoryStorage","config","storage","redirectUri","scope","provider","toBeDefined","redirectUrl","toBe","provider1","provider2","not","sessionId","clientId","clientSecret","clientInfo","clientInformation","toEqual","client_id","client_secret","state1","state","state2","toBeString","metadata","clientMetadata","toHaveProperty","redirect_uris","toContain","customMetadata","client_name","grant_types","issuer","authorization_endpoint","token_endpoint","authorizationServerMetadata","toBeUndefined","tokens","mockTokens","access_token","token_type","expires_in","refresh_token","saveTokens","retrieved","invalidateCredentials","initialTokens","tokens1","newTokens","tokens2","client_id_issued_at","Date","now","saveClientInformation","toMatchObject","staticCreds","retrieved1","retrieved2","response_types_supported","grant_types_supported","code_challenge_methods_supported","expiredTokens","validTokens"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/integration.test.ts"],"sourcesContent":["import { beforeEach, describe, expect, test } from 'bun:test';\nimport type { OAuthConfig, OAuthTokens } from '../client/config.js';\nimport { MCPOAuthClientProvider } from '../client/index.js';\nimport { MemoryStorage } from '../client/storage.js';\n\ndescribe('MCPOAuthClientProvider Integration', () => {\n let config: OAuthConfig;\n let storage: MemoryStorage;\n\n beforeEach(() => {\n storage = new MemoryStorage();\n config = {\n redirectUri: 'http://localhost:8080/callback',\n scope: 'openid profile email',\n storage,\n };\n });\n\n describe('initialization', () => {\n test('should create provider with minimal config', () => {\n const provider = new MCPOAuthClientProvider(config);\n\n expect(provider).toBeDefined();\n expect(provider.redirectUrl).toBe(config.redirectUri);\n });\n\n test('should generate session ID if not provided', () => {\n const provider1 = new MCPOAuthClientProvider(config);\n const provider2 = new MCPOAuthClientProvider(config);\n\n // Each provider should have its own session ID\n // We can't directly access sessionId, but we can verify they work independently\n expect(provider1).not.toBe(provider2);\n });\n\n test('should use provided session ID', () => {\n const sessionId = 'test-session-123';\n const provider = new MCPOAuthClientProvider({\n ...config,\n sessionId,\n });\n\n expect(provider).toBeDefined();\n });\n\n test('should use provided client credentials', async () => {\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientId: 'test-client-id',\n clientSecret: 'test-secret',\n });\n\n const clientInfo = await provider.clientInformation();\n\n expect(clientInfo).toEqual({\n client_id: 'test-client-id',\n client_secret: 'test-secret',\n });\n });\n });\n\n describe('OAuth state management', () => {\n test('should generate unique state values', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const state1 = await provider.state();\n const state2 = await provider.state();\n\n expect(state1).not.toBe(state2);\n expect(state1).toBeString();\n expect(state2).toBeString();\n });\n });\n\n describe('client metadata', () => {\n test('should provide default client metadata', () => {\n const provider = new MCPOAuthClientProvider(config);\n const metadata = provider.clientMetadata;\n\n expect(metadata).toHaveProperty('client_name');\n expect(metadata).toHaveProperty('grant_types');\n expect(metadata).toHaveProperty('response_types');\n expect(metadata.redirect_uris).toContain(config.redirectUri);\n });\n\n test('should merge custom metadata with defaults', () => {\n const customMetadata = {\n client_name: 'Custom OAuth Client',\n };\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientMetadata: customMetadata,\n });\n const metadata = provider.clientMetadata;\n\n expect(metadata.client_name).toBe('Custom OAuth Client');\n expect(metadata.grant_types).toBeDefined(); // Should still have defaults\n });\n });\n\n describe('token management', () => {\n test('should return undefined when no tokens exist', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const tokens = await provider.tokens();\n\n expect(tokens).toBeUndefined();\n });\n\n test('should save and retrieve tokens', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const mockTokens = {\n access_token: 'test-access-token',\n token_type: 'Bearer' as const,\n expires_in: 3600,\n refresh_token: 'test-refresh-token',\n };\n\n await provider.saveTokens(mockTokens);\n const retrieved = await provider.tokens();\n\n expect(retrieved).toEqual(mockTokens);\n });\n\n test('should clear tokens', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const mockTokens = {\n access_token: 'test-access-token',\n token_type: 'Bearer' as const,\n };\n\n await provider.saveTokens(mockTokens);\n await provider.invalidateCredentials('tokens');\n const tokens = await provider.tokens();\n\n expect(tokens).toBeUndefined();\n });\n\n test('should initialize tokens from config and allow updates', async () => {\n const initialTokens = {\n access_token: 'initial-token',\n token_type: 'Bearer' as const,\n refresh_token: 'initial-refresh',\n };\n const provider = new MCPOAuthClientProvider({\n ...config,\n tokens: initialTokens,\n });\n\n // Should return initial tokens from config on first access\n const tokens1 = await provider.tokens();\n\n expect(tokens1).toEqual(initialTokens);\n\n // Should be able to update tokens\n const newTokens = {\n access_token: 'new-token',\n token_type: 'Bearer' as const,\n refresh_token: 'new-refresh',\n };\n\n await provider.saveTokens(newTokens);\n\n // Should return updated tokens (storage takes precedence after initialization)\n const tokens2 = await provider.tokens();\n\n expect(tokens2).toEqual(newTokens);\n expect(tokens2?.access_token).not.toBe(initialTokens.access_token);\n });\n });\n\n describe('client information management', () => {\n test('should return undefined when no client info exists and no static credentials', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const clientInfo = await provider.clientInformation();\n\n expect(clientInfo).toBeUndefined();\n });\n\n test('should save and retrieve client information from dynamic registration', async () => {\n const provider = new MCPOAuthClientProvider(config);\n const clientInfo = {\n client_id: 'dynamic-client-id',\n client_secret: 'dynamic-secret',\n redirect_uris: [config.redirectUri],\n client_id_issued_at: Date.now(),\n };\n\n await provider.saveClientInformation(clientInfo);\n const retrieved = await provider.clientInformation();\n\n expect(retrieved).toMatchObject({\n client_id: clientInfo.client_id,\n client_secret: clientInfo.client_secret,\n });\n });\n\n test('should prefer static credentials over stored credentials', async () => {\n const staticCreds = {\n clientId: 'static-id',\n clientSecret: 'static-secret',\n };\n const provider = new MCPOAuthClientProvider({\n ...config,\n ...staticCreds,\n });\n\n // Save dynamic credentials\n await provider.saveClientInformation({\n client_id: 'dynamic-id',\n client_secret: 'dynamic-secret',\n redirect_uris: [config.redirectUri],\n });\n\n // Should return static credentials\n const clientInfo = await provider.clientInformation();\n\n expect(clientInfo?.client_id).toBe(staticCreds.clientId);\n });\n });\n\n describe('storage isolation', () => {\n test('should isolate tokens between different sessions', async () => {\n const provider1 = new MCPOAuthClientProvider({\n ...config,\n sessionId: 'session-1',\n });\n const provider2 = new MCPOAuthClientProvider({\n ...config,\n sessionId: 'session-2',\n });\n const tokens1 = {\n access_token: 'token-1',\n token_type: 'Bearer' as const,\n };\n const tokens2 = {\n access_token: 'token-2',\n token_type: 'Bearer' as const,\n };\n\n await provider1.saveTokens(tokens1);\n await provider2.saveTokens(tokens2);\n const retrieved1 = await provider1.tokens();\n const retrieved2 = await provider2.tokens();\n\n expect(retrieved1?.access_token).toBe('token-1');\n expect(retrieved2?.access_token).toBe('token-2');\n });\n });\n\n describe('automatic token refresh', () => {\n test('should automatically refresh expired tokens when metadata is available', async () => {\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientId: 'test-client',\n clientSecret: 'test-secret',\n });\n\n // Set authorization server metadata with token endpoint\n provider.authorizationServerMetadata = {\n issuer: 'https://auth.example.com',\n authorization_endpoint: 'https://auth.example.com/authorize',\n token_endpoint: 'https://auth.example.com/token',\n response_types_supported: ['code'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n };\n\n // Save expired tokens with refresh token\n const expiredTokens: OAuthTokens = {\n access_token: 'expired-token',\n token_type: 'Bearer',\n expires_in: 100, // Less than 5 minute buffer, will trigger refresh\n refresh_token: 'refresh-token-123',\n };\n\n await provider.saveTokens(expiredTokens);\n\n // Mock the refresh (we can't easily test the actual HTTP call in unit test)\n // The tokens() method should detect expired tokens and attempt refresh\n const tokens = await provider.tokens();\n\n // Should return the expired tokens (refresh will fail due to no network mock)\n // but the attempt to refresh should have been made\n expect(tokens).toBeDefined();\n expect(tokens?.access_token).toBe('expired-token');\n });\n\n test('should return tokens immediately if not expired', async () => {\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientId: 'test-client',\n clientSecret: 'test-secret',\n });\n\n // Save valid tokens that won't expire soon\n const validTokens: OAuthTokens = {\n access_token: 'valid-token',\n token_type: 'Bearer',\n expires_in: 3600, // 1 hour, well beyond 5 minute buffer\n refresh_token: 'refresh-token-123',\n };\n\n await provider.saveTokens(validTokens);\n\n // Should return tokens without attempting refresh\n const tokens = await provider.tokens();\n\n expect(tokens).toBeDefined();\n expect(tokens?.access_token).toBe('valid-token');\n });\n\n test('should not attempt refresh if no refresh token available', async () => {\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientId: 'test-client',\n clientSecret: 'test-secret',\n });\n\n provider.authorizationServerMetadata = {\n issuer: 'https://auth.example.com',\n authorization_endpoint: 'https://auth.example.com/authorize',\n token_endpoint: 'https://auth.example.com/token',\n response_types_supported: ['code'],\n grant_types_supported: ['authorization_code', 'refresh_token'],\n code_challenge_methods_supported: ['S256'],\n };\n\n // Save expired tokens WITHOUT refresh token\n const expiredTokens: OAuthTokens = {\n access_token: 'expired-token',\n token_type: 'Bearer',\n expires_in: 100, // Less than 5 minute buffer\n // No refresh_token\n };\n\n await provider.saveTokens(expiredTokens);\n\n // Should return expired tokens without attempting refresh\n const tokens = await provider.tokens();\n\n expect(tokens).toBeDefined();\n expect(tokens?.access_token).toBe('expired-token');\n });\n\n test('should not attempt refresh if no metadata available', async () => {\n const provider = new MCPOAuthClientProvider({\n ...config,\n clientId: 'test-client',\n clientSecret: 'test-secret',\n });\n\n // No authorizationServerMetadata set\n\n // Save expired tokens with refresh token\n const expiredTokens: OAuthTokens = {\n access_token: 'expired-token',\n token_type: 'Bearer',\n expires_in: 100, // Less than 5 minute buffer\n refresh_token: 'refresh-token-123',\n };\n\n await provider.saveTokens(expiredTokens);\n\n // Should return expired tokens without attempting refresh (no metadata)\n const tokens = await provider.tokens();\n\n expect(tokens).toBeDefined();\n expect(tokens?.access_token).toBe('expired-token');\n });\n });\n});\n"],"names":["beforeEach","describe","expect","test","MCPOAuthClientProvider","MemoryStorage","config","storage","redirectUri","scope","provider","toBeDefined","redirectUrl","toBe","provider1","provider2","not","sessionId","clientId","clientSecret","clientInfo","clientInformation","toEqual","client_id","client_secret","state1","state","state2","toBeString","metadata","clientMetadata","toHaveProperty","redirect_uris","toContain","customMetadata","client_name","grant_types","tokens","toBeUndefined","mockTokens","access_token","token_type","expires_in","refresh_token","saveTokens","retrieved","invalidateCredentials","initialTokens","tokens1","newTokens","tokens2","client_id_issued_at","Date","now","saveClientInformation","toMatchObject","staticCreds","retrieved1","retrieved2","authorizationServerMetadata","issuer","authorization_endpoint","token_endpoint","response_types_supported","grant_types_supported","code_challenge_methods_supported","expiredTokens","validTokens"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/config.ts"],"sourcesContent":["import type {\n OAuthClientInformation,\n OAuthClientInformationFull,\n OAuthClientMetadata,\n OAuthTokens,\n} from '@modelcontextprotocol/sdk/shared/auth.js';\n\nexport type {\n OAuthClientInformation,\n OAuthClientInformationFull,\n OAuthClientMetadata,\n OAuthTokens,\n};\n\n/**\n * Storage adapter interface for persisting OAuth data\n */\nexport interface StorageAdapter {\n /**\n * Get a value by key\n */\n get(key: string): Promise<string | undefined> | string | undefined;\n\n /**\n * Set a value by key\n */\n set(key: string, value: string): Promise<void> | void;\n\n /**\n * Delete a value by key\n */\n delete(key: string): Promise<void> | void;\n}\n\n/**\n * Configuration for OAuth client provider\n */\nexport interface OAuthConfig {\n /**\n * OAuth client ID (can be provided or dynamically registered)\n */\n clientId?: string;\n\n /**\n * OAuth client secret (optional for public clients)\n */\n clientSecret?: string;\n\n /**\n * Redirect URI for OAuth callbacks\n */\n redirectUri
|
|
1
|
+
{"version":3,"sources":["../../src/client/config.ts"],"sourcesContent":["import type {\n OAuthClientInformation,\n OAuthClientInformationFull,\n OAuthClientMetadata,\n OAuthTokens,\n} from '@modelcontextprotocol/sdk/shared/auth.js';\n\nexport type {\n OAuthClientInformation,\n OAuthClientInformationFull,\n OAuthClientMetadata,\n OAuthTokens,\n};\n\n/**\n * Storage adapter interface for persisting OAuth data\n */\nexport interface StorageAdapter {\n /**\n * Get a value by key\n */\n get(key: string): Promise<string | undefined> | string | undefined;\n\n /**\n * Set a value by key\n */\n set(key: string, value: string): Promise<void> | void;\n\n /**\n * Delete a value by key\n */\n delete(key: string): Promise<void> | void;\n}\n\n/**\n * Configuration for OAuth client provider\n */\nexport interface OAuthConfig {\n /**\n * OAuth client ID (can be provided or dynamically registered)\n */\n clientId?: string;\n\n /**\n * OAuth client secret (optional for public clients)\n */\n clientSecret?: string;\n\n /**\n * Redirect URI for OAuth callbacks\n */\n redirectUri?: string;\n\n /**\n * OAuth scope to request\n */\n scope?: string;\n\n /**\n * Session identifier for this OAuth client instance\n */\n sessionId?: string;\n\n /**\n * Storage adapter for persisting OAuth data\n */\n storage?: StorageAdapter;\n\n /**\n * OAuth client metadata for registration\n */\n clientMetadata?: Partial<OAuthClientMetadata>;\n\n /**\n * OAuth tokens (can be provided statically or loaded from storage)\n */\n tokens?: OAuthTokens;\n\n /**\n * Token refresh endpoint URL (optional, can be obtained from metadata)\n */\n tokenEndpoint?: string;\n\n /**\n * Token refresh configuration\n */\n tokenRefresh?: {\n /**\n * Maximum number of retry attempts for token refresh\n */\n maxRetries?: number;\n\n /**\n * Delay between retry attempts in milliseconds\n */\n retryDelay?: number;\n };\n}\n\n/**\n * Default OAuth client metadata\n */\nexport const DEFAULT_CLIENT_METADATA: OAuthClientMetadata = {\n redirect_uris: [],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'client_secret_post',\n scope: 'openid profile email',\n client_name: 'MCP OAuth Client',\n client_uri: 'https://github.com/modelcontextprotocol/typescript-sdk',\n};\n\n/**\n * Generate a random session ID\n */\nexport function generateSessionId(): string {\n return crypto.randomUUID();\n}\n\n/**\n * Generate a random state parameter for CSRF protection\n */\nexport function generateState(): string {\n const array = new Uint8Array(32);\n\n crypto.getRandomValues(array);\n\n return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\n}\n"],"names":["DEFAULT_CLIENT_METADATA","redirect_uris","grant_types","response_types","token_endpoint_auth_method","scope","client_name","client_uri","generateSessionId","crypto","randomUUID","generateState","array","Uint8Array","getRandomValues","Array","from","byte","toString","padStart","join"],"mappings":"AAmGA;;CAEC,GACD,OAAO,MAAMA,0BAA+C;IAC1DC,eAAe,EAAE;IACjBC,aAAa;QAAC;QAAsB;KAAgB;IACpDC,gBAAgB;QAAC;KAAO;IACxBC,4BAA4B;IAC5BC,OAAO;IACPC,aAAa;IACbC,YAAY;AACd,EAAE;AAEF;;CAEC,GACD,OAAO,SAASC;IACd,OAAOC,OAAOC,UAAU;AAC1B;AAEA;;CAEC,GACD,OAAO,SAASC;IACd,MAAMC,QAAQ,IAAIC,WAAW;IAE7BJ,OAAOK,eAAe,CAACF;IAEvB,OAAOG,MAAMC,IAAI,CAACJ,OAAOK,CAAAA,OAAQA,KAAKC,QAAQ,CAAC,IAAIC,QAAQ,CAAC,GAAG,MAAMC,IAAI,CAAC;AAC5E"}
|
package/dist/client/index.js
CHANGED
|
@@ -2,16 +2,21 @@ import { _ as _define_property } from "@swc/helpers/_/_define_property";
|
|
|
2
2
|
import { DEFAULT_CLIENT_METADATA, generateSessionId, generateState } from './config.js';
|
|
3
3
|
import { areTokensExpired, refreshTokensWithRetry } from './oauth-flow.js';
|
|
4
4
|
import { MemoryStorage, OAuthStorage } from './storage.js';
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* MCP OAuth Client Provider implementation with automatic token refresh
|
|
7
|
+
*/ export class MCPOAuthClientProvider {
|
|
6
8
|
/**
|
|
7
9
|
* The URL to redirect the user agent to after authorization
|
|
8
10
|
*/ get redirectUrl() {
|
|
9
|
-
|
|
11
|
+
if (!this.redirectUri) {
|
|
12
|
+
throw new Error('No redirect URI configured for this OAuth client');
|
|
13
|
+
}
|
|
14
|
+
return this.redirectUri;
|
|
10
15
|
}
|
|
11
16
|
/**
|
|
12
17
|
* Metadata about this OAuth client
|
|
13
18
|
*/ get clientMetadata() {
|
|
14
|
-
return this.
|
|
19
|
+
return this._clientMetadata;
|
|
15
20
|
}
|
|
16
21
|
/**
|
|
17
22
|
* Returns a OAuth2 state parameter for CSRF protection
|
|
@@ -42,7 +47,7 @@ export class MCPOAuthClientProvider {
|
|
|
42
47
|
// Check if tokens are expired or about to expire (within 5 minutes)
|
|
43
48
|
const needsRefresh = areTokensExpired(currentTokens, undefined, 300);
|
|
44
49
|
// If tokens need refresh and we have the server metadata and refresh token, refresh automatically
|
|
45
|
-
if (needsRefresh && currentTokens.refresh_token && this.
|
|
50
|
+
if (needsRefresh && currentTokens.refresh_token && this.tokenEndpoint) {
|
|
46
51
|
try {
|
|
47
52
|
// Use the token endpoint from authorization server metadata
|
|
48
53
|
const newTokens = await this.refreshTokens();
|
|
@@ -62,10 +67,10 @@ export class MCPOAuthClientProvider {
|
|
|
62
67
|
* @returns New OAuth tokens
|
|
63
68
|
* @throws Error if no authorization server metadata is available
|
|
64
69
|
*/ async refreshTokens() {
|
|
65
|
-
if (!this.
|
|
66
|
-
throw new Error('No
|
|
70
|
+
if (!this.tokenEndpoint) {
|
|
71
|
+
throw new Error('No token endpoint available. Cannot refresh tokens without token_endpoint.');
|
|
67
72
|
}
|
|
68
|
-
const { maxRetries, retryDelay } = this.
|
|
73
|
+
const { maxRetries, retryDelay } = this.tokenRefreshConfig;
|
|
69
74
|
const currentTokens = await this.oauthStorage.getTokens();
|
|
70
75
|
if (!currentTokens?.refresh_token) {
|
|
71
76
|
throw new Error('No refresh token available');
|
|
@@ -76,7 +81,7 @@ export class MCPOAuthClientProvider {
|
|
|
76
81
|
}
|
|
77
82
|
try {
|
|
78
83
|
// Use helper function for retry logic with token endpoint
|
|
79
|
-
const newTokens = await refreshTokensWithRetry(this.
|
|
84
|
+
const newTokens = await refreshTokensWithRetry(this.tokenEndpoint, clientInfo, currentTokens.refresh_token, this.addClientAuthentication, maxRetries, retryDelay);
|
|
80
85
|
// Save the new tokens
|
|
81
86
|
await this.oauthStorage.saveTokens(newTokens);
|
|
82
87
|
return newTokens;
|
|
@@ -176,16 +181,20 @@ export class MCPOAuthClientProvider {
|
|
|
176
181
|
await this.oauthStorage.clearSession();
|
|
177
182
|
}
|
|
178
183
|
constructor(config){
|
|
179
|
-
_define_property(this, "config", void 0);
|
|
180
184
|
_define_property(this, "oauthStorage", void 0);
|
|
181
185
|
_define_property(this, "sessionId", void 0);
|
|
186
|
+
_define_property(this, "redirectUri", void 0);
|
|
187
|
+
_define_property(this, "tokenRefreshConfig", void 0);
|
|
188
|
+
_define_property(this, "_clientMetadata", void 0);
|
|
182
189
|
_define_property(this, "cachedClientInformation", void 0);
|
|
190
|
+
_define_property(this, "tokenEndpoint", void 0);
|
|
183
191
|
_define_property(this, "authorizationServerMetadata", void 0);
|
|
184
192
|
/**
|
|
185
193
|
* Adds custom client authentication to OAuth token requests
|
|
186
194
|
*/ _define_property(this, "addClientAuthentication", (headers, params, url, metadata)=>{
|
|
187
195
|
const clientInfo = this.cachedClientInformation;
|
|
188
196
|
this.authorizationServerMetadata = metadata;
|
|
197
|
+
this.tokenEndpoint = metadata?.token_endpoint;
|
|
189
198
|
if (!clientInfo) {
|
|
190
199
|
throw new Error('No client information available for authentication');
|
|
191
200
|
}
|
|
@@ -197,10 +206,27 @@ export class MCPOAuthClientProvider {
|
|
|
197
206
|
});
|
|
198
207
|
this.sessionId = config.sessionId ?? generateSessionId();
|
|
199
208
|
const storage = config.storage ?? new MemoryStorage();
|
|
200
|
-
// Set
|
|
201
|
-
if (config.
|
|
202
|
-
this.
|
|
209
|
+
// Set token endpoint if provided
|
|
210
|
+
if (config.tokenEndpoint) {
|
|
211
|
+
this.tokenEndpoint = config.tokenEndpoint;
|
|
203
212
|
}
|
|
213
|
+
// Store redirect URI
|
|
214
|
+
this.redirectUri = config.redirectUri;
|
|
215
|
+
// Store token refresh configuration
|
|
216
|
+
this.tokenRefreshConfig = {
|
|
217
|
+
maxRetries: 3,
|
|
218
|
+
retryDelay: 1000,
|
|
219
|
+
...config.tokenRefresh ?? {}
|
|
220
|
+
};
|
|
221
|
+
// Store client metadata
|
|
222
|
+
this._clientMetadata = {
|
|
223
|
+
...DEFAULT_CLIENT_METADATA,
|
|
224
|
+
...config.clientMetadata ?? {},
|
|
225
|
+
redirect_uris: config.redirectUri ? [
|
|
226
|
+
config.redirectUri
|
|
227
|
+
] : [],
|
|
228
|
+
scope: config.scope ?? DEFAULT_CLIENT_METADATA.scope
|
|
229
|
+
};
|
|
204
230
|
this.oauthStorage = new OAuthStorage(storage, this.sessionId, {
|
|
205
231
|
staticClientInfo: config.clientId ? {
|
|
206
232
|
client_id: config.clientId,
|
|
@@ -208,29 +234,6 @@ export class MCPOAuthClientProvider {
|
|
|
208
234
|
} : undefined,
|
|
209
235
|
initialTokens: config.tokens
|
|
210
236
|
});
|
|
211
|
-
// Merge with defaults
|
|
212
|
-
this.config = {
|
|
213
|
-
clientId: config.clientId ?? '',
|
|
214
|
-
clientSecret: config.clientSecret ?? undefined,
|
|
215
|
-
redirectUri: config.redirectUri,
|
|
216
|
-
scope: config.scope ?? 'read write',
|
|
217
|
-
sessionId: this.sessionId,
|
|
218
|
-
storage,
|
|
219
|
-
tokens: config.tokens,
|
|
220
|
-
clientMetadata: {
|
|
221
|
-
...DEFAULT_CLIENT_METADATA,
|
|
222
|
-
...config.clientMetadata ?? {},
|
|
223
|
-
redirect_uris: [
|
|
224
|
-
config.redirectUri
|
|
225
|
-
],
|
|
226
|
-
scope: config.scope ?? DEFAULT_CLIENT_METADATA.scope
|
|
227
|
-
},
|
|
228
|
-
tokenRefresh: {
|
|
229
|
-
maxRetries: 3,
|
|
230
|
-
retryDelay: 1000,
|
|
231
|
-
...config.tokenRefresh ?? {}
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
237
|
}
|
|
235
238
|
}
|
|
236
239
|
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/index.ts"],"sourcesContent":["import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';\nimport { AuthorizationServerMetadata } from '@modelcontextprotocol/sdk/shared/auth.js';\nimport type {\n OAuthClientInformation,\n OAuthClientInformationFull,\n OAuthClientMetadata,\n OAuthConfig,\n OAuthTokens,\n} from './config.js';\nimport {\n DEFAULT_CLIENT_METADATA,\n generateSessionId,\n generateState,\n} from './config.js';\nimport { areTokensExpired, refreshTokensWithRetry } from './oauth-flow.js';\nimport { MemoryStorage, OAuthStorage } from './storage.js';\n\n/**\n * Options for token refresh\n */\ninterface TokenRefreshConfig {\n maxRetries: number;\n retryDelay: number;\n}\n\n/**\n * MCP OAuth Client Provider implementation with automatic token refresh\n */\ninterface ProcessedOAuthConfig\n extends Omit<\n Required<OAuthConfig>,\n 'clientSecret' | 'tokens' | 'authorizationServerMetadata'\n > {\n clientSecret?: string;\n tokens?: OAuthTokens;\n clientMetadata: OAuthClientMetadata;\n tokenRefresh: TokenRefreshConfig;\n}\n\nexport class MCPOAuthClientProvider implements OAuthClientProvider {\n private readonly config: ProcessedOAuthConfig;\n private readonly oauthStorage: OAuthStorage;\n private readonly sessionId: string;\n private cachedClientInformation?: OAuthClientInformationFull;\n\n public authorizationServerMetadata?: AuthorizationServerMetadata;\n\n constructor(config: OAuthConfig) {\n this.sessionId = config.sessionId ?? generateSessionId();\n const storage = config.storage ?? new MemoryStorage();\n\n // Set authorization server metadata if provided\n if (config.authorizationServerMetadata) {\n this.authorizationServerMetadata =\n config.authorizationServerMetadata as AuthorizationServerMetadata;\n }\n\n this.oauthStorage = new OAuthStorage(storage, this.sessionId, {\n staticClientInfo: config.clientId\n ? {\n client_id: config.clientId,\n client_secret: config.clientSecret,\n }\n : undefined,\n initialTokens: config.tokens,\n });\n\n // Merge with defaults\n this.config = {\n clientId: config.clientId ?? '',\n clientSecret: config.clientSecret ?? undefined,\n redirectUri: config.redirectUri,\n scope: config.scope ?? 'read write',\n sessionId: this.sessionId,\n storage,\n tokens: config.tokens,\n clientMetadata: {\n ...DEFAULT_CLIENT_METADATA,\n ...(config.clientMetadata ?? {}),\n redirect_uris: [config.redirectUri], // Always required\n scope: config.scope ?? DEFAULT_CLIENT_METADATA.scope,\n },\n tokenRefresh: {\n maxRetries: 3,\n retryDelay: 1000,\n ...(config.tokenRefresh ?? {}),\n },\n };\n }\n\n /**\n * The URL to redirect the user agent to after authorization\n */\n get redirectUrl(): string | URL {\n return this.config.redirectUri;\n }\n\n /**\n * Metadata about this OAuth client\n */\n get clientMetadata(): OAuthClientMetadata {\n return this.config.clientMetadata;\n }\n\n /**\n * Returns a OAuth2 state parameter for CSRF protection\n */\n async state(): Promise<string> {\n return generateState();\n }\n\n /**\n * Loads information about this OAuth client\n * Config credentials are initialized into storage, so all reads go through storage\n */\n async clientInformation(): Promise<OAuthClientInformation | undefined> {\n return await this.oauthStorage.getClientInfo();\n }\n\n /**\n * Saves client information after dynamic registration\n */\n async saveClientInformation(\n clientInformation: OAuthClientInformationFull\n ): Promise<void> {\n this.cachedClientInformation = clientInformation;\n\n await this.oauthStorage.saveClientInfo(clientInformation);\n }\n\n /**\n * Loads any existing OAuth tokens for the current session\n * Automatically refreshes tokens if they're expired or about to expire (within 5 minutes)\n * Requires authorizationServerMetadata to be set (from auth flow) for automatic refresh\n */\n async tokens(): Promise<OAuthTokens | undefined> {\n const currentTokens = await this.oauthStorage.getTokens();\n\n if (!currentTokens) {\n return undefined;\n }\n\n // Check if tokens are expired or about to expire (within 5 minutes)\n const needsRefresh = areTokensExpired(currentTokens, undefined, 300);\n\n // If tokens need refresh and we have the server metadata and refresh token, refresh automatically\n if (\n needsRefresh &&\n currentTokens.refresh_token &&\n this.authorizationServerMetadata?.token_endpoint\n ) {\n try {\n // Use the token endpoint from authorization server metadata\n const newTokens = await this.refreshTokens();\n\n return newTokens;\n } catch (error) {\n // If refresh fails, return the current tokens and let the caller handle it\n // This prevents breaking existing flows that might handle refresh differently\n return currentTokens;\n }\n }\n\n return currentTokens;\n }\n\n /**\n * Refresh tokens using helper function\n * Uses the token endpoint from authorizationServerMetadata\n *\n * @returns New OAuth tokens\n * @throws Error if no authorization server metadata is available\n */\n async refreshTokens(): Promise<OAuthTokens> {\n if (!this.authorizationServerMetadata?.token_endpoint) {\n throw new Error(\n 'No authorization server metadata available. Cannot refresh tokens without token_endpoint.'\n );\n }\n\n const { maxRetries, retryDelay } = this.config.tokenRefresh;\n\n const currentTokens = await this.oauthStorage.getTokens();\n\n if (!currentTokens?.refresh_token) {\n throw new Error('No refresh token available');\n }\n\n const clientInfo = await this.clientInformation();\n\n if (!clientInfo) {\n throw new Error('No client information available');\n }\n\n try {\n // Use helper function for retry logic with token endpoint\n const newTokens = await refreshTokensWithRetry(\n this.authorizationServerMetadata.token_endpoint,\n clientInfo,\n currentTokens.refresh_token,\n this.addClientAuthentication,\n maxRetries,\n retryDelay\n );\n\n // Save the new tokens\n await this.oauthStorage.saveTokens(newTokens);\n\n return newTokens;\n } catch (error) {\n // All retries failed, invalidate tokens\n await this.invalidateCredentials('tokens');\n throw error;\n }\n }\n\n /**\n * Loads any existing OAuth tokens for the current session (without auto-refresh)\n * Use this if you want to check tokens without triggering a refresh\n */\n async getStoredTokens(): Promise<OAuthTokens | undefined> {\n return this.oauthStorage.getTokens();\n }\n\n /**\n * Stores new OAuth tokens for the current session\n */\n async saveTokens(tokens: OAuthTokens): Promise<void> {\n await this.oauthStorage.saveTokens(tokens);\n }\n\n /**\n * Invoked to redirect the user agent to the given URL to begin the authorization flow\n */\n async redirectToAuthorization(authorizationUrl: URL): Promise<void> {\n // In a real implementation, this would open a browser or redirect the user\n // For now, we'll let the caller handle the redirect by throwing an error with the URL\n\n // In a Bun environment, we could automatically open the browser:\n if (typeof Bun !== 'undefined') {\n try {\n await Bun.$`open ${authorizationUrl.toString()}`;\n\n return;\n } catch {\n // Fallback if 'open' command is not available\n }\n }\n\n // If we can't automatically open, throw an error with the URL for the caller to handle\n throw new Error(`Please navigate to: ${authorizationUrl.toString()}`);\n }\n\n /**\n * Saves a PKCE code verifier for the current session\n */\n async saveCodeVerifier(codeVerifier: string): Promise<void> {\n await this.oauthStorage.saveCodeVerifier(codeVerifier);\n }\n\n /**\n * Loads the PKCE code verifier for the current session\n */\n async codeVerifier(): Promise<string> {\n const verifier = await this.oauthStorage.getCodeVerifier();\n\n if (!verifier) {\n throw new Error('No code verifier found for current session');\n }\n\n return verifier;\n }\n\n /**\n * Adds custom client authentication to OAuth token requests\n */\n addClientAuthentication = (\n headers: Headers,\n params: URLSearchParams,\n url: string | URL,\n metadata?: AuthorizationServerMetadata\n ): void => {\n const clientInfo = this.cachedClientInformation;\n\n this.authorizationServerMetadata = metadata;\n\n if (!clientInfo) {\n throw new Error('No client information available for authentication');\n }\n\n // Use client_secret_post method by default\n params.set('client_id', clientInfo.client_id);\n\n if (clientInfo.client_secret) {\n params.set('client_secret', clientInfo.client_secret);\n }\n };\n\n /**\n * Validates the resource URL for OAuth requests\n */\n async validateResourceURL(\n serverUrl: string | URL,\n resource?: string\n ): Promise<URL | undefined> {\n // Simple validation - in a real implementation you might want more sophisticated logic\n if (resource) {\n try {\n return new URL(resource);\n } catch {\n throw new Error(`Invalid resource URL: ${resource}`);\n }\n }\n\n // Default to server URL if no specific resource is provided\n return new URL(serverUrl);\n }\n\n /**\n * Invalidates stored credentials based on the specified scope\n */\n async invalidateCredentials(\n scope: 'all' | 'client' | 'tokens' | 'verifier'\n ): Promise<void> {\n switch (scope) {\n case 'all':\n await this.oauthStorage.clearAll();\n break;\n case 'client':\n await this.oauthStorage.clearClientInfo();\n break;\n case 'tokens':\n await this.oauthStorage.clearTokens();\n break;\n case 'verifier':\n await this.oauthStorage.clearCodeVerifier();\n break;\n }\n }\n\n /**\n * Get the current session ID\n */\n getSessionId(): string {\n return this.sessionId;\n }\n\n /**\n * Get the OAuth storage helper\n */\n getOAuthStorage(): OAuthStorage {\n return this.oauthStorage;\n }\n\n /**\n * Clear the current session\n */\n async clearSession(): Promise<void> {\n await this.oauthStorage.clearSession();\n }\n}\n"],"names":["DEFAULT_CLIENT_METADATA","generateSessionId","generateState","areTokensExpired","refreshTokensWithRetry","MemoryStorage","OAuthStorage","MCPOAuthClientProvider","redirectUrl","config","redirectUri","clientMetadata","state","clientInformation","oauthStorage","getClientInfo","saveClientInformation","cachedClientInformation","saveClientInfo","tokens","currentTokens","getTokens","undefined","needsRefresh","refresh_token","authorizationServerMetadata","token_endpoint","newTokens","refreshTokens","error","Error","maxRetries","retryDelay","tokenRefresh","clientInfo","addClientAuthentication","saveTokens","invalidateCredentials","getStoredTokens","redirectToAuthorization","authorizationUrl","Bun","$","toString","saveCodeVerifier","codeVerifier","verifier","getCodeVerifier","validateResourceURL","serverUrl","resource","URL","scope","clearAll","clearClientInfo","clearTokens","clearCodeVerifier","getSessionId","sessionId","getOAuthStorage","clearSession","headers","params","url","metadata","set","client_id","client_secret","storage","staticClientInfo","clientId","clientSecret","initialTokens","redirect_uris"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/client/index.ts"],"sourcesContent":["import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';\nimport { AuthorizationServerMetadata } from '@modelcontextprotocol/sdk/shared/auth.js';\nimport type {\n OAuthClientInformation,\n OAuthClientInformationFull,\n OAuthClientMetadata,\n OAuthConfig,\n OAuthTokens,\n} from './config.js';\nimport {\n DEFAULT_CLIENT_METADATA,\n generateSessionId,\n generateState,\n} from './config.js';\nimport { areTokensExpired, refreshTokensWithRetry } from './oauth-flow.js';\nimport { MemoryStorage, OAuthStorage } from './storage.js';\n\n/**\n * Options for token refresh\n */\ninterface TokenRefreshConfig {\n maxRetries: number;\n retryDelay: number;\n}\n\n/**\n * MCP OAuth Client Provider implementation with automatic token refresh\n */\nexport class MCPOAuthClientProvider implements OAuthClientProvider {\n private readonly oauthStorage: OAuthStorage;\n private readonly sessionId: string;\n private readonly redirectUri: string | undefined;\n private readonly tokenRefreshConfig: TokenRefreshConfig;\n private readonly _clientMetadata: OAuthClientMetadata;\n private cachedClientInformation?: OAuthClientInformationFull;\n\n public tokenEndpoint?: string;\n public authorizationServerMetadata?: AuthorizationServerMetadata;\n\n constructor(config: OAuthConfig) {\n this.sessionId = config.sessionId ?? generateSessionId();\n const storage = config.storage ?? new MemoryStorage();\n\n // Set token endpoint if provided\n if (config.tokenEndpoint) {\n this.tokenEndpoint = config.tokenEndpoint;\n }\n\n // Store redirect URI\n this.redirectUri = config.redirectUri;\n\n // Store token refresh configuration\n this.tokenRefreshConfig = {\n maxRetries: 3,\n retryDelay: 1000,\n ...(config.tokenRefresh ?? {}),\n };\n\n // Store client metadata\n this._clientMetadata = {\n ...DEFAULT_CLIENT_METADATA,\n ...(config.clientMetadata ?? {}),\n redirect_uris: config.redirectUri ? [config.redirectUri] : [], // Always required\n scope: config.scope ?? DEFAULT_CLIENT_METADATA.scope,\n };\n\n this.oauthStorage = new OAuthStorage(storage, this.sessionId, {\n staticClientInfo: config.clientId\n ? {\n client_id: config.clientId,\n client_secret: config.clientSecret,\n }\n : undefined,\n initialTokens: config.tokens,\n });\n }\n\n /**\n * The URL to redirect the user agent to after authorization\n */\n get redirectUrl(): string | URL {\n if (!this.redirectUri) {\n throw new Error('No redirect URI configured for this OAuth client');\n }\n\n return this.redirectUri;\n }\n\n /**\n * Metadata about this OAuth client\n */\n get clientMetadata(): OAuthClientMetadata {\n return this._clientMetadata;\n }\n\n /**\n * Returns a OAuth2 state parameter for CSRF protection\n */\n async state(): Promise<string> {\n return generateState();\n }\n\n /**\n * Loads information about this OAuth client\n * Config credentials are initialized into storage, so all reads go through storage\n */\n async clientInformation(): Promise<OAuthClientInformation | undefined> {\n return await this.oauthStorage.getClientInfo();\n }\n\n /**\n * Saves client information after dynamic registration\n */\n async saveClientInformation(\n clientInformation: OAuthClientInformationFull\n ): Promise<void> {\n this.cachedClientInformation = clientInformation;\n\n await this.oauthStorage.saveClientInfo(clientInformation);\n }\n\n /**\n * Loads any existing OAuth tokens for the current session\n * Automatically refreshes tokens if they're expired or about to expire (within 5 minutes)\n * Requires authorizationServerMetadata to be set (from auth flow) for automatic refresh\n */\n async tokens(): Promise<OAuthTokens | undefined> {\n const currentTokens = await this.oauthStorage.getTokens();\n\n if (!currentTokens) {\n return undefined;\n }\n\n // Check if tokens are expired or about to expire (within 5 minutes)\n const needsRefresh = areTokensExpired(currentTokens, undefined, 300);\n\n // If tokens need refresh and we have the server metadata and refresh token, refresh automatically\n if (needsRefresh && currentTokens.refresh_token && this.tokenEndpoint) {\n try {\n // Use the token endpoint from authorization server metadata\n const newTokens = await this.refreshTokens();\n\n return newTokens;\n } catch (error) {\n // If refresh fails, return the current tokens and let the caller handle it\n // This prevents breaking existing flows that might handle refresh differently\n return currentTokens;\n }\n }\n\n return currentTokens;\n }\n\n /**\n * Refresh tokens using helper function\n * Uses the token endpoint from authorizationServerMetadata\n *\n * @returns New OAuth tokens\n * @throws Error if no authorization server metadata is available\n */\n async refreshTokens(): Promise<OAuthTokens> {\n if (!this.tokenEndpoint) {\n throw new Error(\n 'No token endpoint available. Cannot refresh tokens without token_endpoint.'\n );\n }\n\n const { maxRetries, retryDelay } = this.tokenRefreshConfig;\n\n const currentTokens = await this.oauthStorage.getTokens();\n\n if (!currentTokens?.refresh_token) {\n throw new Error('No refresh token available');\n }\n\n const clientInfo = await this.clientInformation();\n\n if (!clientInfo) {\n throw new Error('No client information available');\n }\n\n try {\n // Use helper function for retry logic with token endpoint\n const newTokens = await refreshTokensWithRetry(\n this.tokenEndpoint,\n clientInfo,\n currentTokens.refresh_token,\n this.addClientAuthentication,\n maxRetries,\n retryDelay\n );\n\n // Save the new tokens\n await this.oauthStorage.saveTokens(newTokens);\n\n return newTokens;\n } catch (error) {\n // All retries failed, invalidate tokens\n await this.invalidateCredentials('tokens');\n throw error;\n }\n }\n\n /**\n * Loads any existing OAuth tokens for the current session (without auto-refresh)\n * Use this if you want to check tokens without triggering a refresh\n */\n async getStoredTokens(): Promise<OAuthTokens | undefined> {\n return this.oauthStorage.getTokens();\n }\n\n /**\n * Stores new OAuth tokens for the current session\n */\n async saveTokens(tokens: OAuthTokens): Promise<void> {\n await this.oauthStorage.saveTokens(tokens);\n }\n\n /**\n * Invoked to redirect the user agent to the given URL to begin the authorization flow\n */\n async redirectToAuthorization(authorizationUrl: URL): Promise<void> {\n // In a real implementation, this would open a browser or redirect the user\n // For now, we'll let the caller handle the redirect by throwing an error with the URL\n\n // In a Bun environment, we could automatically open the browser:\n if (typeof Bun !== 'undefined') {\n try {\n await Bun.$`open ${authorizationUrl.toString()}`;\n\n return;\n } catch {\n // Fallback if 'open' command is not available\n }\n }\n\n // If we can't automatically open, throw an error with the URL for the caller to handle\n throw new Error(`Please navigate to: ${authorizationUrl.toString()}`);\n }\n\n /**\n * Saves a PKCE code verifier for the current session\n */\n async saveCodeVerifier(codeVerifier: string): Promise<void> {\n await this.oauthStorage.saveCodeVerifier(codeVerifier);\n }\n\n /**\n * Loads the PKCE code verifier for the current session\n */\n async codeVerifier(): Promise<string> {\n const verifier = await this.oauthStorage.getCodeVerifier();\n\n if (!verifier) {\n throw new Error('No code verifier found for current session');\n }\n\n return verifier;\n }\n\n /**\n * Adds custom client authentication to OAuth token requests\n */\n addClientAuthentication = (\n headers: Headers,\n params: URLSearchParams,\n url: string | URL,\n metadata?: AuthorizationServerMetadata\n ): void => {\n const clientInfo = this.cachedClientInformation;\n\n this.authorizationServerMetadata = metadata;\n this.tokenEndpoint = metadata?.token_endpoint;\n\n if (!clientInfo) {\n throw new Error('No client information available for authentication');\n }\n\n // Use client_secret_post method by default\n params.set('client_id', clientInfo.client_id);\n\n if (clientInfo.client_secret) {\n params.set('client_secret', clientInfo.client_secret);\n }\n };\n\n /**\n * Validates the resource URL for OAuth requests\n */\n async validateResourceURL(\n serverUrl: string | URL,\n resource?: string\n ): Promise<URL | undefined> {\n // Simple validation - in a real implementation you might want more sophisticated logic\n if (resource) {\n try {\n return new URL(resource);\n } catch {\n throw new Error(`Invalid resource URL: ${resource}`);\n }\n }\n\n // Default to server URL if no specific resource is provided\n return new URL(serverUrl);\n }\n\n /**\n * Invalidates stored credentials based on the specified scope\n */\n async invalidateCredentials(\n scope: 'all' | 'client' | 'tokens' | 'verifier'\n ): Promise<void> {\n switch (scope) {\n case 'all':\n await this.oauthStorage.clearAll();\n break;\n case 'client':\n await this.oauthStorage.clearClientInfo();\n break;\n case 'tokens':\n await this.oauthStorage.clearTokens();\n break;\n case 'verifier':\n await this.oauthStorage.clearCodeVerifier();\n break;\n }\n }\n\n /**\n * Get the current session ID\n */\n getSessionId(): string {\n return this.sessionId;\n }\n\n /**\n * Get the OAuth storage helper\n */\n getOAuthStorage(): OAuthStorage {\n return this.oauthStorage;\n }\n\n /**\n * Clear the current session\n */\n async clearSession(): Promise<void> {\n await this.oauthStorage.clearSession();\n }\n}\n"],"names":["DEFAULT_CLIENT_METADATA","generateSessionId","generateState","areTokensExpired","refreshTokensWithRetry","MemoryStorage","OAuthStorage","MCPOAuthClientProvider","redirectUrl","redirectUri","Error","clientMetadata","_clientMetadata","state","clientInformation","oauthStorage","getClientInfo","saveClientInformation","cachedClientInformation","saveClientInfo","tokens","currentTokens","getTokens","undefined","needsRefresh","refresh_token","tokenEndpoint","newTokens","refreshTokens","error","maxRetries","retryDelay","tokenRefreshConfig","clientInfo","addClientAuthentication","saveTokens","invalidateCredentials","getStoredTokens","redirectToAuthorization","authorizationUrl","Bun","$","toString","saveCodeVerifier","codeVerifier","verifier","getCodeVerifier","validateResourceURL","serverUrl","resource","URL","scope","clearAll","clearClientInfo","clearTokens","clearCodeVerifier","getSessionId","sessionId","getOAuthStorage","clearSession","config","authorizationServerMetadata","headers","params","url","metadata","token_endpoint","set","client_id","client_secret","storage","tokenRefresh","redirect_uris","staticClientInfo","clientId","clientSecret","initialTokens"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -97,34 +97,6 @@ describe('MCPOAuthClientProvider Integration', () => {
|
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
describe('authorization server metadata', () => {
|
|
101
|
-
test('should accept authorizationServerMetadata through constructor', () => {
|
|
102
|
-
const metadata = {
|
|
103
|
-
issuer: 'https://auth.example.com',
|
|
104
|
-
authorization_endpoint: 'https://auth.example.com/authorize',
|
|
105
|
-
token_endpoint: 'https://auth.example.com/token',
|
|
106
|
-
};
|
|
107
|
-
const provider = new MCPOAuthClientProvider({
|
|
108
|
-
...config,
|
|
109
|
-
authorizationServerMetadata: metadata,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
expect(provider.authorizationServerMetadata).toBeDefined();
|
|
113
|
-
expect(provider.authorizationServerMetadata?.issuer).toBe(
|
|
114
|
-
'https://auth.example.com'
|
|
115
|
-
);
|
|
116
|
-
expect(provider.authorizationServerMetadata?.token_endpoint).toBe(
|
|
117
|
-
'https://auth.example.com/token'
|
|
118
|
-
);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('should be undefined when not provided', () => {
|
|
122
|
-
const provider = new MCPOAuthClientProvider(config);
|
|
123
|
-
|
|
124
|
-
expect(provider.authorizationServerMetadata).toBeUndefined();
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
100
|
describe('token management', () => {
|
|
129
101
|
test('should return undefined when no tokens exist', async () => {
|
|
130
102
|
const provider = new MCPOAuthClientProvider(config);
|
package/src/client/config.ts
CHANGED
|
@@ -49,7 +49,7 @@ export interface OAuthConfig {
|
|
|
49
49
|
/**
|
|
50
50
|
* Redirect URI for OAuth callbacks
|
|
51
51
|
*/
|
|
52
|
-
redirectUri
|
|
52
|
+
redirectUri?: string;
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* OAuth scope to request
|
|
@@ -76,6 +76,11 @@ export interface OAuthConfig {
|
|
|
76
76
|
*/
|
|
77
77
|
tokens?: OAuthTokens;
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Token refresh endpoint URL (optional, can be obtained from metadata)
|
|
81
|
+
*/
|
|
82
|
+
tokenEndpoint?: string;
|
|
83
|
+
|
|
79
84
|
/**
|
|
80
85
|
* Token refresh configuration
|
|
81
86
|
*/
|
|
@@ -90,16 +95,6 @@ export interface OAuthConfig {
|
|
|
90
95
|
*/
|
|
91
96
|
retryDelay?: number;
|
|
92
97
|
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Authorization server metadata (can be provided or obtained during auth flow)
|
|
96
|
-
*/
|
|
97
|
-
authorizationServerMetadata?: {
|
|
98
|
-
issuer: string;
|
|
99
|
-
authorization_endpoint: string;
|
|
100
|
-
token_endpoint: string;
|
|
101
|
-
[key: string]: unknown;
|
|
102
|
-
};
|
|
103
98
|
}
|
|
104
99
|
|
|
105
100
|
/**
|
package/src/client/index.ts
CHANGED
|
@@ -26,35 +26,44 @@ interface TokenRefreshConfig {
|
|
|
26
26
|
/**
|
|
27
27
|
* MCP OAuth Client Provider implementation with automatic token refresh
|
|
28
28
|
*/
|
|
29
|
-
interface ProcessedOAuthConfig
|
|
30
|
-
extends Omit<
|
|
31
|
-
Required<OAuthConfig>,
|
|
32
|
-
'clientSecret' | 'tokens' | 'authorizationServerMetadata'
|
|
33
|
-
> {
|
|
34
|
-
clientSecret?: string;
|
|
35
|
-
tokens?: OAuthTokens;
|
|
36
|
-
clientMetadata: OAuthClientMetadata;
|
|
37
|
-
tokenRefresh: TokenRefreshConfig;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
29
|
export class MCPOAuthClientProvider implements OAuthClientProvider {
|
|
41
|
-
private readonly config: ProcessedOAuthConfig;
|
|
42
30
|
private readonly oauthStorage: OAuthStorage;
|
|
43
31
|
private readonly sessionId: string;
|
|
32
|
+
private readonly redirectUri: string | undefined;
|
|
33
|
+
private readonly tokenRefreshConfig: TokenRefreshConfig;
|
|
34
|
+
private readonly _clientMetadata: OAuthClientMetadata;
|
|
44
35
|
private cachedClientInformation?: OAuthClientInformationFull;
|
|
45
36
|
|
|
37
|
+
public tokenEndpoint?: string;
|
|
46
38
|
public authorizationServerMetadata?: AuthorizationServerMetadata;
|
|
47
39
|
|
|
48
40
|
constructor(config: OAuthConfig) {
|
|
49
41
|
this.sessionId = config.sessionId ?? generateSessionId();
|
|
50
42
|
const storage = config.storage ?? new MemoryStorage();
|
|
51
43
|
|
|
52
|
-
// Set
|
|
53
|
-
if (config.
|
|
54
|
-
this.
|
|
55
|
-
config.authorizationServerMetadata as AuthorizationServerMetadata;
|
|
44
|
+
// Set token endpoint if provided
|
|
45
|
+
if (config.tokenEndpoint) {
|
|
46
|
+
this.tokenEndpoint = config.tokenEndpoint;
|
|
56
47
|
}
|
|
57
48
|
|
|
49
|
+
// Store redirect URI
|
|
50
|
+
this.redirectUri = config.redirectUri;
|
|
51
|
+
|
|
52
|
+
// Store token refresh configuration
|
|
53
|
+
this.tokenRefreshConfig = {
|
|
54
|
+
maxRetries: 3,
|
|
55
|
+
retryDelay: 1000,
|
|
56
|
+
...(config.tokenRefresh ?? {}),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Store client metadata
|
|
60
|
+
this._clientMetadata = {
|
|
61
|
+
...DEFAULT_CLIENT_METADATA,
|
|
62
|
+
...(config.clientMetadata ?? {}),
|
|
63
|
+
redirect_uris: config.redirectUri ? [config.redirectUri] : [], // Always required
|
|
64
|
+
scope: config.scope ?? DEFAULT_CLIENT_METADATA.scope,
|
|
65
|
+
};
|
|
66
|
+
|
|
58
67
|
this.oauthStorage = new OAuthStorage(storage, this.sessionId, {
|
|
59
68
|
staticClientInfo: config.clientId
|
|
60
69
|
? {
|
|
@@ -64,42 +73,24 @@ export class MCPOAuthClientProvider implements OAuthClientProvider {
|
|
|
64
73
|
: undefined,
|
|
65
74
|
initialTokens: config.tokens,
|
|
66
75
|
});
|
|
67
|
-
|
|
68
|
-
// Merge with defaults
|
|
69
|
-
this.config = {
|
|
70
|
-
clientId: config.clientId ?? '',
|
|
71
|
-
clientSecret: config.clientSecret ?? undefined,
|
|
72
|
-
redirectUri: config.redirectUri,
|
|
73
|
-
scope: config.scope ?? 'read write',
|
|
74
|
-
sessionId: this.sessionId,
|
|
75
|
-
storage,
|
|
76
|
-
tokens: config.tokens,
|
|
77
|
-
clientMetadata: {
|
|
78
|
-
...DEFAULT_CLIENT_METADATA,
|
|
79
|
-
...(config.clientMetadata ?? {}),
|
|
80
|
-
redirect_uris: [config.redirectUri], // Always required
|
|
81
|
-
scope: config.scope ?? DEFAULT_CLIENT_METADATA.scope,
|
|
82
|
-
},
|
|
83
|
-
tokenRefresh: {
|
|
84
|
-
maxRetries: 3,
|
|
85
|
-
retryDelay: 1000,
|
|
86
|
-
...(config.tokenRefresh ?? {}),
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
76
|
}
|
|
90
77
|
|
|
91
78
|
/**
|
|
92
79
|
* The URL to redirect the user agent to after authorization
|
|
93
80
|
*/
|
|
94
81
|
get redirectUrl(): string | URL {
|
|
95
|
-
|
|
82
|
+
if (!this.redirectUri) {
|
|
83
|
+
throw new Error('No redirect URI configured for this OAuth client');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return this.redirectUri;
|
|
96
87
|
}
|
|
97
88
|
|
|
98
89
|
/**
|
|
99
90
|
* Metadata about this OAuth client
|
|
100
91
|
*/
|
|
101
92
|
get clientMetadata(): OAuthClientMetadata {
|
|
102
|
-
return this.
|
|
93
|
+
return this._clientMetadata;
|
|
103
94
|
}
|
|
104
95
|
|
|
105
96
|
/**
|
|
@@ -144,11 +135,7 @@ export class MCPOAuthClientProvider implements OAuthClientProvider {
|
|
|
144
135
|
const needsRefresh = areTokensExpired(currentTokens, undefined, 300);
|
|
145
136
|
|
|
146
137
|
// If tokens need refresh and we have the server metadata and refresh token, refresh automatically
|
|
147
|
-
if (
|
|
148
|
-
needsRefresh &&
|
|
149
|
-
currentTokens.refresh_token &&
|
|
150
|
-
this.authorizationServerMetadata?.token_endpoint
|
|
151
|
-
) {
|
|
138
|
+
if (needsRefresh && currentTokens.refresh_token && this.tokenEndpoint) {
|
|
152
139
|
try {
|
|
153
140
|
// Use the token endpoint from authorization server metadata
|
|
154
141
|
const newTokens = await this.refreshTokens();
|
|
@@ -172,13 +159,13 @@ export class MCPOAuthClientProvider implements OAuthClientProvider {
|
|
|
172
159
|
* @throws Error if no authorization server metadata is available
|
|
173
160
|
*/
|
|
174
161
|
async refreshTokens(): Promise<OAuthTokens> {
|
|
175
|
-
if (!this.
|
|
162
|
+
if (!this.tokenEndpoint) {
|
|
176
163
|
throw new Error(
|
|
177
|
-
'No
|
|
164
|
+
'No token endpoint available. Cannot refresh tokens without token_endpoint.'
|
|
178
165
|
);
|
|
179
166
|
}
|
|
180
167
|
|
|
181
|
-
const { maxRetries, retryDelay } = this.
|
|
168
|
+
const { maxRetries, retryDelay } = this.tokenRefreshConfig;
|
|
182
169
|
|
|
183
170
|
const currentTokens = await this.oauthStorage.getTokens();
|
|
184
171
|
|
|
@@ -195,7 +182,7 @@ export class MCPOAuthClientProvider implements OAuthClientProvider {
|
|
|
195
182
|
try {
|
|
196
183
|
// Use helper function for retry logic with token endpoint
|
|
197
184
|
const newTokens = await refreshTokensWithRetry(
|
|
198
|
-
this.
|
|
185
|
+
this.tokenEndpoint,
|
|
199
186
|
clientInfo,
|
|
200
187
|
currentTokens.refresh_token,
|
|
201
188
|
this.addClientAuthentication,
|
|
@@ -283,6 +270,7 @@ export class MCPOAuthClientProvider implements OAuthClientProvider {
|
|
|
283
270
|
const clientInfo = this.cachedClientInformation;
|
|
284
271
|
|
|
285
272
|
this.authorizationServerMetadata = metadata;
|
|
273
|
+
this.tokenEndpoint = metadata?.token_endpoint;
|
|
286
274
|
|
|
287
275
|
if (!clientInfo) {
|
|
288
276
|
throw new Error('No client information available for authentication');
|