@mcp-ts/sdk 1.3.2 → 1.3.4

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