@mcp-ts/sdk 2.3.3 → 2.4.0
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/README.md +113 -30
- package/dist/adapters/agui-adapter.d.mts +3 -3
- package/dist/adapters/agui-adapter.d.ts +3 -3
- package/dist/adapters/agui-middleware.d.mts +3 -3
- package/dist/adapters/agui-middleware.d.ts +3 -3
- package/dist/adapters/ai-adapter.d.mts +3 -3
- package/dist/adapters/ai-adapter.d.ts +3 -3
- package/dist/adapters/langchain-adapter.d.mts +3 -3
- package/dist/adapters/langchain-adapter.d.ts +3 -3
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +2 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +9 -8
- package/dist/client/react.d.ts +9 -8
- package/dist/client/react.js +66 -26
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +67 -27
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +6 -5
- package/dist/client/vue.d.ts +6 -5
- package/dist/client/vue.js +27 -17
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +27 -17
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-Cfjsme-a.d.mts → index-ByIjEReo.d.mts} +2 -2
- package/dist/{index-CmjMd2ac.d.ts → index-CtXvKl8N.d.ts} +2 -2
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +729 -397
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +724 -396
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-DYNe6az3.d.mts → multi-session-client-CIMUGF8S.d.mts} +15 -41
- package/dist/{multi-session-client-BYtguGJm.d.ts → multi-session-client-CnvZEGPY.d.ts} +15 -41
- package/dist/server/index.d.mts +45 -17
- package/dist/server/index.d.ts +45 -17
- package/dist/server/index.js +721 -391
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +718 -391
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +9 -8
- package/dist/shared/index.d.ts +9 -8
- package/dist/shared/index.js +7 -5
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +5 -4
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-BMzhoNYt.d.ts → tool-router-CZMrOG8J.d.ts} +1 -1
- package/dist/{tool-router-BhHsvBfP.d.mts → tool-router-CuApsDiV.d.mts} +1 -1
- package/dist/{types-CxFaaZrM.d.mts → types-DCk_IF4L.d.mts} +5 -3
- package/dist/{types-CxFaaZrM.d.ts → types-DCk_IF4L.d.ts} +5 -3
- package/migrations/neon/20260513010000_install_mcp_sessions.sql +40 -3
- package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +4 -4
- package/migrations/supabase/20260330195700_install_mcp_sessions.sql +67 -3
- package/migrations/supabase/20260421010000_add_session_cleanup_cron.sql +6 -6
- package/package.json +13 -3
- package/src/client/core/sse-client.ts +2 -2
- package/src/client/react/index.ts +1 -1
- package/src/client/react/oauth-popup.tsx +34 -13
- package/src/client/react/use-mcp.ts +19 -45
- package/src/client/utils/session-state.ts +43 -0
- package/src/client/vue/use-mcp.ts +18 -47
- package/src/server/handlers/sse-handler.ts +9 -4
- package/src/server/mcp/multi-session-client.ts +2 -6
- package/src/server/mcp/oauth-client.ts +73 -220
- package/src/server/mcp/storage-oauth-provider.ts +79 -50
- package/src/server/storage/file-backend.ts +74 -18
- package/src/server/storage/index.ts +2 -4
- package/src/server/storage/memory-backend.ts +49 -13
- package/src/server/storage/neon-backend.ts +201 -68
- package/src/server/storage/redis-backend.ts +81 -23
- package/src/server/storage/session-lifecycle.ts +78 -0
- package/src/server/storage/sqlite-backend.ts +89 -15
- package/src/server/storage/supabase-backend.ts +188 -63
- package/src/server/storage/types.ts +49 -16
- package/src/shared/constants.ts +5 -6
- package/src/shared/types.ts +5 -3
- package/src/shared/utils.ts +26 -0
|
@@ -4,9 +4,6 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
|
|
|
4
4
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
5
5
|
import {
|
|
6
6
|
UnauthorizedError as SDKUnauthorizedError,
|
|
7
|
-
refreshAuthorization,
|
|
8
|
-
discoverOAuthProtectedResourceMetadata,
|
|
9
|
-
discoverAuthorizationServerMetadata,
|
|
10
7
|
} from '@modelcontextprotocol/sdk/client/auth.js';
|
|
11
8
|
import {
|
|
12
9
|
ListToolsRequest,
|
|
@@ -28,17 +25,14 @@ import {
|
|
|
28
25
|
ReadResourceResult,
|
|
29
26
|
ReadResourceResultSchema,
|
|
30
27
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
31
|
-
import type { OAuthTokens, OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
32
28
|
import { StorageOAuthClientProvider, type AgentsOAuthProvider } from './storage-oauth-provider.js';
|
|
33
|
-
import { sanitizeServerLabel } from '../../shared/utils.js';
|
|
34
29
|
import { Emitter, type McpConnectionEvent, type McpObservabilityEvent, type McpConnectionState } from '../../shared/events.js';
|
|
35
30
|
import { UnauthorizedError } from '../../shared/errors.js';
|
|
36
31
|
import { sessions } from '../storage/index.js';
|
|
32
|
+
import type { Session, SessionStatus } from '../storage/types.js';
|
|
37
33
|
import {
|
|
38
34
|
MCP_CLIENT_NAME,
|
|
39
35
|
MCP_CLIENT_VERSION,
|
|
40
|
-
SESSION_TTL_SECONDS,
|
|
41
|
-
STATE_EXPIRATION_MS,
|
|
42
36
|
} from '../../shared/constants.js';
|
|
43
37
|
|
|
44
38
|
/**
|
|
@@ -60,20 +54,6 @@ interface McpAppClientCapabilities extends Omit<ClientCapabilities, 'extensions'
|
|
|
60
54
|
};
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
function isInvalidRefreshTokenError(error: unknown): boolean {
|
|
64
|
-
if (!(error instanceof Error)) {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const text = `${error.name} ${error.message}`.toLowerCase();
|
|
69
|
-
return (
|
|
70
|
-
text.includes('invalidgrant') ||
|
|
71
|
-
text.includes('invalid_grant') ||
|
|
72
|
-
text.includes('invalid refresh token') ||
|
|
73
|
-
/refresh\s+token\s+(?:is\s+)?(?:invalid|expired|revoked)/i.test(text)
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
57
|
export interface MCPOAuthClientOptions {
|
|
78
58
|
serverUrl?: string;
|
|
79
59
|
serverName?: string;
|
|
@@ -296,23 +276,25 @@ export class MCPClient {
|
|
|
296
276
|
this.emitStateChange('INITIALIZING');
|
|
297
277
|
this.emitProgress('Loading session configuration...');
|
|
298
278
|
|
|
279
|
+
let existingSession: Session | null = null;
|
|
280
|
+
|
|
299
281
|
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
300
|
-
|
|
301
|
-
if (!
|
|
282
|
+
existingSession = await sessions.get(this.userId, this.sessionId);
|
|
283
|
+
if (!existingSession) {
|
|
302
284
|
throw new Error(`Session not found: ${this.sessionId}`);
|
|
303
285
|
}
|
|
304
286
|
|
|
305
|
-
this.serverUrl = this.serverUrl ||
|
|
306
|
-
this.callbackUrl = this.callbackUrl ||
|
|
287
|
+
this.serverUrl = this.serverUrl || existingSession.serverUrl;
|
|
288
|
+
this.callbackUrl = this.callbackUrl || existingSession.callbackUrl;
|
|
307
289
|
/**
|
|
308
290
|
* Do NOT load transportType from session if not explicitly provided.
|
|
309
291
|
* We want to re-negotiate (try streamable -> sse) on new connections if in "Auto" mode.
|
|
310
292
|
* this.transportType = this.transportType || sessionData.transportType;
|
|
311
293
|
*/
|
|
312
|
-
this.serverName = this.serverName ||
|
|
313
|
-
this.serverId = this.serverId ||
|
|
314
|
-
this.headers = this.headers ||
|
|
315
|
-
this.createdAt =
|
|
294
|
+
this.serverName = this.serverName || existingSession.serverName;
|
|
295
|
+
this.serverId = this.serverId || existingSession.serverId || 'unknown';
|
|
296
|
+
this.headers = this.headers || existingSession.headers;
|
|
297
|
+
this.createdAt = existingSession.createdAt;
|
|
316
298
|
}
|
|
317
299
|
|
|
318
300
|
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
@@ -363,10 +345,13 @@ export class MCPClient {
|
|
|
363
345
|
// Create session in the session store if it doesn't exist yet
|
|
364
346
|
// This is needed BEFORE OAuth flow starts because the OAuth provider
|
|
365
347
|
// will call saveCodeVerifier() which requires the session to exist
|
|
366
|
-
|
|
348
|
+
if (existingSession === null) {
|
|
349
|
+
existingSession = await sessions.get(this.userId, this.sessionId);
|
|
350
|
+
}
|
|
367
351
|
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
368
352
|
this.createdAt = Date.now();
|
|
369
|
-
|
|
353
|
+
const updatedAt = this.createdAt;
|
|
354
|
+
console.log(`[MCPClient] Creating pending session ${this.sessionId} for connection setup`);
|
|
370
355
|
await sessions.create({
|
|
371
356
|
sessionId: this.sessionId,
|
|
372
357
|
userId: this.userId,
|
|
@@ -377,21 +362,21 @@ export class MCPClient {
|
|
|
377
362
|
transportType: this.transportType || 'streamable-http',
|
|
378
363
|
headers: this.headers,
|
|
379
364
|
createdAt: this.createdAt,
|
|
380
|
-
|
|
381
|
-
|
|
365
|
+
updatedAt,
|
|
366
|
+
status: 'pending',
|
|
367
|
+
});
|
|
382
368
|
}
|
|
383
369
|
}
|
|
384
370
|
|
|
385
371
|
/**
|
|
386
372
|
* Saves current session state to the session store
|
|
387
373
|
* Creates new session if it doesn't exist, updates if it does
|
|
388
|
-
* @param
|
|
389
|
-
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
374
|
+
* @param status - Session lifecycle status used by storage cleanup
|
|
390
375
|
* @private
|
|
391
376
|
*/
|
|
392
377
|
private async saveSession(
|
|
393
|
-
|
|
394
|
-
|
|
378
|
+
status: SessionStatus = 'active',
|
|
379
|
+
existingSession?: Session | null
|
|
395
380
|
): Promise<void> {
|
|
396
381
|
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
397
382
|
return;
|
|
@@ -407,15 +392,33 @@ export class MCPClient {
|
|
|
407
392
|
transportType: (this.transportType || 'streamable-http') as TransportType,
|
|
408
393
|
headers: this.headers,
|
|
409
394
|
createdAt: this.createdAt || Date.now(),
|
|
410
|
-
|
|
395
|
+
updatedAt: Date.now(),
|
|
396
|
+
status,
|
|
411
397
|
};
|
|
398
|
+
if (status === 'active') {
|
|
399
|
+
(sessionData as typeof sessionData & { authUrl: null }).authUrl = null;
|
|
400
|
+
}
|
|
412
401
|
|
|
413
402
|
// Try to update first, create if doesn't exist
|
|
414
|
-
|
|
403
|
+
if (existingSession === undefined) {
|
|
404
|
+
existingSession = await sessions.get(this.userId, this.sessionId);
|
|
405
|
+
}
|
|
415
406
|
if (existingSession) {
|
|
416
|
-
await sessions.update(this.userId, this.sessionId, sessionData
|
|
407
|
+
await sessions.update(this.userId, this.sessionId, sessionData);
|
|
417
408
|
} else {
|
|
418
|
-
await sessions.create(sessionData
|
|
409
|
+
await sessions.create(sessionData);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Removes transient setup/auth sessions without masking the original error.
|
|
415
|
+
* @private
|
|
416
|
+
*/
|
|
417
|
+
private async deleteTransientSession(): Promise<void> {
|
|
418
|
+
try {
|
|
419
|
+
await sessions.delete(this.userId, this.sessionId);
|
|
420
|
+
} catch {
|
|
421
|
+
// Best effort only: preserve the original connection/auth error.
|
|
419
422
|
}
|
|
420
423
|
}
|
|
421
424
|
|
|
@@ -504,9 +507,6 @@ export class MCPClient {
|
|
|
504
507
|
}
|
|
505
508
|
|
|
506
509
|
try {
|
|
507
|
-
this.emitProgress('Validating OAuth tokens...');
|
|
508
|
-
await this.getValidTokens();
|
|
509
|
-
|
|
510
510
|
this.emitStateChange('CONNECTING');
|
|
511
511
|
|
|
512
512
|
/** Use the tryConnect loop to handle transport fallbacks */
|
|
@@ -520,8 +520,8 @@ export class MCPClient {
|
|
|
520
520
|
|
|
521
521
|
// Refresh session metadata on every successful connect so active sessions
|
|
522
522
|
// record ongoing usage and don't look dormant to session cleanup jobs.
|
|
523
|
-
console.log(`[MCPClient] Saving session ${this.sessionId}
|
|
524
|
-
await this.saveSession(
|
|
523
|
+
console.log(`[MCPClient] Saving active session ${this.sessionId} (connect success)`);
|
|
524
|
+
await this.saveSession('active');
|
|
525
525
|
} catch (error) {
|
|
526
526
|
/** Handle Authentication Errors */
|
|
527
527
|
if (
|
|
@@ -550,23 +550,16 @@ export class MCPClient {
|
|
|
550
550
|
this.emitError(message, 'auth');
|
|
551
551
|
this.emitStateChange('FAILED');
|
|
552
552
|
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
|
|
556
|
-
try {
|
|
557
|
-
await sessions.delete(this.userId, this.sessionId);
|
|
558
|
-
} catch {
|
|
559
|
-
// Non-blocking: Proactive cleanup failures are suppressed to prioritize
|
|
560
|
-
// the original error context.
|
|
561
|
-
}
|
|
553
|
+
// Remove terminal setup failures immediately. Active sessions are not
|
|
554
|
+
// deleted here because this branch only runs before OAuth is available.
|
|
555
|
+
await this.deleteTransientSession();
|
|
562
556
|
|
|
563
557
|
throw new Error(message);
|
|
564
558
|
}
|
|
565
559
|
|
|
566
560
|
this.emitStateChange('AUTHENTICATING');
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1000), false);
|
|
561
|
+
console.log(`[MCPClient] Saving pending OAuth session ${this.sessionId}`);
|
|
562
|
+
await this.saveSession('pending');
|
|
570
563
|
|
|
571
564
|
if (this.serverId) {
|
|
572
565
|
this._onConnectionEvent.fire({
|
|
@@ -590,16 +583,15 @@ export class MCPClient {
|
|
|
590
583
|
this.emitError(errorMessage, 'connection');
|
|
591
584
|
this.emitStateChange('FAILED');
|
|
592
585
|
|
|
593
|
-
//
|
|
594
|
-
// sessions may still hold
|
|
586
|
+
// Remove transient sessions that failed before becoming restorable.
|
|
587
|
+
// Existing active sessions may still hold usable credentials for reconnect.
|
|
595
588
|
try {
|
|
596
589
|
const existingSession = await sessions.get(this.userId, this.sessionId);
|
|
597
|
-
if (!existingSession || existingSession.
|
|
590
|
+
if (!existingSession || existingSession.status !== 'active') {
|
|
598
591
|
await sessions.delete(this.userId, this.sessionId);
|
|
599
592
|
}
|
|
600
593
|
} catch {
|
|
601
|
-
//
|
|
602
|
-
// not interfere with the primary error propagation.
|
|
594
|
+
// Best effort only: preserve the original connection error.
|
|
603
595
|
}
|
|
604
596
|
|
|
605
597
|
throw error;
|
|
@@ -614,7 +606,7 @@ export class MCPClient {
|
|
|
614
606
|
*/
|
|
615
607
|
|
|
616
608
|
// TODO: needs to be optimized
|
|
617
|
-
async finishAuth(authCode: string): Promise<void> {
|
|
609
|
+
async finishAuth(authCode: string, state?: string): Promise<void> {
|
|
618
610
|
this.emitStateChange('AUTHENTICATING');
|
|
619
611
|
this.emitProgress('Exchanging authorization code for tokens...');
|
|
620
612
|
|
|
@@ -627,6 +619,18 @@ export class MCPClient {
|
|
|
627
619
|
throw new Error(error);
|
|
628
620
|
}
|
|
629
621
|
|
|
622
|
+
if (state) {
|
|
623
|
+
const stateCheck = await this.oauthProvider.checkState(state);
|
|
624
|
+
if (!stateCheck.valid) {
|
|
625
|
+
const error = stateCheck.error || 'Invalid OAuth state';
|
|
626
|
+
this.emitError(error, 'auth');
|
|
627
|
+
this.emitStateChange('FAILED');
|
|
628
|
+
throw new Error(error);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
await this.oauthProvider.consumeState(state);
|
|
632
|
+
}
|
|
633
|
+
|
|
630
634
|
/**
|
|
631
635
|
* Determine which transports to try for finishing auth
|
|
632
636
|
* If transportType is set, use only that. Otherwise try streamable_http then sse.
|
|
@@ -687,9 +691,8 @@ export class MCPClient {
|
|
|
687
691
|
this.transportType = currentType;
|
|
688
692
|
|
|
689
693
|
this.emitStateChange('CONNECTED');
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
694
|
+
console.log(`[MCPClient] Saving active session ${this.sessionId} (OAuth complete)`);
|
|
695
|
+
await this.saveSession('active');
|
|
693
696
|
|
|
694
697
|
return; // Success, exit function
|
|
695
698
|
|
|
@@ -710,6 +713,7 @@ export class MCPClient {
|
|
|
710
713
|
const msg = error instanceof Error ? error.message : 'Authentication failed';
|
|
711
714
|
this.emitError(msg, 'auth');
|
|
712
715
|
this.emitStateChange('FAILED');
|
|
716
|
+
await this.deleteTransientSession();
|
|
713
717
|
throw error;
|
|
714
718
|
}
|
|
715
719
|
|
|
@@ -717,6 +721,7 @@ export class MCPClient {
|
|
|
717
721
|
const msg = error instanceof Error ? error.message : 'Authentication failed';
|
|
718
722
|
this.emitError(msg, 'auth');
|
|
719
723
|
this.emitStateChange('FAILED');
|
|
724
|
+
await this.deleteTransientSession();
|
|
720
725
|
throw error;
|
|
721
726
|
}
|
|
722
727
|
|
|
@@ -729,6 +734,7 @@ export class MCPClient {
|
|
|
729
734
|
const errorMessage = lastError instanceof Error ? lastError.message : 'Authentication failed';
|
|
730
735
|
this.emitError(errorMessage, 'auth');
|
|
731
736
|
this.emitStateChange('FAILED');
|
|
737
|
+
await this.deleteTransientSession();
|
|
732
738
|
throw lastError;
|
|
733
739
|
}
|
|
734
740
|
}
|
|
@@ -947,79 +953,6 @@ export class MCPClient {
|
|
|
947
953
|
return await this.client.request(request, ReadResourceResultSchema);
|
|
948
954
|
}
|
|
949
955
|
|
|
950
|
-
/**
|
|
951
|
-
* Refreshes the OAuth access token using the refresh token
|
|
952
|
-
* Discovers OAuth metadata from server and exchanges refresh token for new access token
|
|
953
|
-
* @returns True if refresh was successful, false otherwise
|
|
954
|
-
*/
|
|
955
|
-
async refreshToken(): Promise<boolean> {
|
|
956
|
-
await this.initialize();
|
|
957
|
-
|
|
958
|
-
if (!this.oauthProvider) {
|
|
959
|
-
return false;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
const tokens = await this.oauthProvider.tokens();
|
|
963
|
-
if (!tokens || !tokens.refresh_token) {
|
|
964
|
-
return false;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
const clientInformation = await this.oauthProvider.clientInformation();
|
|
968
|
-
if (!clientInformation) {
|
|
969
|
-
return false;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
try {
|
|
973
|
-
const resourceMetadata = await discoverOAuthProtectedResourceMetadata(this.serverUrl!);
|
|
974
|
-
const authServerUrl = resourceMetadata?.authorization_servers?.[0] || this.serverUrl!;
|
|
975
|
-
const authMetadata = await discoverAuthorizationServerMetadata(authServerUrl);
|
|
976
|
-
|
|
977
|
-
const newTokens = await refreshAuthorization(authServerUrl, {
|
|
978
|
-
metadata: authMetadata,
|
|
979
|
-
clientInformation,
|
|
980
|
-
refreshToken: tokens.refresh_token,
|
|
981
|
-
});
|
|
982
|
-
|
|
983
|
-
await this.oauthProvider.saveTokens(newTokens);
|
|
984
|
-
return true;
|
|
985
|
-
} catch (error) {
|
|
986
|
-
console.error('[OAuth] Token refresh failed:', error);
|
|
987
|
-
if (isInvalidRefreshTokenError(error)) {
|
|
988
|
-
try {
|
|
989
|
-
await this.oauthProvider.invalidateCredentials?.('tokens');
|
|
990
|
-
this.emitProgress('OAuth refresh token is invalid; requesting reauthorization...');
|
|
991
|
-
} catch (invalidateError) {
|
|
992
|
-
console.warn('[OAuth] Failed to invalidate stale refresh token credentials:', invalidateError);
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
return false;
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
/**
|
|
1000
|
-
* Ensures OAuth tokens are valid, refreshing them if expired
|
|
1001
|
-
* Called automatically by connect() - rarely needs to be called manually
|
|
1002
|
-
* @returns True if valid tokens are available, false otherwise
|
|
1003
|
-
*/
|
|
1004
|
-
async getValidTokens(): Promise<boolean> {
|
|
1005
|
-
await this.initialize();
|
|
1006
|
-
|
|
1007
|
-
if (!this.oauthProvider) {
|
|
1008
|
-
return false;
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
const tokens = await this.oauthProvider.tokens();
|
|
1012
|
-
if (!tokens) {
|
|
1013
|
-
return false;
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
if (this.oauthProvider.isTokenExpired()) {
|
|
1017
|
-
return await this.refreshToken();
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
return true;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
956
|
/**
|
|
1024
957
|
* Reconnects to MCP server using existing OAuth provider from Redis
|
|
1025
958
|
* Used for session restoration in serverless environments
|
|
@@ -1174,84 +1107,4 @@ export class MCPClient {
|
|
|
1174
1107
|
return this.sessionId;
|
|
1175
1108
|
}
|
|
1176
1109
|
|
|
1177
|
-
/**
|
|
1178
|
-
* Gets MCP server configuration for all active user sessions
|
|
1179
|
-
* Loads sessions from Redis, validates OAuth tokens, refreshes if expired
|
|
1180
|
-
* Returns ready-to-use configuration with valid auth headers
|
|
1181
|
-
* @param userId - User ID to fetch sessions for
|
|
1182
|
-
* @returns Object keyed by sanitized server labels containing transport, url, headers, etc.
|
|
1183
|
-
* @static
|
|
1184
|
-
*/
|
|
1185
|
-
static async getMcpServerConfig(userId: string): Promise<Record<string, any>> {
|
|
1186
|
-
const mcpConfig: Record<string, any> = {};
|
|
1187
|
-
const sessionList = await sessions.list(userId);
|
|
1188
|
-
|
|
1189
|
-
await Promise.all(
|
|
1190
|
-
sessionList.map(async (sessionData) => {
|
|
1191
|
-
const { sessionId } = sessionData;
|
|
1192
|
-
|
|
1193
|
-
try {
|
|
1194
|
-
// Validate session - remove if missing required fields
|
|
1195
|
-
if (
|
|
1196
|
-
!sessionData.serverId ||
|
|
1197
|
-
!sessionData.transportType ||
|
|
1198
|
-
!sessionData.serverUrl ||
|
|
1199
|
-
!sessionData.callbackUrl
|
|
1200
|
-
) {
|
|
1201
|
-
await sessions.delete(userId, sessionId);
|
|
1202
|
-
return;
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
// Get OAuth headers if session requires authentication
|
|
1206
|
-
let headers: Record<string, string> | undefined;
|
|
1207
|
-
try {
|
|
1208
|
-
// Inject existing session data to avoid redundant session store reads in initialize()
|
|
1209
|
-
const client = new MCPClient({
|
|
1210
|
-
userId,
|
|
1211
|
-
sessionId,
|
|
1212
|
-
serverId: sessionData.serverId,
|
|
1213
|
-
serverUrl: sessionData.serverUrl,
|
|
1214
|
-
callbackUrl: sessionData.callbackUrl,
|
|
1215
|
-
serverName: sessionData.serverName,
|
|
1216
|
-
transportType: sessionData.transportType,
|
|
1217
|
-
headers: sessionData.headers,
|
|
1218
|
-
});
|
|
1219
|
-
|
|
1220
|
-
await client.initialize();
|
|
1221
|
-
|
|
1222
|
-
const hasValidTokens = await client.getValidTokens();
|
|
1223
|
-
if (hasValidTokens && client.oauthProvider) {
|
|
1224
|
-
const tokens = await client.oauthProvider.tokens();
|
|
1225
|
-
if (tokens?.access_token) {
|
|
1226
|
-
headers = { Authorization: `Bearer ${tokens.access_token}` };
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
} catch (error) {
|
|
1230
|
-
console.warn(`[MCP] Failed to get OAuth tokens for ${sessionId}:`, error);
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
// Build server config
|
|
1234
|
-
const label = sanitizeServerLabel(
|
|
1235
|
-
sessionData.serverName || sessionData.serverId || 'server'
|
|
1236
|
-
);
|
|
1237
|
-
|
|
1238
|
-
mcpConfig[label] = {
|
|
1239
|
-
transport: sessionData.transportType,
|
|
1240
|
-
url: sessionData.serverUrl,
|
|
1241
|
-
...(sessionData.serverName && {
|
|
1242
|
-
serverName: sessionData.serverName,
|
|
1243
|
-
serverLabel: label,
|
|
1244
|
-
}),
|
|
1245
|
-
...(headers && { headers }),
|
|
1246
|
-
};
|
|
1247
|
-
} catch (error) {
|
|
1248
|
-
await sessions.delete(userId, sessionId);
|
|
1249
|
-
console.warn(`[MCP] Failed to process session ${sessionId}:`, error);
|
|
1250
|
-
}
|
|
1251
|
-
})
|
|
1252
|
-
);
|
|
1253
|
-
|
|
1254
|
-
return mcpConfig;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
1110
|
}
|