@mcp-ts/sdk 1.3.2 → 1.3.3

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 (58) hide show
  1. package/README.md +405 -406
  2. package/dist/adapters/agui-adapter.d.mts +1 -1
  3. package/dist/adapters/agui-adapter.d.ts +1 -1
  4. package/dist/adapters/agui-middleware.d.mts +1 -1
  5. package/dist/adapters/agui-middleware.d.ts +1 -1
  6. package/dist/adapters/ai-adapter.d.mts +1 -1
  7. package/dist/adapters/ai-adapter.d.ts +1 -1
  8. package/dist/adapters/langchain-adapter.d.mts +1 -1
  9. package/dist/adapters/langchain-adapter.d.ts +1 -1
  10. package/dist/adapters/mastra-adapter.d.mts +1 -1
  11. package/dist/adapters/mastra-adapter.d.ts +1 -1
  12. package/dist/client/index.d.mts +8 -64
  13. package/dist/client/index.d.ts +8 -64
  14. package/dist/client/index.js +91 -173
  15. package/dist/client/index.js.map +1 -1
  16. package/dist/client/index.mjs +91 -173
  17. package/dist/client/index.mjs.map +1 -1
  18. package/dist/client/react.d.mts +12 -2
  19. package/dist/client/react.d.ts +12 -2
  20. package/dist/client/react.js +119 -182
  21. package/dist/client/react.js.map +1 -1
  22. package/dist/client/react.mjs +119 -182
  23. package/dist/client/react.mjs.map +1 -1
  24. package/dist/client/vue.d.mts +24 -4
  25. package/dist/client/vue.d.ts +24 -4
  26. package/dist/client/vue.js +121 -182
  27. package/dist/client/vue.js.map +1 -1
  28. package/dist/client/vue.mjs +121 -182
  29. package/dist/client/vue.mjs.map +1 -1
  30. package/dist/index.d.mts +2 -2
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +215 -250
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.mjs +215 -250
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/{multi-session-client-B1DBx5yR.d.mts → multi-session-client-DzjmT7FX.d.mts} +1 -0
  37. package/dist/{multi-session-client-DyFzyJUx.d.ts → multi-session-client-FAFpUzZ4.d.ts} +1 -0
  38. package/dist/server/index.d.mts +16 -21
  39. package/dist/server/index.d.ts +16 -21
  40. package/dist/server/index.js +124 -77
  41. package/dist/server/index.js.map +1 -1
  42. package/dist/server/index.mjs +124 -77
  43. package/dist/server/index.mjs.map +1 -1
  44. package/dist/shared/index.d.mts +2 -2
  45. package/dist/shared/index.d.ts +2 -2
  46. package/dist/shared/index.js.map +1 -1
  47. package/dist/shared/index.mjs.map +1 -1
  48. package/dist/{types-PjM1W07s.d.mts → types-CW6lghof.d.mts} +5 -0
  49. package/dist/{types-PjM1W07s.d.ts → types-CW6lghof.d.ts} +5 -0
  50. package/package.json +1 -1
  51. package/src/client/core/sse-client.ts +354 -493
  52. package/src/client/react/use-mcp.ts +75 -23
  53. package/src/client/vue/use-mcp.ts +111 -48
  54. package/src/server/handlers/nextjs-handler.ts +207 -217
  55. package/src/server/handlers/sse-handler.ts +10 -0
  56. package/src/server/mcp/oauth-client.ts +41 -32
  57. package/src/server/storage/types.ts +12 -5
  58. package/src/shared/types.ts +5 -0
@@ -8,62 +8,27 @@ var jsxRuntime = require('react/jsx-runtime');
8
8
  var __defProp = Object.defineProperty;
