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