@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.
Files changed (56) hide show
  1. package/dist/adapters/agui-adapter.d.mts +3 -3
  2. package/dist/adapters/agui-adapter.d.ts +3 -3
  3. package/dist/adapters/agui-middleware.d.mts +3 -3
  4. package/dist/adapters/agui-middleware.d.ts +3 -3
  5. package/dist/adapters/ai-adapter.d.mts +3 -3
  6. package/dist/adapters/ai-adapter.d.ts +3 -3
  7. package/dist/adapters/langchain-adapter.d.mts +3 -3
  8. package/dist/adapters/langchain-adapter.d.ts +3 -3
  9. package/dist/adapters/mastra-adapter.d.mts +2 -2
  10. package/dist/adapters/mastra-adapter.d.ts +2 -2
  11. package/dist/client/index.d.mts +2 -2
  12. package/dist/client/index.d.ts +2 -2
  13. package/dist/client/react.d.mts +4 -5
  14. package/dist/client/react.d.ts +4 -5
  15. package/dist/client/react.js +2 -6
  16. package/dist/client/react.js.map +1 -1
  17. package/dist/client/react.mjs +2 -6
  18. package/dist/client/react.mjs.map +1 -1
  19. package/dist/client/vue.d.mts +4 -5
  20. package/dist/client/vue.d.ts +4 -5
  21. package/dist/client/vue.js.map +1 -1
  22. package/dist/client/vue.mjs.map +1 -1
  23. package/dist/{index-B8kJSrBJ.d.ts → index-CZk2gu2E.d.ts} +1 -1
  24. package/dist/{index-DiJsm_lK.d.mts → index-CkM3p0eE.d.mts} +1 -1
  25. package/dist/index.d.mts +4 -4
  26. package/dist/index.d.ts +4 -4
  27. package/dist/index.js +96 -104
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +96 -104
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/{multi-session-client-DOBvtaQV.d.ts → multi-session-client-ChQrBZhF.d.mts} +4 -5
  32. package/dist/{multi-session-client-B6hsYO8V.d.mts → multi-session-client-QOOPdEVp.d.ts} +4 -5
  33. package/dist/server/index.d.mts +4 -4
  34. package/dist/server/index.d.ts +4 -4
  35. package/dist/server/index.js +96 -104
  36. package/dist/server/index.js.map +1 -1
  37. package/dist/server/index.mjs +96 -104
  38. package/dist/server/index.mjs.map +1 -1
  39. package/dist/shared/index.d.mts +4 -4
  40. package/dist/shared/index.d.ts +4 -4
  41. package/dist/shared/index.js +2 -6
  42. package/dist/shared/index.js.map +1 -1
  43. package/dist/shared/index.mjs +2 -6
  44. package/dist/shared/index.mjs.map +1 -1
  45. package/dist/{tool-router-CbG4Tum6.d.mts → tool-router-CGs3IDOJ.d.mts} +1 -1
  46. package/dist/{tool-router-ChIhPwgP.d.ts → tool-router-DQ-HrD7W.d.ts} +1 -1
  47. package/dist/{types-CjczQwNX.d.mts → types-Bf-7GOLW.d.mts} +0 -2
  48. package/dist/{types-CjczQwNX.d.ts → types-Bf-7GOLW.d.ts} +0 -2
  49. package/package.json +1 -1
  50. package/src/client/react/use-mcp.ts +0 -1
  51. package/src/client/vue/use-mcp.ts +0 -1
  52. package/src/server/handlers/sse-handler.ts +2 -2
  53. package/src/server/mcp/oauth-client.ts +94 -121
  54. package/src/server/storage/index.ts +1 -1
  55. package/src/shared/events.ts +0 -2
  56. 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-CjczQwNX.mjs';
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-CjczQwNX.js';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-ts/sdk",
3
- "version": "2.4.3",
3
+ "version": "2.4.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * useMcp React Hook
3
3
  * Manages MCP connections with SSE-based real-time updates
4
- * Based on Cloudflare's agents pattern
5
4
  */
6
5
 
7
6
  import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * useMcp Vue Composable
3
3
  * Manages MCP connections with SSE-based real-time updates
4
- * Based on Cloudflare's agents pattern
5
4
  */
6
5
 
7
6
  import { ref, onMounted, onUnmounted, watch, computed, shallowRef } from 'vue';
@@ -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
- : await sessions.generateSessionId();
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 | null = null;
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
- * Initializes client components (client, transport, OAuth provider)
279
- * Loads missing configuration from Redis session store if needed
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 initialize(): Promise<void> {
284
- if (this.client && this.oauthProvider) {
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
- console.log(`[MCPClient] Creating pending session ${this.sessionId} for connection setup`);
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
- // Re-entry guard: if a previous SDK Client is still attached to a transport
513
- // (e.g. a stale session from a prior connect() call on the same MCPClient
514
- // instance), close and detach it before proceeding. The MCP SDK client will
515
- // throw if asked to connect while a transport is already attached, and the
516
- // stale transport would send an expired mcp-session-id to the remote server
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.initialize();
530
+ await this.ensureSession();
534
531
 
535
- if (!this.client || !this.oauthProvider) {
536
- const error = 'Client or OAuth provider not initialized';
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
- console.log(`[MCPClient] Saving active session ${this.sessionId} (connect success)`);
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
- console.log(`[MCPClient] Saving pending OAuth session ${this.sessionId}`);
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.initialize();
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
- /** We explicitly try to connect with the transport we just auth'd with first */
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
- console.log(`[MCPClient] Saving active session ${this.sessionId} (OAuth complete)`);
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.initialize();
1034
-
1035
- if (!this.oauthProvider) {
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.initialize();
1031
+ await this.ensureSession();
1067
1032
  } catch (error) {
1068
- console.warn('[MCPClient] Initialization failed during clearSession:', error);
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 !== null;
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 {
@@ -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();
@@ -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
- /** first char: letters only (required by OpenAI) */
6
- const firstChar = customAlphabet(
7
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
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 standard 12-character session ID compliant with external tool restrictions.
36
- * First character is always a letter.
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 firstChar() + rest();
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 {