mcp-use 1.5.0 → 1.5.1-canary.1
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/.tsbuildinfo +1 -1
- package/dist/{chunk-WERYJ6PF.js → chunk-2AOGMX4T.js} +1 -1
- package/dist/{chunk-DSBKVAWD.js → chunk-2JBWOW4S.js} +152 -0
- package/dist/{chunk-UT7O4SIJ.js → chunk-BWOTID2D.js} +209 -75
- package/dist/{chunk-GPAOZN2F.js → chunk-QRABML5H.js} +15 -3
- package/dist/index.cjs +395 -84
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -10
- package/dist/src/agents/index.cjs +1 -0
- package/dist/src/agents/index.js +2 -2
- package/dist/src/browser.cjs +351 -72
- package/dist/src/browser.d.ts +2 -0
- package/dist/src/browser.d.ts.map +1 -1
- package/dist/src/browser.js +2 -2
- package/dist/src/client/browser.d.ts.map +1 -1
- package/dist/src/client/prompts.cjs +3 -0
- package/dist/src/client/prompts.js +2 -2
- package/dist/src/client.d.ts +8 -0
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/config.d.ts +2 -1
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/connectors/base.d.ts +79 -1
- package/dist/src/connectors/base.d.ts.map +1 -1
- package/dist/src/connectors/http.d.ts +1 -0
- package/dist/src/connectors/http.d.ts.map +1 -1
- package/dist/src/connectors/stdio.d.ts.map +1 -1
- package/dist/src/connectors/websocket.d.ts +6 -0
- package/dist/src/connectors/websocket.d.ts.map +1 -1
- package/dist/src/react/index.cjs +365 -73
- package/dist/src/react/index.d.ts +1 -0
- package/dist/src/react/index.d.ts.map +1 -1
- package/dist/src/react/index.js +3 -3
- package/dist/src/react/types.d.ts +9 -1
- package/dist/src/react/types.d.ts.map +1 -1
- package/dist/src/react/useMcp.d.ts.map +1 -1
- package/dist/src/server/adapters/mcp-ui-adapter.d.ts +1 -0
- package/dist/src/server/adapters/mcp-ui-adapter.d.ts.map +1 -1
- package/dist/src/server/index.cjs +730 -192
- package/dist/src/server/index.js +730 -192
- package/dist/src/server/logging.d.ts +6 -0
- package/dist/src/server/logging.d.ts.map +1 -1
- package/dist/src/server/mcp-server.d.ts +201 -10
- package/dist/src/server/mcp-server.d.ts.map +1 -1
- package/dist/src/server/types/common.d.ts +59 -0
- package/dist/src/server/types/common.d.ts.map +1 -1
- package/dist/src/session.d.ts +40 -1
- package/dist/src/session.d.ts.map +1 -1
- package/package.json +10 -4
|
@@ -108,7 +108,8 @@ function createAppsSdkResource(uri, htmlTemplate, metadata) {
|
|
|
108
108
|
}
|
|
109
109
|
__name(createAppsSdkResource, "createAppsSdkResource");
|
|
110
110
|
function createUIResourceFromDefinition(definition, params, config) {
|
|
111
|
-
const
|
|
111
|
+
const buildIdPart = config.buildId ? `-${config.buildId}` : "";
|
|
112
|
+
const uri = definition.type === "appsSdk" ? `ui://widget/${definition.name}${buildIdPart}.html` : `ui://widget/${definition.name}${buildIdPart}`;
|
|
112
113
|
const encoding = definition.encoding || "text";
|
|
113
114
|
switch (definition.type) {
|
|
114
115
|
case "externalUrl": {
|
|
@@ -300,15 +301,46 @@ async function adaptConnectMiddleware(connectMiddleware, middlewarePath) {
|
|
|
300
301
|
__name(adaptConnectMiddleware, "adaptConnectMiddleware");
|
|
301
302
|
|
|
302
303
|
// src/server/logging.ts
|
|
304
|
+
var isDeno = typeof globalThis.Deno !== "undefined";
|
|
305
|
+
function getEnv(key) {
|
|
306
|
+
if (isDeno) {
|
|
307
|
+
return globalThis.Deno.env.get(key);
|
|
308
|
+
}
|
|
309
|
+
return typeof process !== "undefined" && process.env ? process.env[key] : void 0;
|
|
310
|
+
}
|
|
311
|
+
__name(getEnv, "getEnv");
|
|
312
|
+
function isDebugMode() {
|
|
313
|
+
const debugEnv = getEnv("DEBUG");
|
|
314
|
+
return debugEnv !== void 0 && debugEnv !== "" && debugEnv !== "0" && debugEnv.toLowerCase() !== "false";
|
|
315
|
+
}
|
|
316
|
+
__name(isDebugMode, "isDebugMode");
|
|
317
|
+
function formatForLogging(obj) {
|
|
318
|
+
try {
|
|
319
|
+
return JSON.stringify(obj, null, 2);
|
|
320
|
+
} catch {
|
|
321
|
+
return String(obj);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
__name(formatForLogging, "formatForLogging");
|
|
303
325
|
async function requestLogger(c, next) {
|
|
304
326
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
|
|
305
327
|
const method = c.req.method;
|
|
306
328
|
const url = c.req.url;
|
|
307
|
-
|
|
308
|
-
|
|
329
|
+
const debugMode = isDebugMode();
|
|
330
|
+
let requestBody = null;
|
|
331
|
+
let requestHeaders = {};
|
|
332
|
+
if (debugMode) {
|
|
333
|
+
const allHeaders = c.req.header();
|
|
334
|
+
if (allHeaders) {
|
|
335
|
+
requestHeaders = allHeaders;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
309
339
|
try {
|
|
310
340
|
const clonedRequest = c.req.raw.clone();
|
|
311
|
-
|
|
341
|
+
requestBody = await clonedRequest.json().catch(() => {
|
|
342
|
+
return clonedRequest.text().catch(() => null);
|
|
343
|
+
});
|
|
312
344
|
} catch {
|
|
313
345
|
}
|
|
314
346
|
}
|
|
@@ -325,26 +357,89 @@ async function requestLogger(c, next) {
|
|
|
325
357
|
statusColor = "\x1B[35m";
|
|
326
358
|
}
|
|
327
359
|
let logMessage = `[${timestamp}] ${method} \x1B[1m${new URL(url).pathname}\x1B[0m`;
|
|
328
|
-
if (method === "POST" && url.includes("/mcp") &&
|
|
329
|
-
logMessage += ` \x1B[1m[${
|
|
360
|
+
if (method === "POST" && url.includes("/mcp") && requestBody?.method) {
|
|
361
|
+
logMessage += ` \x1B[1m[${requestBody.method}]\x1B[0m`;
|
|
330
362
|
}
|
|
331
363
|
logMessage += ` ${statusColor}${statusCode}\x1B[0m`;
|
|
332
364
|
console.log(logMessage);
|
|
365
|
+
if (debugMode) {
|
|
366
|
+
console.log("\n\x1B[36m" + "=".repeat(80) + "\x1B[0m");
|
|
367
|
+
console.log("\x1B[1m\x1B[36m[DEBUG] Request Details\x1B[0m");
|
|
368
|
+
console.log("\x1B[36m" + "-".repeat(80) + "\x1B[0m");
|
|
369
|
+
if (Object.keys(requestHeaders).length > 0) {
|
|
370
|
+
console.log("\x1B[33mRequest Headers:\x1B[0m");
|
|
371
|
+
console.log(formatForLogging(requestHeaders));
|
|
372
|
+
}
|
|
373
|
+
if (requestBody !== null) {
|
|
374
|
+
console.log("\x1B[33mRequest Body:\x1B[0m");
|
|
375
|
+
if (typeof requestBody === "string") {
|
|
376
|
+
console.log(requestBody);
|
|
377
|
+
} else {
|
|
378
|
+
console.log(formatForLogging(requestBody));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const responseHeaders = {};
|
|
382
|
+
c.res.headers.forEach((value, key) => {
|
|
383
|
+
responseHeaders[key] = value;
|
|
384
|
+
});
|
|
385
|
+
if (Object.keys(responseHeaders).length > 0) {
|
|
386
|
+
console.log("\x1B[33mResponse Headers:\x1B[0m");
|
|
387
|
+
console.log(formatForLogging(responseHeaders));
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
if (c.res.body !== null && c.res.body !== void 0) {
|
|
391
|
+
try {
|
|
392
|
+
const clonedResponse = c.res.clone();
|
|
393
|
+
const responseBody = await clonedResponse.text().catch(() => null);
|
|
394
|
+
if (responseBody !== null && responseBody.length > 0) {
|
|
395
|
+
console.log("\x1B[33mResponse Body:\x1B[0m");
|
|
396
|
+
try {
|
|
397
|
+
const jsonBody = JSON.parse(responseBody);
|
|
398
|
+
console.log(formatForLogging(jsonBody));
|
|
399
|
+
} catch {
|
|
400
|
+
const maxLength = 1e4;
|
|
401
|
+
if (responseBody.length > maxLength) {
|
|
402
|
+
console.log(
|
|
403
|
+
responseBody.substring(0, maxLength) + `
|
|
404
|
+
... (truncated, ${responseBody.length - maxLength} more characters)`
|
|
405
|
+
);
|
|
406
|
+
} else {
|
|
407
|
+
console.log(responseBody);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
console.log("\x1B[33mResponse Body:\x1B[0m (empty)");
|
|
412
|
+
}
|
|
413
|
+
} catch (cloneError) {
|
|
414
|
+
console.log("\x1B[33mResponse Body:\x1B[0m (unable to clone/read)");
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
console.log("\x1B[33mResponse Body:\x1B[0m (no body)");
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.log("\x1B[33mResponse Body:\x1B[0m (unable to read)");
|
|
421
|
+
}
|
|
422
|
+
console.log("\x1B[36m" + "=".repeat(80) + "\x1B[0m\n");
|
|
423
|
+
}
|
|
333
424
|
}
|
|
334
425
|
__name(requestLogger, "requestLogger");
|
|
335
426
|
|
|
336
427
|
// src/server/mcp-server.ts
|
|
428
|
+
function generateUUID() {
|
|
429
|
+
return globalThis.crypto.randomUUID();
|
|
430
|
+
}
|
|
431
|
+
__name(generateUUID, "generateUUID");
|
|
337
432
|
var TMP_MCP_USE_DIR = ".mcp-use";
|
|
338
|
-
var
|
|
339
|
-
function
|
|
340
|
-
if (
|
|
433
|
+
var isDeno2 = typeof globalThis.Deno !== "undefined";
|
|
434
|
+
function getEnv2(key) {
|
|
435
|
+
if (isDeno2) {
|
|
341
436
|
return globalThis.Deno.env.get(key);
|
|
342
437
|
}
|
|
343
438
|
return process.env[key];
|
|
344
439
|
}
|
|
345
|
-
__name(
|
|
440
|
+
__name(getEnv2, "getEnv");
|
|
346
441
|
function getCwd() {
|
|
347
|
-
if (
|
|
442
|
+
if (isDeno2) {
|
|
348
443
|
return globalThis.Deno.cwd();
|
|
349
444
|
}
|
|
350
445
|
return process.cwd();
|
|
@@ -352,7 +447,7 @@ function getCwd() {
|
|
|
352
447
|
__name(getCwd, "getCwd");
|
|
353
448
|
var fsHelpers = {
|
|
354
449
|
async readFileSync(path, encoding = "utf8") {
|
|
355
|
-
if (
|
|
450
|
+
if (isDeno2) {
|
|
356
451
|
return await globalThis.Deno.readTextFile(path);
|
|
357
452
|
}
|
|
358
453
|
const { readFileSync } = await import("fs");
|
|
@@ -360,7 +455,7 @@ var fsHelpers = {
|
|
|
360
455
|
return typeof result === "string" ? result : result.toString(encoding);
|
|
361
456
|
},
|
|
362
457
|
async readFile(path) {
|
|
363
|
-
if (
|
|
458
|
+
if (isDeno2) {
|
|
364
459
|
const data = await globalThis.Deno.readFile(path);
|
|
365
460
|
return data.buffer;
|
|
366
461
|
}
|
|
@@ -372,7 +467,7 @@ var fsHelpers = {
|
|
|
372
467
|
);
|
|
373
468
|
},
|
|
374
469
|
async existsSync(path) {
|
|
375
|
-
if (
|
|
470
|
+
if (isDeno2) {
|
|
376
471
|
try {
|
|
377
472
|
await globalThis.Deno.stat(path);
|
|
378
473
|
return true;
|
|
@@ -384,7 +479,7 @@ var fsHelpers = {
|
|
|
384
479
|
return existsSync(path);
|
|
385
480
|
},
|
|
386
481
|
async readdirSync(path) {
|
|
387
|
-
if (
|
|
482
|
+
if (isDeno2) {
|
|
388
483
|
const entries = [];
|
|
389
484
|
for await (const entry of globalThis.Deno.readDir(path)) {
|
|
390
485
|
entries.push(entry.name);
|
|
@@ -397,7 +492,7 @@ var fsHelpers = {
|
|
|
397
492
|
};
|
|
398
493
|
var pathHelpers = {
|
|
399
494
|
join(...paths) {
|
|
400
|
-
if (
|
|
495
|
+
if (isDeno2) {
|
|
401
496
|
return paths.join("/").replace(/\/+/g, "/");
|
|
402
497
|
}
|
|
403
498
|
return paths.join("/").replace(/\/+/g, "/");
|
|
@@ -429,6 +524,9 @@ var McpServer = class {
|
|
|
429
524
|
registeredTools = [];
|
|
430
525
|
registeredPrompts = [];
|
|
431
526
|
registeredResources = [];
|
|
527
|
+
buildId;
|
|
528
|
+
sessions = /* @__PURE__ */ new Map();
|
|
529
|
+
idleCleanupInterval;
|
|
432
530
|
/**
|
|
433
531
|
* Creates a new MCP server instance with Hono integration
|
|
434
532
|
*
|
|
@@ -461,7 +559,9 @@ var McpServer = class {
|
|
|
461
559
|
"mcp-session-id",
|
|
462
560
|
"X-Proxy-Token",
|
|
463
561
|
"X-Target-URL"
|
|
464
|
-
]
|
|
562
|
+
],
|
|
563
|
+
// Expose mcp-session-id so browser clients can read it from responses
|
|
564
|
+
exposeHeaders: ["mcp-session-id"]
|
|
465
565
|
})
|
|
466
566
|
);
|
|
467
567
|
this.app.use("*", requestLogger);
|
|
@@ -524,7 +624,7 @@ var McpServer = class {
|
|
|
524
624
|
if (this.serverBaseUrl) {
|
|
525
625
|
return this.serverBaseUrl;
|
|
526
626
|
}
|
|
527
|
-
const mcpUrl =
|
|
627
|
+
const mcpUrl = getEnv2("MCP_URL");
|
|
528
628
|
if (mcpUrl) {
|
|
529
629
|
return mcpUrl;
|
|
530
630
|
}
|
|
@@ -701,6 +801,11 @@ var McpServer = class {
|
|
|
701
801
|
*/
|
|
702
802
|
tool(toolDefinition) {
|
|
703
803
|
const inputSchema = this.createParamsSchema(toolDefinition.inputs || []);
|
|
804
|
+
const context = {
|
|
805
|
+
sample: /* @__PURE__ */ __name(async (params, options) => {
|
|
806
|
+
return await this.createMessage(params, options);
|
|
807
|
+
}, "sample")
|
|
808
|
+
};
|
|
704
809
|
this.server.registerTool(
|
|
705
810
|
toolDefinition.name,
|
|
706
811
|
{
|
|
@@ -711,6 +816,9 @@ var McpServer = class {
|
|
|
711
816
|
_meta: toolDefinition._meta
|
|
712
817
|
},
|
|
713
818
|
async (params) => {
|
|
819
|
+
if (toolDefinition.cb.length >= 2) {
|
|
820
|
+
return await toolDefinition.cb(params, context);
|
|
821
|
+
}
|
|
714
822
|
return await toolDefinition.cb(params);
|
|
715
823
|
}
|
|
716
824
|
);
|
|
@@ -767,6 +875,46 @@ var McpServer = class {
|
|
|
767
875
|
this.registeredPrompts.push(promptDefinition.name);
|
|
768
876
|
return this;
|
|
769
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* Request LLM sampling from connected clients.
|
|
880
|
+
*
|
|
881
|
+
* This method allows server tools to request LLM completions from clients
|
|
882
|
+
* that support the sampling capability. The client will handle model selection,
|
|
883
|
+
* user approval (human-in-the-loop), and return the generated response.
|
|
884
|
+
*
|
|
885
|
+
* @param params - Sampling request parameters including messages, model preferences, etc.
|
|
886
|
+
* @param options - Optional request options (timeouts, cancellation, etc.)
|
|
887
|
+
* @returns Promise resolving to the generated message from the client's LLM
|
|
888
|
+
*
|
|
889
|
+
* @example
|
|
890
|
+
* ```typescript
|
|
891
|
+
* // In a tool callback
|
|
892
|
+
* server.tool({
|
|
893
|
+
* name: 'analyze-sentiment',
|
|
894
|
+
* description: 'Analyze sentiment using LLM',
|
|
895
|
+
* inputs: [{ name: 'text', type: 'string', required: true }],
|
|
896
|
+
* cb: async (params, ctx) => {
|
|
897
|
+
* const result = await ctx.sample({
|
|
898
|
+
* messages: [{
|
|
899
|
+
* role: 'user',
|
|
900
|
+
* content: { type: 'text', text: `Analyze sentiment: ${params.text}` }
|
|
901
|
+
* }],
|
|
902
|
+
* modelPreferences: {
|
|
903
|
+
* intelligencePriority: 0.8,
|
|
904
|
+
* speedPriority: 0.5
|
|
905
|
+
* }
|
|
906
|
+
* });
|
|
907
|
+
* return {
|
|
908
|
+
* content: [{ type: 'text', text: result.content.text }]
|
|
909
|
+
* };
|
|
910
|
+
* }
|
|
911
|
+
* })
|
|
912
|
+
* ```
|
|
913
|
+
*/
|
|
914
|
+
async createMessage(params, options) {
|
|
915
|
+
console.log("createMessage", params, options);
|
|
916
|
+
return await this.server.server.createMessage(params, options);
|
|
917
|
+
}
|
|
770
918
|
/**
|
|
771
919
|
* Register a UI widget as both a tool and a resource
|
|
772
920
|
*
|
|
@@ -840,19 +988,19 @@ var McpServer = class {
|
|
|
840
988
|
let mimeType;
|
|
841
989
|
switch (definition.type) {
|
|
842
990
|
case "externalUrl":
|
|
843
|
-
resourceUri =
|
|
991
|
+
resourceUri = this.generateWidgetUri(definition.widget);
|
|
844
992
|
mimeType = "text/uri-list";
|
|
845
993
|
break;
|
|
846
994
|
case "rawHtml":
|
|
847
|
-
resourceUri =
|
|
995
|
+
resourceUri = this.generateWidgetUri(definition.name);
|
|
848
996
|
mimeType = "text/html";
|
|
849
997
|
break;
|
|
850
998
|
case "remoteDom":
|
|
851
|
-
resourceUri =
|
|
999
|
+
resourceUri = this.generateWidgetUri(definition.name);
|
|
852
1000
|
mimeType = "application/vnd.mcp-ui.remote-dom+javascript";
|
|
853
1001
|
break;
|
|
854
1002
|
case "appsSdk":
|
|
855
|
-
resourceUri =
|
|
1003
|
+
resourceUri = this.generateWidgetUri(definition.name, ".html");
|
|
856
1004
|
mimeType = "text/html+skybridge";
|
|
857
1005
|
break;
|
|
858
1006
|
default:
|
|
@@ -871,16 +1019,19 @@ var McpServer = class {
|
|
|
871
1019
|
readCallback: /* @__PURE__ */ __name(async () => {
|
|
872
1020
|
const params = definition.type === "externalUrl" ? this.applyDefaultProps(definition.props) : {};
|
|
873
1021
|
const uiResource = this.createWidgetUIResource(definition, params);
|
|
1022
|
+
uiResource.resource.uri = resourceUri;
|
|
874
1023
|
return {
|
|
875
1024
|
contents: [uiResource.resource]
|
|
876
1025
|
};
|
|
877
1026
|
}, "readCallback")
|
|
878
1027
|
});
|
|
879
1028
|
if (definition.type === "appsSdk") {
|
|
1029
|
+
const buildIdPart = this.buildId ? `-${this.buildId}` : "";
|
|
1030
|
+
const uriTemplate = `ui://widget/${definition.name}${buildIdPart}-{id}.html`;
|
|
880
1031
|
this.resourceTemplate({
|
|
881
1032
|
name: `${definition.name}-dynamic`,
|
|
882
1033
|
resourceTemplate: {
|
|
883
|
-
uriTemplate
|
|
1034
|
+
uriTemplate,
|
|
884
1035
|
name: definition.title || definition.name,
|
|
885
1036
|
description: definition.description,
|
|
886
1037
|
mimeType
|
|
@@ -891,6 +1042,7 @@ var McpServer = class {
|
|
|
891
1042
|
annotations: definition.annotations,
|
|
892
1043
|
readCallback: /* @__PURE__ */ __name(async (uri, params) => {
|
|
893
1044
|
const uiResource = this.createWidgetUIResource(definition, {});
|
|
1045
|
+
uiResource.resource.uri = uri.toString();
|
|
894
1046
|
return {
|
|
895
1047
|
contents: [uiResource.resource]
|
|
896
1048
|
};
|
|
@@ -922,7 +1074,11 @@ var McpServer = class {
|
|
|
922
1074
|
const uiResource = this.createWidgetUIResource(definition, params);
|
|
923
1075
|
if (definition.type === "appsSdk") {
|
|
924
1076
|
const randomId = Math.random().toString(36).substring(2, 15);
|
|
925
|
-
const uniqueUri =
|
|
1077
|
+
const uniqueUri = this.generateWidgetUri(
|
|
1078
|
+
definition.name,
|
|
1079
|
+
".html",
|
|
1080
|
+
randomId
|
|
1081
|
+
);
|
|
926
1082
|
const uniqueToolMetadata = {
|
|
927
1083
|
...toolMetadata,
|
|
928
1084
|
"openai/outputTemplate": uniqueUri
|
|
@@ -979,7 +1135,8 @@ var McpServer = class {
|
|
|
979
1135
|
}
|
|
980
1136
|
const urlConfig = {
|
|
981
1137
|
baseUrl: configBaseUrl,
|
|
982
|
-
port: configPort
|
|
1138
|
+
port: configPort,
|
|
1139
|
+
buildId: this.buildId
|
|
983
1140
|
};
|
|
984
1141
|
const uiResource = createUIResourceFromDefinition(
|
|
985
1142
|
definition,
|
|
@@ -994,6 +1151,25 @@ var McpServer = class {
|
|
|
994
1151
|
}
|
|
995
1152
|
return uiResource;
|
|
996
1153
|
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Generate a widget URI with optional build ID for cache busting
|
|
1156
|
+
*
|
|
1157
|
+
* @private
|
|
1158
|
+
* @param widgetName - Widget name/identifier
|
|
1159
|
+
* @param extension - Optional file extension (e.g., '.html')
|
|
1160
|
+
* @param suffix - Optional suffix (e.g., random ID for dynamic URIs)
|
|
1161
|
+
* @returns Widget URI with build ID if available
|
|
1162
|
+
*/
|
|
1163
|
+
generateWidgetUri(widgetName, extension = "", suffix = "") {
|
|
1164
|
+
const parts = [widgetName];
|
|
1165
|
+
if (this.buildId) {
|
|
1166
|
+
parts.push(this.buildId);
|
|
1167
|
+
}
|
|
1168
|
+
if (suffix) {
|
|
1169
|
+
parts.push(suffix);
|
|
1170
|
+
}
|
|
1171
|
+
return `ui://widget/${parts.join("-")}${extension}`;
|
|
1172
|
+
}
|
|
997
1173
|
/**
|
|
998
1174
|
* Build a complete URL for a widget including query parameters
|
|
999
1175
|
*
|
|
@@ -1070,7 +1246,7 @@ var McpServer = class {
|
|
|
1070
1246
|
* @returns true if in production mode, false otherwise
|
|
1071
1247
|
*/
|
|
1072
1248
|
isProductionMode() {
|
|
1073
|
-
return
|
|
1249
|
+
return getEnv2("NODE_ENV") === "production";
|
|
1074
1250
|
}
|
|
1075
1251
|
/**
|
|
1076
1252
|
* Read build manifest file
|
|
@@ -1081,7 +1257,7 @@ var McpServer = class {
|
|
|
1081
1257
|
async readBuildManifest() {
|
|
1082
1258
|
try {
|
|
1083
1259
|
const manifestPath = pathHelpers.join(
|
|
1084
|
-
|
|
1260
|
+
isDeno2 ? "." : getCwd(),
|
|
1085
1261
|
"dist",
|
|
1086
1262
|
"mcp-use.json"
|
|
1087
1263
|
);
|
|
@@ -1103,7 +1279,7 @@ var McpServer = class {
|
|
|
1103
1279
|
* @returns Promise that resolves when all widgets are mounted
|
|
1104
1280
|
*/
|
|
1105
1281
|
async mountWidgets(options) {
|
|
1106
|
-
if (this.isProductionMode() ||
|
|
1282
|
+
if (this.isProductionMode() || isDeno2) {
|
|
1107
1283
|
console.log("[WIDGETS] Mounting widgets in production mode");
|
|
1108
1284
|
await this.mountWidgetsProduction(options);
|
|
1109
1285
|
} else {
|
|
@@ -1523,7 +1699,7 @@ if (container && Component) {
|
|
|
1523
1699
|
async mountWidgetsProduction(options) {
|
|
1524
1700
|
const baseRoute = options?.baseRoute || "/mcp-use/widgets";
|
|
1525
1701
|
const widgetsDir = pathHelpers.join(
|
|
1526
|
-
|
|
1702
|
+
isDeno2 ? "." : getCwd(),
|
|
1527
1703
|
"dist",
|
|
1528
1704
|
"resources",
|
|
1529
1705
|
"widgets"
|
|
@@ -1539,6 +1715,10 @@ if (container && Component) {
|
|
|
1539
1715
|
"utf8"
|
|
1540
1716
|
);
|
|
1541
1717
|
const manifest = JSON.parse(manifestContent);
|
|
1718
|
+
if (manifest.buildId && typeof manifest.buildId === "string") {
|
|
1719
|
+
this.buildId = manifest.buildId;
|
|
1720
|
+
console.log(`[WIDGETS] Build ID: ${this.buildId}`);
|
|
1721
|
+
}
|
|
1542
1722
|
if (manifest.widgets && typeof manifest.widgets === "object" && !Array.isArray(manifest.widgets)) {
|
|
1543
1723
|
widgets = Object.keys(manifest.widgets);
|
|
1544
1724
|
widgetsMetadata = manifest.widgets;
|
|
@@ -1671,6 +1851,7 @@ if (container && Component) {
|
|
|
1671
1851
|
resource_domains: [
|
|
1672
1852
|
"https://*.oaistatic.com",
|
|
1673
1853
|
"https://*.oaiusercontent.com",
|
|
1854
|
+
"https://*.openai.com",
|
|
1674
1855
|
// always also add the base url of the server
|
|
1675
1856
|
...this.getServerBaseUrl() ? [this.getServerBaseUrl()] : [],
|
|
1676
1857
|
...metadata.appsSdkMetadata?.["openai/widgetCSP"]?.resource_domains || []
|
|
@@ -1703,12 +1884,11 @@ if (container && Component) {
|
|
|
1703
1884
|
});
|
|
1704
1885
|
}
|
|
1705
1886
|
/**
|
|
1706
|
-
* Mount MCP server endpoints at /mcp
|
|
1887
|
+
* Mount MCP server endpoints at /mcp and /sse
|
|
1707
1888
|
*
|
|
1708
1889
|
* Sets up the HTTP transport layer for the MCP server, creating endpoints for
|
|
1709
1890
|
* Server-Sent Events (SSE) streaming, POST message handling, and DELETE session cleanup.
|
|
1710
|
-
*
|
|
1711
|
-
* concurrent client connections.
|
|
1891
|
+
* Transports are reused per session ID to maintain state across requests.
|
|
1712
1892
|
*
|
|
1713
1893
|
* This method is called automatically when the server starts listening and ensures
|
|
1714
1894
|
* that MCP clients can communicate with the server over HTTP.
|
|
@@ -1718,14 +1898,91 @@ if (container && Component) {
|
|
|
1718
1898
|
*
|
|
1719
1899
|
* @example
|
|
1720
1900
|
* Endpoints created:
|
|
1721
|
-
* - GET /mcp - SSE streaming endpoint for real-time communication
|
|
1722
|
-
* - POST /mcp - Message handling endpoint for MCP protocol messages
|
|
1723
|
-
* - DELETE /mcp - Session cleanup endpoint
|
|
1901
|
+
* - GET /mcp, GET /sse - SSE streaming endpoint for real-time communication
|
|
1902
|
+
* - POST /mcp, POST /sse - Message handling endpoint for MCP protocol messages
|
|
1903
|
+
* - DELETE /mcp, DELETE /sse - Session cleanup endpoint
|
|
1724
1904
|
*/
|
|
1725
1905
|
async mountMcp() {
|
|
1726
1906
|
if (this.mcpMounted) return;
|
|
1727
1907
|
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
1728
|
-
const
|
|
1908
|
+
const idleTimeoutMs = this.config.sessionIdleTimeoutMs ?? 3e5;
|
|
1909
|
+
const createNewTransport = /* @__PURE__ */ __name(async (closeOldSessionId) => {
|
|
1910
|
+
if (closeOldSessionId && this.sessions.has(closeOldSessionId)) {
|
|
1911
|
+
try {
|
|
1912
|
+
this.sessions.get(closeOldSessionId).transport.close();
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
}
|
|
1915
|
+
this.sessions.delete(closeOldSessionId);
|
|
1916
|
+
}
|
|
1917
|
+
const isProduction = this.isProductionMode();
|
|
1918
|
+
let allowedOrigins = this.config.allowedOrigins;
|
|
1919
|
+
let enableDnsRebindingProtection = false;
|
|
1920
|
+
if (isProduction) {
|
|
1921
|
+
if (allowedOrigins !== void 0) {
|
|
1922
|
+
enableDnsRebindingProtection = allowedOrigins.length > 0;
|
|
1923
|
+
}
|
|
1924
|
+
} else {
|
|
1925
|
+
allowedOrigins = void 0;
|
|
1926
|
+
enableDnsRebindingProtection = false;
|
|
1927
|
+
}
|
|
1928
|
+
const transport = new StreamableHTTPServerTransport({
|
|
1929
|
+
sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
|
|
1930
|
+
enableJsonResponse: true,
|
|
1931
|
+
allowedOrigins,
|
|
1932
|
+
enableDnsRebindingProtection,
|
|
1933
|
+
onsessioninitialized: /* @__PURE__ */ __name((id) => {
|
|
1934
|
+
if (id) {
|
|
1935
|
+
this.sessions.set(id, {
|
|
1936
|
+
transport,
|
|
1937
|
+
lastAccessedAt: Date.now()
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
}, "onsessioninitialized"),
|
|
1941
|
+
onsessionclosed: /* @__PURE__ */ __name((id) => {
|
|
1942
|
+
if (id) {
|
|
1943
|
+
this.sessions.delete(id);
|
|
1944
|
+
}
|
|
1945
|
+
}, "onsessionclosed")
|
|
1946
|
+
});
|
|
1947
|
+
await this.server.connect(transport);
|
|
1948
|
+
return transport;
|
|
1949
|
+
}, "createNewTransport");
|
|
1950
|
+
const getOrCreateTransport = /* @__PURE__ */ __name(async (sessionId, isInit = false) => {
|
|
1951
|
+
if (isInit) {
|
|
1952
|
+
return await createNewTransport(sessionId);
|
|
1953
|
+
}
|
|
1954
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
1955
|
+
const session = this.sessions.get(sessionId);
|
|
1956
|
+
session.lastAccessedAt = Date.now();
|
|
1957
|
+
return session.transport;
|
|
1958
|
+
}
|
|
1959
|
+
if (sessionId) {
|
|
1960
|
+
const autoCreate = this.config.autoCreateSessionOnInvalidId ?? true;
|
|
1961
|
+
if (autoCreate) {
|
|
1962
|
+
console.warn(
|
|
1963
|
+
`[MCP] Session ${sessionId} not found (expired or invalid), auto-creating new session for seamless reconnection`
|
|
1964
|
+
);
|
|
1965
|
+
return await createNewTransport(sessionId);
|
|
1966
|
+
} else {
|
|
1967
|
+
return null;
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
return null;
|
|
1971
|
+
}, "getOrCreateTransport");
|
|
1972
|
+
if (idleTimeoutMs > 0 && !this.idleCleanupInterval) {
|
|
1973
|
+
this.idleCleanupInterval = setInterval(() => {
|
|
1974
|
+
const now = Date.now();
|
|
1975
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
1976
|
+
if (now - session.lastAccessedAt > idleTimeoutMs) {
|
|
1977
|
+
try {
|
|
1978
|
+
session.transport.close();
|
|
1979
|
+
} catch (error) {
|
|
1980
|
+
}
|
|
1981
|
+
this.sessions.delete(sessionId);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
}, 6e4);
|
|
1985
|
+
}
|
|
1729
1986
|
const createExpressLikeObjects = /* @__PURE__ */ __name((c) => {
|
|
1730
1987
|
const req = c.req.raw;
|
|
1731
1988
|
const responseBody = [];
|
|
@@ -1817,7 +2074,7 @@ if (container && Component) {
|
|
|
1817
2074
|
getResponse: /* @__PURE__ */ __name(() => {
|
|
1818
2075
|
if (ended) {
|
|
1819
2076
|
if (responseBody.length > 0) {
|
|
1820
|
-
const body =
|
|
2077
|
+
const body = isDeno2 ? Buffer.concat(responseBody) : Buffer.concat(responseBody);
|
|
1821
2078
|
return new Response(body, {
|
|
1822
2079
|
status: statusCode,
|
|
1823
2080
|
headers
|
|
@@ -1833,164 +2090,250 @@ if (container && Component) {
|
|
|
1833
2090
|
}, "getResponse")
|
|
1834
2091
|
};
|
|
1835
2092
|
}, "createExpressLikeObjects");
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
2093
|
+
const mountEndpoint = /* @__PURE__ */ __name((endpoint) => {
|
|
2094
|
+
this.app.post(endpoint, async (c) => {
|
|
2095
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
2096
|
+
let body = {};
|
|
2097
|
+
try {
|
|
2098
|
+
body = await c.req.json();
|
|
2099
|
+
expressReq.body = body;
|
|
2100
|
+
} catch {
|
|
2101
|
+
expressReq.body = {};
|
|
2102
|
+
}
|
|
2103
|
+
const isInit = body?.method === "initialize";
|
|
2104
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2105
|
+
const transport = await getOrCreateTransport(sessionId, isInit);
|
|
2106
|
+
if (!transport) {
|
|
2107
|
+
if (sessionId) {
|
|
2108
|
+
return c.json(
|
|
2109
|
+
{
|
|
2110
|
+
jsonrpc: "2.0",
|
|
2111
|
+
error: {
|
|
2112
|
+
code: -32e3,
|
|
2113
|
+
message: "Session not found or expired"
|
|
2114
|
+
},
|
|
2115
|
+
// Notifications don't have an id, but we include null for consistency
|
|
2116
|
+
id: body?.id ?? null
|
|
2117
|
+
},
|
|
2118
|
+
404
|
|
2119
|
+
);
|
|
2120
|
+
} else {
|
|
2121
|
+
return c.json(
|
|
2122
|
+
{
|
|
2123
|
+
jsonrpc: "2.0",
|
|
2124
|
+
error: {
|
|
2125
|
+
code: -32e3,
|
|
2126
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2127
|
+
},
|
|
2128
|
+
id: body?.id ?? null
|
|
2129
|
+
},
|
|
2130
|
+
400
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
2135
|
+
this.sessions.get(sessionId).lastAccessedAt = Date.now();
|
|
2136
|
+
}
|
|
2137
|
+
if (expressRes._closeHandler) {
|
|
2138
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
2139
|
+
transport.close();
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
await this.waitForRequestComplete(
|
|
2143
|
+
transport,
|
|
2144
|
+
expressReq,
|
|
2145
|
+
expressRes,
|
|
2146
|
+
expressReq.body
|
|
2147
|
+
);
|
|
2148
|
+
const response = getResponse();
|
|
2149
|
+
if (response) {
|
|
2150
|
+
return response;
|
|
2151
|
+
}
|
|
2152
|
+
return c.text("", 200);
|
|
1846
2153
|
});
|
|
1847
|
-
|
|
2154
|
+
this.app.get(endpoint, async (c) => {
|
|
2155
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2156
|
+
const transport = await getOrCreateTransport(sessionId, false);
|
|
2157
|
+
if (!transport) {
|
|
2158
|
+
if (sessionId) {
|
|
2159
|
+
return c.json(
|
|
2160
|
+
{
|
|
2161
|
+
jsonrpc: "2.0",
|
|
2162
|
+
error: {
|
|
2163
|
+
code: -32e3,
|
|
2164
|
+
message: "Session not found or expired"
|
|
2165
|
+
},
|
|
2166
|
+
id: null
|
|
2167
|
+
},
|
|
2168
|
+
404
|
|
2169
|
+
);
|
|
2170
|
+
} else {
|
|
2171
|
+
return c.json(
|
|
2172
|
+
{
|
|
2173
|
+
jsonrpc: "2.0",
|
|
2174
|
+
error: {
|
|
2175
|
+
code: -32e3,
|
|
2176
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2177
|
+
},
|
|
2178
|
+
id: null
|
|
2179
|
+
},
|
|
2180
|
+
400
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
2185
|
+
this.sessions.get(sessionId).lastAccessedAt = Date.now();
|
|
2186
|
+
}
|
|
1848
2187
|
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1849
2188
|
transport.close();
|
|
1850
2189
|
});
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
2190
|
+
const { readable, writable } = new globalThis.TransformStream();
|
|
2191
|
+
const writer = writable.getWriter();
|
|
2192
|
+
const encoder = new TextEncoder();
|
|
2193
|
+
let resolveResponse;
|
|
2194
|
+
const responsePromise = new Promise((resolve) => {
|
|
2195
|
+
resolveResponse = resolve;
|
|
2196
|
+
});
|
|
2197
|
+
let headersSent = false;
|
|
2198
|
+
const headers = {};
|
|
2199
|
+
let statusCode = 200;
|
|
2200
|
+
const expressRes = {
|
|
2201
|
+
statusCode: 200,
|
|
2202
|
+
headersSent: false,
|
|
2203
|
+
status: /* @__PURE__ */ __name((code) => {
|
|
2204
|
+
statusCode = code;
|
|
2205
|
+
expressRes.statusCode = code;
|
|
2206
|
+
return expressRes;
|
|
2207
|
+
}, "status"),
|
|
2208
|
+
setHeader: /* @__PURE__ */ __name((name, value) => {
|
|
2209
|
+
if (!headersSent) {
|
|
2210
|
+
headers[name] = Array.isArray(value) ? value.join(", ") : value;
|
|
2211
|
+
}
|
|
2212
|
+
}, "setHeader"),
|
|
2213
|
+
getHeader: /* @__PURE__ */ __name((name) => headers[name], "getHeader"),
|
|
2214
|
+
write: /* @__PURE__ */ __name((chunk) => {
|
|
2215
|
+
if (!headersSent) {
|
|
2216
|
+
headersSent = true;
|
|
2217
|
+
resolveResponse(
|
|
2218
|
+
new Response(readable, {
|
|
2219
|
+
status: statusCode,
|
|
2220
|
+
headers
|
|
2221
|
+
})
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
const data = typeof chunk === "string" ? encoder.encode(chunk) : chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
|
|
2225
|
+
writer.write(data);
|
|
2226
|
+
return true;
|
|
2227
|
+
}, "write"),
|
|
2228
|
+
end: /* @__PURE__ */ __name((chunk) => {
|
|
2229
|
+
if (chunk) {
|
|
2230
|
+
expressRes.write(chunk);
|
|
2231
|
+
}
|
|
2232
|
+
if (!headersSent) {
|
|
2233
|
+
headersSent = true;
|
|
2234
|
+
resolveResponse(
|
|
2235
|
+
new Response(null, {
|
|
2236
|
+
status: statusCode,
|
|
2237
|
+
headers
|
|
2238
|
+
})
|
|
2239
|
+
);
|
|
2240
|
+
writer.close();
|
|
2241
|
+
} else {
|
|
2242
|
+
writer.close();
|
|
2243
|
+
}
|
|
2244
|
+
}, "end"),
|
|
2245
|
+
on: /* @__PURE__ */ __name((event, handler) => {
|
|
2246
|
+
if (event === "close") {
|
|
2247
|
+
expressRes._closeHandler = handler;
|
|
2248
|
+
}
|
|
2249
|
+
}, "on"),
|
|
2250
|
+
once: /* @__PURE__ */ __name(() => {
|
|
2251
|
+
}, "once"),
|
|
2252
|
+
removeListener: /* @__PURE__ */ __name(() => {
|
|
2253
|
+
}, "removeListener"),
|
|
2254
|
+
writeHead: /* @__PURE__ */ __name((code, _headers) => {
|
|
2255
|
+
statusCode = code;
|
|
2256
|
+
expressRes.statusCode = code;
|
|
2257
|
+
if (_headers) {
|
|
2258
|
+
Object.assign(headers, _headers);
|
|
2259
|
+
}
|
|
2260
|
+
if (!headersSent) {
|
|
2261
|
+
headersSent = true;
|
|
2262
|
+
resolveResponse(
|
|
2263
|
+
new Response(readable, {
|
|
2264
|
+
status: statusCode,
|
|
2265
|
+
headers
|
|
2266
|
+
})
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
2269
|
+
return expressRes;
|
|
2270
|
+
}, "writeHead"),
|
|
2271
|
+
flushHeaders: /* @__PURE__ */ __name(() => {
|
|
2272
|
+
}, "flushHeaders")
|
|
2273
|
+
};
|
|
2274
|
+
const expressReq = {
|
|
2275
|
+
...c.req.raw,
|
|
2276
|
+
url: new URL(c.req.url).pathname + new URL(c.req.url).search,
|
|
2277
|
+
path: new URL(c.req.url).pathname,
|
|
2278
|
+
query: Object.fromEntries(new URL(c.req.url).searchParams),
|
|
2279
|
+
headers: c.req.header(),
|
|
2280
|
+
method: c.req.method
|
|
2281
|
+
};
|
|
2282
|
+
transport.handleRequest(expressReq, expressRes).catch((err) => {
|
|
2283
|
+
console.error("MCP Transport error:", err);
|
|
2284
|
+
try {
|
|
2285
|
+
writer.close();
|
|
2286
|
+
} catch {
|
|
1914
2287
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2288
|
+
});
|
|
2289
|
+
return responsePromise;
|
|
2290
|
+
});
|
|
2291
|
+
this.app.delete(endpoint, async (c) => {
|
|
2292
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
2293
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2294
|
+
const transport = await getOrCreateTransport(sessionId, false);
|
|
2295
|
+
if (!transport) {
|
|
2296
|
+
if (sessionId) {
|
|
2297
|
+
return c.json(
|
|
2298
|
+
{
|
|
2299
|
+
jsonrpc: "2.0",
|
|
2300
|
+
error: {
|
|
2301
|
+
code: -32e3,
|
|
2302
|
+
message: "Session not found or expired"
|
|
2303
|
+
},
|
|
2304
|
+
id: null
|
|
2305
|
+
},
|
|
2306
|
+
404
|
|
1922
2307
|
);
|
|
1923
|
-
writer.close();
|
|
1924
2308
|
} else {
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
removeListener: /* @__PURE__ */ __name(() => {
|
|
1936
|
-
}, "removeListener"),
|
|
1937
|
-
writeHead: /* @__PURE__ */ __name((code, _headers) => {
|
|
1938
|
-
statusCode = code;
|
|
1939
|
-
expressRes.statusCode = code;
|
|
1940
|
-
if (_headers) {
|
|
1941
|
-
Object.assign(headers, _headers);
|
|
1942
|
-
}
|
|
1943
|
-
if (!headersSent) {
|
|
1944
|
-
headersSent = true;
|
|
1945
|
-
resolveResponse(
|
|
1946
|
-
new Response(readable, {
|
|
1947
|
-
status: statusCode,
|
|
1948
|
-
headers
|
|
1949
|
-
})
|
|
2309
|
+
return c.json(
|
|
2310
|
+
{
|
|
2311
|
+
jsonrpc: "2.0",
|
|
2312
|
+
error: {
|
|
2313
|
+
code: -32e3,
|
|
2314
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2315
|
+
},
|
|
2316
|
+
id: null
|
|
2317
|
+
},
|
|
2318
|
+
400
|
|
1950
2319
|
);
|
|
1951
2320
|
}
|
|
1952
|
-
return expressRes;
|
|
1953
|
-
}, "writeHead"),
|
|
1954
|
-
flushHeaders: /* @__PURE__ */ __name(() => {
|
|
1955
|
-
}, "flushHeaders")
|
|
1956
|
-
};
|
|
1957
|
-
const expressReq = {
|
|
1958
|
-
...c.req.raw,
|
|
1959
|
-
url: new URL(c.req.url).pathname + new URL(c.req.url).search,
|
|
1960
|
-
path: new URL(c.req.url).pathname,
|
|
1961
|
-
query: Object.fromEntries(new URL(c.req.url).searchParams),
|
|
1962
|
-
headers: c.req.header(),
|
|
1963
|
-
method: c.req.method
|
|
1964
|
-
};
|
|
1965
|
-
await this.server.connect(transport);
|
|
1966
|
-
transport.handleRequest(expressReq, expressRes).catch((err) => {
|
|
1967
|
-
console.error("MCP Transport error:", err);
|
|
1968
|
-
try {
|
|
1969
|
-
writer.close();
|
|
1970
|
-
} catch {
|
|
1971
2321
|
}
|
|
2322
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
2323
|
+
transport.close();
|
|
2324
|
+
});
|
|
2325
|
+
await this.waitForRequestComplete(transport, expressReq, expressRes);
|
|
2326
|
+
const response = getResponse();
|
|
2327
|
+
if (response) {
|
|
2328
|
+
return response;
|
|
2329
|
+
}
|
|
2330
|
+
return c.text("", 200);
|
|
1972
2331
|
});
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1977
|
-
const transport = new StreamableHTTPServerTransport({
|
|
1978
|
-
sessionIdGenerator: void 0,
|
|
1979
|
-
enableJsonResponse: true
|
|
1980
|
-
});
|
|
1981
|
-
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1982
|
-
transport.close();
|
|
1983
|
-
});
|
|
1984
|
-
await this.server.connect(transport);
|
|
1985
|
-
await this.waitForRequestComplete(transport, expressReq, expressRes);
|
|
1986
|
-
const response = getResponse();
|
|
1987
|
-
if (response) {
|
|
1988
|
-
return response;
|
|
1989
|
-
}
|
|
1990
|
-
return c.text("", 200);
|
|
1991
|
-
});
|
|
2332
|
+
}, "mountEndpoint");
|
|
2333
|
+
mountEndpoint("/mcp");
|
|
2334
|
+
mountEndpoint("/sse");
|
|
1992
2335
|
this.mcpMounted = true;
|
|
1993
|
-
console.log(`[MCP] Server mounted at
|
|
2336
|
+
console.log(`[MCP] Server mounted at /mcp and /sse`);
|
|
1994
2337
|
}
|
|
1995
2338
|
/**
|
|
1996
2339
|
* Start the Hono server with MCP endpoints
|
|
@@ -1999,7 +2342,7 @@ if (container && Component) {
|
|
|
1999
2342
|
* the inspector UI (if available), and starting the server to listen
|
|
2000
2343
|
* for incoming connections. This is the main entry point for running the server.
|
|
2001
2344
|
*
|
|
2002
|
-
* The server will be accessible at the specified port with MCP endpoints at /mcp
|
|
2345
|
+
* The server will be accessible at the specified port with MCP endpoints at /mcp and /sse
|
|
2003
2346
|
* and inspector UI at /inspector (if the inspector package is installed).
|
|
2004
2347
|
*
|
|
2005
2348
|
* @param port - Port number to listen on (defaults to 3001 if not specified)
|
|
@@ -2009,7 +2352,7 @@ if (container && Component) {
|
|
|
2009
2352
|
* ```typescript
|
|
2010
2353
|
* await server.listen(8080)
|
|
2011
2354
|
* // Server now running at http://localhost:8080 (or configured host)
|
|
2012
|
-
* // MCP endpoints: http://localhost:8080/mcp
|
|
2355
|
+
* // MCP endpoints: http://localhost:8080/mcp and http://localhost:8080/sse
|
|
2013
2356
|
* // Inspector UI: http://localhost:8080/inspector
|
|
2014
2357
|
* ```
|
|
2015
2358
|
*/
|
|
@@ -2039,9 +2382,9 @@ if (container && Component) {
|
|
|
2039
2382
|
console.log("");
|
|
2040
2383
|
}
|
|
2041
2384
|
async listen(port) {
|
|
2042
|
-
const portEnv =
|
|
2385
|
+
const portEnv = getEnv2("PORT");
|
|
2043
2386
|
this.serverPort = port || (portEnv ? parseInt(portEnv, 10) : 3001);
|
|
2044
|
-
const hostEnv =
|
|
2387
|
+
const hostEnv = getEnv2("HOST");
|
|
2045
2388
|
if (hostEnv) {
|
|
2046
2389
|
this.serverHost = hostEnv;
|
|
2047
2390
|
}
|
|
@@ -2052,7 +2395,7 @@ if (container && Component) {
|
|
|
2052
2395
|
await this.mountMcp();
|
|
2053
2396
|
await this.mountInspector();
|
|
2054
2397
|
this.logRegisteredItems();
|
|
2055
|
-
if (
|
|
2398
|
+
if (isDeno2) {
|
|
2056
2399
|
const corsHeaders = {
|
|
2057
2400
|
"Access-Control-Allow-Origin": "*",
|
|
2058
2401
|
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type"
|
|
@@ -2113,7 +2456,7 @@ if (container && Component) {
|
|
|
2113
2456
|
`[SERVER] Listening on http://${this.serverHost}:${this.serverPort}`
|
|
2114
2457
|
);
|
|
2115
2458
|
console.log(
|
|
2116
|
-
`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp`
|
|
2459
|
+
`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp and http://${this.serverHost}:${this.serverPort}/sse`
|
|
2117
2460
|
);
|
|
2118
2461
|
}
|
|
2119
2462
|
);
|
|
@@ -2197,6 +2540,192 @@ if (container && Component) {
|
|
|
2197
2540
|
return result;
|
|
2198
2541
|
};
|
|
2199
2542
|
}
|
|
2543
|
+
/**
|
|
2544
|
+
* Get array of active session IDs
|
|
2545
|
+
*
|
|
2546
|
+
* Returns an array of all currently active session IDs. This is useful for
|
|
2547
|
+
* sending targeted notifications to specific clients or iterating over
|
|
2548
|
+
* connected clients.
|
|
2549
|
+
*
|
|
2550
|
+
* Note: This only works in stateful mode. In stateless mode (edge environments),
|
|
2551
|
+
* this will return an empty array.
|
|
2552
|
+
*
|
|
2553
|
+
* @returns Array of active session ID strings
|
|
2554
|
+
*
|
|
2555
|
+
* @example
|
|
2556
|
+
* ```typescript
|
|
2557
|
+
* const sessions = server.getActiveSessions();
|
|
2558
|
+
* console.log(`${sessions.length} clients connected`);
|
|
2559
|
+
*
|
|
2560
|
+
* // Send notification to first connected client
|
|
2561
|
+
* if (sessions.length > 0) {
|
|
2562
|
+
* server.sendNotificationToSession(sessions[0], "custom/hello", { message: "Hi!" });
|
|
2563
|
+
* }
|
|
2564
|
+
* ```
|
|
2565
|
+
*/
|
|
2566
|
+
getActiveSessions() {
|
|
2567
|
+
return Array.from(this.sessions.keys());
|
|
2568
|
+
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Send a notification to all connected clients
|
|
2571
|
+
*
|
|
2572
|
+
* Broadcasts a JSON-RPC notification to all active sessions. Notifications are
|
|
2573
|
+
* one-way messages that do not expect a response from the client.
|
|
2574
|
+
*
|
|
2575
|
+
* Note: This only works in stateful mode with active sessions. If no sessions
|
|
2576
|
+
* are connected, the notification is silently discarded (per MCP spec: "server MAY send").
|
|
2577
|
+
*
|
|
2578
|
+
* @param method - The notification method name (e.g., "custom/my-notification")
|
|
2579
|
+
* @param params - Optional parameters to include in the notification
|
|
2580
|
+
*
|
|
2581
|
+
* @example
|
|
2582
|
+
* ```typescript
|
|
2583
|
+
* // Send a simple notification to all clients
|
|
2584
|
+
* server.sendNotification("custom/server-status", {
|
|
2585
|
+
* status: "ready",
|
|
2586
|
+
* timestamp: new Date().toISOString()
|
|
2587
|
+
* });
|
|
2588
|
+
*
|
|
2589
|
+
* // Notify all clients that resources have changed
|
|
2590
|
+
* server.sendNotification("notifications/resources/list_changed");
|
|
2591
|
+
* ```
|
|
2592
|
+
*/
|
|
2593
|
+
async sendNotification(method, params) {
|
|
2594
|
+
const notification = {
|
|
2595
|
+
jsonrpc: "2.0",
|
|
2596
|
+
method,
|
|
2597
|
+
...params && { params }
|
|
2598
|
+
};
|
|
2599
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
2600
|
+
try {
|
|
2601
|
+
await session.transport.send(notification);
|
|
2602
|
+
} catch (error) {
|
|
2603
|
+
console.warn(
|
|
2604
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2605
|
+
error
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
/**
|
|
2611
|
+
* Send a notification to a specific client session
|
|
2612
|
+
*
|
|
2613
|
+
* Sends a JSON-RPC notification to a single client identified by their session ID.
|
|
2614
|
+
* This allows sending customized notifications to individual clients.
|
|
2615
|
+
*
|
|
2616
|
+
* Note: This only works in stateful mode. If the session ID doesn't exist,
|
|
2617
|
+
* the notification is silently discarded.
|
|
2618
|
+
*
|
|
2619
|
+
* @param sessionId - The target session ID (from getActiveSessions())
|
|
2620
|
+
* @param method - The notification method name (e.g., "custom/my-notification")
|
|
2621
|
+
* @param params - Optional parameters to include in the notification
|
|
2622
|
+
* @returns true if the notification was sent, false if session not found
|
|
2623
|
+
*
|
|
2624
|
+
* @example
|
|
2625
|
+
* ```typescript
|
|
2626
|
+
* const sessions = server.getActiveSessions();
|
|
2627
|
+
*
|
|
2628
|
+
* // Send different messages to different clients
|
|
2629
|
+
* sessions.forEach((sessionId, index) => {
|
|
2630
|
+
* server.sendNotificationToSession(sessionId, "custom/welcome", {
|
|
2631
|
+
* message: `Hello client #${index + 1}!`,
|
|
2632
|
+
* clientNumber: index + 1
|
|
2633
|
+
* });
|
|
2634
|
+
* });
|
|
2635
|
+
* ```
|
|
2636
|
+
*/
|
|
2637
|
+
async sendNotificationToSession(sessionId, method, params) {
|
|
2638
|
+
const session = this.sessions.get(sessionId);
|
|
2639
|
+
if (!session) {
|
|
2640
|
+
return false;
|
|
2641
|
+
}
|
|
2642
|
+
const notification = {
|
|
2643
|
+
jsonrpc: "2.0",
|
|
2644
|
+
method,
|
|
2645
|
+
...params && { params }
|
|
2646
|
+
};
|
|
2647
|
+
try {
|
|
2648
|
+
await session.transport.send(notification);
|
|
2649
|
+
return true;
|
|
2650
|
+
} catch (error) {
|
|
2651
|
+
console.warn(
|
|
2652
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2653
|
+
error
|
|
2654
|
+
);
|
|
2655
|
+
return false;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
// Store the roots changed callback
|
|
2659
|
+
onRootsChangedCallback;
|
|
2660
|
+
/**
|
|
2661
|
+
* Register a callback for when a client's roots change
|
|
2662
|
+
*
|
|
2663
|
+
* When a client sends a `notifications/roots/list_changed` notification,
|
|
2664
|
+
* the server will automatically request the updated roots list and call
|
|
2665
|
+
* this callback with the new roots.
|
|
2666
|
+
*
|
|
2667
|
+
* @param callback - Function called with the updated roots array
|
|
2668
|
+
*
|
|
2669
|
+
* @example
|
|
2670
|
+
* ```typescript
|
|
2671
|
+
* server.onRootsChanged(async (roots) => {
|
|
2672
|
+
* console.log("Client roots updated:", roots);
|
|
2673
|
+
* roots.forEach(root => {
|
|
2674
|
+
* console.log(` - ${root.name || "unnamed"}: ${root.uri}`);
|
|
2675
|
+
* });
|
|
2676
|
+
* });
|
|
2677
|
+
* ```
|
|
2678
|
+
*/
|
|
2679
|
+
onRootsChanged(callback) {
|
|
2680
|
+
this.onRootsChangedCallback = callback;
|
|
2681
|
+
return this;
|
|
2682
|
+
}
|
|
2683
|
+
/**
|
|
2684
|
+
* Request the current roots list from a specific client session
|
|
2685
|
+
*
|
|
2686
|
+
* This sends a `roots/list` request to the client and returns
|
|
2687
|
+
* the list of roots the client has configured.
|
|
2688
|
+
*
|
|
2689
|
+
* @param sessionId - The session ID of the client to query
|
|
2690
|
+
* @returns Array of roots, or null if the session doesn't exist or request fails
|
|
2691
|
+
*
|
|
2692
|
+
* @example
|
|
2693
|
+
* ```typescript
|
|
2694
|
+
* const sessions = server.getActiveSessions();
|
|
2695
|
+
* if (sessions.length > 0) {
|
|
2696
|
+
* const roots = await server.listRoots(sessions[0]);
|
|
2697
|
+
* if (roots) {
|
|
2698
|
+
* console.log(`Client has ${roots.length} roots:`);
|
|
2699
|
+
* roots.forEach(r => console.log(` - ${r.uri}`));
|
|
2700
|
+
* }
|
|
2701
|
+
* }
|
|
2702
|
+
* ```
|
|
2703
|
+
*/
|
|
2704
|
+
async listRoots(sessionId) {
|
|
2705
|
+
const session = this.sessions.get(sessionId);
|
|
2706
|
+
if (!session) {
|
|
2707
|
+
return null;
|
|
2708
|
+
}
|
|
2709
|
+
try {
|
|
2710
|
+
const request = {
|
|
2711
|
+
jsonrpc: "2.0",
|
|
2712
|
+
id: generateUUID(),
|
|
2713
|
+
method: "roots/list",
|
|
2714
|
+
params: {}
|
|
2715
|
+
};
|
|
2716
|
+
const response = await session.transport.send(request);
|
|
2717
|
+
if (response && typeof response === "object" && "roots" in response) {
|
|
2718
|
+
return response.roots;
|
|
2719
|
+
}
|
|
2720
|
+
return [];
|
|
2721
|
+
} catch (error) {
|
|
2722
|
+
console.warn(
|
|
2723
|
+
`[MCP] Failed to list roots from session ${sessionId}:`,
|
|
2724
|
+
error
|
|
2725
|
+
);
|
|
2726
|
+
return null;
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2200
2729
|
/**
|
|
2201
2730
|
* Mount MCP Inspector UI at /inspector
|
|
2202
2731
|
*
|
|
@@ -2213,7 +2742,7 @@ if (container && Component) {
|
|
|
2213
2742
|
* @example
|
|
2214
2743
|
* If @mcp-use/inspector is installed:
|
|
2215
2744
|
* - Inspector UI available at http://localhost:PORT/inspector
|
|
2216
|
-
* - Automatically connects to http://localhost:PORT/mcp
|
|
2745
|
+
* - Automatically connects to http://localhost:PORT/mcp (or /sse)
|
|
2217
2746
|
*
|
|
2218
2747
|
* If not installed:
|
|
2219
2748
|
* - Server continues to function normally
|
|
@@ -2232,7 +2761,14 @@ if (container && Component) {
|
|
|
2232
2761
|
}
|
|
2233
2762
|
try {
|
|
2234
2763
|
const { mountInspector } = await import("@mcp-use/inspector");
|
|
2235
|
-
|
|
2764
|
+
const mcpUrl = `http://${this.serverHost}:${this.serverPort}/mcp`;
|
|
2765
|
+
const autoConnectConfig = JSON.stringify({
|
|
2766
|
+
url: mcpUrl,
|
|
2767
|
+
name: "Local MCP Server",
|
|
2768
|
+
transportType: "sse",
|
|
2769
|
+
connectionType: "Direct"
|
|
2770
|
+
});
|
|
2771
|
+
mountInspector(this.app, { autoConnectUrl: autoConnectConfig });
|
|
2236
2772
|
this.inspectorMounted = true;
|
|
2237
2773
|
console.log(
|
|
2238
2774
|
`[INSPECTOR] UI available at http://${this.serverHost}:${this.serverPort}/inspector`
|
|
@@ -2556,7 +3092,9 @@ function createMCPServer(name, config = {}) {
|
|
|
2556
3092
|
version: config.version || "1.0.0",
|
|
2557
3093
|
description: config.description,
|
|
2558
3094
|
host: config.host,
|
|
2559
|
-
baseUrl: config.baseUrl
|
|
3095
|
+
baseUrl: config.baseUrl,
|
|
3096
|
+
allowedOrigins: config.allowedOrigins,
|
|
3097
|
+
sessionIdleTimeoutMs: config.sessionIdleTimeoutMs
|
|
2560
3098
|
});
|
|
2561
3099
|
return instance;
|
|
2562
3100
|
}
|