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