@mcp-ts/sdk 1.3.6 → 1.3.9

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 (103) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +398 -404
  3. package/dist/adapters/agui-adapter.d.mts +1 -1
  4. package/dist/adapters/agui-adapter.d.ts +1 -1
  5. package/dist/adapters/agui-adapter.js +2 -2
  6. package/dist/adapters/agui-adapter.js.map +1 -1
  7. package/dist/adapters/agui-adapter.mjs +2 -2
  8. package/dist/adapters/agui-adapter.mjs.map +1 -1
  9. package/dist/adapters/agui-middleware.d.mts +1 -1
  10. package/dist/adapters/agui-middleware.d.ts +1 -1
  11. package/dist/adapters/agui-middleware.js.map +1 -1
  12. package/dist/adapters/agui-middleware.mjs.map +1 -1
  13. package/dist/adapters/ai-adapter.d.mts +1 -1
  14. package/dist/adapters/ai-adapter.d.ts +1 -1
  15. package/dist/adapters/ai-adapter.js +1 -1
  16. package/dist/adapters/ai-adapter.js.map +1 -1
  17. package/dist/adapters/ai-adapter.mjs +1 -1
  18. package/dist/adapters/ai-adapter.mjs.map +1 -1
  19. package/dist/adapters/langchain-adapter.d.mts +1 -1
  20. package/dist/adapters/langchain-adapter.d.ts +1 -1
  21. package/dist/adapters/langchain-adapter.js +1 -1
  22. package/dist/adapters/langchain-adapter.js.map +1 -1
  23. package/dist/adapters/langchain-adapter.mjs +1 -1
  24. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  25. package/dist/adapters/mastra-adapter.d.mts +1 -1
  26. package/dist/adapters/mastra-adapter.d.ts +1 -1
  27. package/dist/adapters/mastra-adapter.js +1 -1
  28. package/dist/adapters/mastra-adapter.js.map +1 -1
  29. package/dist/adapters/mastra-adapter.mjs +1 -1
  30. package/dist/adapters/mastra-adapter.mjs.map +1 -1
  31. package/dist/bin/mcp-ts.js +0 -0
  32. package/dist/bin/mcp-ts.js.map +1 -1
  33. package/dist/bin/mcp-ts.mjs +0 -0
  34. package/dist/bin/mcp-ts.mjs.map +1 -1
  35. package/dist/client/index.js.map +1 -1
  36. package/dist/client/index.mjs.map +1 -1
  37. package/dist/client/react.d.mts +2 -2
  38. package/dist/client/react.d.ts +2 -2
  39. package/dist/client/react.js +25 -2
  40. package/dist/client/react.js.map +1 -1
  41. package/dist/client/react.mjs +26 -3
  42. package/dist/client/react.mjs.map +1 -1
  43. package/dist/client/vue.js.map +1 -1
  44. package/dist/client/vue.mjs.map +1 -1
  45. package/dist/index.d.mts +1 -1
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +134 -71
  48. package/dist/index.js.map +1 -1
  49. package/dist/index.mjs +134 -71
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/{multi-session-client-BYLarghq.d.ts → multi-session-client-CHE8QpVE.d.ts} +75 -5
  52. package/dist/{multi-session-client-CzhMkE0k.d.mts → multi-session-client-CQsRbxYI.d.mts} +75 -5
  53. package/dist/server/index.d.mts +1 -1
  54. package/dist/server/index.d.ts +1 -1
  55. package/dist/server/index.js +134 -71
  56. package/dist/server/index.js.map +1 -1
  57. package/dist/server/index.mjs +134 -71
  58. package/dist/server/index.mjs.map +1 -1
  59. package/dist/shared/index.js +10 -2
  60. package/dist/shared/index.js.map +1 -1
  61. package/dist/shared/index.mjs +10 -2
  62. package/dist/shared/index.mjs.map +1 -1
  63. package/package.json +185 -185
  64. package/src/adapters/agui-adapter.ts +222 -222
  65. package/src/adapters/agui-middleware.ts +382 -382
  66. package/src/adapters/ai-adapter.ts +115 -115
  67. package/src/adapters/langchain-adapter.ts +127 -127
  68. package/src/adapters/mastra-adapter.ts +126 -126
  69. package/src/bin/mcp-ts.ts +102 -102
  70. package/src/client/core/app-host.ts +417 -417
  71. package/src/client/core/sse-client.ts +371 -371
  72. package/src/client/core/types.ts +31 -31
  73. package/src/client/index.ts +27 -27
  74. package/src/client/react/index.ts +16 -16
  75. package/src/client/react/use-app-host.ts +73 -73
  76. package/src/client/react/use-mcp-apps.tsx +247 -214
  77. package/src/client/react/use-mcp.ts +641 -641
  78. package/src/client/vue/index.ts +10 -10
  79. package/src/client/vue/use-mcp.ts +617 -617
  80. package/src/index.ts +11 -11
  81. package/src/server/handlers/nextjs-handler.ts +204 -204
  82. package/src/server/handlers/sse-handler.ts +631 -631
  83. package/src/server/index.ts +57 -57
  84. package/src/server/mcp/multi-session-client.ts +228 -132
  85. package/src/server/mcp/oauth-client.ts +1188 -1188
  86. package/src/server/mcp/storage-oauth-provider.ts +272 -272
  87. package/src/server/storage/file-backend.ts +157 -170
  88. package/src/server/storage/index.ts +176 -175
  89. package/src/server/storage/memory-backend.ts +123 -136
  90. package/src/server/storage/redis-backend.ts +276 -289
  91. package/src/server/storage/redis.ts +160 -160
  92. package/src/server/storage/sqlite-backend.ts +182 -186
  93. package/src/server/storage/supabase-backend.ts +228 -227
  94. package/src/server/storage/types.ts +116 -116
  95. package/src/shared/constants.ts +29 -29
  96. package/src/shared/errors.ts +133 -133
  97. package/src/shared/event-routing.ts +28 -28
  98. package/src/shared/events.ts +180 -180
  99. package/src/shared/index.ts +75 -75
  100. package/src/shared/tool-utils.ts +61 -61
  101. package/src/shared/types.ts +282 -282
  102. package/src/shared/utils.ts +38 -16
  103. package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -84
