@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.js
CHANGED
|
@@ -1103,18 +1103,15 @@ var MCPClient = class _MCPClient {
|
|
|
1103
1103
|
__publicField(this, "serverUrl");
|
|
1104
1104
|
__publicField(this, "callbackUrl");
|
|
1105
1105
|
__publicField(this, "onRedirect");
|
|
1106
|
-
__publicField(this, "tokens");
|
|
1107
|
-
__publicField(this, "tokenExpiresAt");
|
|
1108
|
-
__publicField(this, "clientInformation");
|
|
1109
1106
|
__publicField(this, "clientId");
|
|
1110
1107
|
__publicField(this, "clientSecret");
|
|
1111
|
-
__publicField(this, "onSaveTokens");
|
|
1112
1108
|
__publicField(this, "headers");
|
|
1113
1109
|
/** OAuth Client Metadata */
|
|
1114
1110
|
__publicField(this, "clientName");
|
|
1115
1111
|
__publicField(this, "clientUri");
|
|
1116
1112
|
__publicField(this, "logoUri");
|
|
1117
1113
|
__publicField(this, "policyUri");
|
|
1114
|
+
__publicField(this, "createdAt");
|
|
1118
1115
|
/** Event emitters for connection lifecycle */
|
|
1119
1116
|
__publicField(this, "_onConnectionEvent", new Emitter());
|
|
1120
1117
|
__publicField(this, "onConnectionEvent", this._onConnectionEvent.event);
|
|
@@ -1129,12 +1126,8 @@ var MCPClient = class _MCPClient {
|
|
|
1129
1126
|
this.serverId = options.serverId;
|
|
1130
1127
|
this.sessionId = options.sessionId;
|
|
1131
1128
|
this.transportType = options.transportType;
|
|
1132
|
-
this.tokens = options.tokens;
|
|
1133
|
-
this.tokenExpiresAt = options.tokenExpiresAt;
|
|
1134
|
-
this.clientInformation = options.clientInformation;
|
|
1135
1129
|
this.clientId = options.clientId;
|
|
1136
1130
|
this.clientSecret = options.clientSecret;
|
|
1137
|
-
this.onSaveTokens = options.onSaveTokens;
|
|
1138
1131
|
this.headers = options.headers;
|
|
1139
1132
|
this.clientName = options.clientName;
|
|
1140
1133
|
this.clientUri = options.clientUri;
|
|
@@ -1154,6 +1147,8 @@ var MCPClient = class _MCPClient {
|
|
|
1154
1147
|
sessionId: this.sessionId,
|
|
1155
1148
|
serverId: this.serverId,
|
|
1156
1149
|
serverName: this.serverName || this.serverId,
|
|
1150
|
+
serverUrl: this.serverUrl || "",
|
|
1151
|
+
createdAt: this.createdAt,
|
|
1157
1152
|
state: newState,
|
|
1158
1153
|
previousState,
|
|
1159
1154
|
timestamp: Date.now()
|
|
@@ -1274,6 +1269,7 @@ var MCPClient = class _MCPClient {
|
|
|
1274
1269
|
this.serverName = this.serverName || sessionData.serverName;
|
|
1275
1270
|
this.serverId = this.serverId || sessionData.serverId || "unknown";
|
|
1276
1271
|
this.headers = this.headers || sessionData.headers;
|
|
1272
|
+
this.createdAt = sessionData.createdAt;
|
|
1277
1273
|
}
|
|
1278
1274
|
if (!this.serverUrl || !this.callbackUrl || !this.serverId) {
|
|
1279
1275
|
throw new Error("Missing required connection metadata");
|
|
@@ -1327,6 +1323,7 @@ var MCPClient = class _MCPClient {
|
|
|
1327
1323
|
}
|
|
1328
1324
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1329
1325
|
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
1326
|
+
this.createdAt = Date.now();
|
|
1330
1327
|
console.log(`[MCPClient] Creating initial session ${this.sessionId} for OAuth flow`);
|
|
1331
1328
|
await storage.createSession({
|
|
1332
1329
|
sessionId: this.sessionId,
|
|
@@ -1336,7 +1333,8 @@ var MCPClient = class _MCPClient {
|
|
|
1336
1333
|
serverUrl: this.serverUrl,
|
|
1337
1334
|
callbackUrl: this.callbackUrl,
|
|
1338
1335
|
transportType: this.transportType || "streamable_http",
|
|
1339
|
-
createdAt:
|
|
1336
|
+
createdAt: this.createdAt,
|
|
1337
|
+
active: false
|
|
1340
1338
|
}, Math.floor(STATE_EXPIRATION_MS / 1e3));
|
|
1341
1339
|
}
|
|
1342
1340
|
}
|
|
@@ -1344,9 +1342,10 @@ var MCPClient = class _MCPClient {
|
|
|
1344
1342
|
* Saves current session state to storage
|
|
1345
1343
|
* Creates new session if it doesn't exist, updates if it does
|
|
1346
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
|
|
1347
1346
|
* @private
|
|
1348
1347
|
*/
|
|
1349
|
-
async saveSession(ttl = SESSION_TTL_SECONDS) {
|
|
1348
|
+
async saveSession(ttl = SESSION_TTL_SECONDS, active = true) {
|
|
1350
1349
|
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
1351
1350
|
return;
|
|
1352
1351
|
}
|
|
@@ -1358,7 +1357,8 @@ var MCPClient = class _MCPClient {
|
|
|
1358
1357
|
serverUrl: this.serverUrl,
|
|
1359
1358
|
callbackUrl: this.callbackUrl,
|
|
1360
1359
|
transportType: this.transportType || "streamable_http",
|
|
1361
|
-
createdAt: Date.now()
|
|
1360
|
+
createdAt: this.createdAt || Date.now(),
|
|
1361
|
+
active
|
|
1362
1362
|
};
|
|
1363
1363
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1364
1364
|
if (existingSession) {
|
|
@@ -1432,15 +1432,17 @@ var MCPClient = class _MCPClient {
|
|
|
1432
1432
|
this.emitStateChange("CONNECTED");
|
|
1433
1433
|
this.emitProgress("Connected successfully");
|
|
1434
1434
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
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);
|
|
1438
1440
|
}
|
|
1439
1441
|
} catch (error) {
|
|
1440
1442
|
if (error instanceof auth_js.UnauthorizedError || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
|
|
1441
1443
|
this.emitStateChange("AUTHENTICATING");
|
|
1442
1444
|
console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
|
|
1443
|
-
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3));
|
|
1445
|
+
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
|
|
1444
1446
|
let authUrl = "";
|
|
1445
1447
|
if (this.oauthProvider) {
|
|
1446
1448
|
authUrl = this.oauthProvider.authUrl || "";
|
|
@@ -1518,7 +1520,7 @@ var MCPClient = class _MCPClient {
|
|
|
1518
1520
|
await this.client.connect(this.transport);
|
|
1519
1521
|
this.emitStateChange("CONNECTED");
|
|
1520
1522
|
console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
|
|
1521
|
-
await this.saveSession(SESSION_TTL_SECONDS);
|
|
1523
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
1522
1524
|
return;
|
|
1523
1525
|
} catch (error) {
|
|
1524
1526
|
lastError = error;
|
|
@@ -2182,7 +2184,9 @@ var SSEConnectionManager = class {
|
|
|
2182
2184
|
serverId: s.serverId,
|
|
2183
2185
|
serverName: s.serverName,
|
|
2184
2186
|
serverUrl: s.serverUrl,
|
|
2185
|
-
transport: s.transportType
|
|
2187
|
+
transport: s.transportType,
|
|
2188
|
+
createdAt: s.createdAt,
|
|
2189
|
+
active: s.active !== false
|
|
2186
2190
|
}))
|
|
2187
2191
|
};
|
|
2188
2192
|
}
|
|
@@ -2197,6 +2201,13 @@ var SSEConnectionManager = class {
|
|
|
2197
2201
|
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
2198
2202
|
);
|
|
2199
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
|
+
}
|
|
2200
2211
|
throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
|
|
2201
2212
|
}
|
|
2202
2213
|
const sessionId = await storage.generateSessionId();
|
|
@@ -2205,6 +2216,7 @@ var SSEConnectionManager = class {
|
|
|
2205
2216
|
sessionId,
|
|
2206
2217
|
serverId,
|
|
2207
2218
|
serverName,
|
|
2219
|
+
serverUrl,
|
|
2208
2220
|
state: "CONNECTING",
|
|
2209
2221
|
previousState: "DISCONNECTED",
|
|
2210
2222
|
timestamp: Date.now()
|
|
@@ -2337,6 +2349,7 @@ var SSEConnectionManager = class {
|
|
|
2337
2349
|
sessionId,
|
|
2338
2350
|
serverId: session.serverId ?? "unknown",
|
|
2339
2351
|
serverName: session.serverName ?? "Unknown",
|
|
2352
|
+
serverUrl: session.serverUrl,
|
|
2340
2353
|
state: "VALIDATING",
|
|
2341
2354
|
previousState: "DISCONNECTED",
|
|
2342
2355
|
timestamp: Date.now()
|
|
@@ -2388,6 +2401,7 @@ var SSEConnectionManager = class {
|
|
|
2388
2401
|
sessionId,
|
|
2389
2402
|
serverId: session.serverId ?? "unknown",
|
|
2390
2403
|
serverName: session.serverName ?? "Unknown",
|
|
2404
|
+
serverUrl: session.serverUrl,
|
|
2391
2405
|
state: "AUTHENTICATING",
|
|
2392
2406
|
previousState: "DISCONNECTED",
|
|
2393
2407
|
timestamp: Date.now()
|
|
@@ -2519,7 +2533,9 @@ function writeSSEEvent(res, event, data) {
|
|
|
2519
2533
|
}
|
|
2520
2534
|
|
|
2521
2535
|
// src/server/handlers/nextjs-handler.ts
|
|
2522
|
-
|
|
2536
|
+
function isRpcResponseEvent(event) {
|
|
2537
|
+
return "id" in event && ("result" in event || "error" in event);
|
|
2538
|
+
}
|
|
2523
2539
|
function createNextMcpHandler(options = {}) {
|
|
2524
2540
|
const {
|
|
2525
2541
|
getIdentity = (request) => new URL(request.url).searchParams.get("identity"),
|
|
@@ -2532,72 +2548,29 @@ function createNextMcpHandler(options = {}) {
|
|
|
2532
2548
|
clientDefaults,
|
|
2533
2549
|
getClientMetadata
|
|
2534
2550
|
} = options;
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
const stream = new TransformStream();
|
|
2546
|
-
const writer = stream.writable.getWriter();
|
|
2547
|
-
const encoder = new TextEncoder();
|
|
2548
|
-
const sendSSE = (event, data) => {
|
|
2549
|
-
const message = `event: ${event}
|
|
2550
|
-
data: ${JSON.stringify(data)}
|
|
2551
|
-
|
|
2552
|
-
`;
|
|
2553
|
-
writer.write(encoder.encode(message)).catch(() => {
|
|
2554
|
-
});
|
|
2555
|
-
};
|
|
2556
|
-
const previousManager = managers.get(identity);
|
|
2557
|
-
if (previousManager) {
|
|
2558
|
-
previousManager.dispose();
|
|
2559
|
-
}
|
|
2560
|
-
const resolvedClientMetadata = getClientMetadata ? await getClientMetadata(request) : clientDefaults;
|
|
2561
|
-
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(
|
|
2562
2561
|
{
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
// Pass resolved metadata
|
|
2567
|
-
},
|
|
2568
|
-
(event) => {
|
|
2569
|
-
if ("id" in event) {
|
|
2570
|
-
sendSSE("rpc-response", event);
|
|
2571
|
-
} else if ("type" in event && "sessionId" in event) {
|
|
2572
|
-
sendSSE("connection", event);
|
|
2573
|
-
} else {
|
|
2574
|
-
sendSSE("observability", event);
|
|
2562
|
+
error: {
|
|
2563
|
+
code: "METHOD_NOT_ALLOWED",
|
|
2564
|
+
message: "Use POST /api/mcp. For streaming use Accept: text/event-stream."
|
|
2575
2565
|
}
|
|
2576
|
-
}
|
|
2566
|
+
},
|
|
2567
|
+
{ status: 405 }
|
|
2577
2568
|
);
|
|
2578
|
-
managers.set(identity, manager);
|
|
2579
|
-
sendSSE("connected", { timestamp: Date.now() });
|
|
2580
|
-
const abortController = new AbortController();
|
|
2581
|
-
request.signal?.addEventListener("abort", () => {
|
|
2582
|
-
manager.dispose();
|
|
2583
|
-
managers.delete(identity);
|
|
2584
|
-
writer.close().catch(() => {
|
|
2585
|
-
});
|
|
2586
|
-
abortController.abort();
|
|
2587
|
-
});
|
|
2588
|
-
return new Response(stream.readable, {
|
|
2589
|
-
status: 200,
|
|
2590
|
-
headers: {
|
|
2591
|
-
"Content-Type": "text/event-stream",
|
|
2592
|
-
"Cache-Control": "no-cache, no-transform",
|
|
2593
|
-
"Connection": "keep-alive",
|
|
2594
|
-
"X-Accel-Buffering": "no"
|
|
2595
|
-
}
|
|
2596
|
-
});
|
|
2597
2569
|
}
|
|
2598
2570
|
async function POST(request) {
|
|
2599
2571
|
const identity = getIdentity(request);
|
|
2600
2572
|
const authToken = getAuthToken(request);
|
|
2573
|
+
const acceptsEventStream = (request.headers.get("accept") || "").toLowerCase().includes("text/event-stream");
|
|
2601
2574
|
if (!identity) {
|
|
2602
2575
|
return Response.json({ error: { code: "MISSING_IDENTITY", message: "Missing identity" } }, { status: 400 });
|
|
2603
2576
|
}
|
|
@@ -2605,28 +2578,103 @@ data: ${JSON.stringify(data)}
|
|
|
2605
2578
|
if (!isAuthorized) {
|
|
2606
2579
|
return Response.json({ error: { code: "UNAUTHORIZED", message: "Unauthorized" } }, { status: 401 });
|
|
2607
2580
|
}
|
|
2581
|
+
let rawBody = "";
|
|
2608
2582
|
try {
|
|
2609
|
-
|
|
2610
|
-
const
|
|
2611
|
-
if (!
|
|
2583
|
+
rawBody = await request.text();
|
|
2584
|
+
const body = rawBody ? JSON.parse(rawBody) : null;
|
|
2585
|
+
if (!body || typeof body !== "object") {
|
|
2612
2586
|
return Response.json(
|
|
2613
2587
|
{
|
|
2614
2588
|
error: {
|
|
2615
|
-
code: "
|
|
2616
|
-
message: "
|
|
2589
|
+
code: "INVALID_REQUEST",
|
|
2590
|
+
message: "Invalid JSON-RPC request body"
|
|
2617
2591
|
}
|
|
2618
2592
|
},
|
|
2619
2593
|
{ status: 400 }
|
|
2620
2594
|
);
|
|
2621
2595
|
}
|
|
2622
|
-
const
|
|
2623
|
-
|
|
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
|
+
});
|
|
2624
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
|
+
});
|
|
2625
2673
|
return Response.json(
|
|
2626
2674
|
{
|
|
2627
2675
|
error: {
|
|
2628
2676
|
code: "EXECUTION_ERROR",
|
|
2629
|
-
message:
|
|
2677
|
+
message: err.message
|
|
2630
2678
|
}
|
|
2631
2679
|
},
|
|
2632
2680
|
{ status: 500 }
|
|
@@ -2635,62 +2683,27 @@ data: ${JSON.stringify(data)}
|
|
|
2635
2683
|
}
|
|
2636
2684
|
return { GET, POST };
|
|
2637
2685
|
}
|
|
2638
|
-
var DEFAULT_REQUEST_TIMEOUT = 6e4;
|
|
2639
|
-
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
2640
|
-
var BASE_RECONNECT_DELAY = 1e3;
|
|
2641
2686
|
var SSEClient = class {
|
|
2642
2687
|
constructor(options) {
|
|
2643
2688
|
this.options = options;
|
|
2644
|
-
__publicField(this, "eventSource", null);
|
|
2645
|
-
__publicField(this, "pendingRequests", /* @__PURE__ */ new Map());
|
|
2646
2689
|
__publicField(this, "resourceCache", /* @__PURE__ */ new Map());
|
|
2647
|
-
__publicField(this, "
|
|
2648
|
-
__publicField(this, "isManuallyDisconnected", false);
|
|
2649
|
-
__publicField(this, "connectionPromise", null);
|
|
2650
|
-
__publicField(this, "connectionResolver", null);
|
|
2690
|
+
__publicField(this, "connected", false);
|
|
2651
2691
|
}
|
|
2652
|
-
// ============================================
|
|
2653
|
-
// Connection Management
|
|
2654
|
-
// ============================================
|
|
2655
|
-
/**
|
|
2656
|
-
* Connect to the SSE endpoint
|
|
2657
|
-
*/
|
|
2658
2692
|
connect() {
|
|
2659
|
-
if (this.
|
|
2693
|
+
if (this.connected) {
|
|
2660
2694
|
return;
|
|
2661
2695
|
}
|
|
2662
|
-
this.
|
|
2663
|
-
this.options.onStatusChange?.("
|
|
2664
|
-
this.
|
|
2665
|
-
this.connectionResolver = resolve;
|
|
2666
|
-
});
|
|
2667
|
-
const url = this.buildUrl();
|
|
2668
|
-
this.eventSource = new EventSource(url);
|
|
2669
|
-
this.setupEventListeners();
|
|
2696
|
+
this.connected = true;
|
|
2697
|
+
this.options.onStatusChange?.("connected");
|
|
2698
|
+
this.log("RPC mode: post_stream");
|
|
2670
2699
|
}
|
|
2671
|
-
/**
|
|
2672
|
-
* Disconnect from the SSE endpoint
|
|
2673
|
-
*/
|
|
2674
2700
|
disconnect() {
|
|
2675
|
-
this.
|
|
2676
|
-
if (this.eventSource) {
|
|
2677
|
-
this.eventSource.close();
|
|
2678
|
-
this.eventSource = null;
|
|
2679
|
-
}
|
|
2680
|
-
this.connectionPromise = null;
|
|
2681
|
-
this.connectionResolver = null;
|
|
2682
|
-
this.rejectAllPendingRequests(new Error("Connection closed"));
|
|
2701
|
+
this.connected = false;
|
|
2683
2702
|
this.options.onStatusChange?.("disconnected");
|
|
2684
2703
|
}
|
|
2685
|
-
/**
|
|
2686
|
-
* Check if connected to the SSE endpoint
|
|
2687
|
-
*/
|
|
2688
2704
|
isConnected() {
|
|
2689
|
-
return this.
|
|
2705
|
+
return this.connected;
|
|
2690
2706
|
}
|
|
2691
|
-
// ============================================
|
|
2692
|
-
// RPC Methods
|
|
2693
|
-
// ============================================
|
|
2694
2707
|
async getSessions() {
|
|
2695
2708
|
return this.sendRequest("getSessions");
|
|
2696
2709
|
}
|
|
@@ -2726,22 +2739,10 @@ var SSEClient = class {
|
|
|
2726
2739
|
async readResource(sessionId, uri) {
|
|
2727
2740
|
return this.sendRequest("readResource", { sessionId, uri });
|
|
2728
2741
|
}
|
|
2729
|
-
// ============================================
|
|
2730
|
-
// Resource Preloading (for instant UI loading)
|
|
2731
|
-
// ============================================
|
|
2732
|
-
/**
|
|
2733
|
-
* Preload UI resources for tools that have UI metadata.
|
|
2734
|
-
* Call this when tools are discovered to enable instant MCP App UI loading.
|
|
2735
|
-
*/
|
|
2736
2742
|
preloadToolUiResources(sessionId, tools) {
|
|
2737
2743
|
for (const tool of tools) {
|
|
2738
2744
|
const uri = this.extractUiResourceUri(tool);
|
|
2739
|
-
if (!uri) continue;
|
|
2740
|
-
if (this.resourceCache.has(uri)) {
|
|
2741
|
-
this.log(`Resource already cached: ${uri}`);
|
|
2742
|
-
continue;
|
|
2743
|
-
}
|
|
2744
|
-
this.log(`Preloading UI resource for tool "${tool.name}": ${uri}`);
|
|
2745
|
+
if (!uri || this.resourceCache.has(uri)) continue;
|
|
2745
2746
|
const promise = this.sendRequest("readResource", { sessionId, uri }).catch((err) => {
|
|
2746
2747
|
this.log(`Failed to preload resource ${uri}: ${err.message}`, "warn");
|
|
2747
2748
|
this.resourceCache.delete(uri);
|
|
@@ -2750,43 +2751,24 @@ var SSEClient = class {
|
|
|
2750
2751
|
this.resourceCache.set(uri, promise);
|
|
2751
2752
|
}
|
|
2752
2753
|
}
|
|
2753
|
-
/**
|
|
2754
|
-
* Get a preloaded resource from cache, or fetch if not cached.
|
|
2755
|
-
*/
|
|
2756
2754
|
getOrFetchResource(sessionId, uri) {
|
|
2757
2755
|
const cached = this.resourceCache.get(uri);
|
|
2758
|
-
if (cached)
|
|
2759
|
-
this.log(`Cache hit for resource: ${uri}`);
|
|
2760
|
-
return cached;
|
|
2761
|
-
}
|
|
2762
|
-
this.log(`Cache miss, fetching resource: ${uri}`);
|
|
2756
|
+
if (cached) return cached;
|
|
2763
2757
|
const promise = this.sendRequest("readResource", { sessionId, uri });
|
|
2764
2758
|
this.resourceCache.set(uri, promise);
|
|
2765
2759
|
return promise;
|
|
2766
2760
|
}
|
|
2767
|
-
/**
|
|
2768
|
-
* Check if a resource is already cached
|
|
2769
|
-
*/
|
|
2770
2761
|
hasPreloadedResource(uri) {
|
|
2771
2762
|
return this.resourceCache.has(uri);
|
|
2772
2763
|
}
|
|
2773
|
-
/**
|
|
2774
|
-
* Clear the resource cache
|
|
2775
|
-
*/
|
|
2776
2764
|
clearResourceCache() {
|
|
2777
2765
|
this.resourceCache.clear();
|
|
2778
2766
|
}
|
|
2779
|
-
// ============================================
|
|
2780
|
-
// Private: Request Handling
|
|
2781
|
-
// ============================================
|
|
2782
|
-
/**
|
|
2783
|
-
* Send an RPC request and return the response directly from HTTP.
|
|
2784
|
-
* This bypasses SSE latency by returning results in the HTTP response body.
|
|
2785
|
-
*/
|
|
2786
2767
|
async sendRequest(method, params) {
|
|
2787
|
-
if (this.
|
|
2788
|
-
|
|
2768
|
+
if (!this.connected) {
|
|
2769
|
+
this.connect();
|
|
2789
2770
|
}
|
|
2771
|
+
this.log(`RPC request via post_stream: ${method}`);
|
|
2790
2772
|
const request = {
|
|
2791
2773
|
id: `rpc_${nanoid.nanoid(10)}`,
|
|
2792
2774
|
method,
|
|
@@ -2800,103 +2782,93 @@ var SSEClient = class {
|
|
|
2800
2782
|
if (!response.ok) {
|
|
2801
2783
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
2802
2784
|
}
|
|
2803
|
-
const
|
|
2804
|
-
|
|
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;
|
|
2805
2859
|
}
|
|
2806
|
-
|
|
2807
|
-
* Parse RPC response and handle different response formats
|
|
2808
|
-
*/
|
|
2809
|
-
parseRpcResponse(data, requestId) {
|
|
2860
|
+
parseRpcResponse(data) {
|
|
2810
2861
|
if ("result" in data) {
|
|
2811
2862
|
return data.result;
|
|
2812
2863
|
}
|
|
2813
2864
|
if ("error" in data && data.error) {
|
|
2814
2865
|
throw new Error(data.error.message || "Unknown RPC error");
|
|
2815
2866
|
}
|
|
2816
|
-
if ("
|
|
2817
|
-
return
|
|
2867
|
+
if (data && typeof data === "object" && "id" in data) {
|
|
2868
|
+
return void 0;
|
|
2818
2869
|
}
|
|
2819
2870
|
throw new Error("Invalid RPC response format");
|
|
2820
2871
|
}
|
|
2821
|
-
/**
|
|
2822
|
-
* Wait for RPC response via SSE (legacy fallback)
|
|
2823
|
-
*/
|
|
2824
|
-
waitForSseResponse(requestId) {
|
|
2825
|
-
const timeoutMs = this.options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
2826
|
-
return new Promise((resolve, reject) => {
|
|
2827
|
-
const timeoutId = setTimeout(() => {
|
|
2828
|
-
this.pendingRequests.delete(requestId);
|
|
2829
|
-
reject(new Error(`Request timeout after ${timeoutMs}ms`));
|
|
2830
|
-
}, timeoutMs);
|
|
2831
|
-
this.pendingRequests.set(requestId, {
|
|
2832
|
-
resolve,
|
|
2833
|
-
reject,
|
|
2834
|
-
timeoutId
|
|
2835
|
-
});
|
|
2836
|
-
});
|
|
2837
|
-
}
|
|
2838
|
-
/**
|
|
2839
|
-
* Handle RPC response received via SSE (legacy)
|
|
2840
|
-
*/
|
|
2841
|
-
handleRpcResponse(response) {
|
|
2842
|
-
const pending = this.pendingRequests.get(response.id);
|
|
2843
|
-
if (!pending) return;
|
|
2844
|
-
clearTimeout(pending.timeoutId);
|
|
2845
|
-
this.pendingRequests.delete(response.id);
|
|
2846
|
-
if (response.error) {
|
|
2847
|
-
pending.reject(new Error(response.error.message));
|
|
2848
|
-
} else {
|
|
2849
|
-
pending.resolve(response.result);
|
|
2850
|
-
}
|
|
2851
|
-
}
|
|
2852
|
-
// ============================================
|
|
2853
|
-
// Private: Event Handling
|
|
2854
|
-
// ============================================
|
|
2855
|
-
setupEventListeners() {
|
|
2856
|
-
if (!this.eventSource) return;
|
|
2857
|
-
this.eventSource.addEventListener("open", () => {
|
|
2858
|
-
this.log("Connected");
|
|
2859
|
-
this.reconnectAttempts = 0;
|
|
2860
|
-
this.options.onStatusChange?.("connected");
|
|
2861
|
-
});
|
|
2862
|
-
this.eventSource.addEventListener("connected", () => {
|
|
2863
|
-
this.log("Server ready");
|
|
2864
|
-
this.connectionResolver?.();
|
|
2865
|
-
this.connectionResolver = null;
|
|
2866
|
-
});
|
|
2867
|
-
this.eventSource.addEventListener("connection", (e) => {
|
|
2868
|
-
const event = JSON.parse(e.data);
|
|
2869
|
-
this.options.onConnectionEvent?.(event);
|
|
2870
|
-
});
|
|
2871
|
-
this.eventSource.addEventListener("observability", (e) => {
|
|
2872
|
-
const event = JSON.parse(e.data);
|
|
2873
|
-
this.options.onObservabilityEvent?.(event);
|
|
2874
|
-
});
|
|
2875
|
-
this.eventSource.addEventListener("rpc-response", (e) => {
|
|
2876
|
-
const response = JSON.parse(e.data);
|
|
2877
|
-
this.handleRpcResponse(response);
|
|
2878
|
-
});
|
|
2879
|
-
this.eventSource.addEventListener("error", () => {
|
|
2880
|
-
this.log("Connection error", "error");
|
|
2881
|
-
this.options.onStatusChange?.("error");
|
|
2882
|
-
this.attemptReconnect();
|
|
2883
|
-
});
|
|
2884
|
-
}
|
|
2885
|
-
attemptReconnect() {
|
|
2886
|
-
if (this.isManuallyDisconnected || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
2887
|
-
return;
|
|
2888
|
-
}
|
|
2889
|
-
this.reconnectAttempts++;
|
|
2890
|
-
const delay = BASE_RECONNECT_DELAY * this.reconnectAttempts;
|
|
2891
|
-
this.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
2892
|
-
setTimeout(() => {
|
|
2893
|
-
this.disconnect();
|
|
2894
|
-
this.connect();
|
|
2895
|
-
}, delay);
|
|
2896
|
-
}
|
|
2897
|
-
// ============================================
|
|
2898
|
-
// Private: Utilities
|
|
2899
|
-
// ============================================
|
|
2900
2872
|
buildUrl() {
|
|
2901
2873
|
const url = new URL(this.options.url, globalThis.location?.origin);
|
|
2902
2874
|
url.searchParams.set("identity", this.options.identity);
|
|
@@ -2907,20 +2879,14 @@ var SSEClient = class {
|
|
|
2907
2879
|
}
|
|
2908
2880
|
buildHeaders() {
|
|
2909
2881
|
const headers = {
|
|
2910
|
-
"Content-Type": "application/json"
|
|
2882
|
+
"Content-Type": "application/json",
|
|
2883
|
+
"Accept": "text/event-stream"
|
|
2911
2884
|
};
|
|
2912
2885
|
if (this.options.authToken) {
|
|
2913
2886
|
headers["Authorization"] = `Bearer ${this.options.authToken}`;
|
|
2914
2887
|
}
|
|
2915
2888
|
return headers;
|
|
2916
2889
|
}
|
|
2917
|
-
rejectAllPendingRequests(error) {
|
|
2918
|
-
for (const [, pending] of this.pendingRequests) {
|
|
2919
|
-
clearTimeout(pending.timeoutId);
|
|
2920
|
-
pending.reject(error);
|
|
2921
|
-
}
|
|
2922
|
-
this.pendingRequests.clear();
|
|
2923
|
-
}
|
|
2924
2890
|
extractUiResourceUri(tool) {
|
|
2925
2891
|
const meta = tool._meta?.ui;
|
|
2926
2892
|
if (!meta || typeof meta !== "object") return void 0;
|