@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/index.mjs
CHANGED
|
@@ -1081,18 +1081,15 @@ var MCPClient = class _MCPClient {
|
|
|
1081
1081
|
__publicField(this, "serverUrl");
|
|
1082
1082
|
__publicField(this, "callbackUrl");
|
|
1083
1083
|
__publicField(this, "onRedirect");
|
|
1084
|
-
__publicField(this, "tokens");
|
|
1085
|
-
__publicField(this, "tokenExpiresAt");
|
|
1086
|
-
__publicField(this, "clientInformation");
|
|
1087
1084
|
__publicField(this, "clientId");
|
|
1088
1085
|
__publicField(this, "clientSecret");
|
|
1089
|
-
__publicField(this, "onSaveTokens");
|
|
1090
1086
|
__publicField(this, "headers");
|
|
1091
1087
|
/** OAuth Client Metadata */
|
|
1092
1088
|
__publicField(this, "clientName");
|
|
1093
1089
|
__publicField(this, "clientUri");
|
|
1094
1090
|
__publicField(this, "logoUri");
|
|
1095
1091
|
__publicField(this, "policyUri");
|
|
1092
|
+
__publicField(this, "createdAt");
|
|
1096
1093
|
/** Event emitters for connection lifecycle */
|
|
1097
1094
|
__publicField(this, "_onConnectionEvent", new Emitter());
|
|
1098
1095
|
__publicField(this, "onConnectionEvent", this._onConnectionEvent.event);
|
|
@@ -1107,12 +1104,8 @@ var MCPClient = class _MCPClient {
|
|
|
1107
1104
|
this.serverId = options.serverId;
|
|
1108
1105
|
this.sessionId = options.sessionId;
|
|
1109
1106
|
this.transportType = options.transportType;
|
|
1110
|
-
this.tokens = options.tokens;
|
|
1111
|
-
this.tokenExpiresAt = options.tokenExpiresAt;
|
|
1112
|
-
this.clientInformation = options.clientInformation;
|
|
1113
1107
|
this.clientId = options.clientId;
|
|
1114
1108
|
this.clientSecret = options.clientSecret;
|
|
1115
|
-
this.onSaveTokens = options.onSaveTokens;
|
|
1116
1109
|
this.headers = options.headers;
|
|
1117
1110
|
this.clientName = options.clientName;
|
|
1118
1111
|
this.clientUri = options.clientUri;
|
|
@@ -1132,6 +1125,8 @@ var MCPClient = class _MCPClient {
|
|
|
1132
1125
|
sessionId: this.sessionId,
|
|
1133
1126
|
serverId: this.serverId,
|
|
1134
1127
|
serverName: this.serverName || this.serverId,
|
|
1128
|
+
serverUrl: this.serverUrl || "",
|
|
1129
|
+
createdAt: this.createdAt,
|
|
1135
1130
|
state: newState,
|
|
1136
1131
|
previousState,
|
|
1137
1132
|
timestamp: Date.now()
|
|
@@ -1252,6 +1247,7 @@ var MCPClient = class _MCPClient {
|
|
|
1252
1247
|
this.serverName = this.serverName || sessionData.serverName;
|
|
1253
1248
|
this.serverId = this.serverId || sessionData.serverId || "unknown";
|
|
1254
1249
|
this.headers = this.headers || sessionData.headers;
|
|
1250
|
+
this.createdAt = sessionData.createdAt;
|
|
1255
1251
|
}
|
|
1256
1252
|
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
1257
1253
|
throw new Error("Missing required connection metadata");
|
|
@@ -1305,6 +1301,7 @@ var MCPClient = class _MCPClient {
|
|
|
1305
1301
|
}
|
|
1306
1302
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1307
1303
|
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
1304
|
+
this.createdAt = Date.now();
|
|
1308
1305
|
console.log(`[MCPClient] Creating initial session ${this.sessionId} for OAuth flow`);
|
|
1309
1306
|
await storage.createSession({
|
|
1310
1307
|
sessionId: this.sessionId,
|
|
@@ -1314,7 +1311,8 @@ var MCPClient = class _MCPClient {
|
|
|
1314
1311
|
serverUrl: this.serverUrl,
|
|
1315
1312
|
callbackUrl: this.callbackUrl,
|
|
1316
1313
|
transportType: this.transportType || "streamable_http",
|
|
1317
|
-
createdAt:
|
|
1314
|
+
createdAt: this.createdAt,
|
|
1315
|
+
active: false
|
|
1318
1316
|
}, Math.floor(STATE_EXPIRATION_MS / 1e3));
|
|
1319
1317
|
}
|
|
1320
1318
|
}
|
|
@@ -1322,9 +1320,10 @@ var MCPClient = class _MCPClient {
|
|
|
1322
1320
|
* Saves current session state to storage
|
|
1323
1321
|
* Creates new session if it doesn't exist, updates if it does
|
|
1324
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
|
|
1325
1324
|
* @private
|
|
1326
1325
|
*/
|
|
1327
|
-
async saveSession(ttl = SESSION_TTL_SECONDS) {
|
|
1326
|
+
async saveSession(ttl = SESSION_TTL_SECONDS, active = true) {
|
|
1328
1327
|
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
1329
1328
|
return;
|
|
1330
1329
|
}
|
|
@@ -1336,7 +1335,8 @@ var MCPClient = class _MCPClient {
|
|
|
1336
1335
|
serverUrl: this.serverUrl,
|
|
1337
1336
|
callbackUrl: this.callbackUrl,
|
|
1338
1337
|
transportType: this.transportType || "streamable_http",
|
|
1339
|
-
createdAt: Date.now()
|
|
1338
|
+
createdAt: this.createdAt || Date.now(),
|
|
1339
|
+
active
|
|
1340
1340
|
};
|
|
1341
1341
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1342
1342
|
if (existingSession) {
|
|
@@ -1410,15 +1410,17 @@ var MCPClient = class _MCPClient {
|
|
|
1410
1410
|
this.emitStateChange("CONNECTED");
|
|
1411
1411
|
this.emitProgress("Connected successfully");
|
|
1412
1412
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
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);
|
|
1416
1418
|
}
|
|
1417
1419
|
} catch (error) {
|
|
1418
1420
|
if (error instanceof UnauthorizedError$1 || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
|
|
1419
1421
|
this.emitStateChange("AUTHENTICATING");
|
|
1420
1422
|
console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
|
|
1421
|
-
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3));
|
|
1423
|
+
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
|
|
1422
1424
|
let authUrl = "";
|
|
1423
1425
|
if (this.oauthProvider) {
|
|
1424
1426
|
authUrl = this.oauthProvider.authUrl || "";
|
|
@@ -1496,7 +1498,7 @@ var MCPClient = class _MCPClient {
|
|
|
1496
1498
|
await this.client.connect(this.transport);
|
|
1497
1499
|
this.emitStateChange("CONNECTED");
|
|
1498
1500
|
console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
|
|
1499
|
-
await this.saveSession(SESSION_TTL_SECONDS);
|
|
1501
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
1500
1502
|
return;
|
|
1501
1503
|
} catch (error) {
|
|
1502
1504
|
lastError = error;
|
|
@@ -2160,7 +2162,9 @@ var SSEConnectionManager = class {
|
|
|
2160
2162
|
serverId: s.serverId,
|
|
2161
2163
|
serverName: s.serverName,
|
|
2162
2164
|
serverUrl: s.serverUrl,
|
|
2163
|
-
transport: s.transportType
|
|
2165
|
+
transport: s.transportType,
|
|
2166
|
+
createdAt: s.createdAt,
|
|
2167
|
+
active: s.active !== false
|
|
2164
2168
|
}))
|
|
2165
2169
|
};
|
|
2166
2170
|
}
|
|
@@ -2175,6 +2179,13 @@ var SSEConnectionManager = class {
|
|
|
2175
2179
|
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
2176
2180
|
);
|
|
2177
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
|
+
}
|
|
2178
2189
|
throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
|
|
2179
2190
|
}
|
|
2180
2191
|
const sessionId = await storage.generateSessionId();
|
|
@@ -2183,6 +2194,7 @@ var SSEConnectionManager = class {
|
|
|
2183
2194
|
sessionId,
|
|
2184
2195
|
serverId,
|
|
2185
2196
|
serverName,
|
|
2197
|
+
serverUrl,
|
|
2186
2198
|
state: "CONNECTING",
|
|
2187
2199
|
previousState: "DISCONNECTED",
|
|
2188
2200
|
timestamp: Date.now()
|
|
@@ -2315,6 +2327,7 @@ var SSEConnectionManager = class {
|
|
|
2315
2327
|
sessionId,
|
|
2316
2328
|
serverId: session.serverId ?? "unknown",
|
|
2317
2329
|
serverName: session.serverName ?? "Unknown",
|
|
2330
|
+
serverUrl: session.serverUrl,
|
|
2318
2331
|
state: "VALIDATING",
|
|
2319
2332
|
previousState: "DISCONNECTED",
|
|
2320
2333
|
timestamp: Date.now()
|
|
@@ -2366,6 +2379,7 @@ var SSEConnectionManager = class {
|
|
|
2366
2379
|
sessionId,
|
|
2367
2380
|
serverId: session.serverId ?? "unknown",
|
|
2368
2381
|
serverName: session.serverName ?? "Unknown",
|
|
2382
|
+
serverUrl: session.serverUrl,
|
|
2369
2383
|
state: "AUTHENTICATING",
|
|
2370
2384
|
previousState: "DISCONNECTED",
|
|
2371
2385
|
timestamp: Date.now()
|
|
@@ -2497,7 +2511,9 @@ function writeSSEEvent(res, event, data) {
|
|
|
2497
2511
|
}
|
|
2498
2512
|
|
|
2499
2513
|
// src/server/handlers/nextjs-handler.ts
|
|
2500
|
-
|
|
2514
|
+
function isRpcResponseEvent(event) {
|
|
2515
|
+
return "id" in event && ("result" in event || "error" in event);
|
|
2516
|
+
}
|
|
2501
2517
|
function createNextMcpHandler(options = {}) {
|
|
2502
2518
|
const {
|
|
2503
2519
|
getIdentity = (request) => new URL(request.url).searchParams.get("identity"),
|
|
@@ -2510,72 +2526,29 @@ function createNextMcpHandler(options = {}) {
|
|
|
2510
2526
|
clientDefaults,
|
|
2511
2527
|
getClientMetadata
|
|
2512
2528
|
} = options;
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
const stream = new TransformStream();
|
|
2524
|
-
const writer = stream.writable.getWriter();
|
|
2525
|
-
const encoder = new TextEncoder();
|
|
2526
|
-
const sendSSE = (event, data) => {
|
|
2527
|
-
const message = `event: ${event}
|
|
2528
|
-
data: ${JSON.stringify(data)}
|
|
2529
|
-
|
|
2530
|
-
`;
|
|
2531
|
-
writer.write(encoder.encode(message)).catch(() => {
|
|
2532
|
-
});
|
|
2533
|
-
};
|
|
2534
|
-
const previousManager = managers.get(identity);
|
|
2535
|
-
if (previousManager) {
|
|
2536
|
-
previousManager.dispose();
|
|
2537
|
-
}
|
|
2538
|
-
const resolvedClientMetadata = getClientMetadata ? await getClientMetadata(request) : clientDefaults;
|
|
2539
|
-
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(
|
|
2540
2539
|
{
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
// Pass resolved metadata
|
|
2545
|
-
},
|
|
2546
|
-
(event) => {
|
|
2547
|
-
if ("id" in event) {
|
|
2548
|
-
sendSSE("rpc-response", event);
|
|
2549
|
-
} else if ("type" in event && "sessionId" in event) {
|
|
2550
|
-
sendSSE("connection", event);
|
|
2551
|
-
} else {
|
|
2552
|
-
sendSSE("observability", event);
|
|
2540
|
+
error: {
|
|
2541
|
+
code: "METHOD_NOT_ALLOWED",
|
|
2542
|
+
message: "Use POST /api/mcp. For streaming use Accept: text/event-stream."
|
|
2553
2543
|
}
|
|
2554
|
-
}
|
|
2544
|
+
},
|
|
2545
|
+
{ status: 405 }
|
|
2555
2546
|
);
|
|
2556
|
-
managers.set(identity, manager);
|
|
2557
|
-
sendSSE("connected", { timestamp: Date.now() });
|
|
2558
|
-
const abortController = new AbortController();
|
|
2559
|
-
request.signal?.addEventListener("abort", () => {
|
|
2560
|
-
manager.dispose();
|
|
2561
|
-
managers.delete(identity);
|
|
2562
|
-
writer.close().catch(() => {
|
|
2563
|
-
});
|
|
2564
|
-
abortController.abort();
|
|
2565
|
-
});
|
|
2566
|
-
return new Response(stream.readable, {
|
|
2567
|
-
status: 200,
|
|
2568
|
-
headers: {
|
|
2569
|
-
"Content-Type": "text/event-stream",
|
|
2570
|
-
"Cache-Control": "no-cache, no-transform",
|
|
2571
|
-
"Connection": "keep-alive",
|
|
2572
|
-
"X-Accel-Buffering": "no"
|
|
2573
|
-
}
|
|
2574
|
-
});
|
|
2575
2547
|
}
|
|
2576
2548
|
async function POST(request) {
|
|
2577
2549
|
const identity = getIdentity(request);
|
|
2578
2550
|
const authToken = getAuthToken(request);
|
|
2551
|
+
const acceptsEventStream = (request.headers.get("accept") || "").toLowerCase().includes("text/event-stream");
|
|
2579
2552
|
if (!identity) {
|
|
2580
2553
|
return Response.json({ error: { code: "MISSING_IDENTITY", message: "Missing identity" } }, { status: 400 });
|
|
2581
2554
|
}
|
|
@@ -2583,28 +2556,103 @@ data: ${JSON.stringify(data)}
|
|
|
2583
2556
|
if (!isAuthorized) {
|
|
2584
2557
|
return Response.json({ error: { code: "UNAUTHORIZED", message: "Unauthorized" } }, { status: 401 });
|
|
2585
2558
|
}
|
|
2559
|
+
let rawBody = "";
|
|
2586
2560
|
try {
|
|
2587
|
-
|
|
2588
|
-
const
|
|
2589
|
-
if (!
|
|
2561
|
+
rawBody = await request.text();
|
|
2562
|
+
const body = rawBody ? JSON.parse(rawBody) : null;
|
|
2563
|
+
if (!body || typeof body !== "object") {
|
|
2590
2564
|
return Response.json(
|
|
2591
2565
|
{
|
|
2592
2566
|
error: {
|
|
2593
|
-
code: "
|
|
2594
|
-
message: "
|
|
2567
|
+
code: "INVALID_REQUEST",
|
|
2568
|
+
message: "Invalid JSON-RPC request body"
|
|
2595
2569
|
}
|
|
2596
2570
|
},
|
|
2597
2571
|
{ status: 400 }
|
|
2598
2572
|
);
|
|
2599
2573
|
}
|
|
2600
|
-
const
|
|
2601
|
-
|
|
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
|
+
});
|
|
2602
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
|
+
});
|
|
2603
2651
|
return Response.json(
|
|
2604
2652
|
{
|
|
2605
2653
|
error: {
|
|
2606
2654
|
code: "EXECUTION_ERROR",
|
|
2607
|
-
message:
|
|
2655
|
+
message: err.message
|
|
2608
2656
|
}
|
|
2609
2657
|
},
|
|
2610
2658
|
{ status: 500 }
|
|
@@ -2613,62 +2661,27 @@ data: ${JSON.stringify(data)}
|
|
|
2613
2661
|
}
|
|
2614
2662
|
return { GET, POST };
|
|
2615
2663
|
}
|
|
2616
|
-
var DEFAULT_REQUEST_TIMEOUT = 6e4;
|
|
2617
|
-
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
2618
|
-
var BASE_RECONNECT_DELAY = 1e3;
|
|
2619
2664
|
var SSEClient = class {
|
|
2620
2665
|
constructor(options) {
|
|
2621
2666
|
this.options = options;
|
|
2622
|
-
__publicField(this, "eventSource", null);
|
|
2623
|
-
__publicField(this, "pendingRequests", /* @__PURE__ */ new Map());
|
|
2624
2667
|
__publicField(this, "resourceCache", /* @__PURE__ */ new Map());
|
|
2625
|
-
__publicField(this, "
|
|
2626
|
-
__publicField(this, "isManuallyDisconnected", false);
|
|
2627
|
-
__publicField(this, "connectionPromise", null);
|
|
2628
|
-
__publicField(this, "connectionResolver", null);
|
|
2668
|
+
__publicField(this, "connected", false);
|
|
2629
2669
|
}
|
|
2630
|
-
// ============================================
|
|
2631
|
-
// Connection Management
|
|
2632
|
-
// ============================================
|
|
2633
|
-
/**
|
|
2634
|
-
* Connect to the SSE endpoint
|
|
2635
|
-
*/
|
|
2636
2670
|
connect() {
|
|
2637
|
-
if (this.
|
|
2671
|
+
if (this.connected) {
|
|
2638
2672
|
return;
|
|
2639
2673
|
}
|
|
2640
|
-
this.
|
|
2641
|
-
this.options.onStatusChange?.("
|
|
2642
|
-
this.
|
|
2643
|
-
this.connectionResolver = resolve;
|
|
2644
|
-
});
|
|
2645
|
-
const url = this.buildUrl();
|
|
2646
|
-
this.eventSource = new EventSource(url);
|
|
2647
|
-
this.setupEventListeners();
|
|
2674
|
+
this.connected = true;
|
|
2675
|
+
this.options.onStatusChange?.("connected");
|
|
2676
|
+
this.log("RPC mode: post_stream");
|
|
2648
2677
|
}
|
|
2649
|
-
/**
|
|
2650
|
-
* Disconnect from the SSE endpoint
|
|
2651
|
-
*/
|
|
2652
2678
|
disconnect() {
|
|
2653
|
-
this.
|
|
2654
|
-
if (this.eventSource) {
|
|
2655
|
-
this.eventSource.close();
|
|
2656
|
-
this.eventSource = null;
|
|
2657
|
-
}
|
|
2658
|
-
this.connectionPromise = null;
|
|
2659
|
-
this.connectionResolver = null;
|
|
2660
|
-
this.rejectAllPendingRequests(new Error("Connection closed"));
|
|
2679
|
+
this.connected = false;
|
|
2661
2680
|
this.options.onStatusChange?.("disconnected");
|
|
2662
2681
|
}
|
|
2663
|
-
/**
|
|
2664
|
-
* Check if connected to the SSE endpoint
|
|
2665
|
-
*/
|
|
2666
2682
|
isConnected() {
|
|
2667
|
-
return this.
|
|
2683
|
+
return this.connected;
|
|
2668
2684
|
}
|
|
2669
|
-
// ============================================
|
|
2670
|
-
// RPC Methods
|
|
2671
|
-
// ============================================
|
|
2672
2685
|
async getSessions() {
|
|
2673
2686
|
return this.sendRequest("getSessions");
|
|
2674
2687
|
}
|
|
@@ -2704,22 +2717,10 @@ var SSEClient = class {
|
|
|
2704
2717
|
async readResource(sessionId, uri) {
|
|
2705
2718
|
return this.sendRequest("readResource", { sessionId, uri });
|
|
2706
2719
|
}
|
|
2707
|
-
// ============================================
|
|
2708
|
-
// Resource Preloading (for instant UI loading)
|
|
2709
|
-
// ============================================
|
|
2710
|
-
/**
|
|
2711
|
-
* Preload UI resources for tools that have UI metadata.
|
|
2712
|
-
* Call this when tools are discovered to enable instant MCP App UI loading.
|
|
2713
|
-
*/
|
|
2714
2720
|
preloadToolUiResources(sessionId, tools) {
|
|
2715
2721
|
for (const tool of tools) {
|
|
2716
2722
|
const uri = this.extractUiResourceUri(tool);
|
|
2717
|
-
if (!uri) continue;
|
|
2718
|
-
if (this.resourceCache.has(uri)) {
|
|
2719
|
-
this.log(`Resource already cached: ${uri}`);
|
|
2720
|
-
continue;
|
|
2721
|
-
}
|
|
2722
|
-
this.log(`Preloading UI resource for tool "${tool.name}": ${uri}`);
|
|
2723
|
+
if (!uri || this.resourceCache.has(uri)) continue;
|
|
2723
2724
|
const promise = this.sendRequest("readResource", { sessionId, uri }).catch((err) => {
|
|
2724
2725
|
this.log(`Failed to preload resource ${uri}: ${err.message}`, "warn");
|
|
2725
2726
|
this.resourceCache.delete(uri);
|
|
@@ -2728,43 +2729,24 @@ var SSEClient = class {
|
|
|
2728
2729
|
this.resourceCache.set(uri, promise);
|
|
2729
2730
|
}
|
|
2730
2731
|
}
|
|
2731
|
-
/**
|
|
2732
|
-
* Get a preloaded resource from cache, or fetch if not cached.
|
|
2733
|
-
*/
|
|
2734
2732
|
getOrFetchResource(sessionId, uri) {
|
|
2735
2733
|
const cached = this.resourceCache.get(uri);
|
|
2736
|
-
if (cached)
|
|
2737
|
-
this.log(`Cache hit for resource: ${uri}`);
|
|
2738
|
-
return cached;
|
|
2739
|
-
}
|
|
2740
|
-
this.log(`Cache miss, fetching resource: ${uri}`);
|
|
2734
|
+
if (cached) return cached;
|
|
2741
2735
|
const promise = this.sendRequest("readResource", { sessionId, uri });
|
|
2742
2736
|
this.resourceCache.set(uri, promise);
|
|
2743
2737
|
return promise;
|
|
2744
2738
|
}
|
|
2745
|
-
/**
|
|
2746
|
-
* Check if a resource is already cached
|
|
2747
|
-
*/
|
|
2748
2739
|
hasPreloadedResource(uri) {
|
|
2749
2740
|
return this.resourceCache.has(uri);
|
|
2750
2741
|
}
|
|
2751
|
-
/**
|
|
2752
|
-
* Clear the resource cache
|
|
2753
|
-
*/
|
|
2754
2742
|
clearResourceCache() {
|
|
2755
2743
|
this.resourceCache.clear();
|
|
2756
2744
|
}
|
|
2757
|
-
// ============================================
|
|
2758
|
-
// Private: Request Handling
|
|
2759
|
-
// ============================================
|
|
2760
|
-
/**
|
|
2761
|
-
* Send an RPC request and return the response directly from HTTP.
|
|
2762
|
-
* This bypasses SSE latency by returning results in the HTTP response body.
|
|
2763
|
-
*/
|
|
2764
2745
|
async sendRequest(method, params) {
|
|
2765
|
-
if (this.
|
|
2766
|
-
|
|
2746
|
+
if (!this.connected) {
|
|
2747
|
+
this.connect();
|
|
2767
2748
|
}
|
|
2749
|
+
this.log(`RPC request via post_stream: ${method}`);
|
|
2768
2750
|
const request = {
|
|
2769
2751
|
id: `rpc_${nanoid(10)}`,
|
|
2770
2752
|
method,
|
|
@@ -2778,103 +2760,93 @@ var SSEClient = class {
|
|
|
2778
2760
|
if (!response.ok) {
|
|
2779
2761
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
2780
2762
|
}
|
|
2781
|
-
const
|
|
2782
|
-
|
|
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;
|
|
2783
2837
|
}
|
|
2784
|
-
|
|
2785
|
-
* Parse RPC response and handle different response formats
|
|
2786
|
-
*/
|
|
2787
|
-
parseRpcResponse(data, requestId) {
|
|
2838
|
+
parseRpcResponse(data) {
|
|
2788
2839
|
if ("result" in data) {
|
|
2789
2840
|
return data.result;
|
|
2790
2841
|
}
|
|
2791
2842
|
if ("error" in data && data.error) {
|
|
2792
2843
|
throw new Error(data.error.message || "Unknown RPC error");
|
|
2793
2844
|
}
|
|
2794
|
-
if ("
|
|
2795
|
-
return
|
|
2845
|
+
if (data && typeof data === "object" && "id" in data) {
|
|
2846
|
+
return void 0;
|
|
2796
2847
|
}
|
|
2797
2848
|
throw new Error("Invalid RPC response format");
|
|
2798
2849
|
}
|
|
2799
|
-
/**
|
|
2800
|
-
* Wait for RPC response via SSE (legacy fallback)
|
|
2801
|
-
*/
|
|
2802
|
-
waitForSseResponse(requestId) {
|
|
2803
|
-
const timeoutMs = this.options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
2804
|
-
return new Promise((resolve, reject) => {
|
|
2805
|
-
const timeoutId = setTimeout(() => {
|
|
2806
|
-
this.pendingRequests.delete(requestId);
|
|
2807
|
-
reject(new Error(`Request timeout after ${timeoutMs}ms`));
|
|
2808
|
-
}, timeoutMs);
|
|
2809
|
-
this.pendingRequests.set(requestId, {
|
|
2810
|
-
resolve,
|
|
2811
|
-
reject,
|
|
2812
|
-
timeoutId
|
|
2813
|
-
});
|
|
2814
|
-
});
|
|
2815
|
-
}
|
|
2816
|
-
/**
|
|
2817
|
-
* Handle RPC response received via SSE (legacy)
|
|
2818
|
-
*/
|
|
2819
|
-
handleRpcResponse(response) {
|
|
2820
|
-
const pending = this.pendingRequests.get(response.id);
|
|
2821
|
-
if (!pending) return;
|
|
2822
|
-
clearTimeout(pending.timeoutId);
|
|
2823
|
-
this.pendingRequests.delete(response.id);
|
|
2824
|
-
if (response.error) {
|
|
2825
|
-
pending.reject(new Error(response.error.message));
|
|
2826
|
-
} else {
|
|
2827
|
-
pending.resolve(response.result);
|
|
2828
|
-
}
|
|
2829
|
-
}
|
|
2830
|
-
// ============================================
|
|
2831
|
-
// Private: Event Handling
|
|
2832
|
-
// ============================================
|
|
2833
|
-
setupEventListeners() {
|
|
2834
|
-
if (!this.eventSource) return;
|
|
2835
|
-
this.eventSource.addEventListener("open", () => {
|
|
2836
|
-
this.log("Connected");
|
|
2837
|
-
this.reconnectAttempts = 0;
|
|
2838
|
-
this.options.onStatusChange?.("connected");
|
|
2839
|
-
});
|
|
2840
|
-
this.eventSource.addEventListener("connected", () => {
|
|
2841
|
-
this.log("Server ready");
|
|
2842
|
-
this.connectionResolver?.();
|
|
2843
|
-
this.connectionResolver = null;
|
|
2844
|
-
});
|
|
2845
|
-
this.eventSource.addEventListener("connection", (e) => {
|
|
2846
|
-
const event = JSON.parse(e.data);
|
|
2847
|
-
this.options.onConnectionEvent?.(event);
|
|
2848
|
-
});
|
|
2849
|
-
this.eventSource.addEventListener("observability", (e) => {
|
|
2850
|
-
const event = JSON.parse(e.data);
|
|
2851
|
-
this.options.onObservabilityEvent?.(event);
|
|
2852
|
-
});
|
|
2853
|
-
this.eventSource.addEventListener("rpc-response", (e) => {
|
|
2854
|
-
const response = JSON.parse(e.data);
|
|
2855
|
-
this.handleRpcResponse(response);
|
|
2856
|
-
});
|
|
2857
|
-
this.eventSource.addEventListener("error", () => {
|
|
2858
|
-
this.log("Connection error", "error");
|
|
2859
|
-
this.options.onStatusChange?.("error");
|
|
2860
|
-
this.attemptReconnect();
|
|
2861
|
-
});
|
|
2862
|
-
}
|
|
2863
|
-
attemptReconnect() {
|
|
2864
|
-
if (this.isManuallyDisconnected || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
2865
|
-
return;
|
|
2866
|
-
}
|
|
2867
|
-
this.reconnectAttempts++;
|
|
2868
|
-
const delay = BASE_RECONNECT_DELAY * this.reconnectAttempts;
|
|
2869
|
-
this.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
2870
|
-
setTimeout(() => {
|
|
2871
|
-
this.disconnect();
|
|
2872
|
-
this.connect();
|
|
2873
|
-
}, delay);
|
|
2874
|
-
}
|
|
2875
|
-
// ============================================
|
|
2876
|
-
// Private: Utilities
|
|
2877
|
-
// ============================================
|
|
2878
2850
|
buildUrl() {
|
|
2879
2851
|
const url = new URL(this.options.url, globalThis.location?.origin);
|
|
2880
2852
|
url.searchParams.set("identity", this.options.identity);
|
|
@@ -2885,20 +2857,14 @@ var SSEClient = class {
|
|
|
2885
2857
|
}
|
|
2886
2858
|
buildHeaders() {
|
|
2887
2859
|
const headers = {
|
|
2888
|
-
"Content-Type": "application/json"
|
|
2860
|
+
"Content-Type": "application/json",
|
|
2861
|
+
"Accept": "text/event-stream"
|
|
2889
2862
|
};
|
|
2890
2863
|
if (this.options.authToken) {
|
|
2891
2864
|
headers["Authorization"] = `Bearer ${this.options.authToken}`;
|
|
2892
2865
|
}
|
|
2893
2866
|
return headers;
|
|
2894
2867
|
}
|
|
2895
|
-
rejectAllPendingRequests(error) {
|
|
2896
|
-
for (const [, pending] of this.pendingRequests) {
|
|
2897
|
-
clearTimeout(pending.timeoutId);
|
|
2898
|
-
pending.reject(error);
|
|
2899
|
-
}
|
|
2900
|
-
this.pendingRequests.clear();
|
|
2901
|
-
}
|
|
2902
2868
|
extractUiResourceUri(tool) {
|
|
2903
2869
|
const meta = tool._meta?.ui;
|
|
2904
2870
|
if (!meta || typeof meta !== "object") return void 0;
|