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