open-mcp-app 0.0.14 → 0.0.15
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/dist/server/index.d.ts +40 -2
- package/dist/server/index.js +210 -37
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.d.ts
CHANGED
|
@@ -321,6 +321,35 @@ interface TransportSessionInfo {
|
|
|
321
321
|
/** The transport type for this session */
|
|
322
322
|
transport: TransportType;
|
|
323
323
|
}
|
|
324
|
+
type ServerlessTransportMode = "stateful" | "stateless";
|
|
325
|
+
interface StateAdapter {
|
|
326
|
+
get: (instanceId: string) => Promise<unknown | undefined>;
|
|
327
|
+
set: (instanceId: string, state: unknown) => Promise<void>;
|
|
328
|
+
delete: (instanceId: string) => Promise<void>;
|
|
329
|
+
}
|
|
330
|
+
interface ServerlessAdapterOptions {
|
|
331
|
+
/**
|
|
332
|
+
* Controls whether MCP transport sessions are persisted between requests.
|
|
333
|
+
*
|
|
334
|
+
* - `"stateful"` keeps normal MCP session semantics and expects sticky routing.
|
|
335
|
+
* - `"stateless"` disables MCP transport session IDs so each request stands alone.
|
|
336
|
+
*
|
|
337
|
+
* For AWS Lambda and other serverless deployments, `"stateless"` is usually the
|
|
338
|
+
* safest default because requests may land on different warm instances.
|
|
339
|
+
*/
|
|
340
|
+
transportMode?: ServerlessTransportMode;
|
|
341
|
+
/**
|
|
342
|
+
* Enables JSON POST responses for stateless streamable-http mode.
|
|
343
|
+
* This avoids relying on SSE session continuity in serverless environments.
|
|
344
|
+
*/
|
|
345
|
+
enableJsonResponse?: boolean;
|
|
346
|
+
/**
|
|
347
|
+
* Optional backing store for `context.getState()` / `context.setState()`.
|
|
348
|
+
* This is only needed for tools that rely on server-side state across requests.
|
|
349
|
+
*/
|
|
350
|
+
stateAdapter?: StateAdapter;
|
|
351
|
+
}
|
|
352
|
+
type AwsLambdaHandler = (...args: unknown[]) => Promise<unknown>;
|
|
324
353
|
/**
|
|
325
354
|
* App configuration.
|
|
326
355
|
*/
|
|
@@ -619,13 +648,22 @@ declare class App {
|
|
|
619
648
|
* Get the Express application for serverless wrapping (e.g. Lambda).
|
|
620
649
|
* Does NOT start listening on a port.
|
|
621
650
|
*/
|
|
622
|
-
toExpressApp(): express__default.Express;
|
|
651
|
+
toExpressApp(options?: ServerlessAdapterOptions): express__default.Express;
|
|
652
|
+
toAwsLambda(options?: ServerlessAdapterOptions): AwsLambdaHandler;
|
|
623
653
|
/**
|
|
624
654
|
* Close a specific transport session.
|
|
625
655
|
*/
|
|
626
656
|
closeTransportSession(sessionId: string): boolean;
|
|
627
657
|
private getPort;
|
|
628
658
|
private getCallerDir;
|
|
659
|
+
private lambdaEventToRequest;
|
|
660
|
+
private handleLambdaHttpRequest;
|
|
661
|
+
private writeLambdaResponse;
|
|
662
|
+
private isStatelessTransport;
|
|
663
|
+
private getStateAdapter;
|
|
664
|
+
private loadInstanceState;
|
|
665
|
+
private persistInstanceState;
|
|
666
|
+
private deleteInstanceState;
|
|
629
667
|
/**
|
|
630
668
|
* Handle an MCP JSON-RPC request directly.
|
|
631
669
|
* Used for testing without starting a server.
|
|
@@ -1478,4 +1516,4 @@ declare const exp: {
|
|
|
1478
1516
|
sampleMessage: typeof experimental_sampleMessage;
|
|
1479
1517
|
};
|
|
1480
1518
|
|
|
1481
|
-
export { App, type AppConfig, type DisplayMode, type IconConfig, type InstanceDestroyContext, type KvSearchResult, MIME_TYPES, type ResourceConfig, type ServerLogLevel, type ServerLogger, type ToolAnnotations, type ToolCallInfo, type ToolCallResultInfo, type ToolConfig, type ToolContext, type ToolHandler, type ToolResult, type ToolVisibility, type TransportSessionInfo, type TransportType, type VectorSearchResult, type WebSocketConnection, createApp, exp, experimental_blobDelete, experimental_blobDeleteSync, experimental_blobGet, experimental_blobGetSync, experimental_blobIsAvailable, experimental_blobList, experimental_blobListSync, experimental_blobPut, experimental_blobPutSync, experimental_deleteFile, experimental_deleteFileSync, experimental_exists, experimental_existsSync, experimental_getProjectId, experimental_getServerName, experimental_getWritableDirectory, experimental_kvDelete, experimental_kvDeleteSync, experimental_kvGet, experimental_kvGetSync, experimental_kvIsAvailable, experimental_kvList, experimental_kvListSync, experimental_kvSearch, experimental_kvSet, experimental_kvSetSync, experimental_mkdir, experimental_mkdirSync, experimental_readFile, experimental_readFileSync, experimental_readdir, experimental_readdirSync, experimental_rmdir, experimental_rmdirSync, experimental_sampleMessage, experimental_vectorDelete, experimental_vectorIsAvailable, experimental_vectorSearch, experimental_vectorUpsert, experimental_writeFile, experimental_writeFileSync, htmlLoader, isHtmlContent, loadHtml, svgToDataUri, wrapServer };
|
|
1519
|
+
export { App, type AppConfig, type AwsLambdaHandler, type DisplayMode, type IconConfig, type InstanceDestroyContext, type KvSearchResult, MIME_TYPES, type ResourceConfig, type ServerLogLevel, type ServerLogger, type ServerlessAdapterOptions, type ServerlessTransportMode, type StateAdapter, type ToolAnnotations, type ToolCallInfo, type ToolCallResultInfo, type ToolConfig, type ToolContext, type ToolHandler, type ToolResult, type ToolVisibility, type TransportSessionInfo, type TransportType, type VectorSearchResult, type WebSocketConnection, createApp, exp, experimental_blobDelete, experimental_blobDeleteSync, experimental_blobGet, experimental_blobGetSync, experimental_blobIsAvailable, experimental_blobList, experimental_blobListSync, experimental_blobPut, experimental_blobPutSync, experimental_deleteFile, experimental_deleteFileSync, experimental_exists, experimental_existsSync, experimental_getProjectId, experimental_getServerName, experimental_getWritableDirectory, experimental_kvDelete, experimental_kvDeleteSync, experimental_kvGet, experimental_kvGetSync, experimental_kvIsAvailable, experimental_kvList, experimental_kvListSync, experimental_kvSearch, experimental_kvSet, experimental_kvSetSync, experimental_mkdir, experimental_mkdirSync, experimental_readFile, experimental_readFileSync, experimental_readdir, experimental_readdirSync, experimental_rmdir, experimental_rmdirSync, experimental_sampleMessage, experimental_vectorDelete, experimental_vectorIsAvailable, experimental_vectorSearch, experimental_vectorUpsert, experimental_writeFile, experimental_writeFileSync, htmlLoader, isHtmlContent, loadHtml, svgToDataUri, wrapServer };
|
package/dist/server/index.js
CHANGED
|
@@ -6796,6 +6796,8 @@ var require_dist = __commonJS({
|
|
|
6796
6796
|
// src/server/app.ts
|
|
6797
6797
|
import { randomUUID } from "crypto";
|
|
6798
6798
|
import path2 from "path";
|
|
6799
|
+
import { Readable as Readable2 } from "stream";
|
|
6800
|
+
import { pipeline } from "stream/promises";
|
|
6799
6801
|
import { fileURLToPath } from "url";
|
|
6800
6802
|
|
|
6801
6803
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
|
|
@@ -12342,7 +12344,7 @@ var toRequestError = (e) => {
|
|
|
12342
12344
|
return new RequestError(e.message, { cause: e });
|
|
12343
12345
|
};
|
|
12344
12346
|
var GlobalRequest = global.Request;
|
|
12345
|
-
var
|
|
12347
|
+
var Request2 = class extends GlobalRequest {
|
|
12346
12348
|
constructor(input, options) {
|
|
12347
12349
|
if (typeof input === "object" && getRequestCache in input) {
|
|
12348
12350
|
input = input[getRequestCache]();
|
|
@@ -12375,7 +12377,7 @@ var newRequestFromIncoming = (method, url, headers, incoming, abortController) =
|
|
|
12375
12377
|
};
|
|
12376
12378
|
if (method === "TRACE") {
|
|
12377
12379
|
init.method = "GET";
|
|
12378
|
-
const req = new
|
|
12380
|
+
const req = new Request2(url, init);
|
|
12379
12381
|
Object.defineProperty(req, "method", {
|
|
12380
12382
|
get() {
|
|
12381
12383
|
return "TRACE";
|
|
@@ -12412,7 +12414,7 @@ var newRequestFromIncoming = (method, url, headers, incoming, abortController) =
|
|
|
12412
12414
|
init.body = Readable.toWeb(incoming);
|
|
12413
12415
|
}
|
|
12414
12416
|
}
|
|
12415
|
-
return new
|
|
12417
|
+
return new Request2(url, init);
|
|
12416
12418
|
};
|
|
12417
12419
|
var getRequestCache = /* @__PURE__ */ Symbol("getRequestCache");
|
|
12418
12420
|
var requestCache = /* @__PURE__ */ Symbol("requestCache");
|
|
@@ -12473,7 +12475,7 @@ var requestPrototype = {
|
|
|
12473
12475
|
}
|
|
12474
12476
|
});
|
|
12475
12477
|
});
|
|
12476
|
-
Object.setPrototypeOf(requestPrototype,
|
|
12478
|
+
Object.setPrototypeOf(requestPrototype, Request2.prototype);
|
|
12477
12479
|
var newRequest = (incoming, defaultHostname) => {
|
|
12478
12480
|
const req = Object.create(requestPrototype);
|
|
12479
12481
|
req[incomingKey] = incoming;
|
|
@@ -12539,15 +12541,17 @@ var Response2 = class _Response {
|
|
|
12539
12541
|
this.#init = init;
|
|
12540
12542
|
}
|
|
12541
12543
|
if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
|
|
12542
|
-
|
|
12543
|
-
this[cacheKey] = [init?.status || 200, body, headers];
|
|
12544
|
+
;
|
|
12545
|
+
this[cacheKey] = [init?.status || 200, body, headers || init?.headers];
|
|
12544
12546
|
}
|
|
12545
12547
|
}
|
|
12546
12548
|
get headers() {
|
|
12547
12549
|
const cache = this[cacheKey];
|
|
12548
12550
|
if (cache) {
|
|
12549
12551
|
if (!(cache[2] instanceof Headers)) {
|
|
12550
|
-
cache[2] = new Headers(
|
|
12552
|
+
cache[2] = new Headers(
|
|
12553
|
+
cache[2] || { "content-type": "text/plain; charset=UTF-8" }
|
|
12554
|
+
);
|
|
12551
12555
|
}
|
|
12552
12556
|
return cache[2];
|
|
12553
12557
|
}
|
|
@@ -12672,15 +12676,32 @@ var flushHeaders = (outgoing) => {
|
|
|
12672
12676
|
};
|
|
12673
12677
|
var responseViaCache = async (res, outgoing) => {
|
|
12674
12678
|
let [status, body, header] = res[cacheKey];
|
|
12675
|
-
|
|
12679
|
+
let hasContentLength = false;
|
|
12680
|
+
if (!header) {
|
|
12681
|
+
header = { "content-type": "text/plain; charset=UTF-8" };
|
|
12682
|
+
} else if (header instanceof Headers) {
|
|
12683
|
+
hasContentLength = header.has("content-length");
|
|
12676
12684
|
header = buildOutgoingHttpHeaders(header);
|
|
12685
|
+
} else if (Array.isArray(header)) {
|
|
12686
|
+
const headerObj = new Headers(header);
|
|
12687
|
+
hasContentLength = headerObj.has("content-length");
|
|
12688
|
+
header = buildOutgoingHttpHeaders(headerObj);
|
|
12689
|
+
} else {
|
|
12690
|
+
for (const key in header) {
|
|
12691
|
+
if (key.length === 14 && key.toLowerCase() === "content-length") {
|
|
12692
|
+
hasContentLength = true;
|
|
12693
|
+
break;
|
|
12694
|
+
}
|
|
12695
|
+
}
|
|
12677
12696
|
}
|
|
12678
|
-
if (
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12697
|
+
if (!hasContentLength) {
|
|
12698
|
+
if (typeof body === "string") {
|
|
12699
|
+
header["Content-Length"] = Buffer.byteLength(body);
|
|
12700
|
+
} else if (body instanceof Uint8Array) {
|
|
12701
|
+
header["Content-Length"] = body.byteLength;
|
|
12702
|
+
} else if (body instanceof Blob) {
|
|
12703
|
+
header["Content-Length"] = body.size;
|
|
12704
|
+
}
|
|
12684
12705
|
}
|
|
12685
12706
|
outgoing.writeHead(status, header);
|
|
12686
12707
|
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
@@ -12774,9 +12795,9 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
12774
12795
|
};
|
|
12775
12796
|
var getRequestListener = (fetchCallback, options = {}) => {
|
|
12776
12797
|
const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
|
|
12777
|
-
if (options.overrideGlobalObjects !== false && global.Request !==
|
|
12798
|
+
if (options.overrideGlobalObjects !== false && global.Request !== Request2) {
|
|
12778
12799
|
Object.defineProperty(global, "Request", {
|
|
12779
|
-
value:
|
|
12800
|
+
value: Request2
|
|
12780
12801
|
});
|
|
12781
12802
|
Object.defineProperty(global, "Response", {
|
|
12782
12803
|
value: Response2
|
|
@@ -14173,7 +14194,7 @@ var App = class {
|
|
|
14173
14194
|
ws.close();
|
|
14174
14195
|
this.instanceWebSockets.delete(instanceId);
|
|
14175
14196
|
}
|
|
14176
|
-
this.
|
|
14197
|
+
this.deleteInstanceState(instanceId);
|
|
14177
14198
|
console.log(`[MCP] Instance destroyed: ${instanceId}`);
|
|
14178
14199
|
return true;
|
|
14179
14200
|
}
|
|
@@ -14258,8 +14279,30 @@ var App = class {
|
|
|
14258
14279
|
* Get the Express application for serverless wrapping (e.g. Lambda).
|
|
14259
14280
|
* Does NOT start listening on a port.
|
|
14260
14281
|
*/
|
|
14261
|
-
toExpressApp() {
|
|
14262
|
-
return this.createExpressApp();
|
|
14282
|
+
toExpressApp(options) {
|
|
14283
|
+
return this.createExpressApp({ serverless: options });
|
|
14284
|
+
}
|
|
14285
|
+
toAwsLambda(options) {
|
|
14286
|
+
const serverlessOptions = {
|
|
14287
|
+
transportMode: "stateless",
|
|
14288
|
+
...options
|
|
14289
|
+
};
|
|
14290
|
+
if (serverlessOptions.transportMode !== "stateless") {
|
|
14291
|
+
throw new Error("app.toAwsLambda() currently requires transportMode: 'stateless'");
|
|
14292
|
+
}
|
|
14293
|
+
const lambdaRuntime = globalThis.awslambda;
|
|
14294
|
+
if (!lambdaRuntime) {
|
|
14295
|
+
throw new Error("app.toAwsLambda() is only available inside the AWS Lambda runtime");
|
|
14296
|
+
}
|
|
14297
|
+
return lambdaRuntime.streamifyResponse(async (event, responseStream) => {
|
|
14298
|
+
const request = this.lambdaEventToRequest(event);
|
|
14299
|
+
const { response, cleanup } = await this.handleLambdaHttpRequest(request, serverlessOptions);
|
|
14300
|
+
try {
|
|
14301
|
+
await this.writeLambdaResponse(response, responseStream);
|
|
14302
|
+
} finally {
|
|
14303
|
+
await cleanup();
|
|
14304
|
+
}
|
|
14305
|
+
});
|
|
14263
14306
|
}
|
|
14264
14307
|
/**
|
|
14265
14308
|
* Close a specific transport session.
|
|
@@ -14285,6 +14328,97 @@ var App = class {
|
|
|
14285
14328
|
getCallerDir() {
|
|
14286
14329
|
return this.callerDir;
|
|
14287
14330
|
}
|
|
14331
|
+
lambdaEventToRequest(event) {
|
|
14332
|
+
const headers = new Headers();
|
|
14333
|
+
for (const [key, value] of Object.entries(event.headers ?? {})) {
|
|
14334
|
+
if (value !== void 0) {
|
|
14335
|
+
headers.set(key, value);
|
|
14336
|
+
}
|
|
14337
|
+
}
|
|
14338
|
+
if (event.cookies?.length) {
|
|
14339
|
+
headers.set("cookie", event.cookies.join("; "));
|
|
14340
|
+
}
|
|
14341
|
+
const method = event.requestContext?.http?.method || "GET";
|
|
14342
|
+
const scheme = headers.get("x-forwarded-proto") || "https";
|
|
14343
|
+
const host = headers.get("host") || event.requestContext?.domainName || "localhost";
|
|
14344
|
+
const rawPath = event.rawPath || "/mcp";
|
|
14345
|
+
const query = event.rawQueryString ? `?${event.rawQueryString}` : "";
|
|
14346
|
+
const url = `${scheme}://${host}${rawPath}${query}`;
|
|
14347
|
+
const hasBody = event.body !== void 0 && event.body !== null && method !== "GET" && method !== "HEAD";
|
|
14348
|
+
const body = hasBody ? Buffer.from(event.body, event.isBase64Encoded ? "base64" : "utf8") : void 0;
|
|
14349
|
+
return new Request(url, {
|
|
14350
|
+
method,
|
|
14351
|
+
headers,
|
|
14352
|
+
body
|
|
14353
|
+
});
|
|
14354
|
+
}
|
|
14355
|
+
async handleLambdaHttpRequest(request, options) {
|
|
14356
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
14357
|
+
sessionIdGenerator: void 0,
|
|
14358
|
+
enableJsonResponse: options.enableJsonResponse ?? false
|
|
14359
|
+
});
|
|
14360
|
+
const server = this.createMcpServer({ serverless: options });
|
|
14361
|
+
await server.connect(transport);
|
|
14362
|
+
setCurrentServer(server);
|
|
14363
|
+
return {
|
|
14364
|
+
response: await transport.handleRequest(request),
|
|
14365
|
+
cleanup: async () => {
|
|
14366
|
+
await server.close().catch(() => {
|
|
14367
|
+
});
|
|
14368
|
+
}
|
|
14369
|
+
};
|
|
14370
|
+
}
|
|
14371
|
+
async writeLambdaResponse(response, responseStream) {
|
|
14372
|
+
const headers = {};
|
|
14373
|
+
response.headers.forEach((value, key) => {
|
|
14374
|
+
if (key.toLowerCase() !== "set-cookie") {
|
|
14375
|
+
headers[key] = value;
|
|
14376
|
+
}
|
|
14377
|
+
});
|
|
14378
|
+
const getSetCookie = response.headers.getSetCookie;
|
|
14379
|
+
const cookies = typeof getSetCookie === "function" ? getSetCookie.call(response.headers) : void 0;
|
|
14380
|
+
const writable = awslambda.HttpResponseStream.from(responseStream, {
|
|
14381
|
+
statusCode: response.status,
|
|
14382
|
+
headers,
|
|
14383
|
+
...cookies && cookies.length > 0 ? { cookies } : {}
|
|
14384
|
+
});
|
|
14385
|
+
if (!response.body) {
|
|
14386
|
+
writable.end();
|
|
14387
|
+
return;
|
|
14388
|
+
}
|
|
14389
|
+
await pipeline(Readable2.fromWeb(response.body), writable);
|
|
14390
|
+
}
|
|
14391
|
+
isStatelessTransport(options) {
|
|
14392
|
+
return options?.serverless?.transportMode === "stateless";
|
|
14393
|
+
}
|
|
14394
|
+
getStateAdapter(options) {
|
|
14395
|
+
return options?.serverless?.stateAdapter;
|
|
14396
|
+
}
|
|
14397
|
+
async loadInstanceState(instanceId, options) {
|
|
14398
|
+
const adapter = this.getStateAdapter(options);
|
|
14399
|
+
if (adapter) {
|
|
14400
|
+
return adapter.get(instanceId);
|
|
14401
|
+
}
|
|
14402
|
+
return this.instanceState.get(instanceId);
|
|
14403
|
+
}
|
|
14404
|
+
async persistInstanceState(instanceId, state, options) {
|
|
14405
|
+
const adapter = this.getStateAdapter(options);
|
|
14406
|
+
if (adapter) {
|
|
14407
|
+
await adapter.set(instanceId, state);
|
|
14408
|
+
return;
|
|
14409
|
+
}
|
|
14410
|
+
this.instanceState.set(instanceId, state);
|
|
14411
|
+
}
|
|
14412
|
+
deleteInstanceState(instanceId, options) {
|
|
14413
|
+
const adapter = this.getStateAdapter(options);
|
|
14414
|
+
if (adapter) {
|
|
14415
|
+
void adapter.delete(instanceId).catch((error) => {
|
|
14416
|
+
console.error(`[MCP] Failed to delete state for ${instanceId}:`, error);
|
|
14417
|
+
});
|
|
14418
|
+
return;
|
|
14419
|
+
}
|
|
14420
|
+
this.instanceState.delete(instanceId);
|
|
14421
|
+
}
|
|
14288
14422
|
// ==========================================================================
|
|
14289
14423
|
// Public API: MCP Request Handling (for testing)
|
|
14290
14424
|
// ==========================================================================
|
|
@@ -14503,7 +14637,7 @@ var App = class {
|
|
|
14503
14637
|
// ==========================================================================
|
|
14504
14638
|
// Private: Express Server
|
|
14505
14639
|
// ==========================================================================
|
|
14506
|
-
createExpressApp() {
|
|
14640
|
+
createExpressApp(options) {
|
|
14507
14641
|
const app = express();
|
|
14508
14642
|
app.use(express.json({ limit: "50mb" }));
|
|
14509
14643
|
if (this.config.cors !== false) {
|
|
@@ -14531,12 +14665,12 @@ var App = class {
|
|
|
14531
14665
|
websockets: this.instanceWebSockets.size
|
|
14532
14666
|
});
|
|
14533
14667
|
});
|
|
14534
|
-
app.post("/mcp", (req, res) => this.handleMcpPost(req, res));
|
|
14535
|
-
app.get("/mcp", (req, res) => this.handleMcpGet(req, res));
|
|
14536
|
-
app.delete("/mcp", (req, res) => this.handleMcpDelete(req, res));
|
|
14668
|
+
app.post("/mcp", (req, res) => this.handleMcpPost(req, res, options));
|
|
14669
|
+
app.get("/mcp", (req, res) => this.handleMcpGet(req, res, options));
|
|
14670
|
+
app.delete("/mcp", (req, res) => this.handleMcpDelete(req, res, options));
|
|
14537
14671
|
return app;
|
|
14538
14672
|
}
|
|
14539
|
-
async handleMcpPost(req, res) {
|
|
14673
|
+
async handleMcpPost(req, res, options) {
|
|
14540
14674
|
const transportSessionId = req.headers["mcp-session-id"];
|
|
14541
14675
|
try {
|
|
14542
14676
|
if (req.body?.method === "tools/list" && this.tools.size === 0) {
|
|
@@ -14548,6 +14682,14 @@ var App = class {
|
|
|
14548
14682
|
return;
|
|
14549
14683
|
}
|
|
14550
14684
|
let transport;
|
|
14685
|
+
if (this.isStatelessTransport(options)) {
|
|
14686
|
+
transport = this.createTransport(options);
|
|
14687
|
+
const server = this.createMcpServer(options);
|
|
14688
|
+
await server.connect(transport);
|
|
14689
|
+
setCurrentServer(server);
|
|
14690
|
+
await transport.handleRequest(req, res, req.body);
|
|
14691
|
+
return;
|
|
14692
|
+
}
|
|
14551
14693
|
if (transportSessionId && this.transports.has(transportSessionId)) {
|
|
14552
14694
|
transport = this.transports.get(transportSessionId);
|
|
14553
14695
|
} else if (!transportSessionId && isInitializeRequest2(req.body)) {
|
|
@@ -14562,8 +14704,8 @@ var App = class {
|
|
|
14562
14704
|
this.clientType = "unknown";
|
|
14563
14705
|
}
|
|
14564
14706
|
console.log(`[MCP] Client: ${clientName}, type: ${this.clientType}`);
|
|
14565
|
-
transport = this.createTransport();
|
|
14566
|
-
const server = this.createMcpServer();
|
|
14707
|
+
transport = this.createTransport(options);
|
|
14708
|
+
const server = this.createMcpServer(options);
|
|
14567
14709
|
await server.connect(transport);
|
|
14568
14710
|
this.serverByTransport.set(transport, server);
|
|
14569
14711
|
setCurrentServer(server);
|
|
@@ -14589,8 +14731,16 @@ var App = class {
|
|
|
14589
14731
|
}
|
|
14590
14732
|
}
|
|
14591
14733
|
}
|
|
14592
|
-
async handleMcpGet(req, res) {
|
|
14734
|
+
async handleMcpGet(req, res, options) {
|
|
14593
14735
|
const transportSessionId = req.headers["mcp-session-id"];
|
|
14736
|
+
if (this.isStatelessTransport(options)) {
|
|
14737
|
+
const transport2 = this.createTransport(options);
|
|
14738
|
+
const server = this.createMcpServer(options);
|
|
14739
|
+
await server.connect(transport2);
|
|
14740
|
+
setCurrentServer(server);
|
|
14741
|
+
await transport2.handleRequest(req, res);
|
|
14742
|
+
return;
|
|
14743
|
+
}
|
|
14594
14744
|
if (!transportSessionId || !this.transports.has(transportSessionId)) {
|
|
14595
14745
|
res.status(400).send("Invalid or missing transport session ID");
|
|
14596
14746
|
return;
|
|
@@ -14598,8 +14748,16 @@ var App = class {
|
|
|
14598
14748
|
const transport = this.transports.get(transportSessionId);
|
|
14599
14749
|
await transport.handleRequest(req, res);
|
|
14600
14750
|
}
|
|
14601
|
-
async handleMcpDelete(req, res) {
|
|
14751
|
+
async handleMcpDelete(req, res, options) {
|
|
14602
14752
|
const transportSessionId = req.headers["mcp-session-id"];
|
|
14753
|
+
if (this.isStatelessTransport(options)) {
|
|
14754
|
+
const transport2 = this.createTransport(options);
|
|
14755
|
+
const server = this.createMcpServer(options);
|
|
14756
|
+
await server.connect(transport2);
|
|
14757
|
+
setCurrentServer(server);
|
|
14758
|
+
await transport2.handleRequest(req, res);
|
|
14759
|
+
return;
|
|
14760
|
+
}
|
|
14603
14761
|
if (!transportSessionId || !this.transports.has(transportSessionId)) {
|
|
14604
14762
|
res.status(400).send("Invalid or missing transport session ID");
|
|
14605
14763
|
return;
|
|
@@ -14610,10 +14768,12 @@ var App = class {
|
|
|
14610
14768
|
// ==========================================================================
|
|
14611
14769
|
// Private: MCP Server & Transport
|
|
14612
14770
|
// ==========================================================================
|
|
14613
|
-
createTransport() {
|
|
14771
|
+
createTransport(options) {
|
|
14772
|
+
const stateless = this.isStatelessTransport(options);
|
|
14614
14773
|
const transport = new StreamableHTTPServerTransport({
|
|
14615
|
-
sessionIdGenerator: () => randomUUID(),
|
|
14616
|
-
|
|
14774
|
+
sessionIdGenerator: stateless ? void 0 : (() => randomUUID()),
|
|
14775
|
+
enableJsonResponse: options?.serverless?.enableJsonResponse ?? false,
|
|
14776
|
+
onsessioninitialized: stateless ? void 0 : (newSessionId) => {
|
|
14617
14777
|
this.transports.set(newSessionId, transport);
|
|
14618
14778
|
console.log(`[MCP] Transport session created: ${newSessionId}`);
|
|
14619
14779
|
this.config.onTransportSessionCreated?.({
|
|
@@ -14643,7 +14803,7 @@ var App = class {
|
|
|
14643
14803
|
};
|
|
14644
14804
|
return transport;
|
|
14645
14805
|
}
|
|
14646
|
-
createMcpServer() {
|
|
14806
|
+
createMcpServer(options) {
|
|
14647
14807
|
const server = new McpServer(
|
|
14648
14808
|
{
|
|
14649
14809
|
name: this.config.name,
|
|
@@ -14653,7 +14813,7 @@ var App = class {
|
|
|
14653
14813
|
{ capabilities: { logging: {}, tools: {}, resources: {} } }
|
|
14654
14814
|
);
|
|
14655
14815
|
this.registerResources(server);
|
|
14656
|
-
this.registerTools(server);
|
|
14816
|
+
this.registerTools(server, options);
|
|
14657
14817
|
return server;
|
|
14658
14818
|
}
|
|
14659
14819
|
registerResources(server) {
|
|
@@ -14739,7 +14899,7 @@ var App = class {
|
|
|
14739
14899
|
);
|
|
14740
14900
|
}
|
|
14741
14901
|
}
|
|
14742
|
-
registerTools(server) {
|
|
14902
|
+
registerTools(server, options) {
|
|
14743
14903
|
for (const [name, { config, handler }] of this.tools) {
|
|
14744
14904
|
const toolMeta = this.buildToolMeta(config);
|
|
14745
14905
|
const baseSchema = config.input || z3.object({});
|
|
@@ -14756,9 +14916,10 @@ var App = class {
|
|
|
14756
14916
|
},
|
|
14757
14917
|
async (args) => {
|
|
14758
14918
|
let startMs = Date.now();
|
|
14919
|
+
let instanceId;
|
|
14920
|
+
let stateDirty = false;
|
|
14759
14921
|
try {
|
|
14760
14922
|
const input = config.input ? config.input.parse(args) : args;
|
|
14761
|
-
let instanceId;
|
|
14762
14923
|
if (hasUi && config.ui) {
|
|
14763
14924
|
instanceId = this.resolveInstanceId(args._instanceId);
|
|
14764
14925
|
}
|
|
@@ -14766,15 +14927,21 @@ var App = class {
|
|
|
14766
14927
|
const hasWebSocket = resource?.config.experimental?.websocket === true;
|
|
14767
14928
|
let ws;
|
|
14768
14929
|
let websocketUrl;
|
|
14769
|
-
if (hasWebSocket && instanceId) {
|
|
14930
|
+
if (hasWebSocket && instanceId && !options?.serverless) {
|
|
14770
14931
|
ws = this.getOrCreateWebSocket(instanceId);
|
|
14771
14932
|
websocketUrl = ws?.websocketUrl;
|
|
14772
14933
|
}
|
|
14934
|
+
let currentState = instanceId ? await this.loadInstanceState(instanceId, options) : void 0;
|
|
14935
|
+
if (instanceId && currentState !== void 0) {
|
|
14936
|
+
this.instanceState.set(instanceId, currentState);
|
|
14937
|
+
}
|
|
14773
14938
|
const context = {
|
|
14774
14939
|
instanceId: instanceId || "",
|
|
14775
|
-
getState: () =>
|
|
14940
|
+
getState: () => currentState,
|
|
14776
14941
|
setState: (state) => {
|
|
14777
14942
|
if (instanceId) {
|
|
14943
|
+
currentState = state;
|
|
14944
|
+
stateDirty = true;
|
|
14778
14945
|
this.instanceState.set(instanceId, state);
|
|
14779
14946
|
}
|
|
14780
14947
|
},
|
|
@@ -14805,6 +14972,9 @@ var App = class {
|
|
|
14805
14972
|
startMs = Date.now();
|
|
14806
14973
|
const result = await handler(input, context);
|
|
14807
14974
|
const durationMs = Date.now() - startMs;
|
|
14975
|
+
if (instanceId && stateDirty) {
|
|
14976
|
+
await this.persistInstanceState(instanceId, currentState, options);
|
|
14977
|
+
}
|
|
14808
14978
|
try {
|
|
14809
14979
|
this.config.onAfterToolCall?.({ toolName: name, args, result, durationMs, isError: false });
|
|
14810
14980
|
} catch {
|
|
@@ -14818,6 +14988,9 @@ var App = class {
|
|
|
14818
14988
|
const durationMs = Date.now() - startMs;
|
|
14819
14989
|
console.error(`[MCP] Tool "${name}" failed:`, err.message);
|
|
14820
14990
|
this.config.onToolError?.(name, err, args);
|
|
14991
|
+
if (instanceId && stateDirty) {
|
|
14992
|
+
await this.persistInstanceState(instanceId, this.instanceState.get(instanceId), options);
|
|
14993
|
+
}
|
|
14821
14994
|
try {
|
|
14822
14995
|
this.config.onAfterToolCall?.({ toolName: name, args, durationMs, isError: true, error: err });
|
|
14823
14996
|
} catch {
|