@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/index.js
CHANGED
|
@@ -1333,7 +1333,8 @@ var MCPClient = class _MCPClient {
|
|
|
1333
1333
|
serverUrl: this.serverUrl,
|
|
1334
1334
|
callbackUrl: this.callbackUrl,
|
|
1335
1335
|
transportType: this.transportType || "streamable_http",
|
|
1336
|
-
createdAt: this.createdAt
|
|
1336
|
+
createdAt: this.createdAt,
|
|
1337
|
+
active: false
|
|
1337
1338
|
}, Math.floor(STATE_EXPIRATION_MS / 1e3));
|
|
1338
1339
|
}
|
|
1339
1340
|
}
|
|
@@ -1341,9 +1342,10 @@ var MCPClient = class _MCPClient {
|
|
|
1341
1342
|
* Saves current session state to storage
|
|
1342
1343
|
* Creates new session if it doesn't exist, updates if it does
|
|
1343
1344
|
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
1345
|
+
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
1344
1346
|
* @private
|
|
1345
1347
|
*/
|
|
1346
|
-
async saveSession(ttl = SESSION_TTL_SECONDS) {
|
|
1348
|
+
async saveSession(ttl = SESSION_TTL_SECONDS, active = true) {
|
|
1347
1349
|
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
1348
1350
|
return;
|
|
1349
1351
|
}
|
|
@@ -1355,7 +1357,8 @@ var MCPClient = class _MCPClient {
|
|
|
1355
1357
|
serverUrl: this.serverUrl,
|
|
1356
1358
|
callbackUrl: this.callbackUrl,
|
|
1357
1359
|
transportType: this.transportType || "streamable_http",
|
|
1358
|
-
createdAt: this.createdAt || Date.now()
|
|
1360
|
+
createdAt: this.createdAt || Date.now(),
|
|
1361
|
+
active
|
|
1359
1362
|
};
|
|
1360
1363
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1361
1364
|
if (existingSession) {
|
|
@@ -1429,15 +1432,17 @@ var MCPClient = class _MCPClient {
|
|
|
1429
1432
|
this.emitStateChange("CONNECTED");
|
|
1430
1433
|
this.emitProgress("Connected successfully");
|
|
1431
1434
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
+
const needsTransportUpdate = !existingSession || existingSession.transportType !== this.transportType;
|
|
1436
|
+
const needsTtlPromotion = !existingSession || existingSession.active !== true;
|
|
1437
|
+
if (needsTransportUpdate || needsTtlPromotion) {
|
|
1438
|
+
console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
|
|
1439
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
1435
1440
|
}
|
|
1436
1441
|
} catch (error) {
|
|
1437
1442
|
if (error instanceof auth_js.UnauthorizedError || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
|
|
1438
1443
|
this.emitStateChange("AUTHENTICATING");
|
|
1439
1444
|
console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
|
|
1440
|
-
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3));
|
|
1445
|
+
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
|
|
1441
1446
|
let authUrl = "";
|
|
1442
1447
|
if (this.oauthProvider) {
|
|
1443
1448
|
authUrl = this.oauthProvider.authUrl || "";
|
|
@@ -1515,7 +1520,7 @@ var MCPClient = class _MCPClient {
|
|
|
1515
1520
|
await this.client.connect(this.transport);
|
|
1516
1521
|
this.emitStateChange("CONNECTED");
|
|
1517
1522
|
console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
|
|
1518
|
-
await this.saveSession(SESSION_TTL_SECONDS);
|
|
1523
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
1519
1524
|
return;
|
|
1520
1525
|
} catch (error) {
|
|
1521
1526
|
lastError = error;
|
|
@@ -2180,7 +2185,8 @@ var SSEConnectionManager = class {
|
|
|
2180
2185
|
serverName: s.serverName,
|
|
2181
2186
|
serverUrl: s.serverUrl,
|
|
2182
2187
|
transport: s.transportType,
|
|
2183
|
-
createdAt: s.createdAt
|
|
2188
|
+
createdAt: s.createdAt,
|
|
2189
|
+
active: s.active !== false
|
|
2184
2190
|
}))
|
|
2185
2191
|
};
|
|
2186
2192
|
}
|
|
@@ -2195,6 +2201,13 @@ var SSEConnectionManager = class {
|
|
|
2195
2201
|
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
2196
2202
|
);
|
|
2197
2203
|
if (duplicate) {
|
|
2204
|
+
if (duplicate.active === false) {
|
|
2205
|
+
await this.restoreSession({ sessionId: duplicate.sessionId });
|
|
2206
|
+
return {
|
|
2207
|
+
sessionId: duplicate.sessionId,
|
|
2208
|
+
success: true
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2198
2211
|
throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
|
|
2199
2212
|
}
|
|
2200
2213
|
const sessionId = await storage.generateSessionId();
|
|
@@ -2520,7 +2533,9 @@ function writeSSEEvent(res, event, data) {
|
|
|
2520
2533
|
}
|
|
2521
2534
|
|
|
2522
2535
|
// src/server/handlers/nextjs-handler.ts
|
|
2523
|
-
|
|
2536
|
+
function isRpcResponseEvent(event) {
|
|
2537
|
+
return "id" in event && ("result" in event || "error" in event);
|
|
2538
|
+
}
|
|
2524
2539
|
function createNextMcpHandler(options = {}) {
|
|
2525
2540
|
const {
|
|
2526
2541
|
getIdentity = (request) => new URL(request.url).searchParams.get("identity"),
|
|
@@ -2533,72 +2548,29 @@ function createNextMcpHandler(options = {}) {
|
|
|
2533
2548
|
clientDefaults,
|
|
2534
2549
|
getClientMetadata
|
|
2535
2550
|
} = options;
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
const stream = new TransformStream();
|
|
2547
|
-
const writer = stream.writable.getWriter();
|
|
2548
|
-
const encoder = new TextEncoder();
|
|
2549
|
-
const sendSSE = (event, data) => {
|
|
2550
|
-
const message = `event: ${event}
|
|
2551
|
-
data: ${JSON.stringify(data)}
|
|
2552
|
-
|
|
2553
|
-
`;
|
|
2554
|
-
writer.write(encoder.encode(message)).catch(() => {
|
|
2555
|
-
});
|
|
2556
|
-
};
|
|
2557
|
-
const previousManager = managers.get(identity);
|
|
2558
|
-
if (previousManager) {
|
|
2559
|
-
previousManager.dispose();
|
|
2560
|
-
}
|
|
2561
|
-
const resolvedClientMetadata = getClientMetadata ? await getClientMetadata(request) : clientDefaults;
|
|
2562
|
-
const manager = new SSEConnectionManager(
|
|
2551
|
+
const toManagerOptions = (identity, resolvedClientMetadata) => ({
|
|
2552
|
+
identity,
|
|
2553
|
+
heartbeatInterval,
|
|
2554
|
+
clientDefaults: resolvedClientMetadata
|
|
2555
|
+
});
|
|
2556
|
+
async function resolveClientMetadata(request) {
|
|
2557
|
+
return getClientMetadata ? await getClientMetadata(request) : clientDefaults;
|
|
2558
|
+
}
|
|
2559
|
+
async function GET() {
|
|
2560
|
+
return Response.json(
|
|
2563
2561
|
{
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
// Pass resolved metadata
|
|
2568
|
-
},
|
|
2569
|
-
(event) => {
|
|
2570
|
-
if ("id" in event) {
|
|
2571
|
-
sendSSE("rpc-response", event);
|
|
2572
|
-
} else if ("type" in event && "sessionId" in event) {
|
|
2573
|
-
sendSSE("connection", event);
|
|
2574
|
-
} else {
|
|
2575
|
-
sendSSE("observability", event);
|
|
2562
|
+
error: {
|
|
2563
|
+
code: "METHOD_NOT_ALLOWED",
|
|
2564
|
+
message: "Use POST /api/mcp. For streaming use Accept: text/event-stream."
|
|
2576
2565
|
}
|
|
2577
|
-
}
|
|
2566
|
+
},
|
|
2567
|
+
{ status: 405 }
|
|
2578
2568
|
);
|
|
2579
|
-
managers.set(identity, manager);
|
|
2580
|
-
sendSSE("connected", { timestamp: Date.now() });
|
|
2581
|
-
const abortController = new AbortController();
|
|
2582
|
-
request.signal?.addEventListener("abort", () => {
|
|
2583
|
-
manager.dispose();
|
|
2584
|
-
managers.delete(identity);
|
|
2585
|
-
writer.close().catch(() => {
|
|
2586
|
-
});
|
|
2587
|
-
abortController.abort();
|
|
2588
|
-
});
|
|
2589
|
-
return new Response(stream.readable, {
|
|
2590
|
-
status: 200,
|
|
2591
|
-
headers: {
|
|
2592
|
-
"Content-Type": "text/event-stream",
|
|
2593
|
-
"Cache-Control": "no-cache, no-transform",
|
|
2594
|
-
"Connection": "keep-alive",
|
|
2595
|
-
"X-Accel-Buffering": "no"
|
|
2596
|
-
}
|
|
2597
|
-
});
|
|
2598
2569
|
}
|
|
2599
2570
|
async function POST(request) {
|
|
2600
2571
|
const identity = getIdentity(request);
|
|
2601
2572
|
const authToken = getAuthToken(request);
|
|
2573
|
+
const acceptsEventStream = (request.headers.get("accept") || "").toLowerCase().includes("text/event-stream");
|
|
2602
2574
|
if (!identity) {
|
|
2603
2575
|
return Response.json({ error: { code: "MISSING_IDENTITY", message: "Missing identity" } }, { status: 400 });
|
|
2604
2576
|
}
|
|
@@ -2606,28 +2578,103 @@ data: ${JSON.stringify(data)}
|
|
|
2606
2578
|
if (!isAuthorized) {
|
|
2607
2579
|
return Response.json({ error: { code: "UNAUTHORIZED", message: "Unauthorized" } }, { status: 401 });
|
|
2608
2580
|
}
|
|
2581
|
+
let rawBody = "";
|
|
2609
2582
|
try {
|
|
2610
|
-
|
|
2611
|
-
const
|
|
2612
|
-
if (!
|
|
2583
|
+
rawBody = await request.text();
|
|
2584
|
+
const body = rawBody ? JSON.parse(rawBody) : null;
|
|
2585
|
+
if (!body || typeof body !== "object") {
|
|
2613
2586
|
return Response.json(
|
|
2614
2587
|
{
|
|
2615
2588
|
error: {
|
|
2616
|
-
code: "
|
|
2617
|
-
message: "
|
|
2589
|
+
code: "INVALID_REQUEST",
|
|
2590
|
+
message: "Invalid JSON-RPC request body"
|
|
2618
2591
|
}
|
|
2619
2592
|
},
|
|
2620
2593
|
{ status: 400 }
|
|
2621
2594
|
);
|
|
2622
2595
|
}
|
|
2623
|
-
const
|
|
2624
|
-
|
|
2596
|
+
const resolvedClientMetadata = await resolveClientMetadata(request);
|
|
2597
|
+
if (!acceptsEventStream) {
|
|
2598
|
+
const manager2 = new SSEConnectionManager(
|
|
2599
|
+
toManagerOptions(identity, resolvedClientMetadata),
|
|
2600
|
+
() => {
|
|
2601
|
+
}
|
|
2602
|
+
);
|
|
2603
|
+
try {
|
|
2604
|
+
const response = await manager2.handleRequest(body);
|
|
2605
|
+
return Response.json(response);
|
|
2606
|
+
} finally {
|
|
2607
|
+
manager2.dispose();
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
const stream = new TransformStream();
|
|
2611
|
+
const writer = stream.writable.getWriter();
|
|
2612
|
+
const encoder = new TextEncoder();
|
|
2613
|
+
let streamWritable = true;
|
|
2614
|
+
const sendSSE = (event, data) => {
|
|
2615
|
+
if (!streamWritable) return;
|
|
2616
|
+
const message = `event: ${event}
|
|
2617
|
+
data: ${JSON.stringify(data)}
|
|
2618
|
+
|
|
2619
|
+
`;
|
|
2620
|
+
writer.write(encoder.encode(message)).catch(() => {
|
|
2621
|
+
streamWritable = false;
|
|
2622
|
+
});
|
|
2623
|
+
};
|
|
2624
|
+
const manager = new SSEConnectionManager(
|
|
2625
|
+
toManagerOptions(identity, resolvedClientMetadata),
|
|
2626
|
+
(event) => {
|
|
2627
|
+
if (isRpcResponseEvent(event)) {
|
|
2628
|
+
sendSSE("rpc-response", event);
|
|
2629
|
+
} else if ("type" in event && "sessionId" in event) {
|
|
2630
|
+
sendSSE("connection", event);
|
|
2631
|
+
} else {
|
|
2632
|
+
sendSSE("observability", event);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
);
|
|
2636
|
+
sendSSE("connected", { timestamp: Date.now() });
|
|
2637
|
+
void (async () => {
|
|
2638
|
+
try {
|
|
2639
|
+
await manager.handleRequest(body);
|
|
2640
|
+
} catch (error) {
|
|
2641
|
+
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
2642
|
+
sendSSE("rpc-response", {
|
|
2643
|
+
id: body.id || "unknown",
|
|
2644
|
+
error: {
|
|
2645
|
+
code: "EXECUTION_ERROR",
|
|
2646
|
+
message: err.message
|
|
2647
|
+
}
|
|
2648
|
+
});
|
|
2649
|
+
} finally {
|
|
2650
|
+
streamWritable = false;
|
|
2651
|
+
manager.dispose();
|
|
2652
|
+
writer.close().catch(() => {
|
|
2653
|
+
});
|
|
2654
|
+
}
|
|
2655
|
+
})();
|
|
2656
|
+
return new Response(stream.readable, {
|
|
2657
|
+
status: 200,
|
|
2658
|
+
headers: {
|
|
2659
|
+
"Content-Type": "text/event-stream",
|
|
2660
|
+
"Cache-Control": "no-cache, no-transform",
|
|
2661
|
+
"Connection": "keep-alive",
|
|
2662
|
+
"X-Accel-Buffering": "no"
|
|
2663
|
+
}
|
|
2664
|
+
});
|
|
2625
2665
|
} catch (error) {
|
|
2666
|
+
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
2667
|
+
console.error("[MCP Next Handler] Failed to handle RPC", {
|
|
2668
|
+
identity,
|
|
2669
|
+
message: err.message,
|
|
2670
|
+
stack: err.stack,
|
|
2671
|
+
rawBody: rawBody.slice(0, 500)
|
|
2672
|
+
});
|
|
2626
2673
|
return Response.json(
|
|
2627
2674
|
{
|
|
2628
2675
|
error: {
|
|
2629
2676
|
code: "EXECUTION_ERROR",
|
|
2630
|
-
message:
|
|
2677
|
+
message: err.message
|
|
2631
2678
|
}
|
|
2632
2679
|
},
|
|
2633
2680
|
{ status: 500 }
|
|
@@ -2636,62 +2683,27 @@ data: ${JSON.stringify(data)}
|
|
|
2636
2683
|
}
|
|
2637
2684
|
return { GET, POST };
|
|
2638
2685
|
}
|
|
2639
|
-
var DEFAULT_REQUEST_TIMEOUT = 6e4;
|
|
2640
|
-
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
2641
|
-
var BASE_RECONNECT_DELAY = 1e3;
|
|
2642
2686
|
var SSEClient = class {
|
|
2643
2687
|
constructor(options) {
|
|
2644
2688
|
this.options = options;
|
|
2645
|
-
__publicField(this, "eventSource", null);
|
|
2646
|
-
__publicField(this, "pendingRequests", /* @__PURE__ */ new Map());
|
|
2647
2689
|
__publicField(this, "resourceCache", /* @__PURE__ */ new Map());
|
|
2648
|
-
__publicField(this, "
|
|
2649
|
-
__publicField(this, "isManuallyDisconnected", false);
|
|
2650
|
-
__publicField(this, "connectionPromise", null);
|
|
2651
|
-
__publicField(this, "connectionResolver", null);
|
|
2690
|
+
__publicField(this, "connected", false);
|
|
2652
2691
|
}
|
|
2653
|
-
// ============================================
|
|
2654
|
-
// Connection Management
|
|
2655
|
-
// ============================================
|
|
2656
|
-
/**
|
|
2657
|
-
* Connect to the SSE endpoint
|
|
2658
|
-
*/
|
|
2659
2692
|
connect() {
|
|
2660
|
-
if (this.
|
|
2693
|
+
if (this.connected) {
|
|
2661
2694
|
return;
|
|
2662
2695
|
}
|
|
2663
|
-
this.
|
|
2664
|
-
this.options.onStatusChange?.("
|
|
2665
|
-
this.
|
|
2666
|
-
this.connectionResolver = resolve;
|
|
2667
|
-
});
|
|
2668
|
-
const url = this.buildUrl();
|
|
2669
|
-
this.eventSource = new EventSource(url);
|
|
2670
|
-
this.setupEventListeners();
|
|
2696
|
+
this.connected = true;
|
|
2697
|
+
this.options.onStatusChange?.("connected");
|
|
2698
|
+
this.log("RPC mode: post_stream");
|
|
2671
2699
|
}
|
|
2672
|
-
/**
|
|
2673
|
-
* Disconnect from the SSE endpoint
|
|
2674
|
-
*/
|
|
2675
2700
|
disconnect() {
|
|
2676
|
-
this.
|
|
2677
|
-
if (this.eventSource) {
|
|
2678
|
-
this.eventSource.close();
|
|
2679
|
-
this.eventSource = null;
|
|
2680
|
-
}
|
|
2681
|
-
this.connectionPromise = null;
|
|
2682
|
-
this.connectionResolver = null;
|
|
2683
|
-
this.rejectAllPendingRequests(new Error("Connection closed"));
|
|
2701
|
+
this.connected = false;
|
|
2684
2702
|
this.options.onStatusChange?.("disconnected");
|
|
2685
2703
|
}
|
|
2686
|
-
/**
|
|
2687
|
-
* Check if connected to the SSE endpoint
|
|
2688
|
-
*/
|
|
2689
2704
|
isConnected() {
|
|
2690
|
-
return this.
|
|
2705
|
+
return this.connected;
|
|
2691
2706
|
}
|
|
2692
|
-
// ============================================
|
|
2693
|
-
// RPC Methods
|
|
2694
|
-
// ============================================
|
|
2695
2707
|
async getSessions() {
|
|
2696
2708
|
return this.sendRequest("getSessions");
|
|
2697
2709
|
}
|
|
@@ -2727,22 +2739,10 @@ var SSEClient = class {
|
|
|
2727
2739
|
async readResource(sessionId, uri) {
|
|
2728
2740
|
return this.sendRequest("readResource", { sessionId, uri });
|
|
2729
2741
|
}
|
|
2730
|
-
// ============================================
|
|
2731
|
-
// Resource Preloading (for instant UI loading)
|
|
2732
|
-
// ============================================
|
|
2733
|
-
/**
|
|
2734
|
-
* Preload UI resources for tools that have UI metadata.
|
|
2735
|
-
* Call this when tools are discovered to enable instant MCP App UI loading.
|
|
2736
|
-
*/
|
|
2737
2742
|
preloadToolUiResources(sessionId, tools) {
|
|
2738
2743
|
for (const tool of tools) {
|
|
2739
2744
|
const uri = this.extractUiResourceUri(tool);
|
|
2740
|
-
if (!uri) continue;
|
|
2741
|
-
if (this.resourceCache.has(uri)) {
|
|
2742
|
-
this.log(`Resource already cached: ${uri}`);
|
|
2743
|
-
continue;
|
|
2744
|
-
}
|
|
2745
|
-
this.log(`Preloading UI resource for tool "${tool.name}": ${uri}`);
|
|
2745
|
+
if (!uri || this.resourceCache.has(uri)) continue;
|
|
2746
2746
|
const promise = this.sendRequest("readResource", { sessionId, uri }).catch((err) => {
|
|
2747
2747
|
this.log(`Failed to preload resource ${uri}: ${err.message}`, "warn");
|
|
2748
2748
|
this.resourceCache.delete(uri);
|
|
@@ -2751,43 +2751,24 @@ var SSEClient = class {
|
|
|
2751
2751
|
this.resourceCache.set(uri, promise);
|
|
2752
2752
|
}
|
|
2753
2753
|
}
|
|
2754
|
-
/**
|
|
2755
|
-
* Get a preloaded resource from cache, or fetch if not cached.
|
|
2756
|
-
*/
|
|
2757
2754
|
getOrFetchResource(sessionId, uri) {
|
|
2758
2755
|
const cached = this.resourceCache.get(uri);
|
|
2759
|
-
if (cached)
|
|
2760
|
-
this.log(`Cache hit for resource: ${uri}`);
|
|
2761
|
-
return cached;
|
|
2762
|
-
}
|
|
2763
|
-
this.log(`Cache miss, fetching resource: ${uri}`);
|
|
2756
|
+
if (cached) return cached;
|
|
2764
2757
|
const promise = this.sendRequest("readResource", { sessionId, uri });
|
|
2765
2758
|
this.resourceCache.set(uri, promise);
|
|
2766
2759
|
return promise;
|
|
2767
2760
|
}
|
|
2768
|
-
/**
|
|
2769
|
-
* Check if a resource is already cached
|
|
2770
|
-
*/
|
|
2771
2761
|
hasPreloadedResource(uri) {
|
|
2772
2762
|
return this.resourceCache.has(uri);
|
|
2773
2763
|
}
|
|
2774
|
-
/**
|
|
2775
|
-
* Clear the resource cache
|
|
2776
|
-
*/
|
|
2777
2764
|
clearResourceCache() {
|
|
2778
2765
|
this.resourceCache.clear();
|
|
2779
2766
|
}
|
|
2780
|
-
// ============================================
|
|
2781
|
-
// Private: Request Handling
|
|
2782
|
-
// ============================================
|
|
2783
|
-
/**
|
|
2784
|
-
* Send an RPC request and return the response directly from HTTP.
|
|
2785
|
-
* This bypasses SSE latency by returning results in the HTTP response body.
|
|
2786
|
-
*/
|
|
2787
2767
|
async sendRequest(method, params) {
|
|
2788
|
-
if (this.
|
|
2789
|
-
|
|
2768
|
+
if (!this.connected) {
|
|
2769
|
+
this.connect();
|
|
2790
2770
|
}
|
|
2771
|
+
this.log(`RPC request via post_stream: ${method}`);
|
|
2791
2772
|
const request = {
|
|
2792
2773
|
id: `rpc_${nanoid.nanoid(10)}`,
|
|
2793
2774
|
method,
|
|
@@ -2801,103 +2782,93 @@ var SSEClient = class {
|
|
|
2801
2782
|
if (!response.ok) {
|
|
2802
2783
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
2803
2784
|
}
|
|
2804
|
-
const
|
|
2805
|
-
|
|
2785
|
+
const contentType = (response.headers.get("content-type") || "").toLowerCase();
|
|
2786
|
+
if (!contentType.includes("text/event-stream")) {
|
|
2787
|
+
const data2 = await response.json();
|
|
2788
|
+
return this.parseRpcResponse(data2);
|
|
2789
|
+
}
|
|
2790
|
+
const data = await this.readRpcResponseFromStream(response);
|
|
2791
|
+
return this.parseRpcResponse(data);
|
|
2792
|
+
}
|
|
2793
|
+
async readRpcResponseFromStream(response) {
|
|
2794
|
+
if (!response.body) {
|
|
2795
|
+
throw new Error("Streaming response body is missing");
|
|
2796
|
+
}
|
|
2797
|
+
const reader = response.body.getReader();
|
|
2798
|
+
const decoder = new TextDecoder();
|
|
2799
|
+
let buffer = "";
|
|
2800
|
+
let rpcResponse = null;
|
|
2801
|
+
const dispatchBlock = (block) => {
|
|
2802
|
+
const lines = block.split("\n");
|
|
2803
|
+
let eventName = "message";
|
|
2804
|
+
const dataLines = [];
|
|
2805
|
+
for (const rawLine of lines) {
|
|
2806
|
+
const line = rawLine.replace(/\r$/, "");
|
|
2807
|
+
if (!line || line.startsWith(":")) continue;
|
|
2808
|
+
if (line.startsWith("event:")) {
|
|
2809
|
+
eventName = line.slice("event:".length).trim();
|
|
2810
|
+
continue;
|
|
2811
|
+
}
|
|
2812
|
+
if (line.startsWith("data:")) {
|
|
2813
|
+
dataLines.push(line.slice("data:".length).trimStart());
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
if (!dataLines.length) return;
|
|
2817
|
+
const payloadText = dataLines.join("\n");
|
|
2818
|
+
let payload = payloadText;
|
|
2819
|
+
try {
|
|
2820
|
+
payload = JSON.parse(payloadText);
|
|
2821
|
+
} catch {
|
|
2822
|
+
}
|
|
2823
|
+
switch (eventName) {
|
|
2824
|
+
case "connected":
|
|
2825
|
+
this.options.onStatusChange?.("connected");
|
|
2826
|
+
break;
|
|
2827
|
+
case "connection":
|
|
2828
|
+
this.options.onConnectionEvent?.(payload);
|
|
2829
|
+
break;
|
|
2830
|
+
case "observability":
|
|
2831
|
+
this.options.onObservabilityEvent?.(payload);
|
|
2832
|
+
break;
|
|
2833
|
+
case "rpc-response":
|
|
2834
|
+
rpcResponse = payload;
|
|
2835
|
+
break;
|
|
2836
|
+
}
|
|
2837
|
+
};
|
|
2838
|
+
while (true) {
|
|
2839
|
+
const { value, done } = await reader.read();
|
|
2840
|
+
if (done) break;
|
|
2841
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2842
|
+
let separatorMatch = buffer.match(/\r?\n\r?\n/);
|
|
2843
|
+
while (separatorMatch && separatorMatch.index !== void 0) {
|
|
2844
|
+
const separatorIndex = separatorMatch.index;
|
|
2845
|
+
const separatorLength = separatorMatch[0].length;
|
|
2846
|
+
const block = buffer.slice(0, separatorIndex);
|
|
2847
|
+
buffer = buffer.slice(separatorIndex + separatorLength);
|
|
2848
|
+
dispatchBlock(block);
|
|
2849
|
+
separatorMatch = buffer.match(/\r?\n\r?\n/);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
if (buffer.trim()) {
|
|
2853
|
+
dispatchBlock(buffer);
|
|
2854
|
+
}
|
|
2855
|
+
if (!rpcResponse) {
|
|
2856
|
+
throw new Error("Missing rpc-response event in streamed RPC result");
|
|
2857
|
+
}
|
|
2858
|
+
return rpcResponse;
|
|
2806
2859
|
}
|
|
2807
|
-
|
|
2808
|
-
* Parse RPC response and handle different response formats
|
|
2809
|
-
*/
|
|
2810
|
-
parseRpcResponse(data, requestId) {
|
|
2860
|
+
parseRpcResponse(data) {
|
|
2811
2861
|
if ("result" in data) {
|
|
2812
2862
|
return data.result;
|
|
2813
2863
|
}
|
|
2814
2864
|
if ("error" in data && data.error) {
|
|
2815
2865
|
throw new Error(data.error.message || "Unknown RPC error");
|
|
2816
2866
|
}
|
|
2817
|
-
if ("
|
|
2818
|
-
return
|
|
2867
|
+
if (data && typeof data === "object" && "id" in data) {
|
|
2868
|
+
return void 0;
|
|
2819
2869
|
}
|
|
2820
2870
|
throw new Error("Invalid RPC response format");
|
|
2821
2871
|
}
|
|
2822
|
-
/**
|
|
2823
|
-
* Wait for RPC response via SSE (legacy fallback)
|
|
2824
|
-
*/
|
|
2825
|
-
waitForSseResponse(requestId) {
|
|
2826
|
-
const timeoutMs = this.options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
2827
|
-
return new Promise((resolve, reject) => {
|
|
2828
|
-
const timeoutId = setTimeout(() => {
|
|
2829
|
-
this.pendingRequests.delete(requestId);
|
|
2830
|
-
reject(new Error(`Request timeout after ${timeoutMs}ms`));
|
|
2831
|
-
}, timeoutMs);
|
|
2832
|
-
this.pendingRequests.set(requestId, {
|
|
2833
|
-
resolve,
|
|
2834
|
-
reject,
|
|
2835
|
-
timeoutId
|
|
2836
|
-
});
|
|
2837
|
-
});
|
|
2838
|
-
}
|
|
2839
|
-
/**
|
|
2840
|
-
* Handle RPC response received via SSE (legacy)
|
|
2841
|
-
*/
|
|
2842
|
-
handleRpcResponse(response) {
|
|
2843
|
-
const pending = this.pendingRequests.get(response.id);
|
|
2844
|
-
if (!pending) return;
|
|
2845
|
-
clearTimeout(pending.timeoutId);
|
|
2846
|
-
this.pendingRequests.delete(response.id);
|
|
2847
|
-
if (response.error) {
|
|
2848
|
-
pending.reject(new Error(response.error.message));
|
|
2849
|
-
} else {
|
|
2850
|
-
pending.resolve(response.result);
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
// ============================================
|
|
2854
|
-
// Private: Event Handling
|
|
2855
|
-
// ============================================
|
|
2856
|
-
setupEventListeners() {
|
|
2857
|
-
if (!this.eventSource) return;
|
|
2858
|
-
this.eventSource.addEventListener("open", () => {
|
|
2859
|
-
this.log("Connected");
|
|
2860
|
-
this.reconnectAttempts = 0;
|
|
2861
|
-
this.options.onStatusChange?.("connected");
|
|
2862
|
-
});
|
|
2863
|
-
this.eventSource.addEventListener("connected", () => {
|
|
2864
|
-
this.log("Server ready");
|
|
2865
|
-
this.connectionResolver?.();
|
|
2866
|
-
this.connectionResolver = null;
|
|
2867
|
-
});
|
|
2868
|
-
this.eventSource.addEventListener("connection", (e) => {
|
|
2869
|
-
const event = JSON.parse(e.data);
|
|
2870
|
-
this.options.onConnectionEvent?.(event);
|
|
2871
|
-
});
|
|
2872
|
-
this.eventSource.addEventListener("observability", (e) => {
|
|
2873
|
-
const event = JSON.parse(e.data);
|
|
2874
|
-
this.options.onObservabilityEvent?.(event);
|
|
2875
|
-
});
|
|
2876
|
-
this.eventSource.addEventListener("rpc-response", (e) => {
|
|
2877
|
-
const response = JSON.parse(e.data);
|
|
2878
|
-
this.handleRpcResponse(response);
|
|
2879
|
-
});
|
|
2880
|
-
this.eventSource.addEventListener("error", () => {
|
|
2881
|
-
this.log("Connection error", "error");
|
|
2882
|
-
this.options.onStatusChange?.("error");
|
|
2883
|
-
this.attemptReconnect();
|
|
2884
|
-
});
|
|
2885
|
-
}
|
|
2886
|
-
attemptReconnect() {
|
|
2887
|
-
if (this.isManuallyDisconnected || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
2888
|
-
return;
|
|
2889
|
-
}
|
|
2890
|
-
this.reconnectAttempts++;
|
|
2891
|
-
const delay = BASE_RECONNECT_DELAY * this.reconnectAttempts;
|
|
2892
|
-
this.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
2893
|
-
setTimeout(() => {
|
|
2894
|
-
this.disconnect();
|
|
2895
|
-
this.connect();
|
|
2896
|
-
}, delay);
|
|
2897
|
-
}
|
|
2898
|
-
// ============================================
|
|
2899
|
-
// Private: Utilities
|
|
2900
|
-
// ============================================
|
|
2901
2872
|
buildUrl() {
|
|
2902
2873
|
const url = new URL(this.options.url, globalThis.location?.origin);
|
|
2903
2874
|
url.searchParams.set("identity", this.options.identity);
|
|
@@ -2908,20 +2879,14 @@ var SSEClient = class {
|
|
|
2908
2879
|
}
|
|
2909
2880
|
buildHeaders() {
|
|
2910
2881
|
const headers = {
|
|
2911
|
-
"Content-Type": "application/json"
|
|
2882
|
+
"Content-Type": "application/json",
|
|
2883
|
+
"Accept": "text/event-stream"
|
|
2912
2884
|
};
|
|
2913
2885
|
if (this.options.authToken) {
|
|
2914
2886
|
headers["Authorization"] = `Bearer ${this.options.authToken}`;
|
|
2915
2887
|
}
|
|
2916
2888
|
return headers;
|
|
2917
2889
|
}
|
|
2918
|
-
rejectAllPendingRequests(error) {
|
|
2919
|
-
for (const [, pending] of this.pendingRequests) {
|
|
2920
|
-
clearTimeout(pending.timeoutId);
|
|
2921
|
-
pending.reject(error);
|
|
2922
|
-
}
|
|
2923
|
-
this.pendingRequests.clear();
|
|
2924
|
-
}
|
|
2925
2890
|
extractUiResourceUri(tool) {
|
|
2926
2891
|
const meta = tool._meta?.ui;
|
|
2927
2892
|
if (!meta || typeof meta !== "object") return void 0;
|