@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
@@ -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,23 +259,35 @@ 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
- connections.value[index] = { ...existing, state: event.state };
275
+ connections.value[index] = {
276
+ ...existing,
277
+ state: nextState,
278
+ // update createdAt if present in event, otherwise keep existing
279
+ createdAt: event.createdAt ? new Date(event.createdAt) : existing.createdAt
280
+ };
355
281
  } else {
282
+ if (event.state === "DISCONNECTED") {
283
+ break;
284
+ }
356
285
  connections.value = [...connections.value, {
357
286
  sessionId: event.sessionId,
358
287
  serverId: event.serverId,
359
288
  serverName: event.serverName,
360
289
  state: event.state,
290
+ createdAt: event.createdAt ? new Date(event.createdAt) : void 0,
361
291
  tools: []
362
292
  }];
363
293
  }
@@ -373,15 +303,17 @@ function useMcp(options) {
373
303
  case "auth_required": {
374
304
  if (event.authUrl) {
375
305
  onLog?.("info", `OAuth required - redirecting to ${event.authUrl}`, { authUrl: event.authUrl });
376
- if (onRedirect) {
377
- onRedirect(event.authUrl);
378
- } else if (typeof window !== "undefined") {
379
- 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
+ }
380
312
  }
381
313
  }
382
314
  const index = connections.value.findIndex((c) => c.sessionId === event.sessionId);
383
315
  if (index !== -1) {
384
- connections.value[index] = { ...connections.value[index], state: "AUTHENTICATING" };
316
+ connections.value[index] = { ...connections.value[index], state: "AUTHENTICATING", authUrl: event.authUrl };
385
317
  }
386
318
  break;
387
319
  }
@@ -411,7 +343,8 @@ function useMcp(options) {
411
343
  serverName: s.serverName ?? "Unknown Server",
412
344
  serverUrl: s.serverUrl,
413
345
  transport: s.transport,
414
- state: "VALIDATING",
346
+ state: s.active === false ? "AUTHENTICATING" : "VALIDATING",
347
+ createdAt: new Date(s.createdAt),
415
348
  tools: []
416
349
  }));
417
350
  }
@@ -419,9 +352,15 @@ function useMcp(options) {
419
352
  sessions.map(async (session) => {
420
353
  if (clientRef.value) {
421
354
  try {
355
+ if (session.active === false) {
356
+ return;
357
+ }
358
+ suppressAuthRedirectSessions.value.add(session.sessionId);
422
359
  await clientRef.value.restoreSession(session.sessionId);
423
360
  } catch (error) {
424
361
  console.error(`[useMcp] Failed to validate session ${session.sessionId}:`, error);
362
+ } finally {
363
+ suppressAuthRedirectSessions.value.delete(session.sessionId);
425
364
  }
426
365
  }
427
366
  })
@@ -454,7 +393,8 @@ function useMcp(options) {
454
393
  if (isMountedRef.value) {
455
394
  status.value = newStatus;
456
395
  }
457
- }
396
+ },
397
+ debug: options.debug
458
398
  };
459
399
  const client = new SSEClient(clientOptions);
460
400
  clientRef.value = client;
@@ -504,6 +444,13 @@ function useMcp(options) {
504
444
  }
505
445
  return await clientRef.value.finishAuth(sessionId, code);
506
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
+ };
507
454
  const callTool = async (sessionId, toolName, toolArgs) => {
508
455
  if (!clientRef.value) {
509
456
  throw new Error("SSE client not initialized");
@@ -565,12 +512,14 @@ function useMcp(options) {
565
512
  connectSSE,
566
513
  disconnectSSE,
567
514
  finishAuth,
515
+ resumeAuth,
568
516
  callTool,
569
517
  listTools,
570
518
  listPrompts,
571
519
  getPrompt,
572
520
  listResources,
573
- readResource
521
+ readResource,
522
+ sseClient: clientRef.value
574
523
  };
575
524
  }
576
525
  var HOST_INFO = { name: "mcp-ts-host", version: "1.0.0" };