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