@mcp-ts/sdk 1.3.1 → 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 (63) hide show
  1. package/README.md +371 -290
  2. package/dist/adapters/agui-adapter.d.mts +3 -3
  3. package/dist/adapters/agui-adapter.d.ts +3 -3
  4. package/dist/adapters/agui-middleware.d.mts +3 -3
  5. package/dist/adapters/agui-middleware.d.ts +3 -3
  6. package/dist/adapters/ai-adapter.d.mts +3 -3
  7. package/dist/adapters/ai-adapter.d.ts +3 -3
  8. package/dist/adapters/langchain-adapter.d.mts +3 -3
  9. package/dist/adapters/langchain-adapter.d.ts +3 -3
  10. package/dist/adapters/mastra-adapter.d.mts +3 -3
  11. package/dist/adapters/mastra-adapter.d.ts +3 -3
  12. package/dist/client/index.d.mts +10 -66
  13. package/dist/client/index.d.ts +10 -66
  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 +15 -5
  19. package/dist/client/react.d.ts +15 -5
  20. package/dist/client/react.js +130 -182
  21. package/dist/client/react.js.map +1 -1
  22. package/dist/client/react.mjs +130 -182
  23. package/dist/client/react.mjs.map +1 -1
  24. package/dist/client/vue.d.mts +27 -7
  25. package/dist/client/vue.d.ts +27 -7
  26. package/dist/client/vue.js +131 -182
  27. package/dist/client/vue.js.map +1 -1
  28. package/dist/client/vue.mjs +131 -182
  29. package/dist/client/vue.mjs.map +1 -1
  30. package/dist/{events-BgeztGYZ.d.mts → events-CK3N--3g.d.mts} +2 -0
  31. package/dist/{events-BgeztGYZ.d.ts → events-CK3N--3g.d.ts} +2 -0
  32. package/dist/index.d.mts +3 -3
  33. package/dist/index.d.ts +3 -3
  34. package/dist/index.js +224 -258
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +224 -258
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/{multi-session-client-CxogNckF.d.mts → multi-session-client-DzjmT7FX.d.mts} +4 -10
  39. package/dist/{multi-session-client-cox_WXUj.d.ts → multi-session-client-FAFpUzZ4.d.ts} +4 -10
  40. package/dist/server/index.d.mts +18 -23
  41. package/dist/server/index.d.ts +18 -23
  42. package/dist/server/index.js +133 -85
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/index.mjs +133 -85
  45. package/dist/server/index.mjs.map +1 -1
  46. package/dist/shared/index.d.mts +3 -3
  47. package/dist/shared/index.d.ts +3 -3
  48. package/dist/shared/index.js.map +1 -1
  49. package/dist/shared/index.mjs.map +1 -1
  50. package/dist/{types-CLccx9wW.d.mts → types-CW6lghof.d.mts} +6 -0
  51. package/dist/{types-CLccx9wW.d.ts → types-CW6lghof.d.ts} +6 -0
  52. package/package.json +1 -1
  53. package/src/client/core/sse-client.ts +354 -493
  54. package/src/client/react/index.ts +16 -16
  55. package/src/client/react/use-mcp-apps.tsx +214 -214
  56. package/src/client/react/use-mcp.ts +84 -19
  57. package/src/client/vue/use-mcp.ts +119 -44
  58. package/src/server/handlers/nextjs-handler.ts +207 -217
  59. package/src/server/handlers/sse-handler.ts +14 -0
  60. package/src/server/mcp/oauth-client.ts +48 -46
  61. package/src/server/storage/types.ts +12 -5
  62. package/src/shared/events.ts +2 -0
  63. package/src/shared/types.ts +6 -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,22 +301,34 @@ 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
- (c) => c.sessionId === event.sessionId ? { ...c, state: event.state } : c
312
+ (c) => c.sessionId === event.sessionId ? {
313
+ ...c,
314
+ state: nextState,
315
+ // update createdAt if present in event, otherwise keep existing
316
+ createdAt: event.createdAt ? new Date(event.createdAt) : c.createdAt
317
+ } : c
392
318
  );
393
319
  } else {
320
+ if (event.state === "DISCONNECTED") {
321
+ return prev;
322
+ }
394
323
  return [
395
324
  ...prev,
396
325
  {
397
326
  sessionId: event.sessionId,
398
327
  serverId: event.serverId,
399
328
  serverName: event.serverName,
329
+ serverUrl: event.serverUrl,
400
330
  state: event.state,
331
+ createdAt: event.createdAt ? new Date(event.createdAt) : void 0,
401
332
  tools: []
402
333
  }
403
334
  ];
@@ -414,14 +345,16 @@ function useMcp(options) {
414
345
  case "auth_required": {
415
346
  if (event.authUrl) {
416
347
  onLog?.("info", `OAuth required - redirecting to ${event.authUrl}`, { authUrl: event.authUrl });
417
- if (onRedirect) {
418
- onRedirect(event.authUrl);
419
- } else if (typeof window !== "undefined") {
420
- 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
+ }
421
354
  }
422
355
  }
423
356
  return prev.map(
424
- (c) => c.sessionId === event.sessionId ? { ...c, state: "AUTHENTICATING" } : c
357
+ (c) => c.sessionId === event.sessionId ? { ...c, state: "AUTHENTICATING", authUrl: event.authUrl } : c
425
358
  );
426
359
  }
427
360
  case "error": {
@@ -436,7 +369,7 @@ function useMcp(options) {
436
369
  return prev;
437
370
  }
438
371
  });
439
- }, [onLog]);
372
+ }, [onLog, onRedirect]);
440
373
  const loadSessions = useCallback(async () => {
441
374
  if (!clientRef.current) return;
442
375
  try {
@@ -451,7 +384,8 @@ function useMcp(options) {
451
384
  serverName: s.serverName ?? "Unknown Server",
452
385
  serverUrl: s.serverUrl,
453
386
  transport: s.transport,
454
- state: "VALIDATING",
387
+ state: s.active === false ? "AUTHENTICATING" : "VALIDATING",
388
+ createdAt: new Date(s.createdAt),
455
389
  tools: []
456
390
  }))
457
391
  );
@@ -460,9 +394,15 @@ function useMcp(options) {
460
394
  sessions.map(async (session) => {
461
395
  if (clientRef.current) {
462
396
  try {
397
+ if (session.active === false) {
398
+ return;
399
+ }
400
+ suppressAuthRedirectSessionsRef.current.add(session.sessionId);
463
401
  await clientRef.current.restoreSession(session.sessionId);
464
402
  } catch (error) {
465
403
  console.error(`[useMcp] Failed to validate session ${session.sessionId}:`, error);
404
+ } finally {
405
+ suppressAuthRedirectSessionsRef.current.delete(session.sessionId);
466
406
  }
467
407
  }
468
408
  })
@@ -510,6 +450,13 @@ function useMcp(options) {
510
450
  }
511
451
  return await clientRef.current.finishAuth(sessionId, code);
512
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
+ }, []);
513
460
  const callTool = useCallback(
514
461
  async (sessionId, toolName, toolArgs) => {
515
462
  if (!clientRef.current) {
@@ -588,6 +535,7 @@ function useMcp(options) {
588
535
  connectSSE,
589
536
  disconnectSSE,
590
537
  finishAuth,
538
+ resumeAuth,
591
539
  callTool,
592
540
  listTools,
593
541
  listPrompts,