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
package/dist/src/server/index.js
CHANGED
|
@@ -70,7 +70,8 @@ function createAppsSdkResource(uri, htmlTemplate, metadata) {
|
|
|
70
70
|
}
|
|
71
71
|
__name(createAppsSdkResource, "createAppsSdkResource");
|
|
72
72
|
function createUIResourceFromDefinition(definition, params, config) {
|
|
73
|
-
const
|
|
73
|
+
const buildIdPart = config.buildId ? `-${config.buildId}` : "";
|
|
74
|
+
const uri = definition.type === "appsSdk" ? `ui://widget/${definition.name}${buildIdPart}.html` : `ui://widget/${definition.name}${buildIdPart}`;
|
|
74
75
|
const encoding = definition.encoding || "text";
|
|
75
76
|
switch (definition.type) {
|
|
76
77
|
case "externalUrl": {
|
|
@@ -262,15 +263,46 @@ async function adaptConnectMiddleware(connectMiddleware, middlewarePath) {
|
|
|
262
263
|
__name(adaptConnectMiddleware, "adaptConnectMiddleware");
|
|
263
264
|
|
|
264
265
|
// src/server/logging.ts
|
|
266
|
+
var isDeno = typeof globalThis.Deno !== "undefined";
|
|
267
|
+
function getEnv(key) {
|
|
268
|
+
if (isDeno) {
|
|
269
|
+
return globalThis.Deno.env.get(key);
|
|
270
|
+
}
|
|
271
|
+
return typeof process !== "undefined" && process.env ? process.env[key] : void 0;
|
|
272
|
+
}
|
|
273
|
+
__name(getEnv, "getEnv");
|
|
274
|
+
function isDebugMode() {
|
|
275
|
+
const debugEnv = getEnv("DEBUG");
|
|
276
|
+
return debugEnv !== void 0 && debugEnv !== "" && debugEnv !== "0" && debugEnv.toLowerCase() !== "false";
|
|
277
|
+
}
|
|
278
|
+
__name(isDebugMode, "isDebugMode");
|
|
279
|
+
function formatForLogging(obj) {
|
|
280
|
+
try {
|
|
281
|
+
return JSON.stringify(obj, null, 2);
|
|
282
|
+
} catch {
|
|
283
|
+
return String(obj);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
__name(formatForLogging, "formatForLogging");
|
|
265
287
|
async function requestLogger(c, next) {
|
|
266
288
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
|
|
267
289
|
const method = c.req.method;
|
|
268
290
|
const url = c.req.url;
|
|
269
|
-
|
|
270
|
-
|
|
291
|
+
const debugMode = isDebugMode();
|
|
292
|
+
let requestBody = null;
|
|
293
|
+
let requestHeaders = {};
|
|
294
|
+
if (debugMode) {
|
|
295
|
+
const allHeaders = c.req.header();
|
|
296
|
+
if (allHeaders) {
|
|
297
|
+
requestHeaders = allHeaders;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
271
301
|
try {
|
|
272
302
|
const clonedRequest = c.req.raw.clone();
|
|
273
|
-
|
|
303
|
+
requestBody = await clonedRequest.json().catch(() => {
|
|
304
|
+
return clonedRequest.text().catch(() => null);
|
|
305
|
+
});
|
|
274
306
|
} catch {
|
|
275
307
|
}
|
|
276
308
|
}
|
|
@@ -287,26 +319,89 @@ async function requestLogger(c, next) {
|
|
|
287
319
|
statusColor = "\x1B[35m";
|
|
288
320
|
}
|
|
289
321
|
let logMessage = `[${timestamp}] ${method} \x1B[1m${new URL(url).pathname}\x1B[0m`;
|
|
290
|
-
if (method === "POST" && url.includes("/mcp") &&
|
|
291
|
-
logMessage += ` \x1B[1m[${
|
|
322
|
+
if (method === "POST" && url.includes("/mcp") && requestBody?.method) {
|
|
323
|
+
logMessage += ` \x1B[1m[${requestBody.method}]\x1B[0m`;
|
|
292
324
|
}
|
|
293
325
|
logMessage += ` ${statusColor}${statusCode}\x1B[0m`;
|
|
294
326
|
console.log(logMessage);
|
|
327
|
+
if (debugMode) {
|
|
328
|
+
console.log("\n\x1B[36m" + "=".repeat(80) + "\x1B[0m");
|
|
329
|
+
console.log("\x1B[1m\x1B[36m[DEBUG] Request Details\x1B[0m");
|
|
330
|
+
console.log("\x1B[36m" + "-".repeat(80) + "\x1B[0m");
|
|
331
|
+
if (Object.keys(requestHeaders).length > 0) {
|
|
332
|
+
console.log("\x1B[33mRequest Headers:\x1B[0m");
|
|
333
|
+
console.log(formatForLogging(requestHeaders));
|
|
334
|
+
}
|
|
335
|
+
if (requestBody !== null) {
|
|
336
|
+
console.log("\x1B[33mRequest Body:\x1B[0m");
|
|
337
|
+
if (typeof requestBody === "string") {
|
|
338
|
+
console.log(requestBody);
|
|
339
|
+
} else {
|
|
340
|
+
console.log(formatForLogging(requestBody));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const responseHeaders = {};
|
|
344
|
+
c.res.headers.forEach((value, key) => {
|
|
345
|
+
responseHeaders[key] = value;
|
|
346
|
+
});
|
|
347
|
+
if (Object.keys(responseHeaders).length > 0) {
|
|
348
|
+
console.log("\x1B[33mResponse Headers:\x1B[0m");
|
|
349
|
+
console.log(formatForLogging(responseHeaders));
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
if (c.res.body !== null && c.res.body !== void 0) {
|
|
353
|
+
try {
|
|
354
|
+
const clonedResponse = c.res.clone();
|
|
355
|
+
const responseBody = await clonedResponse.text().catch(() => null);
|
|
356
|
+
if (responseBody !== null && responseBody.length > 0) {
|
|
357
|
+
console.log("\x1B[33mResponse Body:\x1B[0m");
|
|
358
|
+
try {
|
|
359
|
+
const jsonBody = JSON.parse(responseBody);
|
|
360
|
+
console.log(formatForLogging(jsonBody));
|
|
361
|
+
} catch {
|
|
362
|
+
const maxLength = 1e4;
|
|
363
|
+
if (responseBody.length > maxLength) {
|
|
364
|
+
console.log(
|
|
365
|
+
responseBody.substring(0, maxLength) + `
|
|
366
|
+
... (truncated, ${responseBody.length - maxLength} more characters)`
|
|
367
|
+
);
|
|
368
|
+
} else {
|
|
369
|
+
console.log(responseBody);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
console.log("\x1B[33mResponse Body:\x1B[0m (empty)");
|
|
374
|
+
}
|
|
375
|
+
} catch (cloneError) {
|
|
376
|
+
console.log("\x1B[33mResponse Body:\x1B[0m (unable to clone/read)");
|
|
377
|
+
}
|
|
378
|
+
} else {
|
|
379
|
+
console.log("\x1B[33mResponse Body:\x1B[0m (no body)");
|
|
380
|
+
}
|
|
381
|
+
} catch (error) {
|
|
382
|
+
console.log("\x1B[33mResponse Body:\x1B[0m (unable to read)");
|
|
383
|
+
}
|
|
384
|
+
console.log("\x1B[36m" + "=".repeat(80) + "\x1B[0m\n");
|
|
385
|
+
}
|
|
295
386
|
}
|
|
296
387
|
__name(requestLogger, "requestLogger");
|
|
297
388
|
|
|
298
389
|
// src/server/mcp-server.ts
|
|
390
|
+
function generateUUID() {
|
|
391
|
+
return globalThis.crypto.randomUUID();
|
|
392
|
+
}
|
|
393
|
+
__name(generateUUID, "generateUUID");
|
|
299
394
|
var TMP_MCP_USE_DIR = ".mcp-use";
|
|
300
|
-
var
|
|
301
|
-
function
|
|
302
|
-
if (
|
|
395
|
+
var isDeno2 = typeof globalThis.Deno !== "undefined";
|
|
396
|
+
function getEnv2(key) {
|
|
397
|
+
if (isDeno2) {
|
|
303
398
|
return globalThis.Deno.env.get(key);
|
|
304
399
|
}
|
|
305
400
|
return process.env[key];
|
|
306
401
|
}
|
|
307
|
-
__name(
|
|
402
|
+
__name(getEnv2, "getEnv");
|
|
308
403
|
function getCwd() {
|
|
309
|
-
if (
|
|
404
|
+
if (isDeno2) {
|
|
310
405
|
return globalThis.Deno.cwd();
|
|
311
406
|
}
|
|
312
407
|
return process.cwd();
|
|
@@ -314,7 +409,7 @@ function getCwd() {
|
|
|
314
409
|
__name(getCwd, "getCwd");
|
|
315
410
|
var fsHelpers = {
|
|
316
411
|
async readFileSync(path, encoding = "utf8") {
|
|
317
|
-
if (
|
|
412
|
+
if (isDeno2) {
|
|
318
413
|
return await globalThis.Deno.readTextFile(path);
|
|
319
414
|
}
|
|
320
415
|
const { readFileSync } = await import("fs");
|
|
@@ -322,7 +417,7 @@ var fsHelpers = {
|
|
|
322
417
|
return typeof result === "string" ? result : result.toString(encoding);
|
|
323
418
|
},
|
|
324
419
|
async readFile(path) {
|
|
325
|
-
if (
|
|
420
|
+
if (isDeno2) {
|
|
326
421
|
const data = await globalThis.Deno.readFile(path);
|
|
327
422
|
return data.buffer;
|
|
328
423
|
}
|
|
@@ -334,7 +429,7 @@ var fsHelpers = {
|
|
|
334
429
|
);
|
|
335
430
|
},
|
|
336
431
|
async existsSync(path) {
|
|
337
|
-
if (
|
|
432
|
+
if (isDeno2) {
|
|
338
433
|
try {
|
|
339
434
|
await globalThis.Deno.stat(path);
|
|
340
435
|
return true;
|
|
@@ -346,7 +441,7 @@ var fsHelpers = {
|
|
|
346
441
|
return existsSync(path);
|
|
347
442
|
},
|
|
348
443
|
async readdirSync(path) {
|
|
349
|
-
if (
|
|
444
|
+
if (isDeno2) {
|
|
350
445
|
const entries = [];
|
|
351
446
|
for await (const entry of globalThis.Deno.readDir(path)) {
|
|
352
447
|
entries.push(entry.name);
|
|
@@ -359,7 +454,7 @@ var fsHelpers = {
|
|
|
359
454
|
};
|
|
360
455
|
var pathHelpers = {
|
|
361
456
|
join(...paths) {
|
|
362
|
-
if (
|
|
457
|
+
if (isDeno2) {
|
|
363
458
|
return paths.join("/").replace(/\/+/g, "/");
|
|
364
459
|
}
|
|
365
460
|
return paths.join("/").replace(/\/+/g, "/");
|
|
@@ -391,6 +486,9 @@ var McpServer = class {
|
|
|
391
486
|
registeredTools = [];
|
|
392
487
|
registeredPrompts = [];
|
|
393
488
|
registeredResources = [];
|
|
489
|
+
buildId;
|
|
490
|
+
sessions = /* @__PURE__ */ new Map();
|
|
491
|
+
idleCleanupInterval;
|
|
394
492
|
/**
|
|
395
493
|
* Creates a new MCP server instance with Hono integration
|
|
396
494
|
*
|
|
@@ -423,7 +521,9 @@ var McpServer = class {
|
|
|
423
521
|
"mcp-session-id",
|
|
424
522
|
"X-Proxy-Token",
|
|
425
523
|
"X-Target-URL"
|
|
426
|
-
]
|
|
524
|
+
],
|
|
525
|
+
// Expose mcp-session-id so browser clients can read it from responses
|
|
526
|
+
exposeHeaders: ["mcp-session-id"]
|
|
427
527
|
})
|
|
428
528
|
);
|
|
429
529
|
this.app.use("*", requestLogger);
|
|
@@ -486,7 +586,7 @@ var McpServer = class {
|
|
|
486
586
|
if (this.serverBaseUrl) {
|
|
487
587
|
return this.serverBaseUrl;
|
|
488
588
|
}
|
|
489
|
-
const mcpUrl =
|
|
589
|
+
const mcpUrl = getEnv2("MCP_URL");
|
|
490
590
|
if (mcpUrl) {
|
|
491
591
|
return mcpUrl;
|
|
492
592
|
}
|
|
@@ -663,6 +763,11 @@ var McpServer = class {
|
|
|
663
763
|
*/
|
|
664
764
|
tool(toolDefinition) {
|
|
665
765
|
const inputSchema = this.createParamsSchema(toolDefinition.inputs || []);
|
|
766
|
+
const context = {
|
|
767
|
+
sample: /* @__PURE__ */ __name(async (params, options) => {
|
|
768
|
+
return await this.createMessage(params, options);
|
|
769
|
+
}, "sample")
|
|
770
|
+
};
|
|
666
771
|
this.server.registerTool(
|
|
667
772
|
toolDefinition.name,
|
|
668
773
|
{
|
|
@@ -673,6 +778,9 @@ var McpServer = class {
|
|
|
673
778
|
_meta: toolDefinition._meta
|
|
674
779
|
},
|
|
675
780
|
async (params) => {
|
|
781
|
+
if (toolDefinition.cb.length >= 2) {
|
|
782
|
+
return await toolDefinition.cb(params, context);
|
|
783
|
+
}
|
|
676
784
|
return await toolDefinition.cb(params);
|
|
677
785
|
}
|
|
678
786
|
);
|
|
@@ -729,6 +837,46 @@ var McpServer = class {
|
|
|
729
837
|
this.registeredPrompts.push(promptDefinition.name);
|
|
730
838
|
return this;
|
|
731
839
|
}
|
|
840
|
+
/**
|
|
841
|
+
* Request LLM sampling from connected clients.
|
|
842
|
+
*
|
|
843
|
+
* This method allows server tools to request LLM completions from clients
|
|
844
|
+
* that support the sampling capability. The client will handle model selection,
|
|
845
|
+
* user approval (human-in-the-loop), and return the generated response.
|
|
846
|
+
*
|
|
847
|
+
* @param params - Sampling request parameters including messages, model preferences, etc.
|
|
848
|
+
* @param options - Optional request options (timeouts, cancellation, etc.)
|
|
849
|
+
* @returns Promise resolving to the generated message from the client's LLM
|
|
850
|
+
*
|
|
851
|
+
* @example
|
|
852
|
+
* ```typescript
|
|
853
|
+
* // In a tool callback
|
|
854
|
+
* server.tool({
|
|
855
|
+
* name: 'analyze-sentiment',
|
|
856
|
+
* description: 'Analyze sentiment using LLM',
|
|
857
|
+
* inputs: [{ name: 'text', type: 'string', required: true }],
|
|
858
|
+
* cb: async (params, ctx) => {
|
|
859
|
+
* const result = await ctx.sample({
|
|
860
|
+
* messages: [{
|
|
861
|
+
* role: 'user',
|
|
862
|
+
* content: { type: 'text', text: `Analyze sentiment: ${params.text}` }
|
|
863
|
+
* }],
|
|
864
|
+
* modelPreferences: {
|
|
865
|
+
* intelligencePriority: 0.8,
|
|
866
|
+
* speedPriority: 0.5
|
|
867
|
+
* }
|
|
868
|
+
* });
|
|
869
|
+
* return {
|
|
870
|
+
* content: [{ type: 'text', text: result.content.text }]
|
|
871
|
+
* };
|
|
872
|
+
* }
|
|
873
|
+
* })
|
|
874
|
+
* ```
|
|
875
|
+
*/
|
|
876
|
+
async createMessage(params, options) {
|
|
877
|
+
console.log("createMessage", params, options);
|
|
878
|
+
return await this.server.server.createMessage(params, options);
|
|
879
|
+
}
|
|
732
880
|
/**
|
|
733
881
|
* Register a UI widget as both a tool and a resource
|
|
734
882
|
*
|
|
@@ -802,19 +950,19 @@ var McpServer = class {
|
|
|
802
950
|
let mimeType;
|
|
803
951
|
switch (definition.type) {
|
|
804
952
|
case "externalUrl":
|
|
805
|
-
resourceUri =
|
|
953
|
+
resourceUri = this.generateWidgetUri(definition.widget);
|
|
806
954
|
mimeType = "text/uri-list";
|
|
807
955
|
break;
|
|
808
956
|
case "rawHtml":
|
|
809
|
-
resourceUri =
|
|
957
|
+
resourceUri = this.generateWidgetUri(definition.name);
|
|
810
958
|
mimeType = "text/html";
|
|
811
959
|
break;
|
|
812
960
|
case "remoteDom":
|
|
813
|
-
resourceUri =
|
|
961
|
+
resourceUri = this.generateWidgetUri(definition.name);
|
|
814
962
|
mimeType = "application/vnd.mcp-ui.remote-dom+javascript";
|
|
815
963
|
break;
|
|
816
964
|
case "appsSdk":
|
|
817
|
-
resourceUri =
|
|
965
|
+
resourceUri = this.generateWidgetUri(definition.name, ".html");
|
|
818
966
|
mimeType = "text/html+skybridge";
|
|
819
967
|
break;
|
|
820
968
|
default:
|
|
@@ -833,16 +981,19 @@ var McpServer = class {
|
|
|
833
981
|
readCallback: /* @__PURE__ */ __name(async () => {
|
|
834
982
|
const params = definition.type === "externalUrl" ? this.applyDefaultProps(definition.props) : {};
|
|
835
983
|
const uiResource = this.createWidgetUIResource(definition, params);
|
|
984
|
+
uiResource.resource.uri = resourceUri;
|
|
836
985
|
return {
|
|
837
986
|
contents: [uiResource.resource]
|
|
838
987
|
};
|
|
839
988
|
}, "readCallback")
|
|
840
989
|
});
|
|
841
990
|
if (definition.type === "appsSdk") {
|
|
991
|
+
const buildIdPart = this.buildId ? `-${this.buildId}` : "";
|
|
992
|
+
const uriTemplate = `ui://widget/${definition.name}${buildIdPart}-{id}.html`;
|
|
842
993
|
this.resourceTemplate({
|
|
843
994
|
name: `${definition.name}-dynamic`,
|
|
844
995
|
resourceTemplate: {
|
|
845
|
-
uriTemplate
|
|
996
|
+
uriTemplate,
|
|
846
997
|
name: definition.title || definition.name,
|
|
847
998
|
description: definition.description,
|
|
848
999
|
mimeType
|
|
@@ -853,6 +1004,7 @@ var McpServer = class {
|
|
|
853
1004
|
annotations: definition.annotations,
|
|
854
1005
|
readCallback: /* @__PURE__ */ __name(async (uri, params) => {
|
|
855
1006
|
const uiResource = this.createWidgetUIResource(definition, {});
|
|
1007
|
+
uiResource.resource.uri = uri.toString();
|
|
856
1008
|
return {
|
|
857
1009
|
contents: [uiResource.resource]
|
|
858
1010
|
};
|
|
@@ -884,7 +1036,11 @@ var McpServer = class {
|
|
|
884
1036
|
const uiResource = this.createWidgetUIResource(definition, params);
|
|
885
1037
|
if (definition.type === "appsSdk") {
|
|
886
1038
|
const randomId = Math.random().toString(36).substring(2, 15);
|
|
887
|
-
const uniqueUri =
|
|
1039
|
+
const uniqueUri = this.generateWidgetUri(
|
|
1040
|
+
definition.name,
|
|
1041
|
+
".html",
|
|
1042
|
+
randomId
|
|
1043
|
+
);
|
|
888
1044
|
const uniqueToolMetadata = {
|
|
889
1045
|
...toolMetadata,
|
|
890
1046
|
"openai/outputTemplate": uniqueUri
|
|
@@ -941,7 +1097,8 @@ var McpServer = class {
|
|
|
941
1097
|
}
|
|
942
1098
|
const urlConfig = {
|
|
943
1099
|
baseUrl: configBaseUrl,
|
|
944
|
-
port: configPort
|
|
1100
|
+
port: configPort,
|
|
1101
|
+
buildId: this.buildId
|
|
945
1102
|
};
|
|
946
1103
|
const uiResource = createUIResourceFromDefinition(
|
|
947
1104
|
definition,
|
|
@@ -956,6 +1113,25 @@ var McpServer = class {
|
|
|
956
1113
|
}
|
|
957
1114
|
return uiResource;
|
|
958
1115
|
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Generate a widget URI with optional build ID for cache busting
|
|
1118
|
+
*
|
|
1119
|
+
* @private
|
|
1120
|
+
* @param widgetName - Widget name/identifier
|
|
1121
|
+
* @param extension - Optional file extension (e.g., '.html')
|
|
1122
|
+
* @param suffix - Optional suffix (e.g., random ID for dynamic URIs)
|
|
1123
|
+
* @returns Widget URI with build ID if available
|
|
1124
|
+
*/
|
|
1125
|
+
generateWidgetUri(widgetName, extension = "", suffix = "") {
|
|
1126
|
+
const parts = [widgetName];
|
|
1127
|
+
if (this.buildId) {
|
|
1128
|
+
parts.push(this.buildId);
|
|
1129
|
+
}
|
|
1130
|
+
if (suffix) {
|
|
1131
|
+
parts.push(suffix);
|
|
1132
|
+
}
|
|
1133
|
+
return `ui://widget/${parts.join("-")}${extension}`;
|
|
1134
|
+
}
|
|
959
1135
|
/**
|
|
960
1136
|
* Build a complete URL for a widget including query parameters
|
|
961
1137
|
*
|
|
@@ -1032,7 +1208,7 @@ var McpServer = class {
|
|
|
1032
1208
|
* @returns true if in production mode, false otherwise
|
|
1033
1209
|
*/
|
|
1034
1210
|
isProductionMode() {
|
|
1035
|
-
return
|
|
1211
|
+
return getEnv2("NODE_ENV") === "production";
|
|
1036
1212
|
}
|
|
1037
1213
|
/**
|
|
1038
1214
|
* Read build manifest file
|
|
@@ -1043,7 +1219,7 @@ var McpServer = class {
|
|
|
1043
1219
|
async readBuildManifest() {
|
|
1044
1220
|
try {
|
|
1045
1221
|
const manifestPath = pathHelpers.join(
|
|
1046
|
-
|
|
1222
|
+
isDeno2 ? "." : getCwd(),
|
|
1047
1223
|
"dist",
|
|
1048
1224
|
"mcp-use.json"
|
|
1049
1225
|
);
|
|
@@ -1065,7 +1241,7 @@ var McpServer = class {
|
|
|
1065
1241
|
* @returns Promise that resolves when all widgets are mounted
|
|
1066
1242
|
*/
|
|
1067
1243
|
async mountWidgets(options) {
|
|
1068
|
-
if (this.isProductionMode() ||
|
|
1244
|
+
if (this.isProductionMode() || isDeno2) {
|
|
1069
1245
|
console.log("[WIDGETS] Mounting widgets in production mode");
|
|
1070
1246
|
await this.mountWidgetsProduction(options);
|
|
1071
1247
|
} else {
|
|
@@ -1485,7 +1661,7 @@ if (container && Component) {
|
|
|
1485
1661
|
async mountWidgetsProduction(options) {
|
|
1486
1662
|
const baseRoute = options?.baseRoute || "/mcp-use/widgets";
|
|
1487
1663
|
const widgetsDir = pathHelpers.join(
|
|
1488
|
-
|
|
1664
|
+
isDeno2 ? "." : getCwd(),
|
|
1489
1665
|
"dist",
|
|
1490
1666
|
"resources",
|
|
1491
1667
|
"widgets"
|
|
@@ -1501,6 +1677,10 @@ if (container && Component) {
|
|
|
1501
1677
|
"utf8"
|
|
1502
1678
|
);
|
|
1503
1679
|
const manifest = JSON.parse(manifestContent);
|
|
1680
|
+
if (manifest.buildId && typeof manifest.buildId === "string") {
|
|
1681
|
+
this.buildId = manifest.buildId;
|
|
1682
|
+
console.log(`[WIDGETS] Build ID: ${this.buildId}`);
|
|
1683
|
+
}
|
|
1504
1684
|
if (manifest.widgets && typeof manifest.widgets === "object" && !Array.isArray(manifest.widgets)) {
|
|
1505
1685
|
widgets = Object.keys(manifest.widgets);
|
|
1506
1686
|
widgetsMetadata = manifest.widgets;
|
|
@@ -1633,6 +1813,7 @@ if (container && Component) {
|
|
|
1633
1813
|
resource_domains: [
|
|
1634
1814
|
"https://*.oaistatic.com",
|
|
1635
1815
|
"https://*.oaiusercontent.com",
|
|
1816
|
+
"https://*.openai.com",
|
|
1636
1817
|
// always also add the base url of the server
|
|
1637
1818
|
...this.getServerBaseUrl() ? [this.getServerBaseUrl()] : [],
|
|
1638
1819
|
...metadata.appsSdkMetadata?.["openai/widgetCSP"]?.resource_domains || []
|
|
@@ -1665,12 +1846,11 @@ if (container && Component) {
|
|
|
1665
1846
|
});
|
|
1666
1847
|
}
|
|
1667
1848
|
/**
|
|
1668
|
-
* Mount MCP server endpoints at /mcp
|
|
1849
|
+
* Mount MCP server endpoints at /mcp and /sse
|
|
1669
1850
|
*
|
|
1670
1851
|
* Sets up the HTTP transport layer for the MCP server, creating endpoints for
|
|
1671
1852
|
* Server-Sent Events (SSE) streaming, POST message handling, and DELETE session cleanup.
|
|
1672
|
-
*
|
|
1673
|
-
* concurrent client connections.
|
|
1853
|
+
* Transports are reused per session ID to maintain state across requests.
|
|
1674
1854
|
*
|
|
1675
1855
|
* This method is called automatically when the server starts listening and ensures
|
|
1676
1856
|
* that MCP clients can communicate with the server over HTTP.
|
|
@@ -1680,14 +1860,91 @@ if (container && Component) {
|
|
|
1680
1860
|
*
|
|
1681
1861
|
* @example
|
|
1682
1862
|
* Endpoints created:
|
|
1683
|
-
* - GET /mcp - SSE streaming endpoint for real-time communication
|
|
1684
|
-
* - POST /mcp - Message handling endpoint for MCP protocol messages
|
|
1685
|
-
* - DELETE /mcp - Session cleanup endpoint
|
|
1863
|
+
* - GET /mcp, GET /sse - SSE streaming endpoint for real-time communication
|
|
1864
|
+
* - POST /mcp, POST /sse - Message handling endpoint for MCP protocol messages
|
|
1865
|
+
* - DELETE /mcp, DELETE /sse - Session cleanup endpoint
|
|
1686
1866
|
*/
|
|
1687
1867
|
async mountMcp() {
|
|
1688
1868
|
if (this.mcpMounted) return;
|
|
1689
1869
|
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
1690
|
-
const
|
|
1870
|
+
const idleTimeoutMs = this.config.sessionIdleTimeoutMs ?? 3e5;
|
|
1871
|
+
const createNewTransport = /* @__PURE__ */ __name(async (closeOldSessionId) => {
|
|
1872
|
+
if (closeOldSessionId && this.sessions.has(closeOldSessionId)) {
|
|
1873
|
+
try {
|
|
1874
|
+
this.sessions.get(closeOldSessionId).transport.close();
|
|
1875
|
+
} catch (error) {
|
|
1876
|
+
}
|
|
1877
|
+
this.sessions.delete(closeOldSessionId);
|
|
1878
|
+
}
|
|
1879
|
+
const isProduction = this.isProductionMode();
|
|
1880
|
+
let allowedOrigins = this.config.allowedOrigins;
|
|
1881
|
+
let enableDnsRebindingProtection = false;
|
|
1882
|
+
if (isProduction) {
|
|
1883
|
+
if (allowedOrigins !== void 0) {
|
|
1884
|
+
enableDnsRebindingProtection = allowedOrigins.length > 0;
|
|
1885
|
+
}
|
|
1886
|
+
} else {
|
|
1887
|
+
allowedOrigins = void 0;
|
|
1888
|
+
enableDnsRebindingProtection = false;
|
|
1889
|
+
}
|
|
1890
|
+
const transport = new StreamableHTTPServerTransport({
|
|
1891
|
+
sessionIdGenerator: /* @__PURE__ */ __name(() => generateUUID(), "sessionIdGenerator"),
|
|
1892
|
+
enableJsonResponse: true,
|
|
1893
|
+
allowedOrigins,
|
|
1894
|
+
enableDnsRebindingProtection,
|
|
1895
|
+
onsessioninitialized: /* @__PURE__ */ __name((id) => {
|
|
1896
|
+
if (id) {
|
|
1897
|
+
this.sessions.set(id, {
|
|
1898
|
+
transport,
|
|
1899
|
+
lastAccessedAt: Date.now()
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
}, "onsessioninitialized"),
|
|
1903
|
+
onsessionclosed: /* @__PURE__ */ __name((id) => {
|
|
1904
|
+
if (id) {
|
|
1905
|
+
this.sessions.delete(id);
|
|
1906
|
+
}
|
|
1907
|
+
}, "onsessionclosed")
|
|
1908
|
+
});
|
|
1909
|
+
await this.server.connect(transport);
|
|
1910
|
+
return transport;
|
|
1911
|
+
}, "createNewTransport");
|
|
1912
|
+
const getOrCreateTransport = /* @__PURE__ */ __name(async (sessionId, isInit = false) => {
|
|
1913
|
+
if (isInit) {
|
|
1914
|
+
return await createNewTransport(sessionId);
|
|
1915
|
+
}
|
|
1916
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
1917
|
+
const session = this.sessions.get(sessionId);
|
|
1918
|
+
session.lastAccessedAt = Date.now();
|
|
1919
|
+
return session.transport;
|
|
1920
|
+
}
|
|
1921
|
+
if (sessionId) {
|
|
1922
|
+
const autoCreate = this.config.autoCreateSessionOnInvalidId ?? true;
|
|
1923
|
+
if (autoCreate) {
|
|
1924
|
+
console.warn(
|
|
1925
|
+
`[MCP] Session ${sessionId} not found (expired or invalid), auto-creating new session for seamless reconnection`
|
|
1926
|
+
);
|
|
1927
|
+
return await createNewTransport(sessionId);
|
|
1928
|
+
} else {
|
|
1929
|
+
return null;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
return null;
|
|
1933
|
+
}, "getOrCreateTransport");
|
|
1934
|
+
if (idleTimeoutMs > 0 && !this.idleCleanupInterval) {
|
|
1935
|
+
this.idleCleanupInterval = setInterval(() => {
|
|
1936
|
+
const now = Date.now();
|
|
1937
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
1938
|
+
if (now - session.lastAccessedAt > idleTimeoutMs) {
|
|
1939
|
+
try {
|
|
1940
|
+
session.transport.close();
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
}
|
|
1943
|
+
this.sessions.delete(sessionId);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}, 6e4);
|
|
1947
|
+
}
|
|
1691
1948
|
const createExpressLikeObjects = /* @__PURE__ */ __name((c) => {
|
|
1692
1949
|
const req = c.req.raw;
|
|
1693
1950
|
const responseBody = [];
|
|
@@ -1779,7 +2036,7 @@ if (container && Component) {
|
|
|
1779
2036
|
getResponse: /* @__PURE__ */ __name(() => {
|
|
1780
2037
|
if (ended) {
|
|
1781
2038
|
if (responseBody.length > 0) {
|
|
1782
|
-
const body =
|
|
2039
|
+
const body = isDeno2 ? Buffer.concat(responseBody) : Buffer.concat(responseBody);
|
|
1783
2040
|
return new Response(body, {
|
|
1784
2041
|
status: statusCode,
|
|
1785
2042
|
headers
|
|
@@ -1795,164 +2052,250 @@ if (container && Component) {
|
|
|
1795
2052
|
}, "getResponse")
|
|
1796
2053
|
};
|
|
1797
2054
|
}, "createExpressLikeObjects");
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
2055
|
+
const mountEndpoint = /* @__PURE__ */ __name((endpoint) => {
|
|
2056
|
+
this.app.post(endpoint, async (c) => {
|
|
2057
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
2058
|
+
let body = {};
|
|
2059
|
+
try {
|
|
2060
|
+
body = await c.req.json();
|
|
2061
|
+
expressReq.body = body;
|
|
2062
|
+
} catch {
|
|
2063
|
+
expressReq.body = {};
|
|
2064
|
+
}
|
|
2065
|
+
const isInit = body?.method === "initialize";
|
|
2066
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2067
|
+
const transport = await getOrCreateTransport(sessionId, isInit);
|
|
2068
|
+
if (!transport) {
|
|
2069
|
+
if (sessionId) {
|
|
2070
|
+
return c.json(
|
|
2071
|
+
{
|
|
2072
|
+
jsonrpc: "2.0",
|
|
2073
|
+
error: {
|
|
2074
|
+
code: -32e3,
|
|
2075
|
+
message: "Session not found or expired"
|
|
2076
|
+
},
|
|
2077
|
+
// Notifications don't have an id, but we include null for consistency
|
|
2078
|
+
id: body?.id ?? null
|
|
2079
|
+
},
|
|
2080
|
+
404
|
|
2081
|
+
);
|
|
2082
|
+
} else {
|
|
2083
|
+
return c.json(
|
|
2084
|
+
{
|
|
2085
|
+
jsonrpc: "2.0",
|
|
2086
|
+
error: {
|
|
2087
|
+
code: -32e3,
|
|
2088
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2089
|
+
},
|
|
2090
|
+
id: body?.id ?? null
|
|
2091
|
+
},
|
|
2092
|
+
400
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
2097
|
+
this.sessions.get(sessionId).lastAccessedAt = Date.now();
|
|
2098
|
+
}
|
|
2099
|
+
if (expressRes._closeHandler) {
|
|
2100
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
2101
|
+
transport.close();
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
await this.waitForRequestComplete(
|
|
2105
|
+
transport,
|
|
2106
|
+
expressReq,
|
|
2107
|
+
expressRes,
|
|
2108
|
+
expressReq.body
|
|
2109
|
+
);
|
|
2110
|
+
const response = getResponse();
|
|
2111
|
+
if (response) {
|
|
2112
|
+
return response;
|
|
2113
|
+
}
|
|
2114
|
+
return c.text("", 200);
|
|
1808
2115
|
});
|
|
1809
|
-
|
|
2116
|
+
this.app.get(endpoint, async (c) => {
|
|
2117
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2118
|
+
const transport = await getOrCreateTransport(sessionId, false);
|
|
2119
|
+
if (!transport) {
|
|
2120
|
+
if (sessionId) {
|
|
2121
|
+
return c.json(
|
|
2122
|
+
{
|
|
2123
|
+
jsonrpc: "2.0",
|
|
2124
|
+
error: {
|
|
2125
|
+
code: -32e3,
|
|
2126
|
+
message: "Session not found or expired"
|
|
2127
|
+
},
|
|
2128
|
+
id: null
|
|
2129
|
+
},
|
|
2130
|
+
404
|
|
2131
|
+
);
|
|
2132
|
+
} else {
|
|
2133
|
+
return c.json(
|
|
2134
|
+
{
|
|
2135
|
+
jsonrpc: "2.0",
|
|
2136
|
+
error: {
|
|
2137
|
+
code: -32e3,
|
|
2138
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2139
|
+
},
|
|
2140
|
+
id: null
|
|
2141
|
+
},
|
|
2142
|
+
400
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
if (sessionId && this.sessions.has(sessionId)) {
|
|
2147
|
+
this.sessions.get(sessionId).lastAccessedAt = Date.now();
|
|
2148
|
+
}
|
|
1810
2149
|
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1811
2150
|
transport.close();
|
|
1812
2151
|
});
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
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
|
-
|
|
2152
|
+
const { readable, writable } = new globalThis.TransformStream();
|
|
2153
|
+
const writer = writable.getWriter();
|
|
2154
|
+
const encoder = new TextEncoder();
|
|
2155
|
+
let resolveResponse;
|
|
2156
|
+
const responsePromise = new Promise((resolve) => {
|
|
2157
|
+
resolveResponse = resolve;
|
|
2158
|
+
});
|
|
2159
|
+
let headersSent = false;
|
|
2160
|
+
const headers = {};
|
|
2161
|
+
let statusCode = 200;
|
|
2162
|
+
const expressRes = {
|
|
2163
|
+
statusCode: 200,
|
|
2164
|
+
headersSent: false,
|
|
2165
|
+
status: /* @__PURE__ */ __name((code) => {
|
|
2166
|
+
statusCode = code;
|
|
2167
|
+
expressRes.statusCode = code;
|
|
2168
|
+
return expressRes;
|
|
2169
|
+
}, "status"),
|
|
2170
|
+
setHeader: /* @__PURE__ */ __name((name, value) => {
|
|
2171
|
+
if (!headersSent) {
|
|
2172
|
+
headers[name] = Array.isArray(value) ? value.join(", ") : value;
|
|
2173
|
+
}
|
|
2174
|
+
}, "setHeader"),
|
|
2175
|
+
getHeader: /* @__PURE__ */ __name((name) => headers[name], "getHeader"),
|
|
2176
|
+
write: /* @__PURE__ */ __name((chunk) => {
|
|
2177
|
+
if (!headersSent) {
|
|
2178
|
+
headersSent = true;
|
|
2179
|
+
resolveResponse(
|
|
2180
|
+
new Response(readable, {
|
|
2181
|
+
status: statusCode,
|
|
2182
|
+
headers
|
|
2183
|
+
})
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
const data = typeof chunk === "string" ? encoder.encode(chunk) : chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
|
|
2187
|
+
writer.write(data);
|
|
2188
|
+
return true;
|
|
2189
|
+
}, "write"),
|
|
2190
|
+
end: /* @__PURE__ */ __name((chunk) => {
|
|
2191
|
+
if (chunk) {
|
|
2192
|
+
expressRes.write(chunk);
|
|
2193
|
+
}
|
|
2194
|
+
if (!headersSent) {
|
|
2195
|
+
headersSent = true;
|
|
2196
|
+
resolveResponse(
|
|
2197
|
+
new Response(null, {
|
|
2198
|
+
status: statusCode,
|
|
2199
|
+
headers
|
|
2200
|
+
})
|
|
2201
|
+
);
|
|
2202
|
+
writer.close();
|
|
2203
|
+
} else {
|
|
2204
|
+
writer.close();
|
|
2205
|
+
}
|
|
2206
|
+
}, "end"),
|
|
2207
|
+
on: /* @__PURE__ */ __name((event, handler) => {
|
|
2208
|
+
if (event === "close") {
|
|
2209
|
+
expressRes._closeHandler = handler;
|
|
2210
|
+
}
|
|
2211
|
+
}, "on"),
|
|
2212
|
+
once: /* @__PURE__ */ __name(() => {
|
|
2213
|
+
}, "once"),
|
|
2214
|
+
removeListener: /* @__PURE__ */ __name(() => {
|
|
2215
|
+
}, "removeListener"),
|
|
2216
|
+
writeHead: /* @__PURE__ */ __name((code, _headers) => {
|
|
2217
|
+
statusCode = code;
|
|
2218
|
+
expressRes.statusCode = code;
|
|
2219
|
+
if (_headers) {
|
|
2220
|
+
Object.assign(headers, _headers);
|
|
2221
|
+
}
|
|
2222
|
+
if (!headersSent) {
|
|
2223
|
+
headersSent = true;
|
|
2224
|
+
resolveResponse(
|
|
2225
|
+
new Response(readable, {
|
|
2226
|
+
status: statusCode,
|
|
2227
|
+
headers
|
|
2228
|
+
})
|
|
2229
|
+
);
|
|
2230
|
+
}
|
|
2231
|
+
return expressRes;
|
|
2232
|
+
}, "writeHead"),
|
|
2233
|
+
flushHeaders: /* @__PURE__ */ __name(() => {
|
|
2234
|
+
}, "flushHeaders")
|
|
2235
|
+
};
|
|
2236
|
+
const expressReq = {
|
|
2237
|
+
...c.req.raw,
|
|
2238
|
+
url: new URL(c.req.url).pathname + new URL(c.req.url).search,
|
|
2239
|
+
path: new URL(c.req.url).pathname,
|
|
2240
|
+
query: Object.fromEntries(new URL(c.req.url).searchParams),
|
|
2241
|
+
headers: c.req.header(),
|
|
2242
|
+
method: c.req.method
|
|
2243
|
+
};
|
|
2244
|
+
transport.handleRequest(expressReq, expressRes).catch((err) => {
|
|
2245
|
+
console.error("MCP Transport error:", err);
|
|
2246
|
+
try {
|
|
2247
|
+
writer.close();
|
|
2248
|
+
} catch {
|
|
1876
2249
|
}
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2250
|
+
});
|
|
2251
|
+
return responsePromise;
|
|
2252
|
+
});
|
|
2253
|
+
this.app.delete(endpoint, async (c) => {
|
|
2254
|
+
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
2255
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
2256
|
+
const transport = await getOrCreateTransport(sessionId, false);
|
|
2257
|
+
if (!transport) {
|
|
2258
|
+
if (sessionId) {
|
|
2259
|
+
return c.json(
|
|
2260
|
+
{
|
|
2261
|
+
jsonrpc: "2.0",
|
|
2262
|
+
error: {
|
|
2263
|
+
code: -32e3,
|
|
2264
|
+
message: "Session not found or expired"
|
|
2265
|
+
},
|
|
2266
|
+
id: null
|
|
2267
|
+
},
|
|
2268
|
+
404
|
|
1884
2269
|
);
|
|
1885
|
-
writer.close();
|
|
1886
2270
|
} else {
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
removeListener: /* @__PURE__ */ __name(() => {
|
|
1898
|
-
}, "removeListener"),
|
|
1899
|
-
writeHead: /* @__PURE__ */ __name((code, _headers) => {
|
|
1900
|
-
statusCode = code;
|
|
1901
|
-
expressRes.statusCode = code;
|
|
1902
|
-
if (_headers) {
|
|
1903
|
-
Object.assign(headers, _headers);
|
|
1904
|
-
}
|
|
1905
|
-
if (!headersSent) {
|
|
1906
|
-
headersSent = true;
|
|
1907
|
-
resolveResponse(
|
|
1908
|
-
new Response(readable, {
|
|
1909
|
-
status: statusCode,
|
|
1910
|
-
headers
|
|
1911
|
-
})
|
|
2271
|
+
return c.json(
|
|
2272
|
+
{
|
|
2273
|
+
jsonrpc: "2.0",
|
|
2274
|
+
error: {
|
|
2275
|
+
code: -32e3,
|
|
2276
|
+
message: "Bad Request: Mcp-Session-Id header is required"
|
|
2277
|
+
},
|
|
2278
|
+
id: null
|
|
2279
|
+
},
|
|
2280
|
+
400
|
|
1912
2281
|
);
|
|
1913
2282
|
}
|
|
1914
|
-
return expressRes;
|
|
1915
|
-
}, "writeHead"),
|
|
1916
|
-
flushHeaders: /* @__PURE__ */ __name(() => {
|
|
1917
|
-
}, "flushHeaders")
|
|
1918
|
-
};
|
|
1919
|
-
const expressReq = {
|
|
1920
|
-
...c.req.raw,
|
|
1921
|
-
url: new URL(c.req.url).pathname + new URL(c.req.url).search,
|
|
1922
|
-
path: new URL(c.req.url).pathname,
|
|
1923
|
-
query: Object.fromEntries(new URL(c.req.url).searchParams),
|
|
1924
|
-
headers: c.req.header(),
|
|
1925
|
-
method: c.req.method
|
|
1926
|
-
};
|
|
1927
|
-
await this.server.connect(transport);
|
|
1928
|
-
transport.handleRequest(expressReq, expressRes).catch((err) => {
|
|
1929
|
-
console.error("MCP Transport error:", err);
|
|
1930
|
-
try {
|
|
1931
|
-
writer.close();
|
|
1932
|
-
} catch {
|
|
1933
2283
|
}
|
|
2284
|
+
c.req.raw.signal?.addEventListener("abort", () => {
|
|
2285
|
+
transport.close();
|
|
2286
|
+
});
|
|
2287
|
+
await this.waitForRequestComplete(transport, expressReq, expressRes);
|
|
2288
|
+
const response = getResponse();
|
|
2289
|
+
if (response) {
|
|
2290
|
+
return response;
|
|
2291
|
+
}
|
|
2292
|
+
return c.text("", 200);
|
|
1934
2293
|
});
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
const { expressReq, expressRes, getResponse } = createExpressLikeObjects(c);
|
|
1939
|
-
const transport = new StreamableHTTPServerTransport({
|
|
1940
|
-
sessionIdGenerator: void 0,
|
|
1941
|
-
enableJsonResponse: true
|
|
1942
|
-
});
|
|
1943
|
-
c.req.raw.signal?.addEventListener("abort", () => {
|
|
1944
|
-
transport.close();
|
|
1945
|
-
});
|
|
1946
|
-
await this.server.connect(transport);
|
|
1947
|
-
await this.waitForRequestComplete(transport, expressReq, expressRes);
|
|
1948
|
-
const response = getResponse();
|
|
1949
|
-
if (response) {
|
|
1950
|
-
return response;
|
|
1951
|
-
}
|
|
1952
|
-
return c.text("", 200);
|
|
1953
|
-
});
|
|
2294
|
+
}, "mountEndpoint");
|
|
2295
|
+
mountEndpoint("/mcp");
|
|
2296
|
+
mountEndpoint("/sse");
|
|
1954
2297
|
this.mcpMounted = true;
|
|
1955
|
-
console.log(`[MCP] Server mounted at
|
|
2298
|
+
console.log(`[MCP] Server mounted at /mcp and /sse`);
|
|
1956
2299
|
}
|
|
1957
2300
|
/**
|
|
1958
2301
|
* Start the Hono server with MCP endpoints
|
|
@@ -1961,7 +2304,7 @@ if (container && Component) {
|
|
|
1961
2304
|
* the inspector UI (if available), and starting the server to listen
|
|
1962
2305
|
* for incoming connections. This is the main entry point for running the server.
|
|
1963
2306
|
*
|
|
1964
|
-
* The server will be accessible at the specified port with MCP endpoints at /mcp
|
|
2307
|
+
* The server will be accessible at the specified port with MCP endpoints at /mcp and /sse
|
|
1965
2308
|
* and inspector UI at /inspector (if the inspector package is installed).
|
|
1966
2309
|
*
|
|
1967
2310
|
* @param port - Port number to listen on (defaults to 3001 if not specified)
|
|
@@ -1971,7 +2314,7 @@ if (container && Component) {
|
|
|
1971
2314
|
* ```typescript
|
|
1972
2315
|
* await server.listen(8080)
|
|
1973
2316
|
* // Server now running at http://localhost:8080 (or configured host)
|
|
1974
|
-
* // MCP endpoints: http://localhost:8080/mcp
|
|
2317
|
+
* // MCP endpoints: http://localhost:8080/mcp and http://localhost:8080/sse
|
|
1975
2318
|
* // Inspector UI: http://localhost:8080/inspector
|
|
1976
2319
|
* ```
|
|
1977
2320
|
*/
|
|
@@ -2001,9 +2344,9 @@ if (container && Component) {
|
|
|
2001
2344
|
console.log("");
|
|
2002
2345
|
}
|
|
2003
2346
|
async listen(port) {
|
|
2004
|
-
const portEnv =
|
|
2347
|
+
const portEnv = getEnv2("PORT");
|
|
2005
2348
|
this.serverPort = port || (portEnv ? parseInt(portEnv, 10) : 3001);
|
|
2006
|
-
const hostEnv =
|
|
2349
|
+
const hostEnv = getEnv2("HOST");
|
|
2007
2350
|
if (hostEnv) {
|
|
2008
2351
|
this.serverHost = hostEnv;
|
|
2009
2352
|
}
|
|
@@ -2014,7 +2357,7 @@ if (container && Component) {
|
|
|
2014
2357
|
await this.mountMcp();
|
|
2015
2358
|
await this.mountInspector();
|
|
2016
2359
|
this.logRegisteredItems();
|
|
2017
|
-
if (
|
|
2360
|
+
if (isDeno2) {
|
|
2018
2361
|
const corsHeaders = {
|
|
2019
2362
|
"Access-Control-Allow-Origin": "*",
|
|
2020
2363
|
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type"
|
|
@@ -2075,7 +2418,7 @@ if (container && Component) {
|
|
|
2075
2418
|
`[SERVER] Listening on http://${this.serverHost}:${this.serverPort}`
|
|
2076
2419
|
);
|
|
2077
2420
|
console.log(
|
|
2078
|
-
`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp`
|
|
2421
|
+
`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp and http://${this.serverHost}:${this.serverPort}/sse`
|
|
2079
2422
|
);
|
|
2080
2423
|
}
|
|
2081
2424
|
);
|
|
@@ -2159,6 +2502,192 @@ if (container && Component) {
|
|
|
2159
2502
|
return result;
|
|
2160
2503
|
};
|
|
2161
2504
|
}
|
|
2505
|
+
/**
|
|
2506
|
+
* Get array of active session IDs
|
|
2507
|
+
*
|
|
2508
|
+
* Returns an array of all currently active session IDs. This is useful for
|
|
2509
|
+
* sending targeted notifications to specific clients or iterating over
|
|
2510
|
+
* connected clients.
|
|
2511
|
+
*
|
|
2512
|
+
* Note: This only works in stateful mode. In stateless mode (edge environments),
|
|
2513
|
+
* this will return an empty array.
|
|
2514
|
+
*
|
|
2515
|
+
* @returns Array of active session ID strings
|
|
2516
|
+
*
|
|
2517
|
+
* @example
|
|
2518
|
+
* ```typescript
|
|
2519
|
+
* const sessions = server.getActiveSessions();
|
|
2520
|
+
* console.log(`${sessions.length} clients connected`);
|
|
2521
|
+
*
|
|
2522
|
+
* // Send notification to first connected client
|
|
2523
|
+
* if (sessions.length > 0) {
|
|
2524
|
+
* server.sendNotificationToSession(sessions[0], "custom/hello", { message: "Hi!" });
|
|
2525
|
+
* }
|
|
2526
|
+
* ```
|
|
2527
|
+
*/
|
|
2528
|
+
getActiveSessions() {
|
|
2529
|
+
return Array.from(this.sessions.keys());
|
|
2530
|
+
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Send a notification to all connected clients
|
|
2533
|
+
*
|
|
2534
|
+
* Broadcasts a JSON-RPC notification to all active sessions. Notifications are
|
|
2535
|
+
* one-way messages that do not expect a response from the client.
|
|
2536
|
+
*
|
|
2537
|
+
* Note: This only works in stateful mode with active sessions. If no sessions
|
|
2538
|
+
* are connected, the notification is silently discarded (per MCP spec: "server MAY send").
|
|
2539
|
+
*
|
|
2540
|
+
* @param method - The notification method name (e.g., "custom/my-notification")
|
|
2541
|
+
* @param params - Optional parameters to include in the notification
|
|
2542
|
+
*
|
|
2543
|
+
* @example
|
|
2544
|
+
* ```typescript
|
|
2545
|
+
* // Send a simple notification to all clients
|
|
2546
|
+
* server.sendNotification("custom/server-status", {
|
|
2547
|
+
* status: "ready",
|
|
2548
|
+
* timestamp: new Date().toISOString()
|
|
2549
|
+
* });
|
|
2550
|
+
*
|
|
2551
|
+
* // Notify all clients that resources have changed
|
|
2552
|
+
* server.sendNotification("notifications/resources/list_changed");
|
|
2553
|
+
* ```
|
|
2554
|
+
*/
|
|
2555
|
+
async sendNotification(method, params) {
|
|
2556
|
+
const notification = {
|
|
2557
|
+
jsonrpc: "2.0",
|
|
2558
|
+
method,
|
|
2559
|
+
...params && { params }
|
|
2560
|
+
};
|
|
2561
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
2562
|
+
try {
|
|
2563
|
+
await session.transport.send(notification);
|
|
2564
|
+
} catch (error) {
|
|
2565
|
+
console.warn(
|
|
2566
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2567
|
+
error
|
|
2568
|
+
);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
/**
|
|
2573
|
+
* Send a notification to a specific client session
|
|
2574
|
+
*
|
|
2575
|
+
* Sends a JSON-RPC notification to a single client identified by their session ID.
|
|
2576
|
+
* This allows sending customized notifications to individual clients.
|
|
2577
|
+
*
|
|
2578
|
+
* Note: This only works in stateful mode. If the session ID doesn't exist,
|
|
2579
|
+
* the notification is silently discarded.
|
|
2580
|
+
*
|
|
2581
|
+
* @param sessionId - The target session ID (from getActiveSessions())
|
|
2582
|
+
* @param method - The notification method name (e.g., "custom/my-notification")
|
|
2583
|
+
* @param params - Optional parameters to include in the notification
|
|
2584
|
+
* @returns true if the notification was sent, false if session not found
|
|
2585
|
+
*
|
|
2586
|
+
* @example
|
|
2587
|
+
* ```typescript
|
|
2588
|
+
* const sessions = server.getActiveSessions();
|
|
2589
|
+
*
|
|
2590
|
+
* // Send different messages to different clients
|
|
2591
|
+
* sessions.forEach((sessionId, index) => {
|
|
2592
|
+
* server.sendNotificationToSession(sessionId, "custom/welcome", {
|
|
2593
|
+
* message: `Hello client #${index + 1}!`,
|
|
2594
|
+
* clientNumber: index + 1
|
|
2595
|
+
* });
|
|
2596
|
+
* });
|
|
2597
|
+
* ```
|
|
2598
|
+
*/
|
|
2599
|
+
async sendNotificationToSession(sessionId, method, params) {
|
|
2600
|
+
const session = this.sessions.get(sessionId);
|
|
2601
|
+
if (!session) {
|
|
2602
|
+
return false;
|
|
2603
|
+
}
|
|
2604
|
+
const notification = {
|
|
2605
|
+
jsonrpc: "2.0",
|
|
2606
|
+
method,
|
|
2607
|
+
...params && { params }
|
|
2608
|
+
};
|
|
2609
|
+
try {
|
|
2610
|
+
await session.transport.send(notification);
|
|
2611
|
+
return true;
|
|
2612
|
+
} catch (error) {
|
|
2613
|
+
console.warn(
|
|
2614
|
+
`[MCP] Failed to send notification to session ${sessionId}:`,
|
|
2615
|
+
error
|
|
2616
|
+
);
|
|
2617
|
+
return false;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
// Store the roots changed callback
|
|
2621
|
+
onRootsChangedCallback;
|
|
2622
|
+
/**
|
|
2623
|
+
* Register a callback for when a client's roots change
|
|
2624
|
+
*
|
|
2625
|
+
* When a client sends a `notifications/roots/list_changed` notification,
|
|
2626
|
+
* the server will automatically request the updated roots list and call
|
|
2627
|
+
* this callback with the new roots.
|
|
2628
|
+
*
|
|
2629
|
+
* @param callback - Function called with the updated roots array
|
|
2630
|
+
*
|
|
2631
|
+
* @example
|
|
2632
|
+
* ```typescript
|
|
2633
|
+
* server.onRootsChanged(async (roots) => {
|
|
2634
|
+
* console.log("Client roots updated:", roots);
|
|
2635
|
+
* roots.forEach(root => {
|
|
2636
|
+
* console.log(` - ${root.name || "unnamed"}: ${root.uri}`);
|
|
2637
|
+
* });
|
|
2638
|
+
* });
|
|
2639
|
+
* ```
|
|
2640
|
+
*/
|
|
2641
|
+
onRootsChanged(callback) {
|
|
2642
|
+
this.onRootsChangedCallback = callback;
|
|
2643
|
+
return this;
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Request the current roots list from a specific client session
|
|
2647
|
+
*
|
|
2648
|
+
* This sends a `roots/list` request to the client and returns
|
|
2649
|
+
* the list of roots the client has configured.
|
|
2650
|
+
*
|
|
2651
|
+
* @param sessionId - The session ID of the client to query
|
|
2652
|
+
* @returns Array of roots, or null if the session doesn't exist or request fails
|
|
2653
|
+
*
|
|
2654
|
+
* @example
|
|
2655
|
+
* ```typescript
|
|
2656
|
+
* const sessions = server.getActiveSessions();
|
|
2657
|
+
* if (sessions.length > 0) {
|
|
2658
|
+
* const roots = await server.listRoots(sessions[0]);
|
|
2659
|
+
* if (roots) {
|
|
2660
|
+
* console.log(`Client has ${roots.length} roots:`);
|
|
2661
|
+
* roots.forEach(r => console.log(` - ${r.uri}`));
|
|
2662
|
+
* }
|
|
2663
|
+
* }
|
|
2664
|
+
* ```
|
|
2665
|
+
*/
|
|
2666
|
+
async listRoots(sessionId) {
|
|
2667
|
+
const session = this.sessions.get(sessionId);
|
|
2668
|
+
if (!session) {
|
|
2669
|
+
return null;
|
|
2670
|
+
}
|
|
2671
|
+
try {
|
|
2672
|
+
const request = {
|
|
2673
|
+
jsonrpc: "2.0",
|
|
2674
|
+
id: generateUUID(),
|
|
2675
|
+
method: "roots/list",
|
|
2676
|
+
params: {}
|
|
2677
|
+
};
|
|
2678
|
+
const response = await session.transport.send(request);
|
|
2679
|
+
if (response && typeof response === "object" && "roots" in response) {
|
|
2680
|
+
return response.roots;
|
|
2681
|
+
}
|
|
2682
|
+
return [];
|
|
2683
|
+
} catch (error) {
|
|
2684
|
+
console.warn(
|
|
2685
|
+
`[MCP] Failed to list roots from session ${sessionId}:`,
|
|
2686
|
+
error
|
|
2687
|
+
);
|
|
2688
|
+
return null;
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2162
2691
|
/**
|
|
2163
2692
|
* Mount MCP Inspector UI at /inspector
|
|
2164
2693
|
*
|
|
@@ -2175,7 +2704,7 @@ if (container && Component) {
|
|
|
2175
2704
|
* @example
|
|
2176
2705
|
* If @mcp-use/inspector is installed:
|
|
2177
2706
|
* - Inspector UI available at http://localhost:PORT/inspector
|
|
2178
|
-
* - Automatically connects to http://localhost:PORT/mcp
|
|
2707
|
+
* - Automatically connects to http://localhost:PORT/mcp (or /sse)
|
|
2179
2708
|
*
|
|
2180
2709
|
* If not installed:
|
|
2181
2710
|
* - Server continues to function normally
|
|
@@ -2194,7 +2723,14 @@ if (container && Component) {
|
|
|
2194
2723
|
}
|
|
2195
2724
|
try {
|
|
2196
2725
|
const { mountInspector } = await import("@mcp-use/inspector");
|
|
2197
|
-
|
|
2726
|
+
const mcpUrl = `http://${this.serverHost}:${this.serverPort}/mcp`;
|
|
2727
|
+
const autoConnectConfig = JSON.stringify({
|
|
2728
|
+
url: mcpUrl,
|
|
2729
|
+
name: "Local MCP Server",
|
|
2730
|
+
transportType: "sse",
|
|
2731
|
+
connectionType: "Direct"
|
|
2732
|
+
});
|
|
2733
|
+
mountInspector(this.app, { autoConnectUrl: autoConnectConfig });
|
|
2198
2734
|
this.inspectorMounted = true;
|
|
2199
2735
|
console.log(
|
|
2200
2736
|
`[INSPECTOR] UI available at http://${this.serverHost}:${this.serverPort}/inspector`
|
|
@@ -2518,7 +3054,9 @@ function createMCPServer(name, config = {}) {
|
|
|
2518
3054
|
version: config.version || "1.0.0",
|
|
2519
3055
|
description: config.description,
|
|
2520
3056
|
host: config.host,
|
|
2521
|
-
baseUrl: config.baseUrl
|
|
3057
|
+
baseUrl: config.baseUrl,
|
|
3058
|
+
allowedOrigins: config.allowedOrigins,
|
|
3059
|
+
sessionIdleTimeoutMs: config.sessionIdleTimeoutMs
|
|
2522
3060
|
});
|
|
2523
3061
|
return instance;
|
|
2524
3062
|
}
|