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