mcp-oauth-provider 0.0.2 → 0.0.3
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/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
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');
|