@mcp-ts/sdk 2.4.3 → 2.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +2 -2
- package/dist/adapters/mastra-adapter.d.ts +2 -2
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/react.d.mts +4 -5
- package/dist/client/react.d.ts +4 -5
- package/dist/client/react.js +2 -6
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +2 -6
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +4 -5
- package/dist/client/vue.d.ts +4 -5
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-B8kJSrBJ.d.ts → index-CZk2gu2E.d.ts} +1 -1
- package/dist/{index-DiJsm_lK.d.mts → index-CkM3p0eE.d.mts} +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +96 -104
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +96 -104
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-DOBvtaQV.d.ts → multi-session-client-ChQrBZhF.d.mts} +4 -5
- package/dist/{multi-session-client-B6hsYO8V.d.mts → multi-session-client-QOOPdEVp.d.ts} +4 -5
- package/dist/server/index.d.mts +4 -4
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +96 -104
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +96 -104
- 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 +2 -6
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +2 -6
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-CbG4Tum6.d.mts → tool-router-CGs3IDOJ.d.mts} +1 -1
- package/dist/{tool-router-ChIhPwgP.d.ts → tool-router-DQ-HrD7W.d.ts} +1 -1
- package/dist/{types-CjczQwNX.d.mts → types-Bf-7GOLW.d.mts} +0 -2
- package/dist/{types-CjczQwNX.d.ts → types-Bf-7GOLW.d.ts} +0 -2
- package/package.json +1 -1
- package/src/client/react/use-mcp.ts +0 -1
- package/src/client/vue/use-mcp.ts +0 -1
- package/src/server/handlers/sse-handler.ts +2 -2
- package/src/server/mcp/oauth-client.ts +94 -121
- package/src/server/storage/index.ts +1 -1
- package/src/shared/events.ts +0 -2
- package/src/shared/utils.ts +16 -14
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
import { A as ToolClientProvider, T as ToolClient } from './types-
|
|
2
|
+
import { A as ToolClientProvider, T as ToolClient } from './types-Bf-7GOLW.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* ToolIndex — Lightweight in-memory search index for MCP tool discovery.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
import { A as ToolClientProvider, T as ToolClient } from './types-
|
|
2
|
+
import { A as ToolClientProvider, T as ToolClient } from './types-Bf-7GOLW.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* ToolIndex — Lightweight in-memory search index for MCP tool discovery.
|
|
@@ -2,7 +2,6 @@ import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Simple event emitter pattern for MCP connection events
|
|
5
|
-
* Inspired by Cloudflare's agents pattern but adapted for serverless
|
|
6
5
|
*/
|
|
7
6
|
type Disposable = {
|
|
8
7
|
dispose(): void;
|
|
@@ -10,7 +9,6 @@ type Disposable = {
|
|
|
10
9
|
type Event<T> = (listener: (event: T) => void) => Disposable;
|
|
11
10
|
/**
|
|
12
11
|
* Event emitter class for type-safe event handling
|
|
13
|
-
* Similar to Cloudflare's Emitter but simplified for our use case
|
|
14
12
|
*/
|
|
15
13
|
declare class Emitter<T> {
|
|
16
14
|
private listeners;
|
|
@@ -2,7 +2,6 @@ import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Simple event emitter pattern for MCP connection events
|
|
5
|
-
* Inspired by Cloudflare's agents pattern but adapted for serverless
|
|
6
5
|
*/
|
|
7
6
|
type Disposable = {
|
|
8
7
|
dispose(): void;
|
|
@@ -10,7 +9,6 @@ type Disposable = {
|
|
|
10
9
|
type Event<T> = (listener: (event: T) => void) => Disposable;
|
|
11
10
|
/**
|
|
12
11
|
* Event emitter class for type-safe event handling
|
|
13
|
-
* Similar to Cloudflare's Emitter but simplified for our use case
|
|
14
12
|
*/
|
|
15
13
|
declare class Emitter<T> {
|
|
16
14
|
private listeners;
|
package/package.json
CHANGED
|
@@ -38,7 +38,7 @@ import { UnauthorizedError } from '../../shared/errors.js';
|
|
|
38
38
|
import { isConnectionEvent, isRpcResponseEvent } from '../../shared/event-routing.js';
|
|
39
39
|
import { parseOAuthState } from '../../shared/utils.js';
|
|
40
40
|
import { MCPClient } from '../mcp/oauth-client.js';
|
|
41
|
-
import { sessions } from '../storage/index.js';
|
|
41
|
+
import { sessions, generateServerId } from '../storage/index.js';
|
|
42
42
|
|
|
43
43
|
// ============================================
|
|
44
44
|
// Types & Interfaces
|
|
@@ -256,7 +256,7 @@ export class SSEConnectionManager {
|
|
|
256
256
|
// Tool name format: tool_<serverId>_<toolName> - with 12 char serverId leaves 46 chars for tool name
|
|
257
257
|
const serverId = params.serverId && params.serverId.length <= 12
|
|
258
258
|
? params.serverId
|
|
259
|
-
:
|
|
259
|
+
: generateServerId();
|
|
260
260
|
|
|
261
261
|
// Check for existing connections
|
|
262
262
|
const existingSessions = await sessions.list(this.userId);
|
|
@@ -79,7 +79,7 @@ export interface MCPOAuthClientOptions {
|
|
|
79
79
|
* Emits connection lifecycle events for observability
|
|
80
80
|
*/
|
|
81
81
|
export class MCPClient {
|
|
82
|
-
private client: Client
|
|
82
|
+
private client: Client;
|
|
83
83
|
public oauthProvider: AgentsOAuthProvider | null = null;
|
|
84
84
|
private transport: StreamableHTTPClientTransport | SSEClientTransport | null = null;
|
|
85
85
|
private userId: string;
|
|
@@ -131,6 +131,22 @@ export class MCPClient {
|
|
|
131
131
|
this.clientUri = options.clientUri;
|
|
132
132
|
this.logoUri = options.logoUri;
|
|
133
133
|
this.policyUri = options.policyUri;
|
|
134
|
+
|
|
135
|
+
this.client = new Client(
|
|
136
|
+
{
|
|
137
|
+
name: MCP_CLIENT_NAME,
|
|
138
|
+
version: MCP_CLIENT_VERSION,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
capabilities: {
|
|
142
|
+
extensions: {
|
|
143
|
+
'io.modelcontextprotocol/ui': {
|
|
144
|
+
mimeTypes: ['text/html+mcp'],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
} as McpAppClientCapabilities,
|
|
148
|
+
}
|
|
149
|
+
);
|
|
134
150
|
}
|
|
135
151
|
|
|
136
152
|
/**
|
|
@@ -275,13 +291,12 @@ export class MCPClient {
|
|
|
275
291
|
}
|
|
276
292
|
|
|
277
293
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* This method is idempotent and safe to call multiple times
|
|
294
|
+
* Ensures session metadata and OAuth provider are loaded.
|
|
295
|
+
* Does NOT create the SDK Client — callers that need one create it themselves.
|
|
281
296
|
* @private
|
|
282
297
|
*/
|
|
283
|
-
private async
|
|
284
|
-
if (this.
|
|
298
|
+
private async ensureSession(): Promise<void> {
|
|
299
|
+
if (this.oauthProvider) {
|
|
285
300
|
return;
|
|
286
301
|
}
|
|
287
302
|
|
|
@@ -336,24 +351,6 @@ export class MCPClient {
|
|
|
336
351
|
});
|
|
337
352
|
}
|
|
338
353
|
|
|
339
|
-
if (!this.client) {
|
|
340
|
-
this.client = new Client(
|
|
341
|
-
{
|
|
342
|
-
name: MCP_CLIENT_NAME,
|
|
343
|
-
version: MCP_CLIENT_VERSION,
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
capabilities: {
|
|
347
|
-
extensions: {
|
|
348
|
-
'io.modelcontextprotocol/ui': {
|
|
349
|
-
mimeTypes: ['text/html+mcp'],
|
|
350
|
-
},
|
|
351
|
-
},
|
|
352
|
-
} as McpAppClientCapabilities
|
|
353
|
-
}
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
354
|
// Create session in the session store if it doesn't exist yet
|
|
358
355
|
// This is needed BEFORE OAuth flow starts because the OAuth provider
|
|
359
356
|
// will call saveCodeVerifier() which requires the session to exist
|
|
@@ -363,7 +360,15 @@ export class MCPClient {
|
|
|
363
360
|
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
364
361
|
this.createdAt = Date.now();
|
|
365
362
|
const updatedAt = this.createdAt;
|
|
366
|
-
|
|
363
|
+
this._onObservabilityEvent.fire({
|
|
364
|
+
type: 'mcp:client:session_created',
|
|
365
|
+
level: 'info',
|
|
366
|
+
message: `Creating pending session ${this.sessionId} for connection setup`,
|
|
367
|
+
sessionId: this.sessionId,
|
|
368
|
+
serverId: this.serverId,
|
|
369
|
+
timestamp: Date.now(),
|
|
370
|
+
id: nanoid(),
|
|
371
|
+
});
|
|
367
372
|
await sessions.create({
|
|
368
373
|
sessionId: this.sessionId,
|
|
369
374
|
userId: this.userId,
|
|
@@ -380,6 +385,8 @@ export class MCPClient {
|
|
|
380
385
|
}
|
|
381
386
|
}
|
|
382
387
|
|
|
388
|
+
|
|
389
|
+
|
|
383
390
|
/**
|
|
384
391
|
* Saves current session state to the session store
|
|
385
392
|
* Creates new session if it doesn't exist, updates if it does
|
|
@@ -509,31 +516,21 @@ export class MCPClient {
|
|
|
509
516
|
* @throws {Error} When connection fails for other reasons
|
|
510
517
|
*/
|
|
511
518
|
async connect(): Promise<void> {
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
|
|
517
|
-
// causing "Session not found. Reconnect without session header." errors.
|
|
518
|
-
//
|
|
519
|
-
// We also null out this.client so that initialize() creates a fresh Client
|
|
520
|
-
// instance with a clean transport slot, rather than reusing the old one.
|
|
521
|
-
// The oauthProvider is intentionally preserved — OAuth tokens remain valid
|
|
522
|
-
// across reconnects; only the transport session needs to be renegotiated.
|
|
523
|
-
if (this.client?.transport) {
|
|
519
|
+
// Close any existing transport so we can negotiate a fresh session.
|
|
520
|
+
// The SDK Client throws if asked to connect() while a transport is
|
|
521
|
+
// already attached; close() detaches it cleanly so the same Client
|
|
522
|
+
// instance can be reused.
|
|
523
|
+
if (this.client.transport) {
|
|
524
524
|
this.transport = null;
|
|
525
|
-
try {
|
|
526
|
-
await this.client.close();
|
|
527
|
-
} catch {
|
|
525
|
+
try { await this.client.close(); } catch {
|
|
528
526
|
// Closing a transport that may have already failed is best-effort.
|
|
529
527
|
}
|
|
530
|
-
this.client = null;
|
|
531
528
|
}
|
|
532
529
|
|
|
533
|
-
await this.
|
|
530
|
+
await this.ensureSession();
|
|
534
531
|
|
|
535
|
-
if (!this.
|
|
536
|
-
const error = '
|
|
532
|
+
if (!this.oauthProvider) {
|
|
533
|
+
const error = 'OAuth provider not initialized';
|
|
537
534
|
this.emitError(error, 'connection');
|
|
538
535
|
this.emitStateChange('FAILED');
|
|
539
536
|
throw new Error(error);
|
|
@@ -553,7 +550,15 @@ export class MCPClient {
|
|
|
553
550
|
|
|
554
551
|
// Refresh session metadata on every successful connect so active sessions
|
|
555
552
|
// record ongoing usage and don't look dormant to session cleanup jobs.
|
|
556
|
-
|
|
553
|
+
this._onObservabilityEvent.fire({
|
|
554
|
+
type: 'mcp:client:session_saved',
|
|
555
|
+
level: 'info',
|
|
556
|
+
message: `Saving active session ${this.sessionId} (connect success)`,
|
|
557
|
+
sessionId: this.sessionId,
|
|
558
|
+
serverId: this.serverId,
|
|
559
|
+
timestamp: Date.now(),
|
|
560
|
+
id: nanoid(),
|
|
561
|
+
});
|
|
557
562
|
await this.saveSession('active');
|
|
558
563
|
} catch (error) {
|
|
559
564
|
/** Handle Authentication Errors */
|
|
@@ -591,7 +596,15 @@ export class MCPClient {
|
|
|
591
596
|
}
|
|
592
597
|
|
|
593
598
|
this.emitStateChange('AUTHENTICATING');
|
|
594
|
-
|
|
599
|
+
this._onObservabilityEvent.fire({
|
|
600
|
+
type: 'mcp:client:session_saved',
|
|
601
|
+
level: 'info',
|
|
602
|
+
message: `Saving pending OAuth session ${this.sessionId}`,
|
|
603
|
+
sessionId: this.sessionId,
|
|
604
|
+
serverId: this.serverId,
|
|
605
|
+
timestamp: Date.now(),
|
|
606
|
+
id: nanoid(),
|
|
607
|
+
});
|
|
595
608
|
await this.saveSession('pending');
|
|
596
609
|
|
|
597
610
|
if (this.serverId) {
|
|
@@ -643,7 +656,7 @@ export class MCPClient {
|
|
|
643
656
|
this.emitStateChange('AUTHENTICATING');
|
|
644
657
|
this.emitProgress('Exchanging authorization code for tokens...');
|
|
645
658
|
|
|
646
|
-
await this.
|
|
659
|
+
await this.ensureSession();
|
|
647
660
|
|
|
648
661
|
if (!this.oauthProvider) {
|
|
649
662
|
const error = 'OAuth provider not initialized';
|
|
@@ -697,34 +710,30 @@ export class MCPClient {
|
|
|
697
710
|
authenticatedStateEmitted = true;
|
|
698
711
|
}
|
|
699
712
|
|
|
700
|
-
this.emitProgress('Creating authenticated client...');
|
|
701
|
-
|
|
702
|
-
this.client = new Client(
|
|
703
|
-
{
|
|
704
|
-
name: MCP_CLIENT_NAME,
|
|
705
|
-
version: MCP_CLIENT_VERSION,
|
|
706
|
-
},
|
|
707
|
-
{
|
|
708
|
-
capabilities: {
|
|
709
|
-
extensions: {
|
|
710
|
-
'io.modelcontextprotocol/ui': {
|
|
711
|
-
mimeTypes: ['text/html+mcp'],
|
|
712
|
-
},
|
|
713
|
-
},
|
|
714
|
-
} as McpAppClientCapabilities
|
|
715
|
-
}
|
|
716
|
-
);
|
|
717
|
-
|
|
718
713
|
this.emitStateChange('CONNECTING');
|
|
719
714
|
|
|
720
|
-
|
|
715
|
+
// The SDK Client may still have a transport attached from a prior
|
|
716
|
+
// connect() that failed with UnauthorizedError; close it first so
|
|
717
|
+
// we can negotiate a fresh session with the newly-exchanged tokens.
|
|
718
|
+
if (this.client.transport) {
|
|
719
|
+
try { await this.client.close(); } catch {}
|
|
720
|
+
}
|
|
721
|
+
|
|
721
722
|
await this.client.connect(this.transport);
|
|
722
723
|
|
|
723
724
|
/** Connection succeeded — lock in the transport type */
|
|
724
725
|
this.transportType = currentType;
|
|
725
726
|
|
|
726
727
|
this.emitStateChange('CONNECTED');
|
|
727
|
-
|
|
728
|
+
this._onObservabilityEvent.fire({
|
|
729
|
+
type: 'mcp:client:session_saved',
|
|
730
|
+
level: 'info',
|
|
731
|
+
message: `Saving active session ${this.sessionId} (OAuth complete)`,
|
|
732
|
+
sessionId: this.sessionId,
|
|
733
|
+
serverId: this.serverId,
|
|
734
|
+
timestamp: Date.now(),
|
|
735
|
+
id: nanoid(),
|
|
736
|
+
});
|
|
728
737
|
await this.saveSession('active');
|
|
729
738
|
|
|
730
739
|
return; // Success, exit function
|
|
@@ -778,10 +787,6 @@ export class MCPClient {
|
|
|
778
787
|
* @throws {Error} When client is not connected
|
|
779
788
|
*/
|
|
780
789
|
async listTools(): Promise<ListToolsResult> {
|
|
781
|
-
if (!this.client) {
|
|
782
|
-
throw new Error('Not connected to server');
|
|
783
|
-
}
|
|
784
|
-
|
|
785
790
|
this.emitStateChange('DISCOVERING');
|
|
786
791
|
|
|
787
792
|
try {
|
|
@@ -825,9 +830,6 @@ export class MCPClient {
|
|
|
825
830
|
* @throws {Error} When client is not connected
|
|
826
831
|
*/
|
|
827
832
|
async callTool(toolName: string, toolArgs: Record<string, unknown>): Promise<CallToolResult> {
|
|
828
|
-
if (!this.client) {
|
|
829
|
-
throw new Error('Not connected to server');
|
|
830
|
-
}
|
|
831
833
|
|
|
832
834
|
const request: CallToolRequest = {
|
|
833
835
|
method: 'tools/call',
|
|
@@ -888,10 +890,6 @@ export class MCPClient {
|
|
|
888
890
|
* @throws {Error} When client is not connected
|
|
889
891
|
*/
|
|
890
892
|
async listPrompts(): Promise<ListPromptsResult> {
|
|
891
|
-
if (!this.client) {
|
|
892
|
-
throw new Error('Not connected to server');
|
|
893
|
-
}
|
|
894
|
-
|
|
895
893
|
this.emitStateChange('DISCOVERING');
|
|
896
894
|
|
|
897
895
|
try {
|
|
@@ -924,9 +922,6 @@ export class MCPClient {
|
|
|
924
922
|
* @throws {Error} When client is not connected
|
|
925
923
|
*/
|
|
926
924
|
async getPrompt(name: string, args?: Record<string, string>): Promise<GetPromptResult> {
|
|
927
|
-
if (!this.client) {
|
|
928
|
-
throw new Error('Not connected to server');
|
|
929
|
-
}
|
|
930
925
|
|
|
931
926
|
const request: GetPromptRequest = {
|
|
932
927
|
method: 'prompts/get',
|
|
@@ -947,10 +942,6 @@ export class MCPClient {
|
|
|
947
942
|
* @throws {Error} When client is not connected
|
|
948
943
|
*/
|
|
949
944
|
async listResources(): Promise<ListResourcesResult> {
|
|
950
|
-
if (!this.client) {
|
|
951
|
-
throw new Error('Not connected to server');
|
|
952
|
-
}
|
|
953
|
-
|
|
954
945
|
this.emitStateChange('DISCOVERING');
|
|
955
946
|
|
|
956
947
|
try {
|
|
@@ -982,9 +973,6 @@ export class MCPClient {
|
|
|
982
973
|
* @throws {Error} When client is not connected
|
|
983
974
|
*/
|
|
984
975
|
async readResource(uri: string): Promise<ReadResourceResult> {
|
|
985
|
-
if (!this.client) {
|
|
986
|
-
throw new Error('Not connected to server');
|
|
987
|
-
}
|
|
988
976
|
|
|
989
977
|
const request: ReadResourceRequest = {
|
|
990
978
|
method: 'resources/read',
|
|
@@ -1013,10 +1001,9 @@ export class MCPClient {
|
|
|
1013
1001
|
return await fn();
|
|
1014
1002
|
} catch (error) {
|
|
1015
1003
|
if (!(error instanceof Error && error.message.includes('MCP_SESSION_EXPIRED'))) throw error;
|
|
1016
|
-
if (this.client) {
|
|
1004
|
+
if (this.client.transport) {
|
|
1017
1005
|
try { await this.client.close(); } catch {}
|
|
1018
1006
|
this.transport = null;
|
|
1019
|
-
this.client = null;
|
|
1020
1007
|
}
|
|
1021
1008
|
await this.reconnect();
|
|
1022
1009
|
return await fn();
|
|
@@ -1030,31 +1017,9 @@ export class MCPClient {
|
|
|
1030
1017
|
* @throws {Error} When OAuth provider is not initialized
|
|
1031
1018
|
*/
|
|
1032
1019
|
async reconnect(): Promise<void> {
|
|
1033
|
-
await this.
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
throw new Error('OAuth provider not initialized');
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
// Close the client initialize() may have created — we need a fresh
|
|
1040
|
-
// client that will negotiate a new transport session.
|
|
1041
|
-
if (this.client) {
|
|
1042
|
-
try { await this.client.close(); } catch {}
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
this.client = new Client(
|
|
1046
|
-
{
|
|
1047
|
-
name: MCP_CLIENT_NAME,
|
|
1048
|
-
version: MCP_CLIENT_VERSION,
|
|
1049
|
-
},
|
|
1050
|
-
{ capabilities: {} }
|
|
1051
|
-
);
|
|
1052
|
-
|
|
1053
|
-
// Use default logic to get transport, defaulting to what's stored or auto
|
|
1054
|
-
const tt = this.transportType || 'streamable-http';
|
|
1055
|
-
this.transport = this.getTransport(tt);
|
|
1056
|
-
|
|
1057
|
-
await this.client.connect(this.transport);
|
|
1020
|
+
await this.ensureSession();
|
|
1021
|
+
if (!this.oauthProvider) throw new Error('OAuth provider not initialized');
|
|
1022
|
+
await this.connect();
|
|
1058
1023
|
}
|
|
1059
1024
|
|
|
1060
1025
|
/**
|
|
@@ -1063,9 +1028,18 @@ export class MCPClient {
|
|
|
1063
1028
|
*/
|
|
1064
1029
|
async clearSession(): Promise<void> {
|
|
1065
1030
|
try {
|
|
1066
|
-
await this.
|
|
1031
|
+
await this.ensureSession();
|
|
1067
1032
|
} catch (error) {
|
|
1068
|
-
|
|
1033
|
+
this._onObservabilityEvent.fire({
|
|
1034
|
+
type: 'mcp:client:error',
|
|
1035
|
+
level: 'warn',
|
|
1036
|
+
message: 'Initialization failed during clearSession',
|
|
1037
|
+
sessionId: this.sessionId,
|
|
1038
|
+
serverId: this.serverId,
|
|
1039
|
+
payload: { error: String(error) },
|
|
1040
|
+
timestamp: Date.now(),
|
|
1041
|
+
id: nanoid(),
|
|
1042
|
+
});
|
|
1069
1043
|
}
|
|
1070
1044
|
|
|
1071
1045
|
if (this.oauthProvider) {
|
|
@@ -1081,7 +1055,7 @@ export class MCPClient {
|
|
|
1081
1055
|
* @returns True if connected, false otherwise
|
|
1082
1056
|
*/
|
|
1083
1057
|
isConnected(): boolean {
|
|
1084
|
-
return this.client !==
|
|
1058
|
+
return this.client.transport !== undefined;
|
|
1085
1059
|
}
|
|
1086
1060
|
|
|
1087
1061
|
/**
|
|
@@ -1108,10 +1082,9 @@ export class MCPClient {
|
|
|
1108
1082
|
}
|
|
1109
1083
|
}
|
|
1110
1084
|
|
|
1111
|
-
if (this.client) {
|
|
1112
|
-
this.client.close();
|
|
1085
|
+
if (this.client.transport) {
|
|
1086
|
+
try { await this.client.close(); } catch {}
|
|
1113
1087
|
}
|
|
1114
|
-
this.client = null;
|
|
1115
1088
|
this.oauthProvider = null;
|
|
1116
1089
|
this.transport = null;
|
|
1117
1090
|
|
|
@@ -9,7 +9,7 @@ import type { SessionStore, Session, SessionMutationEvent, SessionMutationListen
|
|
|
9
9
|
|
|
10
10
|
// Re-export types
|
|
11
11
|
export * from './types.js';
|
|
12
|
-
export { generateSessionId } from '../../shared/utils.js';
|
|
12
|
+
export { generateSessionId, generateServerId } from '../../shared/utils.js';
|
|
13
13
|
export { RedisStorageBackend, MemoryStorageBackend, FileStorageBackend, SqliteStorage, SupabaseStorageBackend, NeonStorageBackend };
|
|
14
14
|
|
|
15
15
|
export function createSupabaseStorageBackend(client: any): SupabaseStorageBackend {
|
package/src/shared/events.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Simple event emitter pattern for MCP connection events
|
|
3
|
-
* Inspired by Cloudflare's agents pattern but adapted for serverless
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
export type Disposable = {
|
|
@@ -11,7 +10,6 @@ export type Event<T> = (listener: (event: T) => void) => Disposable;
|
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Event emitter class for type-safe event handling
|
|
14
|
-
* Similar to Cloudflare's Emitter but simplified for our use case
|
|
15
13
|
*/
|
|
16
14
|
export class Emitter<T> {
|
|
17
15
|
private listeners: Set<(event: T) => void> = new Set();
|
package/src/shared/utils.ts
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
import { customAlphabet } from 'nanoid';
|
|
1
|
+
import { customAlphabet, nanoid } from 'nanoid';
|
|
2
2
|
|
|
3
3
|
const OAUTH_STATE_SEPARATOR = '.';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
1
|
|
9
|
-
);
|
|
10
|
-
|
|
11
|
-
/** remaining chars: alphanumeric */
|
|
12
|
-
const rest = customAlphabet(
|
|
13
|
-
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
|
|
14
|
-
11
|
|
5
|
+
const serverIdAlphabet = customAlphabet(
|
|
6
|
+
'abcdefghijklmnopqrstuvwxyz0123456789',
|
|
7
|
+
12
|
|
15
8
|
);
|
|
16
9
|
|
|
17
10
|
/**
|
|
@@ -32,11 +25,20 @@ export function sanitizeServerLabel(name: string): string {
|
|
|
32
25
|
}
|
|
33
26
|
|
|
34
27
|
/**
|
|
35
|
-
* Generates a
|
|
36
|
-
*
|
|
28
|
+
* Generates a session ID with a human-readable prefix for easy identification.
|
|
29
|
+
* Format: sess_<21-char nanoid> (26 chars total).
|
|
30
|
+
* Not used in tool names, so length is not constrained.
|
|
37
31
|
*/
|
|
38
32
|
export function generateSessionId(): string {
|
|
39
|
-
return
|
|
33
|
+
return 'sess_' + nanoid(21);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generates a short server ID suitable for use in tool names.
|
|
38
|
+
* 12-char alphanumeric string that keeps tool_<serverId>_<toolName> under 64 chars.
|
|
39
|
+
*/
|
|
40
|
+
export function generateServerId(): string {
|
|
41
|
+
return serverIdAlphabet();
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
export interface ParsedOAuthState {
|