@mcp-ts/sdk 1.3.4 → 1.3.6
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 +404 -400
- package/dist/adapters/agui-adapter.d.mts +1 -1
- package/dist/adapters/agui-adapter.d.ts +1 -1
- package/dist/adapters/agui-middleware.d.mts +1 -1
- package/dist/adapters/agui-middleware.d.ts +1 -1
- package/dist/adapters/ai-adapter.d.mts +1 -1
- package/dist/adapters/ai-adapter.d.ts +1 -1
- package/dist/adapters/langchain-adapter.d.mts +1 -1
- package/dist/adapters/langchain-adapter.d.ts +1 -1
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/bin/mcp-ts.d.mts +1 -0
- package/dist/bin/mcp-ts.d.ts +1 -0
- package/dist/bin/mcp-ts.js +105 -0
- package/dist/bin/mcp-ts.js.map +1 -0
- package/dist/bin/mcp-ts.mjs +82 -0
- package/dist/bin/mcp-ts.mjs.map +1 -0
- package/dist/client/index.d.mts +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +14 -5
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +14 -5
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.js +15 -6
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +15 -6
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.js +15 -6
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +15 -6
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +480 -179
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +418 -179
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-FAFpUzZ4.d.ts → multi-session-client-BYLarghq.d.ts} +29 -19
- package/dist/{multi-session-client-DzjmT7FX.d.mts → multi-session-client-CzhMkE0k.d.mts} +29 -19
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +455 -172
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +410 -172
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +2 -2
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js +2 -2
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +2 -2
- package/dist/shared/index.mjs.map +1 -1
- package/package.json +19 -6
- package/src/bin/mcp-ts.ts +102 -0
- package/src/client/core/sse-client.ts +371 -354
- package/src/client/react/use-mcp.ts +31 -31
- package/src/client/vue/use-mcp.ts +77 -77
- package/src/server/handlers/nextjs-handler.ts +204 -207
- package/src/server/handlers/sse-handler.ts +14 -63
- package/src/server/mcp/oauth-client.ts +67 -79
- package/src/server/mcp/storage-oauth-provider.ts +71 -38
- package/src/server/storage/file-backend.ts +1 -0
- package/src/server/storage/index.ts +82 -38
- package/src/server/storage/memory-backend.ts +4 -0
- package/src/server/storage/redis-backend.ts +102 -23
- package/src/server/storage/sqlite-backend.ts +1 -0
- package/src/server/storage/supabase-backend.ts +227 -0
- package/src/server/storage/types.ts +12 -12
- package/src/shared/constants.ts +2 -2
- package/src/shared/event-routing.ts +28 -0
- package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -0
|
@@ -28,15 +28,18 @@ import {
|
|
|
28
28
|
ReadResourceResult,
|
|
29
29
|
ReadResourceResultSchema,
|
|
30
30
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
31
|
-
import type {
|
|
31
|
+
import type { OAuthTokens, OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
32
32
|
import { StorageOAuthClientProvider, type AgentsOAuthProvider } from './storage-oauth-provider.js';
|
|
33
33
|
import { sanitizeServerLabel } from '../../shared/utils.js';
|
|
34
34
|
import { Emitter, type McpConnectionEvent, type McpObservabilityEvent, type McpConnectionState } from '../../shared/events.js';
|
|
35
35
|
import { UnauthorizedError } from '../../shared/errors.js';
|
|
36
36
|
import { storage } from '../storage/index.js';
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
import {
|
|
38
|
+
MCP_CLIENT_NAME,
|
|
39
|
+
MCP_CLIENT_VERSION,
|
|
40
|
+
SESSION_TTL_SECONDS,
|
|
41
|
+
STATE_EXPIRATION_MS,
|
|
42
|
+
} from '../../shared/constants.js';
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
45
|
* Supported MCP transport types
|
|
@@ -300,49 +303,34 @@ export class MCPClient {
|
|
|
300
303
|
throw new Error('Missing required connection metadata');
|
|
301
304
|
}
|
|
302
305
|
|
|
303
|
-
const clientMetadata: OAuthClientMetadata = {
|
|
304
|
-
client_name: this.clientName || 'MCP Assistant',
|
|
305
|
-
redirect_uris: [this.callbackUrl],
|
|
306
|
-
grant_types: ['authorization_code', 'refresh_token'],
|
|
307
|
-
response_types: ['code'],
|
|
308
|
-
token_endpoint_auth_method: this.clientSecret ? 'client_secret_basic' : 'none',
|
|
309
|
-
client_uri: this.clientUri || 'https://mcp-assistant.in',
|
|
310
|
-
logo_uri: this.logoUri || 'https://mcp-assistant.in/logo.png',
|
|
311
|
-
policy_uri: this.policyUri || 'https://mcp-assistant.in/privacy',
|
|
312
|
-
software_id: '@mcp-ts',
|
|
313
|
-
software_version: '1.0.0-beta.4',
|
|
314
|
-
...(this.clientId ? { client_id: this.clientId } : {}),
|
|
315
|
-
...(this.clientSecret ? { client_secret: this.clientSecret } : {}),
|
|
316
|
-
};
|
|
317
|
-
|
|
318
306
|
if (!this.oauthProvider) {
|
|
319
307
|
if (!this.serverId) {
|
|
320
308
|
throw new Error('serverId required for OAuth provider initialization');
|
|
321
309
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
this.
|
|
325
|
-
this.
|
|
326
|
-
this.
|
|
327
|
-
|
|
328
|
-
this.
|
|
329
|
-
|
|
310
|
+
this.oauthProvider = new StorageOAuthClientProvider({
|
|
311
|
+
identity: this.identity,
|
|
312
|
+
serverId: this.serverId,
|
|
313
|
+
sessionId: this.sessionId,
|
|
314
|
+
redirectUrl: this.callbackUrl!,
|
|
315
|
+
clientName: this.clientName,
|
|
316
|
+
clientUri: this.clientUri,
|
|
317
|
+
logoUri: this.logoUri,
|
|
318
|
+
policyUri: this.policyUri,
|
|
319
|
+
clientId: this.clientId,
|
|
320
|
+
clientSecret: this.clientSecret,
|
|
321
|
+
onRedirect: (redirectUrl: string) => {
|
|
330
322
|
if (this.onRedirect) {
|
|
331
323
|
this.onRedirect(redirectUrl);
|
|
332
324
|
}
|
|
333
325
|
}
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
if (this.clientId && this.oauthProvider) {
|
|
337
|
-
this.oauthProvider.clientId = this.clientId;
|
|
338
|
-
}
|
|
326
|
+
});
|
|
339
327
|
}
|
|
340
328
|
|
|
341
329
|
if (!this.client) {
|
|
342
330
|
this.client = new Client(
|
|
343
331
|
{
|
|
344
|
-
name:
|
|
345
|
-
version:
|
|
332
|
+
name: MCP_CLIENT_NAME,
|
|
333
|
+
version: MCP_CLIENT_VERSION,
|
|
346
334
|
},
|
|
347
335
|
{
|
|
348
336
|
capabilities: {
|
|
@@ -363,34 +351,34 @@ export class MCPClient {
|
|
|
363
351
|
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
364
352
|
this.createdAt = Date.now();
|
|
365
353
|
console.log(`[MCPClient] Creating initial session ${this.sessionId} for OAuth flow`);
|
|
366
|
-
await storage.createSession({
|
|
367
|
-
sessionId: this.sessionId,
|
|
368
|
-
identity: this.identity,
|
|
369
|
-
serverId: this.serverId,
|
|
370
|
-
serverName: this.serverName,
|
|
371
|
-
serverUrl: this.serverUrl,
|
|
372
|
-
callbackUrl: this.callbackUrl,
|
|
373
|
-
transportType: this.transportType || 'streamable_http',
|
|
374
|
-
createdAt: this.createdAt,
|
|
375
|
-
active: false,
|
|
376
|
-
}, Math.floor(STATE_EXPIRATION_MS / 1000)); // Short TTL until connection succeeds
|
|
377
|
-
}
|
|
378
|
-
}
|
|
354
|
+
await storage.createSession({
|
|
355
|
+
sessionId: this.sessionId,
|
|
356
|
+
identity: this.identity,
|
|
357
|
+
serverId: this.serverId,
|
|
358
|
+
serverName: this.serverName,
|
|
359
|
+
serverUrl: this.serverUrl,
|
|
360
|
+
callbackUrl: this.callbackUrl,
|
|
361
|
+
transportType: this.transportType || 'streamable_http',
|
|
362
|
+
createdAt: this.createdAt,
|
|
363
|
+
active: false,
|
|
364
|
+
}, Math.floor(STATE_EXPIRATION_MS / 1000)); // Short TTL until connection succeeds
|
|
365
|
+
}
|
|
366
|
+
}
|
|
379
367
|
|
|
380
368
|
/**
|
|
381
369
|
* Saves current session state to storage
|
|
382
370
|
* Creates new session if it doesn't exist, updates if it does
|
|
383
|
-
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
384
|
-
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
385
|
-
* @private
|
|
386
|
-
*/
|
|
387
|
-
private async saveSession(
|
|
388
|
-
ttl: number = SESSION_TTL_SECONDS,
|
|
389
|
-
active: boolean = true
|
|
390
|
-
): Promise<void> {
|
|
391
|
-
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
371
|
+
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
372
|
+
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
373
|
+
* @private
|
|
374
|
+
*/
|
|
375
|
+
private async saveSession(
|
|
376
|
+
ttl: number = SESSION_TTL_SECONDS,
|
|
377
|
+
active: boolean = true
|
|
378
|
+
): Promise<void> {
|
|
379
|
+
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
394
382
|
|
|
395
383
|
const sessionData = {
|
|
396
384
|
sessionId: this.sessionId,
|
|
@@ -398,11 +386,11 @@ export class MCPClient {
|
|
|
398
386
|
serverId: this.serverId,
|
|
399
387
|
serverName: this.serverName,
|
|
400
388
|
serverUrl: this.serverUrl,
|
|
401
|
-
callbackUrl: this.callbackUrl,
|
|
402
|
-
transportType: (this.transportType || 'streamable_http') as TransportType,
|
|
403
|
-
createdAt: this.createdAt || Date.now(),
|
|
404
|
-
active,
|
|
405
|
-
};
|
|
389
|
+
callbackUrl: this.callbackUrl,
|
|
390
|
+
transportType: (this.transportType || 'streamable_http') as TransportType,
|
|
391
|
+
createdAt: this.createdAt || Date.now(),
|
|
392
|
+
active,
|
|
393
|
+
};
|
|
406
394
|
|
|
407
395
|
// Try to update first, create if doesn't exist
|
|
408
396
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
@@ -512,16 +500,16 @@ export class MCPClient {
|
|
|
512
500
|
this.emitStateChange('CONNECTED');
|
|
513
501
|
this.emitProgress('Connected successfully');
|
|
514
502
|
|
|
515
|
-
// Promote short-lived OAuth-pending session TTL to long-lived active TTL once.
|
|
516
|
-
// Also persist when transport negotiation changed the effective transport.
|
|
517
|
-
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
518
|
-
const needsTransportUpdate = !existingSession || existingSession.transportType !== this.transportType;
|
|
519
|
-
const needsTtlPromotion = !existingSession || existingSession.active !== true;
|
|
520
|
-
|
|
521
|
-
if (needsTransportUpdate || needsTtlPromotion) {
|
|
522
|
-
console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
|
|
523
|
-
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
524
|
-
}
|
|
503
|
+
// Promote short-lived OAuth-pending session TTL to long-lived active TTL once.
|
|
504
|
+
// Also persist when transport negotiation changed the effective transport.
|
|
505
|
+
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
506
|
+
const needsTransportUpdate = !existingSession || existingSession.transportType !== this.transportType;
|
|
507
|
+
const needsTtlPromotion = !existingSession || existingSession.active !== true;
|
|
508
|
+
|
|
509
|
+
if (needsTransportUpdate || needsTtlPromotion) {
|
|
510
|
+
console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
|
|
511
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
512
|
+
}
|
|
525
513
|
} catch (error) {
|
|
526
514
|
/** Handle Authentication Errors */
|
|
527
515
|
if (
|
|
@@ -531,7 +519,7 @@ export class MCPClient {
|
|
|
531
519
|
this.emitStateChange('AUTHENTICATING');
|
|
532
520
|
// Save session with 10min TTL for OAuth pending state
|
|
533
521
|
console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
|
|
534
|
-
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1000), false);
|
|
522
|
+
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1000), false);
|
|
535
523
|
|
|
536
524
|
/** Get OAuth authorization URL if available */
|
|
537
525
|
let authUrl = '';
|
|
@@ -620,8 +608,8 @@ export class MCPClient {
|
|
|
620
608
|
|
|
621
609
|
this.client = new Client(
|
|
622
610
|
{
|
|
623
|
-
name:
|
|
624
|
-
version:
|
|
611
|
+
name: MCP_CLIENT_NAME,
|
|
612
|
+
version: MCP_CLIENT_VERSION,
|
|
625
613
|
},
|
|
626
614
|
{
|
|
627
615
|
capabilities: {
|
|
@@ -642,7 +630,7 @@ export class MCPClient {
|
|
|
642
630
|
this.emitStateChange('CONNECTED');
|
|
643
631
|
// Update session with 12hr TTL after successful OAuth
|
|
644
632
|
console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
|
|
645
|
-
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
633
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
646
634
|
|
|
647
635
|
return; // Success, exit function
|
|
648
636
|
|
|
@@ -980,8 +968,8 @@ export class MCPClient {
|
|
|
980
968
|
|
|
981
969
|
this.client = new Client(
|
|
982
970
|
{
|
|
983
|
-
name:
|
|
984
|
-
version:
|
|
971
|
+
name: MCP_CLIENT_NAME,
|
|
972
|
+
version: MCP_CLIENT_VERSION,
|
|
985
973
|
},
|
|
986
974
|
{ capabilities: {} }
|
|
987
975
|
);
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
|
|
2
1
|
import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
3
2
|
import type {
|
|
4
|
-
OAuthClientInformation,
|
|
5
3
|
OAuthClientInformationFull,
|
|
4
|
+
OAuthClientInformationMixed,
|
|
6
5
|
OAuthClientMetadata,
|
|
7
6
|
OAuthTokens
|
|
8
7
|
} from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
9
8
|
import { storage, SessionData } from "../storage/index.js";
|
|
10
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_CLIENT_NAME,
|
|
11
|
+
DEFAULT_CLIENT_URI,
|
|
12
|
+
DEFAULT_LOGO_URI,
|
|
13
|
+
DEFAULT_POLICY_URI,
|
|
14
|
+
SOFTWARE_ID,
|
|
15
|
+
SOFTWARE_VERSION,
|
|
16
|
+
TOKEN_EXPIRY_BUFFER_MS,
|
|
17
|
+
} from '../../shared/constants.js';
|
|
11
18
|
|
|
12
19
|
/**
|
|
13
20
|
* Extension of OAuthClientProvider interface with additional methods
|
|
@@ -26,56 +33,74 @@ export interface AgentsOAuthProvider extends OAuthClientProvider {
|
|
|
26
33
|
setTokenExpiresAt(expiresAt: number): void;
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
export interface StorageOAuthClientProviderOptions {
|
|
37
|
+
identity: string;
|
|
38
|
+
serverId: string;
|
|
39
|
+
sessionId: string;
|
|
40
|
+
redirectUrl: string;
|
|
41
|
+
clientName?: string;
|
|
42
|
+
clientUri?: string;
|
|
43
|
+
logoUri?: string;
|
|
44
|
+
policyUri?: string;
|
|
45
|
+
clientId?: string;
|
|
46
|
+
clientSecret?: string;
|
|
47
|
+
onRedirect?: (url: string) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
29
50
|
/**
|
|
30
51
|
* Storage-backed OAuth provider implementation for MCP
|
|
31
52
|
* Stores OAuth tokens, client information, and PKCE verifiers using the configured StorageBackend
|
|
32
53
|
*/
|
|
33
54
|
export class StorageOAuthClientProvider implements AgentsOAuthProvider {
|
|
55
|
+
public readonly identity: string;
|
|
56
|
+
public readonly serverId: string;
|
|
57
|
+
public readonly sessionId: string;
|
|
58
|
+
public readonly redirectUrl: string;
|
|
59
|
+
|
|
60
|
+
private readonly clientName?: string;
|
|
61
|
+
private readonly clientUri?: string;
|
|
62
|
+
private readonly logoUri?: string;
|
|
63
|
+
private readonly policyUri?: string;
|
|
64
|
+
private readonly clientSecret?: string;
|
|
65
|
+
|
|
34
66
|
private _authUrl: string | undefined;
|
|
35
67
|
private _clientId: string | undefined;
|
|
36
68
|
private onRedirectCallback?: (url: string) => void;
|
|
37
69
|
private tokenExpiresAt?: number;
|
|
38
70
|
|
|
39
71
|
/**
|
|
40
|
-
* Creates a new
|
|
41
|
-
* @param
|
|
42
|
-
* @param serverId - Server identifier (for tracking which server this OAuth session belongs to)
|
|
43
|
-
* @param sessionId - Session identifier (used as OAuth state)
|
|
44
|
-
* @param clientName - OAuth client name
|
|
45
|
-
* @param baseRedirectUrl - OAuth callback URL
|
|
46
|
-
* @param onRedirect - Optional callback when redirect to authorization is needed
|
|
72
|
+
* Creates a new storage-backed OAuth provider
|
|
73
|
+
* @param options - Provider configuration
|
|
47
74
|
*/
|
|
48
|
-
constructor(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
this.
|
|
75
|
+
constructor(options: StorageOAuthClientProviderOptions) {
|
|
76
|
+
this.identity = options.identity;
|
|
77
|
+
this.serverId = options.serverId;
|
|
78
|
+
this.sessionId = options.sessionId;
|
|
79
|
+
this.redirectUrl = options.redirectUrl;
|
|
80
|
+
this.clientName = options.clientName;
|
|
81
|
+
this.clientUri = options.clientUri;
|
|
82
|
+
this.logoUri = options.logoUri;
|
|
83
|
+
this.policyUri = options.policyUri;
|
|
84
|
+
this._clientId = options.clientId;
|
|
85
|
+
this.clientSecret = options.clientSecret;
|
|
86
|
+
this.onRedirectCallback = options.onRedirect;
|
|
57
87
|
}
|
|
58
88
|
|
|
59
89
|
get clientMetadata(): OAuthClientMetadata {
|
|
60
90
|
return {
|
|
61
|
-
client_name: this.clientName,
|
|
62
|
-
client_uri: this.clientUri,
|
|
91
|
+
client_name: this.clientName || DEFAULT_CLIENT_NAME,
|
|
92
|
+
client_uri: this.clientUri || DEFAULT_CLIENT_URI,
|
|
93
|
+
logo_uri: this.logoUri || DEFAULT_LOGO_URI,
|
|
94
|
+
policy_uri: this.policyUri || DEFAULT_POLICY_URI,
|
|
63
95
|
grant_types: ["authorization_code", "refresh_token"],
|
|
64
96
|
redirect_uris: [this.redirectUrl],
|
|
65
97
|
response_types: ["code"],
|
|
66
|
-
token_endpoint_auth_method: "none",
|
|
67
|
-
|
|
98
|
+
token_endpoint_auth_method: this.clientSecret ? "client_secret_basic" : "none",
|
|
99
|
+
software_id: SOFTWARE_ID,
|
|
100
|
+
software_version: SOFTWARE_VERSION,
|
|
68
101
|
};
|
|
69
102
|
}
|
|
70
103
|
|
|
71
|
-
get clientUri() {
|
|
72
|
-
return new URL(this.redirectUrl).origin;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
get redirectUrl() {
|
|
76
|
-
return this.baseRedirectUrl;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
104
|
get clientId() {
|
|
80
105
|
return this._clientId;
|
|
81
106
|
}
|
|
@@ -91,7 +116,6 @@ export class StorageOAuthClientProvider implements AgentsOAuthProvider {
|
|
|
91
116
|
private async getSessionData(): Promise<SessionData> {
|
|
92
117
|
const data = await storage.getSession(this.identity, this.sessionId);
|
|
93
118
|
if (!data) {
|
|
94
|
-
// Return empty/partial object if not found
|
|
95
119
|
return {} as SessionData;
|
|
96
120
|
}
|
|
97
121
|
return data;
|
|
@@ -110,14 +134,25 @@ export class StorageOAuthClientProvider implements AgentsOAuthProvider {
|
|
|
110
134
|
/**
|
|
111
135
|
* Retrieves stored OAuth client information
|
|
112
136
|
*/
|
|
113
|
-
async clientInformation(): Promise<
|
|
137
|
+
async clientInformation(): Promise<OAuthClientInformationMixed | undefined> {
|
|
114
138
|
const data = await this.getSessionData();
|
|
115
139
|
|
|
116
140
|
if (data.clientId && !this._clientId) {
|
|
117
141
|
this._clientId = data.clientId;
|
|
118
142
|
}
|
|
119
143
|
|
|
120
|
-
|
|
144
|
+
if (data.clientInformation) {
|
|
145
|
+
return data.clientInformation;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!this._clientId) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
client_id: this._clientId,
|
|
154
|
+
...(this.clientSecret ? { client_secret: this.clientSecret } : {}),
|
|
155
|
+
};
|
|
121
156
|
}
|
|
122
157
|
|
|
123
158
|
/**
|
|
@@ -152,7 +187,7 @@ export class StorageOAuthClientProvider implements AgentsOAuthProvider {
|
|
|
152
187
|
return this.sessionId;
|
|
153
188
|
}
|
|
154
189
|
|
|
155
|
-
async checkState(
|
|
190
|
+
async checkState(_state: string): Promise<{ valid: boolean; serverId?: string; error?: string }> {
|
|
156
191
|
const data = await storage.getSession(this.identity, this.sessionId);
|
|
157
192
|
|
|
158
193
|
if (!data) {
|
|
@@ -162,7 +197,7 @@ export class StorageOAuthClientProvider implements AgentsOAuthProvider {
|
|
|
162
197
|
return { valid: true, serverId: this.serverId };
|
|
163
198
|
}
|
|
164
199
|
|
|
165
|
-
async consumeState(
|
|
200
|
+
async consumeState(_state: string): Promise<void> {
|
|
166
201
|
// No-op
|
|
167
202
|
}
|
|
168
203
|
|
|
@@ -179,8 +214,6 @@ export class StorageOAuthClientProvider implements AgentsOAuthProvider {
|
|
|
179
214
|
if (scope === "all") {
|
|
180
215
|
await storage.removeSession(this.identity, this.sessionId);
|
|
181
216
|
} else {
|
|
182
|
-
const data = await this.getSessionData();
|
|
183
|
-
// Create a copy to modify
|
|
184
217
|
const updates: Partial<SessionData> = {};
|
|
185
218
|
|
|
186
219
|
if (scope === "client") {
|
|
@@ -2,16 +2,28 @@
|
|
|
2
2
|
import { RedisStorageBackend } from './redis-backend';
|
|
3
3
|
import { MemoryStorageBackend } from './memory-backend';
|
|
4
4
|
import { FileStorageBackend } from './file-backend';
|
|
5
|
-
import { SqliteStorage } from './sqlite-backend';
|
|
6
|
-
import
|
|
5
|
+
import { SqliteStorage } from './sqlite-backend.js';
|
|
6
|
+
import { SupabaseStorageBackend } from './supabase-backend.js';
|
|
7
|
+
import type { StorageBackend } from './types.js';
|
|
7
8
|
|
|
8
9
|
// Re-export types
|
|
9
|
-
export * from './types';
|
|
10
|
-
export { RedisStorageBackend, MemoryStorageBackend, FileStorageBackend, SqliteStorage };
|
|
10
|
+
export * from './types.js';
|
|
11
|
+
export { RedisStorageBackend, MemoryStorageBackend, FileStorageBackend, SqliteStorage, SupabaseStorageBackend };
|
|
12
|
+
|
|
13
|
+
export function createSupabaseStorageBackend(client: any): SupabaseStorageBackend {
|
|
14
|
+
return new SupabaseStorageBackend(client);
|
|
15
|
+
}
|
|
11
16
|
|
|
12
17
|
let storageInstance: StorageBackend | null = null;
|
|
13
18
|
let storagePromise: Promise<StorageBackend> | null = null;
|
|
14
19
|
|
|
20
|
+
async function initializeStorage<T extends StorageBackend>(store: T): Promise<T> {
|
|
21
|
+
if (typeof store.init === 'function') {
|
|
22
|
+
await store.init();
|
|
23
|
+
}
|
|
24
|
+
return store;
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
async function createStorage(): Promise<StorageBackend> {
|
|
16
28
|
const type = process.env.MCP_TS_STORAGE_TYPE?.toLowerCase();
|
|
17
29
|
|
|
@@ -23,37 +35,53 @@ async function createStorage(): Promise<StorageBackend> {
|
|
|
23
35
|
try {
|
|
24
36
|
const { getRedis } = await import('./redis.js');
|
|
25
37
|
const redis = await getRedis();
|
|
26
|
-
console.log('[Storage]
|
|
27
|
-
return new RedisStorageBackend(redis);
|
|
38
|
+
console.log('[mcp-ts][Storage] Explicit selection: "redis"');
|
|
39
|
+
return await initializeStorage(new RedisStorageBackend(redis));
|
|
28
40
|
} catch (error: any) {
|
|
29
|
-
console.error('[Storage] Failed to initialize Redis:', error.message);
|
|
30
|
-
console.log('[Storage] Falling back to In-Memory storage');
|
|
31
|
-
return new MemoryStorageBackend();
|
|
41
|
+
console.error('[mcp-ts][Storage] Failed to initialize Redis:', error.message);
|
|
42
|
+
console.log('[mcp-ts][Storage] Falling back to In-Memory storage');
|
|
43
|
+
return await initializeStorage(new MemoryStorageBackend());
|
|
32
44
|
}
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
if (type === 'file') {
|
|
36
48
|
const filePath = process.env.MCP_TS_STORAGE_FILE;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
console.log(`[Storage] Using File storage (${filePath}) (Explicit)`);
|
|
41
|
-
const store = new FileStorageBackend({ path: filePath });
|
|
42
|
-
store.init().catch(err => console.error('[Storage] Failed to initialize file storage:', err));
|
|
43
|
-
return store;
|
|
49
|
+
console.log(`[mcp-ts][Storage] Explicit selection: "file" (${filePath || 'default'})`);
|
|
50
|
+
return await initializeStorage(new FileStorageBackend({ path: filePath }));
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
if (type === 'sqlite') {
|
|
47
54
|
const dbPath = process.env.MCP_TS_STORAGE_SQLITE_PATH;
|
|
48
|
-
console.log(`[Storage]
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
console.log(`[mcp-ts][Storage] Explicit selection: "sqlite" (${dbPath || 'default'})`);
|
|
56
|
+
return await initializeStorage(new SqliteStorage({ path: dbPath }));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (type === 'supabase') {
|
|
60
|
+
const url = process.env.SUPABASE_URL;
|
|
61
|
+
const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
|
|
62
|
+
|
|
63
|
+
if (!url || !key) {
|
|
64
|
+
console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
|
|
65
|
+
} else {
|
|
66
|
+
if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
|
|
67
|
+
console.warn('[mcp-ts][Storage] ⚠️ Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const { createClient } = await import('@supabase/supabase-js');
|
|
71
|
+
const client = createClient(url, key);
|
|
72
|
+
console.log('[mcp-ts][Storage] Explicit selection: "supabase"');
|
|
73
|
+
return await initializeStorage(new SupabaseStorageBackend(client as any));
|
|
74
|
+
} catch (error: any) {
|
|
75
|
+
console.error('[mcp-ts][Storage] Failed to initialize Supabase:', error.message);
|
|
76
|
+
console.log('[mcp-ts][Storage] Falling back to In-Memory storage');
|
|
77
|
+
return await initializeStorage(new MemoryStorageBackend());
|
|
78
|
+
}
|
|
79
|
+
}
|
|
52
80
|
}
|
|
53
81
|
|
|
54
82
|
if (type === 'memory') {
|
|
55
|
-
console.log('[Storage]
|
|
56
|
-
return new MemoryStorageBackend();
|
|
83
|
+
console.log('[mcp-ts][Storage] Explicit selection: "memory"');
|
|
84
|
+
return await initializeStorage(new MemoryStorageBackend());
|
|
57
85
|
}
|
|
58
86
|
|
|
59
87
|
// Automatic inference (Fallback)
|
|
@@ -61,31 +89,44 @@ async function createStorage(): Promise<StorageBackend> {
|
|
|
61
89
|
try {
|
|
62
90
|
const { getRedis } = await import('./redis.js');
|
|
63
91
|
const redis = await getRedis();
|
|
64
|
-
console.log('[Storage] Auto-
|
|
65
|
-
return new RedisStorageBackend(redis);
|
|
92
|
+
console.log('[mcp-ts][Storage] Auto-detection: "redis" (via REDIS_URL)');
|
|
93
|
+
return await initializeStorage(new RedisStorageBackend(redis));
|
|
66
94
|
} catch (error: any) {
|
|
67
|
-
console.error('[Storage] Redis auto-detection failed:', error.message);
|
|
68
|
-
console.log('[Storage] Falling back to
|
|
69
|
-
return new MemoryStorageBackend();
|
|
95
|
+
console.error('[mcp-ts][Storage] Redis auto-detection failed:', error.message);
|
|
96
|
+
console.log('[mcp-ts][Storage] Falling back to next available backend');
|
|
70
97
|
}
|
|
71
98
|
}
|
|
72
99
|
|
|
73
100
|
if (process.env.MCP_TS_STORAGE_FILE) {
|
|
74
|
-
console.log(`[Storage] Auto-
|
|
75
|
-
|
|
76
|
-
store.init().catch(err => console.error('[Storage] Failed to initialize file storage:', err));
|
|
77
|
-
return store;
|
|
101
|
+
console.log(`[mcp-ts][Storage] Auto-detection: "file" (${process.env.MCP_TS_STORAGE_FILE})`);
|
|
102
|
+
return await initializeStorage(new FileStorageBackend({ path: process.env.MCP_TS_STORAGE_FILE }));
|
|
78
103
|
}
|
|
79
104
|
|
|
80
105
|
if (process.env.MCP_TS_STORAGE_SQLITE_PATH) {
|
|
81
|
-
console.log(`[Storage] Auto-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
106
|
+
console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
|
|
107
|
+
return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (process.env.SUPABASE_URL && (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY)) {
|
|
111
|
+
try {
|
|
112
|
+
const { createClient } = await import('@supabase/supabase-js');
|
|
113
|
+
const url = process.env.SUPABASE_URL;
|
|
114
|
+
const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY!;
|
|
115
|
+
|
|
116
|
+
if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
|
|
117
|
+
console.warn('[mcp-ts][Storage] ⚠️ Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const client = createClient(url, key);
|
|
121
|
+
console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
|
|
122
|
+
return await initializeStorage(new SupabaseStorageBackend(client as any));
|
|
123
|
+
} catch (error: any) {
|
|
124
|
+
console.error('[mcp-ts][Storage] Supabase auto-detection failed:', error.message);
|
|
125
|
+
}
|
|
85
126
|
}
|
|
86
127
|
|
|
87
|
-
console.log('[Storage]
|
|
88
|
-
return new MemoryStorageBackend();
|
|
128
|
+
console.log('[mcp-ts][Storage] Defaulting to: "memory"');
|
|
129
|
+
return await initializeStorage(new MemoryStorageBackend());
|
|
89
130
|
}
|
|
90
131
|
|
|
91
132
|
async function getStorage(): Promise<StorageBackend> {
|
|
@@ -94,7 +135,10 @@ async function getStorage(): Promise<StorageBackend> {
|
|
|
94
135
|
}
|
|
95
136
|
|
|
96
137
|
if (!storagePromise) {
|
|
97
|
-
storagePromise = createStorage()
|
|
138
|
+
storagePromise = createStorage().catch((error) => {
|
|
139
|
+
storagePromise = null;
|
|
140
|
+
throw error;
|
|
141
|
+
});
|
|
98
142
|
}
|
|
99
143
|
|
|
100
144
|
storageInstance = await storagePromise;
|
|
@@ -27,6 +27,10 @@ export class MemoryStorageBackend implements StorageBackend {
|
|
|
27
27
|
|
|
28
28
|
constructor() { }
|
|
29
29
|
|
|
30
|
+
async init(): Promise<void> {
|
|
31
|
+
console.log('[mcp-ts][Storage] Memory: ✓ internal memory store active.');
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
private getSessionKey(identity: string, sessionId: string): string {
|
|
31
35
|
return `${identity}:${sessionId}`;
|
|
32
36
|
}
|