@@ -1,57 +1,57 @@
1
- /**
2
- * MCP Redis Server Package
3
- * Node.js server-side exports for MCP connection management with Redis
4
- */
5
-
6
- /** Core MCP client and session management */
7
- export { MCPClient } from './mcp/oauth-client.js';
8
- export { UnauthorizedError } from '../shared/errors.js';
9
- export { storage, type StorageBackend } from './storage/index.js';
10
- export { StorageOAuthClientProvider } from './mcp/storage-oauth-provider.js';
11
- export { MultiSessionClient } from './mcp/multi-session-client.js';
12
-
13
- /** SSE handler for real-time connections */
14
- export { createSSEHandler, SSEConnectionManager, type SSEHandlerOptions, type ClientMetadata } from './handlers/sse-handler.js';
15
-
16
- /** Next.js App Router handler (recommended for Next.js 13+) */
17
- export { createNextMcpHandler, type NextMcpHandlerOptions } from './handlers/nextjs-handler.js';
18
-
19
- /** Session provider abstraction */
20
-
21
- /** Utilities */
22
- export { sanitizeServerLabel } from '../shared/utils';
23
-
24
- /** Re-export shared types */
25
- export type {
26
- McpConnectionEvent,
27
- McpConnectionState,
28
- McpObservabilityEvent,
29
- Emitter,
30
- Disposable,
31
- Event,
32
- } from '../shared/events';
33
-
34
- export type {
35
- ToolInfo,
36
- McpRpcRequest,
37
- McpRpcResponse,
38
- ConnectRequest,
39
- ConnectResponse,
40
- ListToolsResponse,
41
- CallToolRequest,
42
- CallToolResponse,
43
- } from '../shared/types';
44
-
45
- /** Re-export MCP SDK types for convenience */
46
- export type {
47
- OAuthClientMetadata,
48
- OAuthClientInformation,
49
- OAuthClientInformationFull,
50
- OAuthTokens,
51
- } from '@modelcontextprotocol/sdk/shared/auth.js';
52
-
53
- export type {
54
- ListToolsResult,
55
- CallToolResult,
56
- Tool,
57
- } from '@modelcontextprotocol/sdk/types.js';
1
+ /**
2
+ * MCP Redis Server Package
3
+ * Node.js server-side exports for MCP connection management with Redis
4
+ */
5
+
6
+ /** Core MCP client and session management */
7
+ export { MCPClient } from './mcp/oauth-client.js';
8
+ export { UnauthorizedError } from '../shared/errors.js';
9
+ export { storage, type StorageBackend } from './storage/index.js';
10
+ export { StorageOAuthClientProvider } from './mcp/storage-oauth-provider.js';
11
+ export { MultiSessionClient } from './mcp/multi-session-client.js';
12
+
13
+ /** SSE handler for real-time connections */
14
+ export { createSSEHandler, SSEConnectionManager, type SSEHandlerOptions, type ClientMetadata } from './handlers/sse-handler.js';
15
+
16
+ /** Next.js App Router handler (recommended for Next.js 13+) */
17
+ export { createNextMcpHandler, type NextMcpHandlerOptions } from './handlers/nextjs-handler.js';
18
+
19
+ /** Session provider abstraction */
20
+
21
+ /** Utilities */
22
+ export { sanitizeServerLabel } from '../shared/utils';
23
+
24
+ /** Re-export shared types */
25
+ export type {
26
+ McpConnectionEvent,
27
+ McpConnectionState,
28
+ McpObservabilityEvent,
29
+ Emitter,
30
+ Disposable,
31
+ Event,
32
+ } from '../shared/events';
33
+
34
+ export type {
35
+ ToolInfo,
36
+ McpRpcRequest,
37
+ McpRpcResponse,
38
+ ConnectRequest,
39
+ ConnectResponse,
40
+ ListToolsResponse,
41
+ CallToolRequest,
42
+ CallToolResponse,
43
+ } from '../shared/types';
44
+
45
+ /** Re-export MCP SDK types for convenience */
46
+ export type {
47
+ OAuthClientMetadata,
48
+ OAuthClientInformation,
49
+ OAuthClientInformationFull,
50
+ OAuthTokens,
51
+ } from '@modelcontextprotocol/sdk/shared/auth.js';
52
+
53
+ export type {
54
+ ListToolsResult,
55
+ CallToolResult,
56
+ Tool,
57
+ } from '@modelcontextprotocol/sdk/types.js';
@@ -1,132 +1,228 @@
1
-
2
-
3
- import { MCPClient } from './oauth-client.js';
4
- import { storage, type SessionData } from '../storage/index.js';
5
-
6
- /**
7
- * Manages multiple MCP connections for a single user identity.
8
- * Allows aggregating tools from all connected servers.
9
- */
10
- export interface MultiSessionOptions {
11
- /**
12
- * Connection timeout in milliseconds
13
- * @default 15000
14
- */
15
- timeout?: number;
16
- /**
17
- * Maximum number of retry attempts
18
- * @default 2
19
- */
20
- maxRetries?: number;
21
- /**
22
- * Delay between retries in milliseconds
23
- * @default 1000
24
- */
25
- retryDelay?: number;
26
- }
27
-
28
- /**
29
- * Manages multiple MCP connections for a single user identity.
30
- * Allows aggregating tools from all connected servers.
31
- */
32
- export class MultiSessionClient {
33
- private clients: MCPClient[] = [];
34
- private identity: string;
35
- private options: MultiSessionOptions;
36
-
37
- constructor(identity: string, options: MultiSessionOptions = {}) {
38
- this.identity = identity;
39
- this.options = {
40
- timeout: 15000,
41
- maxRetries: 2,
42
- retryDelay: 1000,
43
- ...options
44
- };
45
- }
46
-
47
- private async getActiveSessions(): Promise<SessionData[]> {
48
- const sessions = await storage.getIdentitySessionsData(this.identity);
49
- console.log(`[MultiSessionClient] All sessions for ${this.identity}:`,
50
- sessions.map(s => ({ sessionId: s.sessionId, serverId: s.serverId }))
51
- );
52
- const valid = sessions.filter(s => s.serverId && s.serverUrl && s.callbackUrl);
53
- console.log(`[MultiSessionClient] Filtered valid sessions:`, valid.length);
54
- return valid;
55
- }
56
-
57
- private async connectInBatches(sessions: SessionData[]): Promise<void> {
58
- const BATCH_SIZE = 5;
59
- for (let i = 0; i < sessions.length; i += BATCH_SIZE) {
60
- const batch = sessions.slice(i, i + BATCH_SIZE);
61
- await Promise.all(batch.map(session => this.connectSession(session)));
62
- }
63
- }
64
-
65
- private async connectSession(session: SessionData): Promise<void> {
66
- const existingClient = this.clients.find(c => c.getSessionId() === session.sessionId);
67
- if (existingClient?.isConnected()) {
68
- return;
69
- }
70
-
71
- const maxRetries = this.options.maxRetries ?? 2;
72
- const retryDelay = this.options.retryDelay ?? 1000;
73
- let lastError: unknown;
74
-
75
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
76
- try {
77
- const client = await this.createAndConnectClient(session);
78
- this.clients.push(client);
79
- return;
80
- } catch (error) {
81
- lastError = error;
82
- if (attempt < maxRetries) {
83
- await new Promise(resolve => setTimeout(resolve, retryDelay));
84
- }
85
- }
86
- }
87
-
88
- console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
89
- }
90
-
91
- private async createAndConnectClient(session: SessionData): Promise<MCPClient> {
92
- const client = new MCPClient({
93
- identity: this.identity,
94
- sessionId: session.sessionId,
95
- serverId: session.serverId,
96
- serverUrl: session.serverUrl,
97
- callbackUrl: session.callbackUrl,
98
- serverName: session.serverName,
99
- transportType: session.transportType,
100
- headers: session.headers,
101
- });
102
-
103
- const timeoutMs = this.options.timeout ?? 15000;
104
- const timeoutPromise = new Promise<never>((_, reject) => {
105
- setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
106
- });
107
-
108
- await Promise.race([client.connect(), timeoutPromise]);
109
- return client;
110
- }
111
-
112
- async connect(): Promise<void> {
113
- const sessions = await this.getActiveSessions();
114
- await this.connectInBatches(sessions);
115
- }
116
-
117
- /**
118
- * Returns the array of currently connected clients.
119
- */
120
- getClients(): MCPClient[] {
121
- return this.clients;
122
- }
123
-
124
- /**
125
- * Disconnects all clients.
126
- */
127
- disconnect(): void {
128
- this.clients.forEach((client) => client.disconnect());
129
- this.clients = [];
130
- }
131
- }
132
-
1
+
2
+
3
+ import { MCPClient } from './oauth-client.js';
4
+ import { storage, type SessionData } from '../storage/index.js';
5
+
6
+ const DEFAULT_TIMEOUT_MS = 15000;
7
+ const DEFAULT_MAX_RETRIES = 2;
8
+ const DEFAULT_RETRY_DELAY_MS = 1000;
9
+ const CONNECTION_BATCH_SIZE = 5;
10
+
11
+ /**
12
+ * Manages multiple MCP connections for a single user identity.
13
+ * Allows aggregating tools from all connected servers.
14
+ */
15
+ export interface MultiSessionOptions {
16
+ /**
17
+ * Connection timeout in milliseconds
18
+ * @default 15000
19
+ */
20
+ timeout?: number;
21
+ /**
22
+ * Maximum number of retry attempts
23
+ * @default 2
24
+ */
25
+ maxRetries?: number;
26
+ /**
27
+ * Delay between retries in milliseconds
28
+ * @default 1000
29
+ */
30
+ retryDelay?: number;
31
+ }
32
+
33
+ /**
34
+ * Manages multiple MCP client connections for a single user identity.
35
+ *
36
+ * On a traditional long-running server, you can cache this instance per user
37
+ * so the connections stay alive between requests. On serverless, a new instance
38
+ * will be created per invocation, but the underlying session data is always
39
+ * read from the storage backend so nothing is lost between calls.
40
+ */
41
+ export class MultiSessionClient {
42
+ private clients: MCPClient[] = [];
43
+ private identity: string;
44
+ private options: MultiSessionOptions;
45
+
46
+ /**
47
+ * Creates a new MultiSessionClient for the given user identity.
48
+ *
49
+ * @param identity - A unique string identifying the user (e.g. user ID or email).
50
+ * @param options - Optional tuning for connection timeout, retry count, and retry delay.
51
+ * Falls back to sensible defaults if not provided.
52
+ */
53
+ constructor(identity: string, options: MultiSessionOptions = {}) {
54
+ this.identity = identity;
55
+ this.options = {
56
+ timeout: DEFAULT_TIMEOUT_MS,
57
+ maxRetries: DEFAULT_MAX_RETRIES,
58
+ retryDelay: DEFAULT_RETRY_DELAY_MS,
59
+ ...options
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Fetches all sessions for this identity from storage and returns only the
65
+ * ones that are ready to connect.
66
+ *
67
+ * A session is considered connectable when:
68
+ * - It has a `serverId`, `serverUrl`, and `callbackUrl` (i.e. it was fully initialized)
69
+ * - Its `active` flag is not explicitly `false` — sessions with `active: false` are
70
+ * either mid-OAuth flow, auth-pending, or previously failed. We skip those here
71
+ * and let the OAuth flow complete separately before we try to reconnect them.
72
+ *
73
+ * Note: Sessions where `active` is `undefined` (legacy records) are included
74
+ * for backwards compatibility.
75
+ */
76
+ private async getActiveSessions(): Promise<SessionData[]> {
77
+ const sessions = await storage.getIdentitySessionsData(this.identity);
78
+ const valid = sessions.filter(s =>
79
+ s.serverId &&
80
+ s.serverUrl &&
81
+ s.callbackUrl &&
82
+ s.active !== false // exclude OAuth-pending / failed sessions
83
+ );
84
+ return valid;
85
+ }
86
+
87
+ /**
88
+ * Connects to a list of sessions in controlled batches of `CONNECTION_BATCH_SIZE`.
89
+ *
90
+ * Batching prevents overwhelming the event loop or external servers when a user
91
+ * has many active MCP sessions (e.g. 20+ servers). Within each batch, sessions
92
+ * are connected concurrently using `Promise.all` for speed.
93
+ */
94
+ private async connectInBatches(sessions: SessionData[]): Promise<void> {
95
+ for (let i = 0; i < sessions.length; i += CONNECTION_BATCH_SIZE) {
96
+ const batch = sessions.slice(i, i + CONNECTION_BATCH_SIZE);
97
+ await Promise.all(batch.map(session => this.connectSession(session)));
98
+ }
99
+ }
100
+
101
+ private connectionPromises = new Map<string, Promise<void>>();
102
+
103
+ /**
104
+ * Connects a single session, with built-in deduplication to prevent race conditions.
105
+ *
106
+ * - If a client for this session already exists and is connected, returns immediately.
107
+ * - If a connection attempt for this session is already in-flight (e.g. from a
108
+ * concurrent call), it joins the existing promise instead of starting a new one.
109
+ * This is the key concurrency lock — the `connectionPromises` map acts as a
110
+ * per-session mutex so we never spin up two physical connections for the same session.
111
+ * - On completion (success or failure), the promise is cleaned up from the map.
112
+ */
113
+ private async connectSession(session: SessionData): Promise<void> {
114
+ const existingClient = this.clients.find(c => c.getSessionId() === session.sessionId);
115
+ if (existingClient?.isConnected()) {
116
+ return;
117
+ }
118
+
119
+ // Avoid concurrent connection attempts for the same session
120
+ if (this.connectionPromises.has(session.sessionId)) {
121
+ return this.connectionPromises.get(session.sessionId)!;
122
+ }
123
+
124
+ const connectPromise = this.establishConnectionWithRetries(session);
125
+
126
+ this.connectionPromises.set(session.sessionId, connectPromise);
127
+
128
+ try {
129
+ await connectPromise;
130
+ } finally {
131
+ this.connectionPromises.delete(session.sessionId);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * The core connection loop for a single session.
137
+ *
138
+ * Attempts to establish a physical MCP connection, retrying up to `maxRetries` times
139
+ * if the connection fails. Each attempt:
140
+ * 1. Creates a fresh `MCPClient` instance from the session data.
141
+ * 2. Races the connect call against a timeout promise — if the server doesn't respond
142
+ * within `timeoutMs`, the attempt is aborted and counted as a failure.
143
+ * 3. On success, replaces any stale client entry for this session in the `clients` array.
144
+ * 4. On failure, waits `retryDelay` ms before the next attempt.
145
+ *
146
+ * If all attempts are exhausted, logs an error and returns silently (does not throw),
147
+ * so a single bad server doesn't block the rest of the batch from connecting.
148
+ */
149
+ private async establishConnectionWithRetries(session: SessionData): Promise<void> {
150
+ const maxRetries = this.options.maxRetries ?? DEFAULT_MAX_RETRIES;
151
+ const retryDelay = this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
152
+ let lastError: unknown;
153
+
154
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
155
+ try {
156
+ const client = new MCPClient({
157
+ identity: this.identity,
158
+ sessionId: session.sessionId,
159
+ serverId: session.serverId,
160
+ serverUrl: session.serverUrl,
161
+ callbackUrl: session.callbackUrl,
162
+ serverName: session.serverName,
163
+ transportType: session.transportType,
164
+ headers: session.headers,
165
+ });
166
+
167
+ const timeoutMs = this.options.timeout ?? DEFAULT_TIMEOUT_MS;
168
+ let timeoutTimer: ReturnType<typeof setTimeout>;
169
+ const timeoutPromise = new Promise<never>((_, reject) => {
170
+ timeoutTimer = setTimeout(() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)), timeoutMs);
171
+ });
172
+
173
+ try {
174
+ await Promise.race([client.connect(), timeoutPromise]);
175
+ } finally {
176
+ clearTimeout(timeoutTimer!);
177
+ }
178
+
179
+ // Always replace the disconnected client entry
180
+ this.clients = this.clients.filter(c => c.getSessionId() !== session.sessionId);
181
+ this.clients.push(client);
182
+ return; // successfully connected
183
+ } catch (error) {
184
+ lastError = error;
185
+ if (attempt < maxRetries) {
186
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
187
+ }
188
+ }
189
+ }
190
+
191
+ console.error(`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`, lastError);
192
+ }
193
+
194
+ /**
195
+ * The main entry point. Fetches all active sessions for this identity from
196
+ * storage and establishes connections to all of them in batches.
197
+ *
198
+ * Call this once after creating the client. On traditional servers, you can
199
+ * cache the `MultiSessionClient` instance after calling `connect()` to avoid
200
+ * re-fetching and re-connecting on every request.
201
+ */
202
+ async connect(): Promise<void> {
203
+ const sessions = await this.getActiveSessions();
204
+ await this.connectInBatches(sessions);
205
+ }
206
+
207
+ /**
208
+ * Returns all currently connected `MCPClient` instances.
209
+ *
210
+ * Use this to enumerate available tools across all connected servers,
211
+ * or to route a tool call to the right client by `serverId`.
212
+ */
213
+ getClients(): MCPClient[] {
214
+ return this.clients;
215
+ }
216
+
217
+ /**
218
+ * Gracefully disconnects all active MCP clients and clears the internal client list.
219
+ *
220
+ * Call this during server shutdown or when a user logs out to free up
221
+ * underlying transport resources (SSE streams, HTTP connections, etc.).
222
+ */
223
+ disconnect(): void {
224
+ this.clients.forEach((client) => client.disconnect());
225
+ this.clients = [];
226
+ }
227
+ }
228
+