@mcp-ts/sdk 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +400 -406
- package/dist/adapters/agui-adapter.d.mts +1 -1
- package/dist/adapters/agui-adapter.d.ts +1 -1
- package/dist/adapters/agui-middleware.d.mts +1 -1
- package/dist/adapters/agui-middleware.d.ts +1 -1
- package/dist/adapters/ai-adapter.d.mts +1 -1
- package/dist/adapters/ai-adapter.d.ts +1 -1
- package/dist/adapters/langchain-adapter.d.mts +1 -1
- package/dist/adapters/langchain-adapter.d.ts +1 -1
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/client/index.d.mts +8 -64
- package/dist/client/index.d.ts +8 -64
- package/dist/client/index.js +91 -173
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +91 -173
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +12 -2
- package/dist/client/react.d.ts +12 -2
- package/dist/client/react.js +119 -182
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +119 -182
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +24 -4
- package/dist/client/vue.d.ts +24 -4
- package/dist/client/vue.js +121 -182
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +121 -182
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +215 -250
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +215 -250
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-B1DBx5yR.d.mts → multi-session-client-DzjmT7FX.d.mts} +1 -0
- package/dist/{multi-session-client-DyFzyJUx.d.ts → multi-session-client-FAFpUzZ4.d.ts} +1 -0
- package/dist/server/index.d.mts +16 -21
- package/dist/server/index.d.ts +16 -21
- package/dist/server/index.js +124 -77
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +124 -77
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +2 -2
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{types-PjM1W07s.d.mts → types-CW6lghof.d.mts} +5 -0
- package/dist/{types-PjM1W07s.d.ts → types-CW6lghof.d.ts} +5 -0
- package/package.json +1 -1
- package/src/client/core/sse-client.ts +354 -493
- package/src/client/react/use-mcp.ts +75 -23
- package/src/client/vue/use-mcp.ts +111 -48
- package/src/server/handlers/nextjs-handler.ts +207 -217
- package/src/server/handlers/sse-handler.ts +10 -0
- package/src/server/mcp/oauth-client.ts +41 -32
- package/src/server/storage/types.ts +12 -5
- package/src/shared/types.ts +5 -0
|
@@ -189,6 +189,7 @@ declare class MCPClient {
|
|
|
189
189
|
* Saves current session state to storage
|
|
190
190
|
* Creates new session if it doesn't exist, updates if it does
|
|
191
191
|
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
192
|
+
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
192
193
|
* @private
|
|
193
194
|
*/
|
|
194
195
|
private saveSession;
|
|
@@ -189,6 +189,7 @@ declare class MCPClient {
|
|
|
189
189
|
* Saves current session state to storage
|
|
190
190
|
* Creates new session if it doesn't exist, updates if it does
|
|
191
191
|
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
192
|
+
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
192
193
|
* @private
|
|
193
194
|
*/
|
|
194
195
|
private saveSession;
|
package/dist/server/index.d.mts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-
|
|
1
|
+
export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-DzjmT7FX.mjs';
|
|
2
2
|
export { U as UnauthorizedError, s as sanitizeServerLabel } from '../utils-0qmYrqoa.mjs';
|
|
3
3
|
import { OAuthClientInformationMixed, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
4
4
|
export { OAuthClientInformation, OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
5
5
|
import { M as McpConnectionEvent, d as McpObservabilityEvent } from '../events-CK3N--3g.mjs';
|
|
6
6
|
export { D as Disposable, E as Emitter, b as Event, c as McpConnectionState } from '../events-CK3N--3g.mjs';
|
|
7
|
-
import { q as McpRpcResponse, p as McpRpcRequest } from '../types-
|
|
8
|
-
export { a as CallToolRequest, b as CallToolResponse, f as ConnectRequest, g as ConnectResponse, m as ListToolsResponse, T as ToolInfo } from '../types-
|
|
7
|
+
import { q as McpRpcResponse, p as McpRpcRequest } from '../types-CW6lghof.mjs';
|
|
8
|
+
export { a as CallToolRequest, b as CallToolResponse, f as ConnectRequest, g as ConnectResponse, m as ListToolsResponse, T as ToolInfo } from '../types-CW6lghof.mjs';
|
|
9
9
|
export { CallToolResult, ListToolsResult, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
10
10
|
import '@modelcontextprotocol/sdk/client/auth.js';
|
|
11
11
|
|
|
@@ -19,6 +19,13 @@ interface SessionData {
|
|
|
19
19
|
createdAt: number;
|
|
20
20
|
identity: string;
|
|
21
21
|
headers?: Record<string, string>;
|
|
22
|
+
/**
|
|
23
|
+
* Session status marker used for TTL transitions:
|
|
24
|
+
* - false: short-lived intermediate/error/auth-pending session state
|
|
25
|
+
* (keep this value when connection/auth is incomplete or failed)
|
|
26
|
+
* - true: active long-lived session state after successful connection/auth completion
|
|
27
|
+
*/
|
|
28
|
+
active?: boolean;
|
|
22
29
|
clientInformation?: OAuthClientInformationMixed;
|
|
23
30
|
tokens?: OAuthTokens;
|
|
24
31
|
codeVerifier?: string;
|
|
@@ -220,8 +227,10 @@ declare function createSSEHandler(options: SSEHandlerOptions): (req: {
|
|
|
220
227
|
}) => Promise<void>;
|
|
221
228
|
|
|
222
229
|
/**
|
|
223
|
-
* Next.js App Router Handler for MCP
|
|
224
|
-
*
|
|
230
|
+
* Next.js App Router Handler for MCP
|
|
231
|
+
* Stateless transport for serverless environments:
|
|
232
|
+
* - POST + `Accept: text/event-stream` streams progress + rpc-response
|
|
233
|
+
* - POST + JSON accepts direct RPC result response
|
|
225
234
|
*/
|
|
226
235
|
|
|
227
236
|
interface NextMcpHandlerOptions {
|
|
@@ -244,29 +253,15 @@ interface NextMcpHandlerOptions {
|
|
|
244
253
|
heartbeatInterval?: number;
|
|
245
254
|
/**
|
|
246
255
|
* Static OAuth client metadata defaults (for all connections)
|
|
247
|
-
* Use this for single-tenant applications with fixed branding
|
|
248
256
|
*/
|
|
249
257
|
clientDefaults?: ClientMetadata;
|
|
250
258
|
/**
|
|
251
|
-
* Dynamic OAuth client metadata getter (per-request
|
|
252
|
-
* Use this when you need different branding based on request (tenant, domain, etc.)
|
|
253
|
-
* Takes precedence over clientDefaults
|
|
259
|
+
* Dynamic OAuth client metadata getter (per-request)
|
|
254
260
|
*/
|
|
255
261
|
getClientMetadata?: (request: Request) => ClientMetadata | Promise<ClientMetadata>;
|
|
256
262
|
}
|
|
257
|
-
/**
|
|
258
|
-
* Creates Next.js App Router handlers (GET and POST) for MCP SSE endpoint
|
|
259
|
-
*
|
|
260
|
-
* @example
|
|
261
|
-
* ```typescript
|
|
262
|
-
* // app/api/mcp/route.ts
|
|
263
|
-
* import { createNextMcpHandler } from '@mcp-ts/core/server';
|
|
264
|
-
*
|
|
265
|
-
* export const { GET, POST } = createNextMcpHandler();
|
|
266
|
-
* ```
|
|
267
|
-
*/
|
|
268
263
|
declare function createNextMcpHandler(options?: NextMcpHandlerOptions): {
|
|
269
|
-
GET: (
|
|
264
|
+
GET: () => Promise<Response>;
|
|
270
265
|
POST: (request: Request) => Promise<Response>;
|
|
271
266
|
};
|
|
272
267
|
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-
|
|
1
|
+
export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-FAFpUzZ4.js';
|
|
2
2
|
export { U as UnauthorizedError, s as sanitizeServerLabel } from '../utils-0qmYrqoa.js';
|
|
3
3
|
import { OAuthClientInformationMixed, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
4
4
|
export { OAuthClientInformation, OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
5
5
|
import { M as McpConnectionEvent, d as McpObservabilityEvent } from '../events-CK3N--3g.js';
|
|
6
6
|
export { D as Disposable, E as Emitter, b as Event, c as McpConnectionState } from '../events-CK3N--3g.js';
|
|
7
|
-
import { q as McpRpcResponse, p as McpRpcRequest } from '../types-
|
|
8
|
-
export { a as CallToolRequest, b as CallToolResponse, f as ConnectRequest, g as ConnectResponse, m as ListToolsResponse, T as ToolInfo } from '../types-
|
|
7
|
+
import { q as McpRpcResponse, p as McpRpcRequest } from '../types-CW6lghof.js';
|
|
8
|
+
export { a as CallToolRequest, b as CallToolResponse, f as ConnectRequest, g as ConnectResponse, m as ListToolsResponse, T as ToolInfo } from '../types-CW6lghof.js';
|
|
9
9
|
export { CallToolResult, ListToolsResult, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
10
10
|
import '@modelcontextprotocol/sdk/client/auth.js';
|
|
11
11
|
|
|
@@ -19,6 +19,13 @@ interface SessionData {
|
|
|
19
19
|
createdAt: number;
|
|
20
20
|
identity: string;
|
|
21
21
|
headers?: Record<string, string>;
|
|
22
|
+
/**
|
|
23
|
+
* Session status marker used for TTL transitions:
|
|
24
|
+
* - false: short-lived intermediate/error/auth-pending session state
|
|
25
|
+
* (keep this value when connection/auth is incomplete or failed)
|
|
26
|
+
* - true: active long-lived session state after successful connection/auth completion
|
|
27
|
+
*/
|
|
28
|
+
active?: boolean;
|
|
22
29
|
clientInformation?: OAuthClientInformationMixed;
|
|
23
30
|
tokens?: OAuthTokens;
|
|
24
31
|
codeVerifier?: string;
|
|
@@ -220,8 +227,10 @@ declare function createSSEHandler(options: SSEHandlerOptions): (req: {
|
|
|
220
227
|
}) => Promise<void>;
|
|
221
228
|
|
|
222
229
|
/**
|
|
223
|
-
* Next.js App Router Handler for MCP
|
|
224
|
-
*
|
|
230
|
+
* Next.js App Router Handler for MCP
|
|
231
|
+
* Stateless transport for serverless environments:
|
|
232
|
+
* - POST + `Accept: text/event-stream` streams progress + rpc-response
|
|
233
|
+
* - POST + JSON accepts direct RPC result response
|
|
225
234
|
*/
|
|
226
235
|
|
|
227
236
|
interface NextMcpHandlerOptions {
|
|
@@ -244,29 +253,15 @@ interface NextMcpHandlerOptions {
|
|
|
244
253
|
heartbeatInterval?: number;
|
|
245
254
|
/**
|
|
246
255
|
* Static OAuth client metadata defaults (for all connections)
|
|
247
|
-
* Use this for single-tenant applications with fixed branding
|
|
248
256
|
*/
|
|
249
257
|
clientDefaults?: ClientMetadata;
|
|
250
258
|
/**
|
|
251
|
-
* Dynamic OAuth client metadata getter (per-request
|
|
252
|
-
* Use this when you need different branding based on request (tenant, domain, etc.)
|
|
253
|
-
* Takes precedence over clientDefaults
|
|
259
|
+
* Dynamic OAuth client metadata getter (per-request)
|
|
254
260
|
*/
|
|
255
261
|
getClientMetadata?: (request: Request) => ClientMetadata | Promise<ClientMetadata>;
|
|
256
262
|
}
|
|
257
|
-
/**
|
|
258
|
-
* Creates Next.js App Router handlers (GET and POST) for MCP SSE endpoint
|
|
259
|
-
*
|
|
260
|
-
* @example
|
|
261
|
-
* ```typescript
|
|
262
|
-
* // app/api/mcp/route.ts
|
|
263
|
-
* import { createNextMcpHandler } from '@mcp-ts/core/server';
|
|
264
|
-
*
|
|
265
|
-
* export const { GET, POST } = createNextMcpHandler();
|
|
266
|
-
* ```
|
|
267
|
-
*/
|
|
268
263
|
declare function createNextMcpHandler(options?: NextMcpHandlerOptions): {
|
|
269
|
-
GET: (
|
|
264
|
+
GET: () => Promise<Response>;
|
|
270
265
|
POST: (request: Request) => Promise<Response>;
|
|
271
266
|
};
|
|
272
267
|
|
package/dist/server/index.js
CHANGED
|
@@ -1254,7 +1254,8 @@ var MCPClient = class _MCPClient {
|
|
|
1254
1254
|
serverUrl: this.serverUrl,
|
|
1255
1255
|
callbackUrl: this.callbackUrl,
|
|
1256
1256
|
transportType: this.transportType || "streamable_http",
|
|
1257
|
-
createdAt: this.createdAt
|
|
1257
|
+
createdAt: this.createdAt,
|
|
1258
|
+
active: false
|
|
1258
1259
|
}, Math.floor(STATE_EXPIRATION_MS / 1e3));
|
|
1259
1260
|
}
|
|
1260
1261
|
}
|
|
@@ -1262,9 +1263,10 @@ var MCPClient = class _MCPClient {
|
|
|
1262
1263
|
* Saves current session state to storage
|
|
1263
1264
|
* Creates new session if it doesn't exist, updates if it does
|
|
1264
1265
|
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
1266
|
+
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
1265
1267
|
* @private
|
|
1266
1268
|
*/
|
|
1267
|
-
async saveSession(ttl = SESSION_TTL_SECONDS) {
|
|
1269
|
+
async saveSession(ttl = SESSION_TTL_SECONDS, active = true) {
|
|
1268
1270
|
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
1269
1271
|
return;
|
|
1270
1272
|
}
|
|
@@ -1276,7 +1278,8 @@ var MCPClient = class _MCPClient {
|
|
|
1276
1278
|
serverUrl: this.serverUrl,
|
|
1277
1279
|
callbackUrl: this.callbackUrl,
|
|
1278
1280
|
transportType: this.transportType || "streamable_http",
|
|
1279
|
-
createdAt: this.createdAt || Date.now()
|
|
1281
|
+
createdAt: this.createdAt || Date.now(),
|
|
1282
|
+
active
|
|
1280
1283
|
};
|
|
1281
1284
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1282
1285
|
if (existingSession) {
|
|
@@ -1350,15 +1353,17 @@ var MCPClient = class _MCPClient {
|
|
|
1350
1353
|
this.emitStateChange("CONNECTED");
|
|
1351
1354
|
this.emitProgress("Connected successfully");
|
|
1352
1355
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
+
const needsTransportUpdate = !existingSession || existingSession.transportType !== this.transportType;
|
|
1357
|
+
const needsTtlPromotion = !existingSession || existingSession.active !== true;
|
|
1358
|
+
if (needsTransportUpdate || needsTtlPromotion) {
|
|
1359
|
+
console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
|
|
1360
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
1356
1361
|
}
|
|
1357
1362
|
} catch (error) {
|
|
1358
1363
|
if (error instanceof auth_js.UnauthorizedError || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
|
|
1359
1364
|
this.emitStateChange("AUTHENTICATING");
|
|
1360
1365
|
console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
|
|
1361
|
-
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3));
|
|
1366
|
+
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
|
|
1362
1367
|
let authUrl = "";
|
|
1363
1368
|
if (this.oauthProvider) {
|
|
1364
1369
|
authUrl = this.oauthProvider.authUrl || "";
|
|
@@ -1436,7 +1441,7 @@ var MCPClient = class _MCPClient {
|
|
|
1436
1441
|
await this.client.connect(this.transport);
|
|
1437
1442
|
this.emitStateChange("CONNECTED");
|
|
1438
1443
|
console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
|
|
1439
|
-
await this.saveSession(SESSION_TTL_SECONDS);
|
|
1444
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
1440
1445
|
return;
|
|
1441
1446
|
} catch (error) {
|
|
1442
1447
|
lastError = error;
|
|
@@ -2101,7 +2106,8 @@ var SSEConnectionManager = class {
|
|
|
2101
2106
|
serverName: s.serverName,
|
|
2102
2107
|
serverUrl: s.serverUrl,
|
|
2103
2108
|
transport: s.transportType,
|
|
2104
|
-
createdAt: s.createdAt
|
|
2109
|
+
createdAt: s.createdAt,
|
|
2110
|
+
active: s.active !== false
|
|
2105
2111
|
}))
|
|
2106
2112
|
};
|
|
2107
2113
|
}
|
|
@@ -2116,6 +2122,13 @@ var SSEConnectionManager = class {
|
|
|
2116
2122
|
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
2117
2123
|
);
|
|
2118
2124
|
if (duplicate) {
|
|
2125
|
+
if (duplicate.active === false) {
|
|
2126
|
+
await this.restoreSession({ sessionId: duplicate.sessionId });
|
|
2127
|
+
return {
|
|
2128
|
+
sessionId: duplicate.sessionId,
|
|
2129
|
+
success: true
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2119
2132
|
throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
|
|
2120
2133
|
}
|
|
2121
2134
|
const sessionId = await storage.generateSessionId();
|
|
@@ -2441,7 +2454,9 @@ function writeSSEEvent(res, event, data) {
|
|
|
2441
2454
|
}
|
|
2442
2455
|
|
|
2443
2456
|
// src/server/handlers/nextjs-handler.ts
|
|
2444
|
-
|
|
2457
|
+
function isRpcResponseEvent(event) {
|
|
2458
|
+
return "id" in event && ("result" in event || "error" in event);
|
|
2459
|
+
}
|
|
2445
2460
|
function createNextMcpHandler(options = {}) {
|
|
2446
2461
|
const {
|
|
2447
2462
|
getIdentity = (request) => new URL(request.url).searchParams.get("identity"),
|
|
@@ -2454,72 +2469,29 @@ function createNextMcpHandler(options = {}) {
|
|
|
2454
2469
|
clientDefaults,
|
|
2455
2470
|
getClientMetadata
|
|
2456
2471
|
} = options;
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
const stream = new TransformStream();
|
|
2468
|
-
const writer = stream.writable.getWriter();
|
|
2469
|
-
const encoder = new TextEncoder();
|
|
2470
|
-
const sendSSE = (event, data) => {
|
|
2471
|
-
const message = `event: ${event}
|
|
2472
|
-
data: ${JSON.stringify(data)}
|
|
2473
|
-
|
|
2474
|
-
`;
|
|
2475
|
-
writer.write(encoder.encode(message)).catch(() => {
|
|
2476
|
-
});
|
|
2477
|
-
};
|
|
2478
|
-
const previousManager = managers.get(identity);
|
|
2479
|
-
if (previousManager) {
|
|
2480
|
-
previousManager.dispose();
|
|
2481
|
-
}
|
|
2482
|
-
const resolvedClientMetadata = getClientMetadata ? await getClientMetadata(request) : clientDefaults;
|
|
2483
|
-
const manager = new SSEConnectionManager(
|
|
2472
|
+
const toManagerOptions = (identity, resolvedClientMetadata) => ({
|
|
2473
|
+
identity,
|
|
2474
|
+
heartbeatInterval,
|
|
2475
|
+
clientDefaults: resolvedClientMetadata
|
|
2476
|
+
});
|
|
2477
|
+
async function resolveClientMetadata(request) {
|
|
2478
|
+
return getClientMetadata ? await getClientMetadata(request) : clientDefaults;
|
|
2479
|
+
}
|
|
2480
|
+
async function GET() {
|
|
2481
|
+
return Response.json(
|
|
2484
2482
|
{
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
// Pass resolved metadata
|
|
2489
|
-
},
|
|
2490
|
-
(event) => {
|
|
2491
|
-
if ("id" in event) {
|
|
2492
|
-
sendSSE("rpc-response", event);
|
|
2493
|
-
} else if ("type" in event && "sessionId" in event) {
|
|
2494
|
-
sendSSE("connection", event);
|
|
2495
|
-
} else {
|
|
2496
|
-
sendSSE("observability", event);
|
|
2483
|
+
error: {
|
|
2484
|
+
code: "METHOD_NOT_ALLOWED",
|
|
2485
|
+
message: "Use POST /api/mcp. For streaming use Accept: text/event-stream."
|
|
2497
2486
|
}
|
|
2498
|
-
}
|
|
2487
|
+
},
|
|
2488
|
+
{ status: 405 }
|
|
2499
2489
|
);
|
|
2500
|
-
managers.set(identity, manager);
|
|
2501
|
-
sendSSE("connected", { timestamp: Date.now() });
|
|
2502
|
-
const abortController = new AbortController();
|
|
2503
|
-
request.signal?.addEventListener("abort", () => {
|
|
2504
|
-
manager.dispose();
|
|
2505
|
-
managers.delete(identity);
|
|
2506
|
-
writer.close().catch(() => {
|
|
2507
|
-
});
|
|
2508
|
-
abortController.abort();
|
|
2509
|
-
});
|
|
2510
|
-
return new Response(stream.readable, {
|
|
2511
|
-
status: 200,
|
|
2512
|
-
headers: {
|
|
2513
|
-
"Content-Type": "text/event-stream",
|
|
2514
|
-
"Cache-Control": "no-cache, no-transform",
|
|
2515
|
-
"Connection": "keep-alive",
|
|
2516
|
-
"X-Accel-Buffering": "no"
|
|
2517
|
-
}
|
|
2518
|
-
});
|
|
2519
2490
|
}
|
|
2520
2491
|
async function POST(request) {
|
|
2521
2492
|
const identity = getIdentity(request);
|
|
2522
2493
|
const authToken = getAuthToken(request);
|
|
2494
|
+
const acceptsEventStream = (request.headers.get("accept") || "").toLowerCase().includes("text/event-stream");
|
|
2523
2495
|
if (!identity) {
|
|
2524
2496
|
return Response.json({ error: { code: "MISSING_IDENTITY", message: "Missing identity" } }, { status: 400 });
|
|
2525
2497
|
}
|
|
@@ -2527,28 +2499,103 @@ data: ${JSON.stringify(data)}
|
|
|
2527
2499
|
if (!isAuthorized) {
|
|
2528
2500
|
return Response.json({ error: { code: "UNAUTHORIZED", message: "Unauthorized" } }, { status: 401 });
|
|
2529
2501
|
}
|
|
2502
|
+
let rawBody = "";
|
|
2530
2503
|
try {
|
|
2531
|
-
|
|
2532
|
-
const
|
|
2533
|
-
if (!
|
|
2504
|
+
rawBody = await request.text();
|
|
2505
|
+
const body = rawBody ? JSON.parse(rawBody) : null;
|
|
2506
|
+
if (!body || typeof body !== "object") {
|
|
2534
2507
|
return Response.json(
|
|
2535
2508
|
{
|
|
2536
2509
|
error: {
|
|
2537
|
-
code: "
|
|
2538
|
-
message: "
|
|
2510
|
+
code: "INVALID_REQUEST",
|
|
2511
|
+
message: "Invalid JSON-RPC request body"
|
|
2539
2512
|
}
|
|
2540
2513
|
},
|
|
2541
2514
|
{ status: 400 }
|
|
2542
2515
|
);
|
|
2543
2516
|
}
|
|
2544
|
-
const
|
|
2545
|
-
|
|
2517
|
+
const resolvedClientMetadata = await resolveClientMetadata(request);
|
|
2518
|
+
if (!acceptsEventStream) {
|
|
2519
|
+
const manager2 = new SSEConnectionManager(
|
|
2520
|
+
toManagerOptions(identity, resolvedClientMetadata),
|
|
2521
|
+
() => {
|
|
2522
|
+
}
|
|
2523
|
+
);
|
|
2524
|
+
try {
|
|
2525
|
+
const response = await manager2.handleRequest(body);
|
|
2526
|
+
return Response.json(response);
|
|
2527
|
+
} finally {
|
|
2528
|
+
manager2.dispose();
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
const stream = new TransformStream();
|
|
2532
|
+
const writer = stream.writable.getWriter();
|
|
2533
|
+
const encoder = new TextEncoder();
|
|
2534
|
+
let streamWritable = true;
|
|
2535
|
+
const sendSSE = (event, data) => {
|
|
2536
|
+
if (!streamWritable) return;
|
|
2537
|
+
const message = `event: ${event}
|
|
2538
|
+
data: ${JSON.stringify(data)}
|
|
2539
|
+
|
|
2540
|
+
`;
|
|
2541
|
+
writer.write(encoder.encode(message)).catch(() => {
|
|
2542
|
+
streamWritable = false;
|
|
2543
|
+
});
|
|
2544
|
+
};
|
|
2545
|
+
const manager = new SSEConnectionManager(
|
|
2546
|
+
toManagerOptions(identity, resolvedClientMetadata),
|
|
2547
|
+
(event) => {
|
|
2548
|
+
if (isRpcResponseEvent(event)) {
|
|
2549
|
+
sendSSE("rpc-response", event);
|
|
2550
|
+
} else if ("type" in event && "sessionId" in event) {
|
|
2551
|
+
sendSSE("connection", event);
|
|
2552
|
+
} else {
|
|
2553
|
+
sendSSE("observability", event);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
);
|
|
2557
|
+
sendSSE("connected", { timestamp: Date.now() });
|
|
2558
|
+
void (async () => {
|
|
2559
|
+
try {
|
|
2560
|
+
await manager.handleRequest(body);
|
|
2561
|
+
} catch (error) {
|
|
2562
|
+
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
2563
|
+
sendSSE("rpc-response", {
|
|
2564
|
+
id: body.id || "unknown",
|
|
2565
|
+
error: {
|
|
2566
|
+
code: "EXECUTION_ERROR",
|
|
2567
|
+
message: err.message
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
} finally {
|
|
2571
|
+
streamWritable = false;
|
|
2572
|
+
manager.dispose();
|
|
2573
|
+
writer.close().catch(() => {
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
})();
|
|
2577
|
+
return new Response(stream.readable, {
|
|
2578
|
+
status: 200,
|
|
2579
|
+
headers: {
|
|
2580
|
+
"Content-Type": "text/event-stream",
|
|
2581
|
+
"Cache-Control": "no-cache, no-transform",
|
|
2582
|
+
"Connection": "keep-alive",
|
|
2583
|
+
"X-Accel-Buffering": "no"
|
|
2584
|
+
}
|
|
2585
|
+
});
|
|
2546
2586
|
} catch (error) {
|
|
2587
|
+
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
2588
|
+
console.error("[MCP Next Handler] Failed to handle RPC", {
|
|
2589
|
+
identity,
|
|
2590
|
+
message: err.message,
|
|
2591
|
+
stack: err.stack,
|
|
2592
|
+
rawBody: rawBody.slice(0, 500)
|
|
2593
|
+
});
|
|
2547
2594
|
return Response.json(
|
|
2548
2595
|
{
|
|
2549
2596
|
error: {
|
|
2550
2597
|
code: "EXECUTION_ERROR",
|
|
2551
|
-
message:
|
|
2598
|
+
message: err.message
|
|
2552
2599
|
}
|
|
2553
2600
|
},
|
|
2554
2601
|
{ status: 500 }
|