@mcp-ts/sdk 2.5.3 → 2.6.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 +3 -574
- package/dist/adapters/agui-adapter.d.mts +4 -4
- package/dist/adapters/agui-adapter.d.ts +4 -4
- package/dist/adapters/agui-middleware.d.mts +4 -4
- package/dist/adapters/agui-middleware.d.ts +4 -4
- package/dist/adapters/ai-adapter.d.mts +4 -4
- package/dist/adapters/ai-adapter.d.ts +4 -4
- package/dist/adapters/langchain-adapter.d.mts +4 -4
- package/dist/adapters/langchain-adapter.d.ts +4 -4
- package/dist/adapters/mastra-adapter.d.mts +3 -3
- package/dist/adapters/mastra-adapter.d.ts +3 -3
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +3 -0
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +10 -18
- package/dist/client/react.d.ts +10 -18
- package/dist/client/react.js +17 -0
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +17 -0
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +4 -4
- package/dist/client/vue.d.ts +4 -4
- package/dist/client/vue.js +3 -0
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +3 -0
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-BRZO_JyI.d.mts → index-BrDkbut5.d.ts} +2 -1
- package/dist/{index-C3YGagi2.d.ts → index-DHQvlx4K.d.mts} +2 -1
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1056 -876
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1048 -871
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-CuacvZaQ.d.ts → multi-session-client-CQzymvHl.d.ts} +183 -227
- package/dist/{multi-session-client-DqzT8Oo7.d.mts → multi-session-client-C_Ja2TuV.d.mts} +183 -227
- package/dist/server/index.d.mts +338 -88
- package/dist/server/index.d.ts +338 -88
- package/dist/server/index.js +1053 -876
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1045 -871
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +4 -4
- package/dist/shared/index.d.ts +4 -4
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-B0qpui-V.d.mts → tool-router-C1n7OXbl.d.mts} +1 -1
- package/dist/{tool-router-2qUyZ-cP.d.ts → tool-router-CBIawFnt.d.ts} +1 -1
- package/dist/{types-BkJ_rgzo.d.mts → types-DX71u9gR.d.mts} +11 -5
- package/dist/{types-BkJ_rgzo.d.ts → types-DX71u9gR.d.ts} +11 -5
- package/migrations/neon/20260513010000_install_mcp_sessions.sql +4 -34
- package/migrations/supabase/20260330195700_install_mcp_sessions.sql +5 -62
- package/package.json +2 -1
- package/src/client/core/sse-client.ts +5 -0
- package/src/client/react/use-mcp.ts +27 -28
- package/src/server/handlers/sse-handler.ts +671 -633
- package/src/server/index.ts +2 -1
- package/src/server/mcp/multi-session-client.ts +34 -10
- package/src/server/mcp/oauth-client.ts +197 -205
- package/src/server/mcp/storage-oauth-provider.ts +148 -41
- package/src/server/mcp/tool-policy-gateway.ts +11 -1
- package/src/server/storage/file-backend.ts +25 -21
- package/src/server/storage/index.ts +71 -0
- package/src/server/storage/memory-backend.ts +24 -12
- package/src/server/storage/neon-backend.ts +70 -72
- package/src/server/storage/redis-backend.ts +26 -36
- package/src/server/storage/sqlite-backend.ts +25 -39
- package/src/server/storage/supabase-backend.ts +45 -54
- package/src/server/storage/types.ts +22 -2
- package/src/shared/types.ts +13 -3
|
@@ -24,16 +24,19 @@ import {
|
|
|
24
24
|
ReadResourceRequest,
|
|
25
25
|
ReadResourceResult,
|
|
26
26
|
ReadResourceResultSchema,
|
|
27
|
+
type Implementation,
|
|
27
28
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
28
29
|
import { StorageOAuthClientProvider, type AgentsOAuthProvider } from './storage-oauth-provider.js';
|
|
29
30
|
import { Emitter, type McpConnectionEvent, type McpObservabilityEvent, type McpConnectionState } from '../../shared/events.js';
|
|
30
31
|
import { UnauthorizedError } from '../../shared/errors.js';
|
|
31
32
|
import { sessions } from '../storage/index.js';
|
|
32
|
-
import type { Session, SessionStatus } from '../storage/types.js';
|
|
33
|
+
import type { Session, SessionStatus, SessionStore } from '../storage/types.js';
|
|
33
34
|
import {
|
|
34
35
|
MCP_CLIENT_NAME,
|
|
35
36
|
MCP_CLIENT_VERSION,
|
|
36
37
|
} from '../../shared/constants.js';
|
|
38
|
+
import type { OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
39
|
+
import type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
42
|
* Supported MCP transport types
|
|
@@ -71,6 +74,25 @@ export interface MCPOAuthClientOptions {
|
|
|
71
74
|
clientUri?: string;
|
|
72
75
|
logoUri?: string;
|
|
73
76
|
policyUri?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Credentials already loaded by the caller (e.g. via get({includeCredentials: true})).
|
|
79
|
+
* When provided, the StorageOAuthClientProvider uses these cached values to
|
|
80
|
+
* skip redundant DB reads for tokens() calls during reconnection.
|
|
81
|
+
*/
|
|
82
|
+
cachedCredentials?: { tokens?: OAuthTokens };
|
|
83
|
+
/**
|
|
84
|
+
* When true, skips the redundant session-existence check in ensureSession().
|
|
85
|
+
* Set by callers that have already confirmed the session exists in storage
|
|
86
|
+
* (e.g. getOrCreateClient, getSession, finishAuth in sse-handler).
|
|
87
|
+
* Saves one round-trip per reconnection.
|
|
88
|
+
*/
|
|
89
|
+
hasSession?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Custom session store override. When provided, all storage operations
|
|
92
|
+
* (get/create/update/delete) use this store instead of the default global.
|
|
93
|
+
* Used for wrapping with DB observability or other decorators.
|
|
94
|
+
*/
|
|
95
|
+
sessionStore?: SessionStore;
|
|
74
96
|
}
|
|
75
97
|
|
|
76
98
|
/**
|
|
@@ -80,26 +102,12 @@ export interface MCPOAuthClientOptions {
|
|
|
80
102
|
*/
|
|
81
103
|
export class MCPClient {
|
|
82
104
|
private client: Client;
|
|
83
|
-
public oauthProvider:
|
|
105
|
+
public oauthProvider: OAuthClientProvider | null = null;
|
|
84
106
|
private transport: StreamableHTTPClientTransport | SSEClientTransport | null = null;
|
|
85
|
-
private
|
|
86
|
-
private serverId?: string;
|
|
87
|
-
private sessionId: string;
|
|
88
|
-
private serverName?: string;
|
|
89
|
-
private transportType: TransportType | undefined;
|
|
90
|
-
private serverUrl: string | undefined;
|
|
91
|
-
private callbackUrl: string | undefined;
|
|
92
|
-
private onRedirect: ((url: string) => void) | undefined;
|
|
93
|
-
private clientId?: string;
|
|
94
|
-
private clientSecret?: string;
|
|
95
|
-
private headers?: Record<string, string>;
|
|
96
|
-
/** OAuth Client Metadata */
|
|
97
|
-
private clientName?: string;
|
|
98
|
-
private clientUri?: string;
|
|
99
|
-
private logoUri?: string;
|
|
100
|
-
private policyUri?: string;
|
|
107
|
+
private config!: MCPOAuthClientOptions;
|
|
101
108
|
private createdAt?: number;
|
|
102
|
-
|
|
109
|
+
private _serverInfo: Implementation | undefined;
|
|
110
|
+
private _store!: SessionStore;
|
|
103
111
|
|
|
104
112
|
/** Event emitters for connection lifecycle */
|
|
105
113
|
private readonly _onConnectionEvent = new Emitter<McpConnectionEvent>();
|
|
@@ -116,21 +124,8 @@ export class MCPClient {
|
|
|
116
124
|
* @param options - Client configuration options
|
|
117
125
|
*/
|
|
118
126
|
constructor(options: MCPOAuthClientOptions) {
|
|
119
|
-
this.
|
|
120
|
-
this.
|
|
121
|
-
this.callbackUrl = options.callbackUrl;
|
|
122
|
-
this.onRedirect = options.onRedirect;
|
|
123
|
-
this.userId = options.userId;
|
|
124
|
-
this.serverId = options.serverId;
|
|
125
|
-
this.sessionId = options.sessionId;
|
|
126
|
-
this.transportType = options.transportType;
|
|
127
|
-
this.clientId = options.clientId;
|
|
128
|
-
this.clientSecret = options.clientSecret;
|
|
129
|
-
this.headers = options.headers;
|
|
130
|
-
this.clientName = options.clientName;
|
|
131
|
-
this.clientUri = options.clientUri;
|
|
132
|
-
this.logoUri = options.logoUri;
|
|
133
|
-
this.policyUri = options.policyUri;
|
|
127
|
+
this.config = { ...options };
|
|
128
|
+
this._store = options.sessionStore ?? sessions;
|
|
134
129
|
|
|
135
130
|
this.client = new Client(
|
|
136
131
|
{
|
|
@@ -149,6 +144,22 @@ export class MCPClient {
|
|
|
149
144
|
);
|
|
150
145
|
}
|
|
151
146
|
|
|
147
|
+
/** Shared session-shaped data for ensureSession and saveSession */
|
|
148
|
+
private get session() {
|
|
149
|
+
return {
|
|
150
|
+
sessionId: this.config.sessionId,
|
|
151
|
+
userId: this.config.userId,
|
|
152
|
+
serverId: this.config.serverId!,
|
|
153
|
+
serverName: this.config.serverName,
|
|
154
|
+
serverUrl: this.config.serverUrl!,
|
|
155
|
+
callbackUrl: this.config.callbackUrl!,
|
|
156
|
+
transportType: (this.config.transportType || 'streamable-http') as TransportType,
|
|
157
|
+
headers: this.config.headers,
|
|
158
|
+
createdAt: this.createdAt ?? Date.now(),
|
|
159
|
+
updatedAt: Date.now(),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
152
163
|
/**
|
|
153
164
|
* Emit a connection state change event
|
|
154
165
|
* @private
|
|
@@ -157,14 +168,14 @@ export class MCPClient {
|
|
|
157
168
|
const previousState = this.currentState;
|
|
158
169
|
this.currentState = newState;
|
|
159
170
|
|
|
160
|
-
if (!this.serverId) return;
|
|
171
|
+
if (!this.config.serverId) return;
|
|
161
172
|
|
|
162
173
|
this._onConnectionEvent.fire({
|
|
163
174
|
type: 'state_changed',
|
|
164
|
-
sessionId: this.sessionId,
|
|
165
|
-
serverId: this.serverId,
|
|
166
|
-
serverName: this.serverName || this.serverId,
|
|
167
|
-
serverUrl: this.serverUrl || '',
|
|
175
|
+
sessionId: this.config.sessionId,
|
|
176
|
+
serverId: this.config.serverId,
|
|
177
|
+
serverName: this.config.serverName || this.config.serverId,
|
|
178
|
+
serverUrl: this.config.serverUrl || '',
|
|
168
179
|
createdAt: this.createdAt,
|
|
169
180
|
state: newState,
|
|
170
181
|
previousState,
|
|
@@ -176,8 +187,8 @@ export class MCPClient {
|
|
|
176
187
|
level: 'info',
|
|
177
188
|
message: `Connection state: ${previousState} → ${newState}`,
|
|
178
189
|
displayMessage: `State changed to ${newState}`,
|
|
179
|
-
sessionId: this.sessionId,
|
|
180
|
-
serverId: this.serverId,
|
|
190
|
+
sessionId: this.config.sessionId,
|
|
191
|
+
serverId: this.config.serverId,
|
|
181
192
|
payload: { previousState, newState },
|
|
182
193
|
timestamp: Date.now(),
|
|
183
194
|
id: nanoid(),
|
|
@@ -189,12 +200,12 @@ export class MCPClient {
|
|
|
189
200
|
* @private
|
|
190
201
|
*/
|
|
191
202
|
private emitError(error: string, errorType: 'connection' | 'auth' | 'validation' | 'unknown' = 'unknown'): void {
|
|
192
|
-
if (!this.serverId) return;
|
|
203
|
+
if (!this.config.serverId) return;
|
|
193
204
|
|
|
194
205
|
this._onConnectionEvent.fire({
|
|
195
206
|
type: 'error',
|
|
196
|
-
sessionId: this.sessionId,
|
|
197
|
-
serverId: this.serverId,
|
|
207
|
+
sessionId: this.config.sessionId,
|
|
208
|
+
serverId: this.config.serverId,
|
|
198
209
|
error,
|
|
199
210
|
errorType,
|
|
200
211
|
timestamp: Date.now(),
|
|
@@ -205,8 +216,8 @@ export class MCPClient {
|
|
|
205
216
|
level: 'error',
|
|
206
217
|
message: error,
|
|
207
218
|
displayMessage: error,
|
|
208
|
-
sessionId: this.sessionId,
|
|
209
|
-
serverId: this.serverId,
|
|
219
|
+
sessionId: this.config.sessionId,
|
|
220
|
+
serverId: this.config.serverId,
|
|
210
221
|
payload: { errorType, error },
|
|
211
222
|
timestamp: Date.now(),
|
|
212
223
|
id: nanoid(),
|
|
@@ -218,12 +229,12 @@ export class MCPClient {
|
|
|
218
229
|
* @private
|
|
219
230
|
*/
|
|
220
231
|
private emitProgress(message: string): void {
|
|
221
|
-
if (!this.serverId) return;
|
|
232
|
+
if (!this.config.serverId) return;
|
|
222
233
|
|
|
223
234
|
this._onConnectionEvent.fire({
|
|
224
235
|
type: 'progress',
|
|
225
|
-
sessionId: this.sessionId,
|
|
226
|
-
serverId: this.serverId,
|
|
236
|
+
sessionId: this.config.sessionId,
|
|
237
|
+
serverId: this.config.serverId,
|
|
227
238
|
message,
|
|
228
239
|
timestamp: Date.now(),
|
|
229
240
|
});
|
|
@@ -243,16 +254,16 @@ export class MCPClient {
|
|
|
243
254
|
* @private
|
|
244
255
|
*/
|
|
245
256
|
private getTransport(type: TransportType): StreamableHTTPClientTransport | SSEClientTransport {
|
|
246
|
-
if (!this.serverUrl) {
|
|
257
|
+
if (!this.config.serverUrl) {
|
|
247
258
|
throw new Error('Server URL is required to create transport');
|
|
248
259
|
}
|
|
249
260
|
|
|
250
|
-
const baseUrl = new URL(this.serverUrl);
|
|
251
|
-
const hasAuthorizationHeader = Object.keys(this.headers || {})
|
|
261
|
+
const baseUrl = new URL(this.config.serverUrl);
|
|
262
|
+
const hasAuthorizationHeader = Object.keys(this.config.headers || {})
|
|
252
263
|
.some((key) => key.toLowerCase() === 'authorization');
|
|
253
|
-
const transportOptions = {
|
|
254
|
-
...(!hasAuthorizationHeader && { authProvider: this.oauthProvider
|
|
255
|
-
...(this.headers && { requestInit: { headers: this.headers } }),
|
|
264
|
+
const transportOptions: Record<string, any> = {
|
|
265
|
+
...(!hasAuthorizationHeader && { authProvider: this.oauthProvider }),
|
|
266
|
+
...(this.config.headers && { requestInit: { headers: this.config.headers } }),
|
|
256
267
|
/**
|
|
257
268
|
* Custom fetch implementation to handle connection timeouts.
|
|
258
269
|
* Observation: SDK 1.24.0+ connections may hang indefinitely in some environments.
|
|
@@ -296,96 +307,87 @@ export class MCPClient {
|
|
|
296
307
|
* @private
|
|
297
308
|
*/
|
|
298
309
|
private async ensureSession(): Promise<void> {
|
|
299
|
-
if (this.oauthProvider)
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
310
|
+
if (this.oauthProvider) return;
|
|
302
311
|
|
|
303
312
|
this.emitStateChange('INITIALIZING');
|
|
304
313
|
this.emitProgress('Loading session configuration...');
|
|
305
314
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
309
|
-
existingSession = await sessions.get(this.userId, this.sessionId);
|
|
315
|
+
if (!this.config.serverUrl || !this.config.callbackUrl || !this.config.serverId) {
|
|
316
|
+
const existingSession = await this._store.get(this.config.userId, this.config.sessionId);
|
|
310
317
|
if (!existingSession) {
|
|
311
|
-
throw new Error(`Session not found: ${this.sessionId}`);
|
|
318
|
+
throw new Error(`Session not found: ${this.config.sessionId}`);
|
|
312
319
|
}
|
|
313
320
|
|
|
314
|
-
this.serverUrl = this.serverUrl || existingSession.serverUrl;
|
|
315
|
-
this.callbackUrl = this.callbackUrl || existingSession.callbackUrl;
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
* this.transportType = this.transportType || sessionData.transportType;
|
|
320
|
-
*/
|
|
321
|
-
this.serverName = this.serverName || existingSession.serverName;
|
|
322
|
-
this.serverId = this.serverId || existingSession.serverId || 'unknown';
|
|
323
|
-
this.headers = this.headers || existingSession.headers;
|
|
321
|
+
this.config.serverUrl = this.config.serverUrl || existingSession.serverUrl;
|
|
322
|
+
this.config.callbackUrl = this.config.callbackUrl || existingSession.callbackUrl;
|
|
323
|
+
this.config.serverName = this.config.serverName || existingSession.serverName;
|
|
324
|
+
this.config.serverId = this.config.serverId || existingSession.serverId || 'unknown';
|
|
325
|
+
this.config.headers = this.config.headers || existingSession.headers;
|
|
324
326
|
this.createdAt = existingSession.createdAt;
|
|
325
327
|
}
|
|
326
328
|
|
|
327
|
-
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
329
|
+
if (!this.config.serverUrl || !this.config.callbackUrl || !this.config.serverId) {
|
|
328
330
|
throw new Error('Missing required connection metadata');
|
|
329
331
|
}
|
|
330
332
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
this.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
this.
|
|
349
|
-
|
|
333
|
+
this.oauthProvider = new StorageOAuthClientProvider({
|
|
334
|
+
userId: this.config.userId,
|
|
335
|
+
serverId: this.config.serverId!,
|
|
336
|
+
sessionId: this.config.sessionId,
|
|
337
|
+
redirectUrl: this.config.callbackUrl!,
|
|
338
|
+
clientName: this.config.clientName,
|
|
339
|
+
clientUri: this.config.clientUri,
|
|
340
|
+
logoUri: this.config.logoUri,
|
|
341
|
+
policyUri: this.config.policyUri,
|
|
342
|
+
clientId: this.config.clientId,
|
|
343
|
+
clientSecret: this.config.clientSecret,
|
|
344
|
+
cachedTokens: this.config.cachedCredentials?.tokens,
|
|
345
|
+
sessionStore: this._store,
|
|
346
|
+
onRedirect: (redirectUrl: string) => {
|
|
347
|
+
if (this.config.serverId) {
|
|
348
|
+
this._onConnectionEvent.fire({
|
|
349
|
+
type: 'auth_required',
|
|
350
|
+
sessionId: this.config.sessionId,
|
|
351
|
+
serverId: this.config.serverId,
|
|
352
|
+
authUrl: redirectUrl,
|
|
353
|
+
timestamp: Date.now(),
|
|
354
|
+
});
|
|
350
355
|
}
|
|
351
|
-
|
|
352
|
-
|
|
356
|
+
if (this.config.onRedirect) {
|
|
357
|
+
this.config.onRedirect(redirectUrl);
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
});
|
|
353
361
|
|
|
354
|
-
// Create session
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
362
|
+
// Create session row BEFORE persisting credentials (FK constraint on mcp_credentials)
|
|
363
|
+
// When hasSession is set by the caller, the session is guaranteed
|
|
364
|
+
// to exist — skip the redundant round-trip.
|
|
365
|
+
const existingSession = this.config.hasSession ? {} as Session : await this._store.get(this.config.userId, this.config.sessionId);
|
|
366
|
+
if (!existingSession) {
|
|
361
367
|
this.createdAt = Date.now();
|
|
362
368
|
const updatedAt = this.createdAt;
|
|
363
369
|
this._onObservabilityEvent.fire({
|
|
364
370
|
type: 'mcp:client:session_created',
|
|
365
371
|
level: 'info',
|
|
366
|
-
message: `Creating pending session ${this.sessionId} for connection setup`,
|
|
367
|
-
sessionId: this.sessionId,
|
|
368
|
-
serverId: this.serverId,
|
|
372
|
+
message: `Creating pending session ${this.config.sessionId} for connection setup`,
|
|
373
|
+
sessionId: this.config.sessionId,
|
|
374
|
+
serverId: this.config.serverId,
|
|
369
375
|
timestamp: Date.now(),
|
|
370
376
|
id: nanoid(),
|
|
371
377
|
});
|
|
372
|
-
await
|
|
373
|
-
|
|
374
|
-
userId: this.userId,
|
|
375
|
-
serverId: this.serverId,
|
|
376
|
-
serverName: this.serverName,
|
|
377
|
-
serverUrl: this.serverUrl,
|
|
378
|
-
callbackUrl: this.callbackUrl,
|
|
379
|
-
transportType: this.transportType || 'streamable-http',
|
|
380
|
-
headers: this.headers,
|
|
381
|
-
createdAt: this.createdAt,
|
|
378
|
+
await this._store.create({
|
|
379
|
+
...this.session,
|
|
382
380
|
updatedAt,
|
|
383
381
|
status: 'pending',
|
|
384
382
|
});
|
|
385
383
|
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
384
|
|
|
385
|
+
// Only persist credentials on first connect. Existing sessions already have
|
|
386
|
+
// credentials from the initial connect — no DB read needed to verify.
|
|
387
|
+
if (!existingSession && this.oauthProvider instanceof StorageOAuthClientProvider) {
|
|
388
|
+
await this.oauthProvider.initializeCredentials();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
389
391
|
|
|
390
392
|
/**
|
|
391
393
|
* Saves current session state to the session store
|
|
@@ -393,40 +395,15 @@ export class MCPClient {
|
|
|
393
395
|
* @param status - Session lifecycle status used by storage cleanup
|
|
394
396
|
* @private
|
|
395
397
|
*/
|
|
396
|
-
private async saveSession(
|
|
397
|
-
|
|
398
|
-
existingSession?: Session | null
|
|
399
|
-
): Promise<void> {
|
|
400
|
-
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
398
|
+
private async saveSession(status: SessionStatus = 'active'): Promise<void> {
|
|
399
|
+
if (!this.config.sessionId || !this.config.serverId || !this.config.serverUrl || !this.config.callbackUrl) {
|
|
401
400
|
return;
|
|
402
401
|
}
|
|
403
402
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
userId: this.userId,
|
|
407
|
-
serverId: this.serverId,
|
|
408
|
-
serverName: this.serverName,
|
|
409
|
-
serverUrl: this.serverUrl,
|
|
410
|
-
callbackUrl: this.callbackUrl,
|
|
411
|
-
transportType: (this.transportType || 'streamable-http') as TransportType,
|
|
412
|
-
headers: this.headers,
|
|
413
|
-
createdAt: this.createdAt || Date.now(),
|
|
414
|
-
updatedAt: Date.now(),
|
|
403
|
+
await this._store.update(this.config.userId, this.config.sessionId, {
|
|
404
|
+
...this.session,
|
|
415
405
|
status,
|
|
416
|
-
};
|
|
417
|
-
if (status === 'active') {
|
|
418
|
-
(sessionData as typeof sessionData & { authUrl: null }).authUrl = null;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Try to update first, create if doesn't exist
|
|
422
|
-
if (existingSession === undefined) {
|
|
423
|
-
existingSession = await sessions.get(this.userId, this.sessionId);
|
|
424
|
-
}
|
|
425
|
-
if (existingSession) {
|
|
426
|
-
await sessions.update(this.userId, this.sessionId, sessionData);
|
|
427
|
-
} else {
|
|
428
|
-
await sessions.create(sessionData);
|
|
429
|
-
}
|
|
406
|
+
});
|
|
430
407
|
}
|
|
431
408
|
|
|
432
409
|
/**
|
|
@@ -435,7 +412,7 @@ export class MCPClient {
|
|
|
435
412
|
*/
|
|
436
413
|
private async deleteTransientSession(): Promise<void> {
|
|
437
414
|
try {
|
|
438
|
-
await
|
|
415
|
+
await this._store.delete(this.config.userId, this.config.sessionId);
|
|
439
416
|
} catch {
|
|
440
417
|
// Best effort only: preserve the original connection/auth error.
|
|
441
418
|
}
|
|
@@ -451,8 +428,8 @@ export class MCPClient {
|
|
|
451
428
|
* If exact transport type is known, only try that.
|
|
452
429
|
* Otherwise (auto mode), try streamable_http first, then sse.
|
|
453
430
|
*/
|
|
454
|
-
const transportsToTry: TransportType[] = this.transportType
|
|
455
|
-
? [this.transportType]
|
|
431
|
+
const transportsToTry: TransportType[] = this.config.transportType
|
|
432
|
+
? [this.config.transportType]
|
|
456
433
|
: ['streamable-http', 'sse'];
|
|
457
434
|
|
|
458
435
|
let lastError: unknown;
|
|
@@ -469,6 +446,9 @@ export class MCPClient {
|
|
|
469
446
|
/** Race connection against timeout */
|
|
470
447
|
await this.client!.connect(transport);
|
|
471
448
|
|
|
449
|
+
/** Capture server metadata from the initialize response */
|
|
450
|
+
this._serverInfo = this.client.getServerVersion();
|
|
451
|
+
|
|
472
452
|
/** Success! Return the type that worked */
|
|
473
453
|
return { transportType: currentType };
|
|
474
454
|
|
|
@@ -494,8 +474,8 @@ export class MCPClient {
|
|
|
494
474
|
this._onObservabilityEvent.fire({
|
|
495
475
|
level: 'warn',
|
|
496
476
|
message: `Transport ${currentType} failed, falling back`,
|
|
497
|
-
sessionId: this.sessionId,
|
|
498
|
-
serverId: this.serverId,
|
|
477
|
+
sessionId: this.config.sessionId,
|
|
478
|
+
serverId: this.config.serverId,
|
|
499
479
|
metadata: {
|
|
500
480
|
failedTransport: currentType,
|
|
501
481
|
error: errorMessage
|
|
@@ -550,7 +530,7 @@ export class MCPClient {
|
|
|
550
530
|
const { transportType } = await this.tryConnect();
|
|
551
531
|
|
|
552
532
|
/** Update transport type to the one that actually worked */
|
|
553
|
-
this.transportType = transportType;
|
|
533
|
+
this.config.transportType = transportType;
|
|
554
534
|
|
|
555
535
|
this.emitStateChange('CONNECTED');
|
|
556
536
|
this.emitProgress('Connected successfully');
|
|
@@ -560,9 +540,9 @@ export class MCPClient {
|
|
|
560
540
|
this._onObservabilityEvent.fire({
|
|
561
541
|
type: 'mcp:client:session_saved',
|
|
562
542
|
level: 'info',
|
|
563
|
-
message: `Saving active session ${this.sessionId} (connect success)`,
|
|
564
|
-
sessionId: this.sessionId,
|
|
565
|
-
serverId: this.serverId,
|
|
543
|
+
message: `Saving active session ${this.config.sessionId} (connect success)`,
|
|
544
|
+
sessionId: this.config.sessionId,
|
|
545
|
+
serverId: this.config.serverId,
|
|
566
546
|
timestamp: Date.now(),
|
|
567
547
|
id: nanoid(),
|
|
568
548
|
});
|
|
@@ -576,9 +556,8 @@ export class MCPClient {
|
|
|
576
556
|
/** Set when the SDK calls redirectToAuthorization on the OAuth provider */
|
|
577
557
|
let authUrl = '';
|
|
578
558
|
if (this.oauthProvider) {
|
|
579
|
-
authUrl = (this.oauthProvider.authUrl || '').trim();
|
|
559
|
+
authUrl = ((this.oauthProvider as any).authUrl || '').trim();
|
|
580
560
|
}
|
|
581
|
-
|
|
582
561
|
/**
|
|
583
562
|
* 401 without a usable URL means metadata/DCR failed or the server never started
|
|
584
563
|
* an interactive OAuth flow — not recoverable as "pending OAuth".
|
|
@@ -606,25 +585,25 @@ export class MCPClient {
|
|
|
606
585
|
this._onObservabilityEvent.fire({
|
|
607
586
|
type: 'mcp:client:session_saved',
|
|
608
587
|
level: 'info',
|
|
609
|
-
message: `Saving pending OAuth session ${this.sessionId}`,
|
|
610
|
-
sessionId: this.sessionId,
|
|
611
|
-
serverId: this.serverId,
|
|
588
|
+
message: `Saving pending OAuth session ${this.config.sessionId}`,
|
|
589
|
+
sessionId: this.config.sessionId,
|
|
590
|
+
serverId: this.config.serverId,
|
|
612
591
|
timestamp: Date.now(),
|
|
613
592
|
id: nanoid(),
|
|
614
593
|
});
|
|
615
594
|
await this.saveSession('pending');
|
|
616
595
|
|
|
617
|
-
if (this.serverId) {
|
|
596
|
+
if (this.config.serverId) {
|
|
618
597
|
this._onConnectionEvent.fire({
|
|
619
598
|
type: 'auth_required',
|
|
620
|
-
sessionId: this.sessionId,
|
|
621
|
-
serverId: this.serverId,
|
|
599
|
+
sessionId: this.config.sessionId,
|
|
600
|
+
serverId: this.config.serverId,
|
|
622
601
|
authUrl,
|
|
623
602
|
timestamp: Date.now(),
|
|
624
603
|
});
|
|
625
604
|
|
|
626
|
-
if (authUrl && this.onRedirect) {
|
|
627
|
-
this.onRedirect(authUrl);
|
|
605
|
+
if (authUrl && this.config.onRedirect) {
|
|
606
|
+
this.config.onRedirect(authUrl);
|
|
628
607
|
}
|
|
629
608
|
}
|
|
630
609
|
|
|
@@ -639,9 +618,9 @@ export class MCPClient {
|
|
|
639
618
|
// Remove transient sessions that failed before becoming restorable.
|
|
640
619
|
// Existing active sessions may still hold usable credentials for reconnect.
|
|
641
620
|
try {
|
|
642
|
-
const existingSession = await
|
|
621
|
+
const existingSession = await this._store.get(this.config.userId, this.config.sessionId);
|
|
643
622
|
if (!existingSession || existingSession.status !== 'active') {
|
|
644
|
-
await
|
|
623
|
+
await this._store.delete(this.config.userId, this.config.sessionId);
|
|
645
624
|
}
|
|
646
625
|
} catch {
|
|
647
626
|
// Best effort only: preserve the original connection error.
|
|
@@ -673,23 +652,22 @@ export class MCPClient {
|
|
|
673
652
|
}
|
|
674
653
|
|
|
675
654
|
if (state) {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
655
|
+
try {
|
|
656
|
+
await (this.oauthProvider as AgentsOAuthProvider).consumeState(state);
|
|
657
|
+
} catch (error) {
|
|
658
|
+
const msg = error instanceof Error ? error.message : 'Invalid OAuth state';
|
|
659
|
+
this.emitError(msg, 'auth');
|
|
680
660
|
this.emitStateChange('FAILED');
|
|
681
|
-
throw
|
|
661
|
+
throw error;
|
|
682
662
|
}
|
|
683
|
-
|
|
684
|
-
await this.oauthProvider.consumeState(state);
|
|
685
663
|
}
|
|
686
664
|
|
|
687
665
|
/**
|
|
688
666
|
* Determine which transports to try for finishing auth
|
|
689
667
|
* If transportType is set, use only that. Otherwise try streamable_http then sse.
|
|
690
668
|
*/
|
|
691
|
-
const transportsToTry: TransportType[] = this.transportType
|
|
692
|
-
? [this.transportType]
|
|
669
|
+
const transportsToTry: TransportType[] = this.config.transportType
|
|
670
|
+
? [this.config.transportType]
|
|
693
671
|
: ['streamable-http', 'sse'];
|
|
694
672
|
|
|
695
673
|
let lastError: unknown;
|
|
@@ -728,16 +706,19 @@ export class MCPClient {
|
|
|
728
706
|
|
|
729
707
|
await this.client.connect(this.transport);
|
|
730
708
|
|
|
709
|
+
/** Capture server metadata from the initialize response */
|
|
710
|
+
this._serverInfo = this.client.getServerVersion();
|
|
711
|
+
|
|
731
712
|
/** Connection succeeded — lock in the transport type */
|
|
732
|
-
this.transportType = currentType;
|
|
713
|
+
this.config.transportType = currentType;
|
|
733
714
|
|
|
734
715
|
this.emitStateChange('CONNECTED');
|
|
735
716
|
this._onObservabilityEvent.fire({
|
|
736
717
|
type: 'mcp:client:session_saved',
|
|
737
718
|
level: 'info',
|
|
738
|
-
message: `Saving active session ${this.sessionId} (OAuth complete)`,
|
|
739
|
-
sessionId: this.sessionId,
|
|
740
|
-
serverId: this.serverId,
|
|
719
|
+
message: `Saving active session ${this.config.sessionId} (OAuth complete)`,
|
|
720
|
+
sessionId: this.config.sessionId,
|
|
721
|
+
serverId: this.config.serverId,
|
|
741
722
|
timestamp: Date.now(),
|
|
742
723
|
id: nanoid(),
|
|
743
724
|
});
|
|
@@ -837,11 +818,11 @@ export class MCPClient {
|
|
|
837
818
|
try {
|
|
838
819
|
const result = await this.fetchTools();
|
|
839
820
|
|
|
840
|
-
if (this.serverId) {
|
|
821
|
+
if (this.config.serverId) {
|
|
841
822
|
this._onConnectionEvent.fire({
|
|
842
823
|
type: 'tools_discovered',
|
|
843
|
-
sessionId: this.sessionId,
|
|
844
|
-
serverId: this.serverId,
|
|
824
|
+
sessionId: this.config.sessionId,
|
|
825
|
+
serverId: this.config.serverId,
|
|
845
826
|
toolCount: result.tools.length,
|
|
846
827
|
tools: result.tools,
|
|
847
828
|
timestamp: Date.now(),
|
|
@@ -885,8 +866,8 @@ export class MCPClient {
|
|
|
885
866
|
level: 'info',
|
|
886
867
|
message: `Tool ${toolName} called successfully`,
|
|
887
868
|
displayMessage: `Called tool ${toolName}`,
|
|
888
|
-
sessionId: this.sessionId,
|
|
889
|
-
serverId: this.serverId,
|
|
869
|
+
sessionId: this.config.sessionId,
|
|
870
|
+
serverId: this.config.serverId,
|
|
890
871
|
payload: {
|
|
891
872
|
toolName,
|
|
892
873
|
args: toolArgs,
|
|
@@ -904,8 +885,8 @@ export class MCPClient {
|
|
|
904
885
|
level: 'error',
|
|
905
886
|
message: errorMessage,
|
|
906
887
|
displayMessage: `Failed to call tool ${toolName}`,
|
|
907
|
-
sessionId: this.sessionId,
|
|
908
|
-
serverId: this.serverId,
|
|
888
|
+
sessionId: this.config.sessionId,
|
|
889
|
+
serverId: this.config.serverId,
|
|
909
890
|
payload: {
|
|
910
891
|
errorType: 'tool_execution',
|
|
911
892
|
error: errorMessage,
|
|
@@ -1070,8 +1051,8 @@ export class MCPClient {
|
|
|
1070
1051
|
type: 'mcp:client:error',
|
|
1071
1052
|
level: 'warn',
|
|
1072
1053
|
message: 'Initialization failed during clearSession',
|
|
1073
|
-
sessionId: this.sessionId,
|
|
1074
|
-
serverId: this.serverId,
|
|
1054
|
+
sessionId: this.config.sessionId,
|
|
1055
|
+
serverId: this.config.serverId,
|
|
1075
1056
|
payload: { error: String(error) },
|
|
1076
1057
|
timestamp: Date.now(),
|
|
1077
1058
|
id: nanoid(),
|
|
@@ -1082,7 +1063,7 @@ export class MCPClient {
|
|
|
1082
1063
|
await (this.oauthProvider as any).invalidateCredentials('all');
|
|
1083
1064
|
}
|
|
1084
1065
|
|
|
1085
|
-
await
|
|
1066
|
+
await this._store.delete(this.config.userId, this.config.sessionId);
|
|
1086
1067
|
await this.disconnect();
|
|
1087
1068
|
}
|
|
1088
1069
|
|
|
@@ -1125,20 +1106,20 @@ export class MCPClient {
|
|
|
1125
1106
|
this.transport = null;
|
|
1126
1107
|
|
|
1127
1108
|
// Emit disconnected event
|
|
1128
|
-
if (this.serverId) {
|
|
1109
|
+
if (this.config.serverId) {
|
|
1129
1110
|
this._onConnectionEvent.fire({
|
|
1130
1111
|
type: 'disconnected',
|
|
1131
|
-
sessionId: this.sessionId,
|
|
1132
|
-
serverId: this.serverId,
|
|
1112
|
+
sessionId: this.config.sessionId,
|
|
1113
|
+
serverId: this.config.serverId,
|
|
1133
1114
|
timestamp: Date.now(),
|
|
1134
1115
|
});
|
|
1135
1116
|
|
|
1136
1117
|
this._onObservabilityEvent.fire({
|
|
1137
1118
|
type: 'mcp:client:disconnect',
|
|
1138
1119
|
level: 'info',
|
|
1139
|
-
message: `Disconnected from ${this.serverId}`,
|
|
1140
|
-
sessionId: this.sessionId,
|
|
1141
|
-
serverId: this.serverId,
|
|
1120
|
+
message: `Disconnected from ${this.config.serverId}`,
|
|
1121
|
+
sessionId: this.config.sessionId,
|
|
1122
|
+
serverId: this.config.serverId,
|
|
1142
1123
|
payload: {},
|
|
1143
1124
|
timestamp: Date.now(),
|
|
1144
1125
|
id: nanoid(),
|
|
@@ -1166,7 +1147,7 @@ export class MCPClient {
|
|
|
1166
1147
|
* @returns Server URL or empty string if not set
|
|
1167
1148
|
*/
|
|
1168
1149
|
getServerUrl(): string {
|
|
1169
|
-
return this.serverUrl || '';
|
|
1150
|
+
return this.config.serverUrl || '';
|
|
1170
1151
|
}
|
|
1171
1152
|
|
|
1172
1153
|
/**
|
|
@@ -1174,7 +1155,7 @@ export class MCPClient {
|
|
|
1174
1155
|
* @returns Callback URL or empty string if not set
|
|
1175
1156
|
*/
|
|
1176
1157
|
getCallbackUrl(): string {
|
|
1177
|
-
return this.callbackUrl || '';
|
|
1158
|
+
return this.config.callbackUrl || '';
|
|
1178
1159
|
}
|
|
1179
1160
|
|
|
1180
1161
|
/**
|
|
@@ -1182,18 +1163,29 @@ export class MCPClient {
|
|
|
1182
1163
|
* @returns Transport type (defaults to 'streamable-http')
|
|
1183
1164
|
*/
|
|
1184
1165
|
getTransportType(): TransportType {
|
|
1185
|
-
return this.transportType || 'streamable-http';
|
|
1166
|
+
return this.config.transportType || 'streamable-http';
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Gets the full server metadata from the MCP initialize response.
|
|
1171
|
+
* Includes name, version, icons, title, description, and website URL.
|
|
1172
|
+
* Returns undefined if the client hasn't connected yet.
|
|
1173
|
+
*/
|
|
1174
|
+
getServerInfo(): Implementation | undefined {
|
|
1175
|
+
return this._serverInfo;
|
|
1186
1176
|
}
|
|
1187
1177
|
|
|
1188
1178
|
/**
|
|
1189
|
-
* Gets the human-readable server name
|
|
1179
|
+
* Gets the human-readable server name.
|
|
1180
|
+
* Prefers the server's reported title/name from the initialize response,
|
|
1181
|
+
* falling back to the name provided at construction or session metadata.
|
|
1190
1182
|
* @returns Server name or undefined
|
|
1191
1183
|
*/
|
|
1192
1184
|
getServerName(): string | undefined {
|
|
1193
1185
|
// Temporarily avoid deriving serverName from serverVersion metadata.
|
|
1194
1186
|
// const info = (this.client as any)?.getServerVersion();
|
|
1195
|
-
// return info?.title ?? info?.name ?? this.serverName;
|
|
1196
|
-
return this.serverName;
|
|
1187
|
+
// return info?.title ?? info?.name ?? this.config.serverName;
|
|
1188
|
+
return this.config.serverName;
|
|
1197
1189
|
}
|
|
1198
1190
|
|
|
1199
1191
|
/**
|
|
@@ -1201,7 +1193,7 @@ export class MCPClient {
|
|
|
1201
1193
|
* @returns Server ID or undefined
|
|
1202
1194
|
*/
|
|
1203
1195
|
getServerId(): string | undefined {
|
|
1204
|
-
return this.serverId;
|
|
1196
|
+
return this.config.serverId;
|
|
1205
1197
|
}
|
|
1206
1198
|
|
|
1207
1199
|
/**
|
|
@@ -1209,6 +1201,6 @@ export class MCPClient {
|
|
|
1209
1201
|
* @returns Session ID
|
|
1210
1202
|
*/
|
|
1211
1203
|
getSessionId(): string {
|
|
1212
|
-
return this.sessionId;
|
|
1204
|
+
return this.config.sessionId;
|
|
1213
1205
|
}
|
|
1214
1206
|
}
|