9
9
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
10
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
11
- var DEFAULT_REQUEST_TIMEOUT = 6e4;
12
- var MAX_RECONNECT_ATTEMPTS = 5;
13
- var BASE_RECONNECT_DELAY = 1e3;
14
11
  var SSEClient = class {
15
12
  constructor(options) {
16
13
  this.options = options;
17
- __publicField(this, "eventSource", null);
18
- __publicField(this, "pendingRequests", /* @__PURE__ */ new Map());
19
14
  __publicField(this, "resourceCache", /* @__PURE__ */ new Map());
20
- __publicField(this, "reconnectAttempts", 0);
21
- __publicField(this, "isManuallyDisconnected", false);
22
- __publicField(this, "connectionPromise", null);
23
- __publicField(this, "connectionResolver", null);
15
+ __publicField(this, "connected", false);
24
16
  }
25
- // ============================================
26
- // Connection Management
27
- // ============================================
28
- /**
29
- * Connect to the SSE endpoint
30
- */
31
17
  connect() {
32
- if (this.eventSource) {
18
+ if (this.connected) {
33
19
  return;
34
20
  }
35
- this.isManuallyDisconnected = false;
36
- this.options.onStatusChange?.("connecting");
37
- this.connectionPromise = new Promise((resolve) => {
38
- this.connectionResolver = resolve;
39
- });
40
- const url = this.buildUrl();
41
- this.eventSource = new EventSource(url);
42
- this.setupEventListeners();
21
+ this.connected = true;
22
+ this.options.onStatusChange?.("connected");
23
+ this.log("RPC mode: post_stream");
43
24
  }
44
- /**
45
- * Disconnect from the SSE endpoint
46
- */
47
25
  disconnect() {
48
- this.isManuallyDisconnected = true;
49
- if (this.eventSource) {
50
- this.eventSource.close();
51
- this.eventSource = null;
52
- }
53
- this.connectionPromise = null;
54
- this.connectionResolver = null;
55
- this.rejectAllPendingRequests(new Error("Connection closed"));
26
+ this.connected = false;
56
27
  this.options.onStatusChange?.("disconnected");
57
28
  }
58
- /**
59
- * Check if connected to the SSE endpoint
60
- */
61
29
  isConnected() {
62
- return this.eventSource?.readyState === EventSource.OPEN;
30
+ return this.connected;
63
31
  }
64
- // ============================================
65
- // RPC Methods
66
- // ============================================
67
32
  async getSessions() {
68
33
  return this.sendRequest("getSessions");
69
34
  }
@@ -99,22 +64,10 @@ var SSEClient = class {
99
64
  async readResource(sessionId, uri) {
100
65
  return this.sendRequest("readResource", { sessionId, uri });
101
66
  }
102
- // ============================================
103
- // Resource Preloading (for instant UI loading)
104
- // ============================================
105
- /**
106
- * Preload UI resources for tools that have UI metadata.
107
- * Call this when tools are discovered to enable instant MCP App UI loading.
108
- */
109
67
  preloadToolUiResources(sessionId, tools) {
110
68
  for (const tool of tools) {
111
69
  const uri = this.extractUiResourceUri(tool);
112
- if (!uri) continue;
113
- if (this.resourceCache.has(uri)) {
114
- this.log(`Resource already cached: ${uri}`);
115
- continue;
116
- }
117
- this.log(`Preloading UI resource for tool "${tool.name}": ${uri}`);
70
+ if (!uri || this.resourceCache.has(uri)) continue;
118
71
  const promise = this.sendRequest("readResource", { sessionId, uri }).catch((err) => {
119
72
  this.log(`Failed to preload resource ${uri}: ${err.message}`, "warn");
120
73
  this.resourceCache.delete(uri);
@@ -123,43 +76,24 @@ var SSEClient = class {
123
76
  this.resourceCache.set(uri, promise);
124
77
  }
125
78
  }
126
- /**
127
- * Get a preloaded resource from cache, or fetch if not cached.
128
- */
129
79
  getOrFetchResource(sessionId, uri) {
130
80
  const cached = this.resourceCache.get(uri);
131
- if (cached) {
132
- this.log(`Cache hit for resource: ${uri}`);
133
- return cached;
134
- }
135
- this.log(`Cache miss, fetching resource: ${uri}`);
81
+ if (cached) return cached;
136
82
  const promise = this.sendRequest("readResource", { sessionId, uri });
137
83
  this.resourceCache.set(uri, promise);
138
84
  return promise;
139
85
  }
140
- /**
141
- * Check if a resource is already cached
142
- */
143
86
  hasPreloadedResource(uri) {
144
87
  return this.resourceCache.has(uri);
145
88
  }
146
- /**
147
- * Clear the resource cache
148
- */
149
89
  clearResourceCache() {
150
90
  this.resourceCache.clear();
151
91
  }
152
- // ============================================
153
- // Private: Request Handling
154
- // ============================================
155
- /**
156
- * Send an RPC request and return the response directly from HTTP.
157
- * This bypasses SSE latency by returning results in the HTTP response body.
158
- */
159
92
  async sendRequest(method, params) {
160
- if (this.connectionPromise) {
161
- await this.connectionPromise;
93
+ if (!this.connected) {
94
+ this.connect();
162
95
  }
96
+ this.log(`RPC request via post_stream: ${method}`);
163
97
  const request = {
164
98
  id: `rpc_${nanoid.nanoid(10)}`,
165
99
  method,
@@ -173,103 +107,93 @@ var SSEClient = class {
173
107
  if (!response.ok) {
174
108
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
175
109
  }
176
- const data = await response.json();
177
- return this.parseRpcResponse(data, request.id);
110
+ const contentType = (response.headers.get("content-type") || "").toLowerCase();
111
+ if (!contentType.includes("text/event-stream")) {
112
+ const data2 = await response.json();
113
+ return this.parseRpcResponse(data2);
114
+ }
115
+ const data = await this.readRpcResponseFromStream(response);
116
+ return this.parseRpcResponse(data);
117
+ }
118
+ async readRpcResponseFromStream(response) {
119
+ if (!response.body) {
120
+ throw new Error("Streaming response body is missing");
121
+ }
122
+ const reader = response.body.getReader();
123
+ const decoder = new TextDecoder();
124
+ let buffer = "";
125
+ let rpcResponse = null;
126
+ const dispatchBlock = (block) => {
127
+ const lines = block.split("\n");
128
+ let eventName = "message";
129
+ const dataLines = [];
130
+ for (const rawLine of lines) {
131
+ const line = rawLine.replace(/\r$/, "");
132
+ if (!line || line.startsWith(":")) continue;
133
+ if (line.startsWith("event:")) {
134
+ eventName = line.slice("event:".length).trim();
135
+ continue;
136
+ }
137
+ if (line.startsWith("data:")) {
138
+ dataLines.push(line.slice("data:".length).trimStart());
139
+ }
140
+ }
141
+ if (!dataLines.length) return;
142
+ const payloadText = dataLines.join("\n");
143
+ let payload = payloadText;
144
+ try {
145
+ payload = JSON.parse(payloadText);
146
+ } catch {
147
+ }
148
+ switch (eventName) {
149
+ case "connected":
150
+ this.options.onStatusChange?.("connected");
151
+ break;
152
+ case "connection":
153
+ this.options.onConnectionEvent?.(payload);
154
+ break;
155
+ case "observability":
156
+ this.options.onObservabilityEvent?.(payload);
157
+ break;
158
+ case "rpc-response":
159
+ rpcResponse = payload;
160
+ break;
161
+ }
162
+ };
163
+ while (true) {
164
+ const { value, done } = await reader.read();
165
+ if (done) break;
166
+ buffer += decoder.decode(value, { stream: true });
167
+ let separatorMatch = buffer.match(/\r?\n\r?\n/);
168
+ while (separatorMatch && separatorMatch.index !== void 0) {
169
+ const separatorIndex = separatorMatch.index;
170
+ const separatorLength = separatorMatch[0].length;
171
+ const block = buffer.slice(0, separatorIndex);
172
+ buffer = buffer.slice(separatorIndex + separatorLength);
173
+ dispatchBlock(block);
174
+ separatorMatch = buffer.match(/\r?\n\r?\n/);
175
+ }
176
+ }
177
+ if (buffer.trim()) {
178
+ dispatchBlock(buffer);
179
+ }
180
+ if (!rpcResponse) {
181
+ throw new Error("Missing rpc-response event in streamed RPC result");
182
+ }
183
+ return rpcResponse;
178
184
  }
179
- /**
180
- * Parse RPC response and handle different response formats
181
- */
182
- parseRpcResponse(data, requestId) {
185
+ parseRpcResponse(data) {
183
186
  if ("result" in data) {
184
187
  return data.result;
185
188
  }
186
189
  if ("error" in data && data.error) {
187
190
  throw new Error(data.error.message || "Unknown RPC error");
188
191
  }
189
- if ("acknowledged" in data) {
190
- return this.waitForSseResponse(requestId);
192
+ if (data && typeof data === "object" && "id" in data) {
193
+ return void 0;
191
194
  }
192
195
  throw new Error("Invalid RPC response format");
193
196
  }
194
- /**
195
- * Wait for RPC response via SSE (legacy fallback)
196
- */
197
- waitForSseResponse(requestId) {
198
- const timeoutMs = this.options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
199
- return new Promise((resolve, reject) => {
200
- const timeoutId = setTimeout(() => {
201
- this.pendingRequests.delete(requestId);
202
- reject(new Error(`Request timeout after ${timeoutMs}ms`));
203
- }, timeoutMs);
204
- this.pendingRequests.set(requestId, {
205
- resolve,
206
- reject,
207
- timeoutId
208
- });
209
- });
210
- }
211
- /**
212
- * Handle RPC response received via SSE (legacy)
213
- */
214
- handleRpcResponse(response) {
215
- const pending = this.pendingRequests.get(response.id);
216
- if (!pending) return;
217
- clearTimeout(pending.timeoutId);
218
- this.pendingRequests.delete(response.id);
219
- if (response.error) {
220
- pending.reject(new Error(response.error.message));
221
- } else {
222
- pending.resolve(response.result);
223
- }
224
- }
225
- // ============================================
226
- // Private: Event Handling
227
- // ============================================
228
- setupEventListeners() {
229
- if (!this.eventSource) return;
230
- this.eventSource.addEventListener("open", () => {
231
- this.log("Connected");
232
- this.reconnectAttempts = 0;
233
- this.options.onStatusChange?.("connected");
234
- });
235
- this.eventSource.addEventListener("connected", () => {
236
- this.log("Server ready");
237
- this.connectionResolver?.();
238
- this.connectionResolver = null;
239
- });
240
- this.eventSource.addEventListener("connection", (e) => {
241
- const event = JSON.parse(e.data);
242
- this.options.onConnectionEvent?.(event);
243
- });
244
- this.eventSource.addEventListener("observability", (e) => {
245
- const event = JSON.parse(e.data);
246
- this.options.onObservabilityEvent?.(event);
247
- });
248
- this.eventSource.addEventListener("rpc-response", (e) => {
249
- const response = JSON.parse(e.data);
250
- this.handleRpcResponse(response);
251
- });
252
- this.eventSource.addEventListener("error", () => {
253
- this.log("Connection error", "error");
254
- this.options.onStatusChange?.("error");
255
- this.attemptReconnect();
256
- });
257
- }
258
- attemptReconnect() {
259
- if (this.isManuallyDisconnected || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
260
- return;
261
- }
262
- this.reconnectAttempts++;
263
- const delay = BASE_RECONNECT_DELAY * this.reconnectAttempts;
264
- this.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
265
- setTimeout(() => {
266
- this.disconnect();
267
- this.connect();
268
- }, delay);
269
- }
270
- // ============================================
271
- // Private: Utilities
272
- // ============================================
273
197
  buildUrl() {
274
198
  const url = new URL(this.options.url, globalThis.location?.origin);
275
199
  url.searchParams.set("identity", this.options.identity);
@@ -280,20 +204,14 @@ var SSEClient = class {
280
204
  }
281
205
  buildHeaders() {
282
206
  const headers = {
283
- "Content-Type": "application/json"
207
+ "Content-Type": "application/json",
208
+ "Accept": "text/event-stream"
284
209
  };
285
210
  if (this.options.authToken) {
286
211
  headers["Authorization"] = `Bearer ${this.options.authToken}`;
287
212
  }
288
213
  return headers;
289
214
  }
290
- rejectAllPendingRequests(error) {
291
- for (const [, pending] of this.pendingRequests) {
292
- clearTimeout(pending.timeoutId);
293
- pending.reject(error);
294
- }
295
- this.pendingRequests.clear();
296
- }
297
215
  extractUiResourceUri(tool) {
298
216
  const meta = tool._meta?.ui;
299
217
  if (!meta || typeof meta !== "object") return void 0;
@@ -344,6 +262,7 @@ function useMcp(options) {
344
262
  } = options;
345
263
  const clientRef = react.useRef(null);
346
264
  const isMountedRef = react.useRef(true);
265
+ const suppressAuthRedirectSessionsRef = react.useRef(/* @__PURE__ */ new Set());
347
266
  const [connections, setConnections] = react.useState([]);
348
267
  const [status, setStatus] = react.useState(
349
268
  "disconnected"
@@ -367,7 +286,7 @@ function useMcp(options) {
367
286
  setStatus(newStatus);
368
287
  }
369
288
  },
370
- requestTimeout: options.requestTimeout
289
+ debug: options.debug
371
290
  };
372
291
  const client = new SSEClient(clientOptions);
373
292
  clientRef.current = client;
@@ -384,15 +303,17 @@ function useMcp(options) {
384
303
  }, [url, identity, authToken, autoConnect, autoInitialize]);
385
304
  const updateConnectionsFromEvent = react.useCallback((event) => {
386
305
  if (!isMountedRef.current) return;
306
+ const isTransientReconnectState = (state) => state === "INITIALIZING" || state === "VALIDATING" || state === "RECONNECTING" || state === "CONNECTING" || state === "CONNECTED" || state === "DISCOVERING";
387
307
  setConnections((prev) => {
388
308
  switch (event.type) {
389
309
  case "state_changed": {
390
310
  const existing = prev.find((c) => c.sessionId === event.sessionId);
391
311
  if (existing) {
312
+ const nextState = existing.state === "READY" && isTransientReconnectState(event.state) ? existing.state : event.state;
392
313
  return prev.map(
393
314
  (c) => c.sessionId === event.sessionId ? {
394
315
  ...c,
395
- state: event.state,
316
+ state: nextState,
396
317
  // update createdAt if present in event, otherwise keep existing
397
318
  createdAt: event.createdAt ? new Date(event.createdAt) : c.createdAt
398
319
  } : c
@@ -426,14 +347,16 @@ function useMcp(options) {
426
347
  case "auth_required": {
427
348
  if (event.authUrl) {
428
349
  onLog?.("info", `OAuth required - redirecting to ${event.authUrl}`, { authUrl: event.authUrl });
429
- if (onRedirect) {
430
- onRedirect(event.authUrl);
431
- } else if (typeof window !== "undefined") {
432
- window.location.href = event.authUrl;
350
+ if (!suppressAuthRedirectSessionsRef.current.has(event.sessionId)) {
351
+ if (onRedirect) {
352
+ onRedirect(event.authUrl);
353
+ } else if (typeof window !== "undefined") {
354
+ window.location.href = event.authUrl;
355
+ }
433
356
  }
434
357
  }
435
358
  return prev.map(
436
- (c) => c.sessionId === event.sessionId ? { ...c, state: "AUTHENTICATING" } : c
359
+ (c) => c.sessionId === event.sessionId ? { ...c, state: "AUTHENTICATING", authUrl: event.authUrl } : c
437
360
  );
438
361
  }
439
362
  case "error": {
@@ -448,7 +371,7 @@ function useMcp(options) {
448
371
  return prev;
449
372
  }
450
373
  });
451
- }, [onLog]);
374
+ }, [onLog, onRedirect]);
452
375
  const loadSessions = react.useCallback(async () => {
453
376
  if (!clientRef.current) return;
454
377
  try {
@@ -463,7 +386,7 @@ function useMcp(options) {
463
386
  serverName: s.serverName ?? "Unknown Server",
464
387
  serverUrl: s.serverUrl,
465
388
  transport: s.transport,
466
- state: "VALIDATING",
389
+ state: s.active === false ? "AUTHENTICATING" : "VALIDATING",
467
390
  createdAt: new Date(s.createdAt),
468
391
  tools: []
469
392
  }))
@@ -473,9 +396,15 @@ function useMcp(options) {
473
396
  sessions.map(async (session) => {
474
397
  if (clientRef.current) {
475
398
  try {
399
+ if (session.active === false) {
400
+ return;
401
+ }
402
+ suppressAuthRedirectSessionsRef.current.add(session.sessionId);
476
403
  await clientRef.current.restoreSession(session.sessionId);
477
404
  } catch (error) {
478
405
  console.error(`[useMcp] Failed to validate session ${session.sessionId}:`, error);
406
+ } finally {
407
+ suppressAuthRedirectSessionsRef.current.delete(session.sessionId);
479
408
  }
480
409
  }
481
410
  })
@@ -523,6 +452,13 @@ function useMcp(options) {
523
452
  }
524
453
  return await clientRef.current.finishAuth(sessionId, code);
525
454
  }, []);
455
+ const resumeAuth = react.useCallback(async (sessionId) => {
456
+ if (!clientRef.current) {
457
+ throw new Error("SSE client not initialized");
458
+ }
459
+ suppressAuthRedirectSessionsRef.current.delete(sessionId);
460
+ await clientRef.current.restoreSession(sessionId);
461
+ }, []);
526
462
  const callTool = react.useCallback(
527
463
  async (sessionId, toolName, toolArgs) => {
528
464
  if (!clientRef.current) {
@@ -601,6 +537,7 @@ function useMcp(options) {
601
537
  connectSSE,
602
538
  disconnectSSE,
603
539
  finishAuth,
540
+ resumeAuth,
604
541
  callTool,
605
542
  listTools,
606
543
  listPrompts,