@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.
- package/README.md +400 -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/index.mjs
CHANGED
|
@@ -1311,7 +1311,8 @@ var MCPClient = class _MCPClient {
|
|
|
1311
1311
|
serverUrl: this.serverUrl,
|
|
1312
1312
|
callbackUrl: this.callbackUrl,
|
|
1313
1313
|
transportType: this.transportType || "streamable_http",
|
|
1314
|
-
createdAt: this.createdAt
|
|
1314
|
+
createdAt: this.createdAt,
|
|
1315
|
+
active: false
|
|
1315
1316
|
}, Math.floor(STATE_EXPIRATION_MS / 1e3));
|
|
1316
1317
|
}
|
|
1317
1318
|
}
|
|
@@ -1319,9 +1320,10 @@ var MCPClient = class _MCPClient {
|
|
|
1319
1320
|
* Saves current session state to storage
|
|
1320
1321
|
* Creates new session if it doesn't exist, updates if it does
|
|
1321
1322
|
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
1323
|
+
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
1322
1324
|
* @private
|
|
1323
1325
|
*/
|
|
1324
|
-
async saveSession(ttl = SESSION_TTL_SECONDS) {
|
|
1326
|
+
async saveSession(ttl = SESSION_TTL_SECONDS, active = true) {
|
|
1325
1327
|
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
1326
1328
|
return;
|
|
1327
1329
|
}
|
|
@@ -1333,7 +1335,8 @@ var MCPClient = class _MCPClient {
|
|
|
1333
1335
|
serverUrl: this.serverUrl,
|
|
1334
1336
|
callbackUrl: this.callbackUrl,
|
|
1335
1337
|
transportType: this.transportType || "streamable_http",
|
|
1336
|
-
createdAt: this.createdAt || Date.now()
|
|
1338
|
+
createdAt: this.createdAt || Date.now(),
|
|
1339
|
+
active
|
|
1337
1340
|
};
|
|
1338
1341
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1339
1342
|
if (existingSession) {
|
|
@@ -1407,15 +1410,17 @@ var MCPClient = class _MCPClient {
|
|
|
1407
1410
|
this.emitStateChange("CONNECTED");
|
|
1408
1411
|
this.emitProgress("Connected successfully");
|
|
1409
1412
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
+
const needsTransportUpdate = !existingSession || existingSession.transportType !== this.transportType;
|
|
1414
|
+
const needsTtlPromotion = !existingSession || existingSession.active !== true;
|
|
1415
|
+
if (needsTransportUpdate || needsTtlPromotion) {
|
|
1416
|
+
console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
|
|
1417
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
1413
1418
|
}
|
|
1414
1419
|
} catch (error) {
|
|
1415
1420
|
if (error instanceof UnauthorizedError$1 || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
|
|
1416
1421
|
this.emitStateChange("AUTHENTICATING");
|
|
1417
1422
|
console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
|
|
1418
|
-
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3));
|
|
1423
|
+
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
|
|
1419
1424
|
let authUrl = "";
|
|
1420
1425
|
if (this.oauthProvider) {
|
|
1421
1426
|
authUrl = this.oauthProvider.authUrl || "";
|
|
@@ -1493,7 +1498,7 @@ var MCPClient = class _MCPClient {
|
|
|
1493
1498
|
await this.client.connect(this.transport);
|
|
1494
1499
|
this.emitStateChange("CONNECTED");
|
|
1495
1500
|
console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
|
|
1496
|
-
await this.saveSession(SESSION_TTL_SECONDS);
|
|
1501
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
1497
1502
|
return;
|
|
1498
1503
|
} catch (error) {
|
|
1499
1504
|
lastError = error;
|
|
@@ -2158,7 +2163,8 @@ var SSEConnectionManager = class {
|
|
|
2158
2163
|
serverName: s.serverName,
|
|
2159
2164
|
serverUrl: s.serverUrl,
|
|
2160
2165
|
transport: s.transportType,
|
|
2161
|
-
createdAt: s.createdAt
|
|
2166
|
+
createdAt: s.createdAt,
|
|
2167
|
+
active: s.active !== false
|
|
2162
2168
|
}))
|
|
2163
2169
|
};
|
|
2164
2170
|
}
|
|
@@ -2173,6 +2179,13 @@ var SSEConnectionManager = class {
|
|
|
2173
2179
|
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
2174
2180
|
);
|
|
2175
2181
|
if (duplicate) {
|
|
2182
|
+
if (duplicate.active === false) {
|
|
2183
|
+
await this.restoreSession({ sessionId: duplicate.sessionId });
|
|
2184
|
+
return {
|
|
2185
|
+
sessionId: duplicate.sessionId,
|
|
2186
|
+
success: true
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2176
2189
|
throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
|
|
2177
2190
|
}
|
|
2178
2191
|
const sessionId = await storage.generateSessionId();
|
|
@@ -2498,7 +2511,9 @@ function writeSSEEvent(res, event, data) {
|
|
|
2498
2511
|
}
|
|
2499
2512
|
|
|
2500
2513
|
// src/server/handlers/nextjs-handler.ts
|
|
2501
|
-
|
|
2514
|
+
function isRpcResponseEvent(event) {
|
|
2515
|
+
return "id" in event && ("result" in event || "error" in event);
|
|
2516
|
+
}
|
|
2502
2517
|
function createNextMcpHandler(options = {}) {
|
|
2503
2518
|
const {
|
|
2504
2519
|
getIdentity = (request) => new URL(request.url).searchParams.get("identity"),
|
|
@@ -2511,72 +2526,29 @@ function createNextMcpHandler(options = {}) {
|
|
|
2511
2526
|
clientDefaults,
|
|
2512
2527
|
getClientMetadata
|
|
2513
2528
|
} = options;
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
const stream = new TransformStream();
|
|
2525
|
-
const writer = stream.writable.getWriter();
|
|
2526
|
-
const encoder = new TextEncoder();
|
|
2527
|
-
const sendSSE = (event, data) => {
|
|
2528
|
-
const message = `event: ${event}
|
|
2529
|
-
data: ${JSON.stringify(data)}
|
|
2530
|
-
|
|
2531
|
-
`;
|
|
2532
|
-
writer.write(encoder.encode(message)).catch(() => {
|
|
2533
|
-
});
|
|
2534
|
-
};
|
|
2535
|
-
const previousManager = managers.get(identity);
|
|
2536
|
-
if (previousManager) {
|
|
2537
|
-
previousManager.dispose();
|
|
2538
|
-
}
|
|
2539
|
-
const resolvedClientMetadata = getClientMetadata ? await getClientMetadata(request) : clientDefaults;
|
|
2540
|
-
const manager = new SSEConnectionManager(
|
|
2529
|
+
const toManagerOptions = (identity, resolvedClientMetadata) => ({
|
|
2530
|
+
identity,
|
|
2531
|
+
heartbeatInterval,
|
|
2532
|
+
clientDefaults: resolvedClientMetadata
|
|
2533
|
+
});
|
|
2534
|
+
async function resolveClientMetadata(request) {
|
|
2535
|
+
return getClientMetadata ? await getClientMetadata(request) : clientDefaults;
|
|
2536
|
+
}
|
|
2537
|
+
async function GET() {
|
|
2538
|
+
return Response.json(
|
|
2541
2539
|
{
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
// Pass resolved metadata
|
|
2546
|
-
},
|
|
2547
|
-
(event) => {
|
|
2548
|
-
if ("id" in event) {
|
|
2549
|
-
sendSSE("rpc-response", event);
|
|
2550
|
-
} else if ("type" in event && "sessionId" in event) {
|
|
2551
|
-
sendSSE("connection", event);
|
|
2552
|
-
} else {
|
|
2553
|
-
sendSSE("observability", event);
|
|
2540
|
+
error: {
|
|
2541
|
+
code: "METHOD_NOT_ALLOWED",
|
|
2542
|
+
message: "Use POST /api/mcp. For streaming use Accept: text/event-stream."
|
|
2554
2543
|
}
|
|
2555
|
-
}
|
|
2544
|
+
},
|
|
2545
|
+
{ status: 405 }
|
|
2556
2546
|
);
|
|
2557
|
-
managers.set(identity, manager);
|
|
2558
|
-
sendSSE("connected", { timestamp: Date.now() });
|
|
2559
|
-
const abortController = new AbortController();
|
|
2560
|
-
request.signal?.addEventListener("abort", () => {
|
|
2561
|
-
manager.dispose();
|
|
2562
|
-
managers.delete(identity);
|
|
2563
|
-
writer.close().catch(() => {
|
|
2564
|
-
});
|
|
2565
|
-
abortController.abort();
|
|
2566
|
-
});
|
|
2567
|
-
return new Response(stream.readable, {
|
|
2568
|
-
status: 200,
|
|
2569
|
-
headers: {
|
|
2570
|
-
"Content-Type": "text/event-stream",
|
|
2571
|
-
"Cache-Control": "no-cache, no-transform",
|
|
2572
|
-
"Connection": "keep-alive",
|
|
2573
|
-
"X-Accel-Buffering": "no"
|
|
2574
|
-
}
|
|
2575
|
-
});
|
|
2576
2547
|
}
|
|
2577
2548
|
async function POST(request) {
|
|
2578
2549
|
const identity = getIdentity(request);
|
|
2579
2550
|
const authToken = getAuthToken(request);
|
|
2551
|
+
const acceptsEventStream = (request.headers.get("accept") || "").toLowerCase().includes("text/event-stream");
|
|
2580
2552
|
if (!identity) {
|
|
2581
2553
|
return Response.json({ error: { code: "MISSING_IDENTITY", message: "Missing identity" } }, { status: 400 });
|
|
2582
2554
|
}
|
|
@@ -2584,28 +2556,103 @@ data: ${JSON.stringify(data)}
|
|
|
2584
2556
|
if (!isAuthorized) {
|
|
2585
2557
|
return Response.json({ error: { code: "UNAUTHORIZED", message: "Unauthorized" } }, { status: 401 });
|
|
2586
2558
|
}
|
|
2559
|
+
let rawBody = "";
|
|
2587
2560
|
try {
|
|
2588
|
-
|
|
2589
|
-
const
|
|
2590
|
-
if (!
|
|
2561
|
+
rawBody = await request.text();
|
|
2562
|
+
const body = rawBody ? JSON.parse(rawBody) : null;
|
|
2563
|
+
if (!body || typeof body !== "object") {
|
|
2591
2564
|
return Response.json(
|
|
2592
2565
|
{
|
|
2593
2566
|
error: {
|
|
2594
|
-
code: "
|
|
2595
|
-
message: "
|
|
2567
|
+
code: "INVALID_REQUEST",
|
|
2568
|
+
message: "Invalid JSON-RPC request body"
|
|
2596
2569
|
}
|
|
2597
2570
|
},
|
|
2598
2571
|
{ status: 400 }
|
|
2599
2572
|
);
|
|
2600
2573
|
}
|
|
2601
|
-
const
|
|
2602
|
-
|
|
2574
|
+
const resolvedClientMetadata = await resolveClientMetadata(request);
|
|
2575
|
+
if (!acceptsEventStream) {
|
|
2576
|
+
const manager2 = new SSEConnectionManager(
|
|
2577
|
+
toManagerOptions(identity, resolvedClientMetadata),
|
|
2578
|
+
() => {
|
|
2579
|
+
}
|
|
2580
|
+
);
|
|
2581
|
+
try {
|
|
2582
|
+
const response = await manager2.handleRequest(body);
|
|
2583
|
+
return Response.json(response);
|
|
2584
|
+
} finally {
|
|
2585
|
+
manager2.dispose();
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
const stream = new TransformStream();
|
|
2589
|
+
const writer = stream.writable.getWriter();
|
|
2590
|
+
const encoder = new TextEncoder();
|
|
2591
|
+
let streamWritable = true;
|
|
2592
|
+
const sendSSE = (event, data) => {
|
|
2593
|
+
if (!streamWritable) return;
|
|
2594
|
+
const message = `event: ${event}
|
|
2595
|
+
data: ${JSON.stringify(data)}
|
|
2596
|
+
|
|
2597
|
+
`;
|
|
2598
|
+
writer.write(encoder.encode(message)).catch(() => {
|
|
2599
|
+
streamWritable = false;
|
|
2600
|
+
});
|
|
2601
|
+
};
|
|
2602
|
+
const manager = new SSEConnectionManager(
|
|
2603
|
+
toManagerOptions(identity, resolvedClientMetadata),
|
|
2604
|
+
(event) => {
|
|
2605
|
+
if (isRpcResponseEvent(event)) {
|
|
2606
|
+
sendSSE("rpc-response", event);
|
|
2607
|
+
} else if ("type" in event && "sessionId" in event) {
|
|
2608
|
+
sendSSE("connection", event);
|
|
2609
|
+
} else {
|
|
2610
|
+
sendSSE("observability", event);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
);
|
|
2614
|
+
sendSSE("connected", { timestamp: Date.now() });
|
|
2615
|
+
void (async () => {
|
|
2616
|
+
try {
|
|
2617
|
+
await manager.handleRequest(body);
|
|
2618
|
+
} catch (error) {
|
|
2619
|
+
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
2620
|
+
sendSSE("rpc-response", {
|
|
2621
|
+
id: body.id || "unknown",
|
|
2622
|
+
error: {
|
|
2623
|
+
code: "EXECUTION_ERROR",
|
|
2624
|
+
message: err.message
|
|
2625
|
+
}
|
|
2626
|
+
});
|
|
2627
|
+
} finally {
|
|
2628
|
+
streamWritable = false;
|
|
2629
|
+
manager.dispose();
|
|
2630
|
+
writer.close().catch(() => {
|
|
2631
|
+
});
|
|
2632
|
+
}
|
|
2633
|
+
})();
|
|
2634
|
+
return new Response(stream.readable, {
|
|
2635
|
+
status: 200,
|
|
2636
|
+
headers: {
|
|
2637
|
+
"Content-Type": "text/event-stream",
|
|
2638
|
+
"Cache-Control": "no-cache, no-transform",
|
|
2639
|
+
"Connection": "keep-alive",
|
|
2640
|
+
"X-Accel-Buffering": "no"
|
|
2641
|
+
}
|
|
2642
|
+
});
|
|
2603
2643
|
} catch (error) {
|
|
2644
|
+
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
2645
|
+
console.error("[MCP Next Handler] Failed to handle RPC", {
|
|
2646
|
+
identity,
|
|
2647
|
+
message: err.message,
|
|
2648
|
+
stack: err.stack,
|
|
2649
|
+
rawBody: rawBody.slice(0, 500)
|
|
2650
|
+
});
|
|
2604
2651
|
return Response.json(
|
|
2605
2652
|
{
|
|
2606
2653
|
error: {
|
|
2607
2654
|
code: "EXECUTION_ERROR",
|
|
2608
|
-
message:
|
|
2655
|
+
message: err.message
|
|
2609
2656
|
}
|
|
2610
2657
|
},
|
|
2611
2658
|
{ status: 500 }
|
|
@@ -2614,62 +2661,27 @@ data: ${JSON.stringify(data)}
|
|
|
2614
2661
|
}
|
|
2615
2662
|
return { GET, POST };
|
|
2616
2663
|
}
|
|
2617
|
-
var DEFAULT_REQUEST_TIMEOUT = 6e4;
|
|
2618
|
-
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
2619
|
-
var BASE_RECONNECT_DELAY = 1e3;
|
|
2620
2664
|
var SSEClient = class {
|
|
2621
2665
|
constructor(options) {
|
|
2622
2666
|
this.options = options;
|
|
2623
|
-
__publicField(this, "eventSource", null);
|
|
2624
|
-
__publicField(this, "pendingRequests", /* @__PURE__ */ new Map());
|
|
2625
2667
|
__publicField(this, "resourceCache", /* @__PURE__ */ new Map());
|
|
2626
|
-
__publicField(this, "
|
|
2627
|
-
__publicField(this, "isManuallyDisconnected", false);
|
|
2628
|
-
__publicField(this, "connectionPromise", null);
|
|
2629
|
-
__publicField(this, "connectionResolver", null);
|
|
2668
|
+
__publicField(this, "connected", false);
|
|
2630
2669
|
}
|
|
2631
|
-
// ============================================
|
|
2632
|
-
// Connection Management
|
|
2633
|
-
// ============================================
|
|
2634
|
-
/**
|
|
2635
|
-
* Connect to the SSE endpoint
|
|
2636
|
-
*/
|
|
2637
2670
|
connect() {
|
|
2638
|
-
if (this.
|
|
2671
|
+
if (this.connected) {
|
|
2639
2672
|
return;
|
|
2640
2673
|
}
|
|
2641
|
-
this.
|
|
2642
|
-
this.options.onStatusChange?.("
|
|
2643
|
-
this.
|
|
2644
|
-
this.connectionResolver = resolve;
|
|
2645
|
-
});
|
|
2646
|
-
const url = this.buildUrl();
|
|
2647
|
-
this.eventSource = new EventSource(url);
|
|
2648
|
-
this.setupEventListeners();
|
|
2674
|
+
this.connected = true;
|
|
2675
|
+
this.options.onStatusChange?.("connected");
|
|
2676
|
+
this.log("RPC mode: post_stream");
|
|
2649
2677
|
}
|
|
2650
|
-
/**
|
|
2651
|
-
* Disconnect from the SSE endpoint
|
|
2652
|
-
*/
|
|
2653
2678
|
disconnect() {
|
|
2654
|
-
this.
|
|
2655
|
-
if (this.eventSource) {
|
|
2656
|
-
this.eventSource.close();
|
|
2657
|
-
this.eventSource = null;
|
|
2658
|
-
}
|
|
2659
|
-
this.connectionPromise = null;
|
|
2660
|
-
this.connectionResolver = null;
|
|
2661
|
-
this.rejectAllPendingRequests(new Error("Connection closed"));
|
|
2679
|
+
this.connected = false;
|
|
2662
2680
|
this.options.onStatusChange?.("disconnected");
|
|
2663
2681
|
}
|
|
2664
|
-
/**
|
|
2665
|
-
* Check if connected to the SSE endpoint
|
|
2666
|
-
*/
|
|
2667
2682
|
isConnected() {
|
|
2668
|
-
return this.
|
|
2683
|
+
return this.connected;
|
|
2669
2684
|
}
|
|
2670
|
-
// ============================================
|
|
2671
|
-
// RPC Methods
|
|
2672
|
-
// ============================================
|
|
2673
2685
|
async getSessions() {
|
|
2674
2686
|
return this.sendRequest("getSessions");
|
|
2675
2687
|
}
|
|
@@ -2705,22 +2717,10 @@ var SSEClient = class {
|
|
|
2705
2717
|
async readResource(sessionId, uri) {
|
|
2706
2718
|
return this.sendRequest("readResource", { sessionId, uri });
|
|
2707
2719
|
}
|
|
2708
|
-
// ============================================
|
|
2709
|
-
// Resource Preloading (for instant UI loading)
|
|
2710
|
-
// ============================================
|
|
2711
|
-
/**
|
|
2712
|
-
* Preload UI resources for tools that have UI metadata.
|
|
2713
|
-
* Call this when tools are discovered to enable instant MCP App UI loading.
|
|
2714
|
-
*/
|
|
2715
2720
|
preloadToolUiResources(sessionId, tools) {
|
|
2716
2721
|
for (const tool of tools) {
|
|
2717
2722
|
const uri = this.extractUiResourceUri(tool);
|
|
2718
|
-
if (!uri) continue;
|
|
2719
|
-
if (this.resourceCache.has(uri)) {
|
|
2720
|
-
this.log(`Resource already cached: ${uri}`);
|
|
2721
|
-
continue;
|
|
2722
|
-
}
|
|
2723
|
-
this.log(`Preloading UI resource for tool "${tool.name}": ${uri}`);
|
|
2723
|
+
if (!uri || this.resourceCache.has(uri)) continue;
|
|
2724
2724
|
const promise = this.sendRequest("readResource", { sessionId, uri }).catch((err) => {
|
|
2725
2725
|
this.log(`Failed to preload resource ${uri}: ${err.message}`, "warn");
|
|
2726
2726
|
this.resourceCache.delete(uri);
|
|
@@ -2729,43 +2729,24 @@ var SSEClient = class {
|
|
|
2729
2729
|
this.resourceCache.set(uri, promise);
|
|
2730
2730
|
}
|
|
2731
2731
|
}
|
|
2732
|
-
/**
|
|
2733
|
-
* Get a preloaded resource from cache, or fetch if not cached.
|
|
2734
|
-
*/
|
|
2735
2732
|
getOrFetchResource(sessionId, uri) {
|
|
2736
2733
|
const cached = this.resourceCache.get(uri);
|
|
2737
|
-
if (cached)
|
|
2738
|
-
this.log(`Cache hit for resource: ${uri}`);
|
|
2739
|
-
return cached;
|
|
2740
|
-
}
|
|
2741
|
-
this.log(`Cache miss, fetching resource: ${uri}`);
|
|
2734
|
+
if (cached) return cached;
|
|
2742
2735
|
const promise = this.sendRequest("readResource", { sessionId, uri });
|
|
2743
2736
|
this.resourceCache.set(uri, promise);
|
|
2744
2737
|
return promise;
|
|
2745
2738
|
}
|
|
2746
|
-
/**
|
|
2747
|
-
* Check if a resource is already cached
|
|
2748
|
-
*/
|
|
2749
2739
|
hasPreloadedResource(uri) {
|
|
2750
2740
|
return this.resourceCache.has(uri);
|
|
2751
2741
|
}
|
|
2752
|
-
/**
|
|
2753
|
-
* Clear the resource cache
|
|
2754
|
-
*/
|
|
2755
2742
|
clearResourceCache() {
|
|
2756
2743
|
this.resourceCache.clear();
|
|
2757
2744
|
}
|
|
2758
|
-
// ============================================
|
|
2759
|
-
// Private: Request Handling
|
|
2760
|
-
// ============================================
|
|
2761
|
-
/**
|
|
2762
|
-
* Send an RPC request and return the response directly from HTTP.
|
|
2763
|
-
* This bypasses SSE latency by returning results in the HTTP response body.
|
|
2764
|
-
*/
|
|
2765
2745
|
async sendRequest(method, params) {
|
|
2766
|
-
if (this.
|
|
2767
|
-
|
|
2746
|
+
if (!this.connected) {
|
|
2747
|
+
this.connect();
|
|
2768
2748
|
}
|
|
2749
|
+
this.log(`RPC request via post_stream: ${method}`);
|
|
2769
2750
|
const request = {
|
|
2770
2751
|
id: `rpc_${nanoid(10)}`,
|
|
2771
2752
|
method,
|
|
@@ -2779,103 +2760,93 @@ var SSEClient = class {
|
|
|
2779
2760
|
if (!response.ok) {
|
|
2780
2761
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
2781
2762
|
}
|
|
2782
|
-
const
|
|
2783
|
-
|
|
2763
|
+
const contentType = (response.headers.get("content-type") || "").toLowerCase();
|
|
2764
|
+
if (!contentType.includes("text/event-stream")) {
|
|
2765
|
+
const data2 = await response.json();
|
|
2766
|
+
return this.parseRpcResponse(data2);
|
|
2767
|
+
}
|
|
2768
|
+
const data = await this.readRpcResponseFromStream(response);
|
|
2769
|
+
return this.parseRpcResponse(data);
|
|
2770
|
+
}
|
|
2771
|
+
async readRpcResponseFromStream(response) {
|
|
2772
|
+
if (!response.body) {
|
|
2773
|
+
throw new Error("Streaming response body is missing");
|
|
2774
|
+
}
|
|
2775
|
+
const reader = response.body.getReader();
|
|
2776
|
+
const decoder = new TextDecoder();
|
|
2777
|
+
let buffer = "";
|
|
2778
|
+
let rpcResponse = null;
|
|
2779
|
+
const dispatchBlock = (block) => {
|
|
2780
|
+
const lines = block.split("\n");
|
|
2781
|
+
let eventName = "message";
|
|
2782
|
+
const dataLines = [];
|
|
2783
|
+
for (const rawLine of lines) {
|
|
2784
|
+
const line = rawLine.replace(/\r$/, "");
|
|
2785
|
+
if (!line || line.startsWith(":")) continue;
|
|
2786
|
+
if (line.startsWith("event:")) {
|
|
2787
|
+
eventName = line.slice("event:".length).trim();
|
|
2788
|
+
continue;
|
|
2789
|
+
}
|
|
2790
|
+
if (line.startsWith("data:")) {
|
|
2791
|
+
dataLines.push(line.slice("data:".length).trimStart());
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
if (!dataLines.length) return;
|
|
2795
|
+
const payloadText = dataLines.join("\n");
|
|
2796
|
+
let payload = payloadText;
|
|
2797
|
+
try {
|
|
2798
|
+
payload = JSON.parse(payloadText);
|
|
2799
|
+
} catch {
|
|
2800
|
+
}
|
|
2801
|
+
switch (eventName) {
|
|
2802
|
+
case "connected":
|
|
2803
|
+
this.options.onStatusChange?.("connected");
|
|
2804
|
+
break;
|
|
2805
|
+
case "connection":
|
|
2806
|
+
this.options.onConnectionEvent?.(payload);
|
|
2807
|
+
break;
|
|
2808
|
+
case "observability":
|
|
2809
|
+
this.options.onObservabilityEvent?.(payload);
|
|
2810
|
+
break;
|
|
2811
|
+
case "rpc-response":
|
|
2812
|
+
rpcResponse = payload;
|
|
2813
|
+
break;
|
|
2814
|
+
}
|
|
2815
|
+
};
|
|
2816
|
+
while (true) {
|
|
2817
|
+
const { value, done } = await reader.read();
|
|
2818
|
+
if (done) break;
|
|
2819
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2820
|
+
let separatorMatch = buffer.match(/\r?\n\r?\n/);
|
|
2821
|
+
while (separatorMatch && separatorMatch.index !== void 0) {
|
|
2822
|
+
const separatorIndex = separatorMatch.index;
|
|
2823
|
+
const separatorLength = separatorMatch[0].length;
|
|
2824
|
+
const block = buffer.slice(0, separatorIndex);
|
|
2825
|
+
buffer = buffer.slice(separatorIndex + separatorLength);
|
|
2826
|
+
dispatchBlock(block);
|
|
2827
|
+
separatorMatch = buffer.match(/\r?\n\r?\n/);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
if (buffer.trim()) {
|
|
2831
|
+
dispatchBlock(buffer);
|
|
2832
|
+
}
|
|
2833
|
+
if (!rpcResponse) {
|
|
2834
|
+
throw new Error("Missing rpc-response event in streamed RPC result");
|
|
2835
|
+
}
|
|
2836
|
+
return rpcResponse;
|
|
2784
2837
|
}
|
|
2785
|
-
|
|
2786
|
-
* Parse RPC response and handle different response formats
|
|
2787
|
-
*/
|
|
2788
|
-
parseRpcResponse(data, requestId) {
|
|
2838
|
+
parseRpcResponse(data) {
|
|
2789
2839
|
if ("result" in data) {
|
|
2790
2840
|
return data.result;
|
|
2791
2841
|
}
|
|
2792
2842
|
if ("error" in data && data.error) {
|
|
2793
2843
|
throw new Error(data.error.message || "Unknown RPC error");
|
|
2794
2844
|
}
|
|
2795
|
-
if ("
|
|
2796
|
-
return
|
|
2845
|
+
if (data && typeof data === "object" && "id" in data) {
|
|
2846
|
+
return void 0;
|
|
2797
2847
|
}
|
|
2798
2848
|
throw new Error("Invalid RPC response format");
|
|
2799
2849
|
}
|
|
2800
|
-
/**
|
|
2801
|
-
* Wait for RPC response via SSE (legacy fallback)
|
|
2802
|
-
*/
|
|
2803
|
-
waitForSseResponse(requestId) {
|
|
2804
|
-
const timeoutMs = this.options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
2805
|
-
return new Promise((resolve, reject) => {
|
|
2806
|
-
const timeoutId = setTimeout(() => {
|
|
2807
|
-
this.pendingRequests.delete(requestId);
|
|
2808
|
-
reject(new Error(`Request timeout after ${timeoutMs}ms`));
|
|
2809
|
-
}, timeoutMs);
|
|
2810
|
-
this.pendingRequests.set(requestId, {
|
|
2811
|
-
resolve,
|
|
2812
|
-
reject,
|
|
2813
|
-
timeoutId
|
|
2814
|
-
});
|
|
2815
|
-
});
|
|
2816
|
-
}
|
|
2817
|
-
/**
|
|
2818
|
-
* Handle RPC response received via SSE (legacy)
|
|
2819
|
-
*/
|
|
2820
|
-
handleRpcResponse(response) {
|
|
2821
|
-
const pending = this.pendingRequests.get(response.id);
|
|
2822
|
-
if (!pending) return;
|
|
2823
|
-
clearTimeout(pending.timeoutId);
|
|
2824
|
-
this.pendingRequests.delete(response.id);
|
|
2825
|
-
if (response.error) {
|
|
2826
|
-
pending.reject(new Error(response.error.message));
|
|
2827
|
-
} else {
|
|
2828
|
-
pending.resolve(response.result);
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
// ============================================
|
|
2832
|
-
// Private: Event Handling
|
|
2833
|
-
// ============================================
|
|
2834
|
-
setupEventListeners() {
|
|
2835
|
-
if (!this.eventSource) return;
|
|
2836
|
-
this.eventSource.addEventListener("open", () => {
|
|
2837
|
-
this.log("Connected");
|
|
2838
|
-
this.reconnectAttempts = 0;
|
|
2839
|
-
this.options.onStatusChange?.("connected");
|
|
2840
|
-
});
|
|
2841
|
-
this.eventSource.addEventListener("connected", () => {
|
|
2842
|
-
this.log("Server ready");
|
|
2843
|
-
this.connectionResolver?.();
|
|
2844
|
-
this.connectionResolver = null;
|
|
2845
|
-
});
|
|
2846
|
-
this.eventSource.addEventListener("connection", (e) => {
|
|
2847
|
-
const event = JSON.parse(e.data);
|
|
2848
|
-
this.options.onConnectionEvent?.(event);
|
|
2849
|
-
});
|
|
2850
|
-
this.eventSource.addEventListener("observability", (e) => {
|
|
2851
|
-
const event = JSON.parse(e.data);
|
|
2852
|
-
this.options.onObservabilityEvent?.(event);
|
|
2853
|
-
});
|
|
2854
|
-
this.eventSource.addEventListener("rpc-response", (e) => {
|
|
2855
|
-
const response = JSON.parse(e.data);
|
|
2856
|
-
this.handleRpcResponse(response);
|
|
2857
|
-
});
|
|
2858
|
-
this.eventSource.addEventListener("error", () => {
|
|
2859
|
-
this.log("Connection error", "error");
|
|
2860
|
-
this.options.onStatusChange?.("error");
|
|
2861
|
-
this.attemptReconnect();
|
|
2862
|
-
});
|
|
2863
|
-
}
|
|
2864
|
-
attemptReconnect() {
|
|
2865
|
-
if (this.isManuallyDisconnected || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
2866
|
-
return;
|
|
2867
|
-
}
|
|
2868
|
-
this.reconnectAttempts++;
|
|
2869
|
-
const delay = BASE_RECONNECT_DELAY * this.reconnectAttempts;
|
|
2870
|
-
this.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
2871
|
-
setTimeout(() => {
|
|
2872
|
-
this.disconnect();
|
|
2873
|
-
this.connect();
|
|
2874
|
-
}, delay);
|
|
2875
|
-
}
|
|
2876
|
-
// ============================================
|
|
2877
|
-
// Private: Utilities
|
|
2878
|
-
// ============================================
|
|
2879
2850
|
buildUrl() {
|
|
2880
2851
|
const url = new URL(this.options.url, globalThis.location?.origin);
|
|
2881
2852
|
url.searchParams.set("identity", this.options.identity);
|
|
@@ -2886,20 +2857,14 @@ var SSEClient = class {
|
|
|
2886
2857
|
}
|
|
2887
2858
|
buildHeaders() {
|
|
2888
2859
|
const headers = {
|
|
2889
|
-
"Content-Type": "application/json"
|
|
2860
|
+
"Content-Type": "application/json",
|
|
2861
|
+
"Accept": "text/event-stream"
|
|
2890
2862
|
};
|
|
2891
2863
|
if (this.options.authToken) {
|
|
2892
2864
|
headers["Authorization"] = `Bearer ${this.options.authToken}`;
|
|
2893
2865
|
}
|
|
2894
2866
|
return headers;
|
|
2895
2867
|
}
|
|
2896
|
-
rejectAllPendingRequests(error) {
|
|
2897
|
-
for (const [, pending] of this.pendingRequests) {
|
|
2898
|
-
clearTimeout(pending.timeoutId);
|
|
2899
|
-
pending.reject(error);
|
|
2900
|
-
}
|
|
2901
|
-
this.pendingRequests.clear();
|
|
2902
|
-
}
|
|
2903
2868
|
extractUiResourceUri(tool) {
|
|
2904
2869
|
const meta = tool._meta?.ui;
|
|
2905
2870
|
if (!meta || typeof meta !== "object") return void 0;
|