mcp-use 1.1.6 → 1.1.7-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-B5SNLEZM.js → chunk-JV7HAYUT.js} +165 -1
- package/dist/index.cjs +170 -2
- package/dist/index.js +11 -3
- package/dist/src/react/index.cjs +170 -2
- package/dist/src/react/index.d.ts +2 -0
- package/dist/src/react/index.d.ts.map +1 -1
- package/dist/src/react/index.js +11 -3
- package/dist/src/react/useWidget.d.ts +54 -0
- package/dist/src/react/useWidget.d.ts.map +1 -0
- package/dist/src/react/widget-types.d.ts +124 -0
- package/dist/src/react/widget-types.d.ts.map +1 -0
- package/dist/src/server/adapters/mcp-ui-adapter.d.ts +1 -1
- package/dist/src/server/adapters/mcp-ui-adapter.d.ts.map +1 -1
- package/dist/src/server/index.cjs +409 -43
- package/dist/src/server/index.js +409 -43
- package/dist/src/server/mcp-server.d.ts +98 -8
- package/dist/src/server/mcp-server.d.ts.map +1 -1
- package/dist/src/server/types/common.d.ts +2 -0
- package/dist/src/server/types/common.d.ts.map +1 -1
- package/dist/src/server/types/resource.d.ts +3 -0
- package/dist/src/server/types/resource.d.ts.map +1 -1
- package/dist/tests/helpers/widget-generators.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsup.config.d.ts +3 -0
- package/dist/tsup.config.d.ts.map +1 -0
- package/package.json +13 -7
|
@@ -47,6 +47,7 @@ var import_express = __toESM(require("express"), 1);
|
|
|
47
47
|
var import_cors = __toESM(require("cors"), 1);
|
|
48
48
|
var import_node_fs = require("fs");
|
|
49
49
|
var import_node_path = require("path");
|
|
50
|
+
var import_node_fs2 = require("fs");
|
|
50
51
|
|
|
51
52
|
// src/server/logging.ts
|
|
52
53
|
function requestLogger(req, res, next) {
|
|
@@ -85,13 +86,8 @@ function buildWidgetUrl(widget, props, config) {
|
|
|
85
86
|
`/mcp-use/widgets/${widget}`,
|
|
86
87
|
`${config.baseUrl}:${config.port}`
|
|
87
88
|
);
|
|
88
|
-
if (props) {
|
|
89
|
-
|
|
90
|
-
if (value !== void 0 && value !== null) {
|
|
91
|
-
const stringValue = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
92
|
-
url.searchParams.set(key, stringValue);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
89
|
+
if (props && Object.keys(props).length > 0) {
|
|
90
|
+
url.searchParams.set("props", JSON.stringify(props));
|
|
95
91
|
}
|
|
96
92
|
return url.toString();
|
|
97
93
|
}
|
|
@@ -191,6 +187,8 @@ function createUIResourceFromDefinition(definition, params, config) {
|
|
|
191
187
|
__name(createUIResourceFromDefinition, "createUIResourceFromDefinition");
|
|
192
188
|
|
|
193
189
|
// src/server/mcp-server.ts
|
|
190
|
+
var import_vite = require("vite");
|
|
191
|
+
var TMP_MCP_USE_DIR = ".mcp-use";
|
|
194
192
|
var McpServer = class {
|
|
195
193
|
static {
|
|
196
194
|
__name(this, "McpServer");
|
|
@@ -201,6 +199,8 @@ var McpServer = class {
|
|
|
201
199
|
mcpMounted = false;
|
|
202
200
|
inspectorMounted = false;
|
|
203
201
|
serverPort;
|
|
202
|
+
serverHost;
|
|
203
|
+
serverBaseUrl;
|
|
204
204
|
/**
|
|
205
205
|
* Creates a new MCP server instance with Express integration
|
|
206
206
|
*
|
|
@@ -213,6 +213,8 @@ var McpServer = class {
|
|
|
213
213
|
*/
|
|
214
214
|
constructor(config) {
|
|
215
215
|
this.config = config;
|
|
216
|
+
this.serverHost = config.host || "localhost";
|
|
217
|
+
this.serverBaseUrl = config.baseUrl;
|
|
216
218
|
this.server = new import_mcp.McpServer({
|
|
217
219
|
name: config.name,
|
|
218
220
|
version: config.version
|
|
@@ -225,7 +227,6 @@ var McpServer = class {
|
|
|
225
227
|
allowedHeaders: ["Content-Type", "Accept", "Authorization", "mcp-protocol-version", "mcp-session-id", "X-Proxy-Token", "X-Target-URL"]
|
|
226
228
|
}));
|
|
227
229
|
this.app.use(requestLogger);
|
|
228
|
-
this.setupWidgetRoutes();
|
|
229
230
|
return new Proxy(this, {
|
|
230
231
|
get(target, prop) {
|
|
231
232
|
if (prop in target) {
|
|
@@ -284,7 +285,8 @@ var McpServer = class {
|
|
|
284
285
|
title: resourceDefinition.title,
|
|
285
286
|
description: resourceDefinition.description,
|
|
286
287
|
mimeType: resourceDefinition.mimeType,
|
|
287
|
-
annotations: resourceDefinition.annotations
|
|
288
|
+
annotations: resourceDefinition.annotations,
|
|
289
|
+
_meta: resourceDefinition._meta
|
|
288
290
|
},
|
|
289
291
|
async () => {
|
|
290
292
|
return await resourceDefinition.readCallback();
|
|
@@ -403,7 +405,7 @@ var McpServer = class {
|
|
|
403
405
|
* ```
|
|
404
406
|
*/
|
|
405
407
|
tool(toolDefinition) {
|
|
406
|
-
const inputSchema = this.
|
|
408
|
+
const inputSchema = this.createParamsSchema(toolDefinition.inputs || []);
|
|
407
409
|
this.server.registerTool(
|
|
408
410
|
toolDefinition.name,
|
|
409
411
|
{
|
|
@@ -454,7 +456,7 @@ var McpServer = class {
|
|
|
454
456
|
* ```
|
|
455
457
|
*/
|
|
456
458
|
prompt(promptDefinition) {
|
|
457
|
-
const argsSchema = this.
|
|
459
|
+
const argsSchema = this.createParamsSchema(promptDefinition.args || []);
|
|
458
460
|
this.server.registerPrompt(
|
|
459
461
|
promptDefinition.name,
|
|
460
462
|
{
|
|
@@ -481,19 +483,22 @@ var McpServer = class {
|
|
|
481
483
|
* - remoteDom: Legacy MCP-UI Remote DOM scripting
|
|
482
484
|
* - appsSdk: OpenAI Apps SDK compatible widgets (text/html+skybridge)
|
|
483
485
|
*
|
|
484
|
-
* @param
|
|
486
|
+
* @param widgetNameOrDefinition - Widget name (string) for auto-loading schema, or full configuration object
|
|
485
487
|
* @param definition.name - Unique identifier for the resource
|
|
486
488
|
* @param definition.type - Type of UI resource (externalUrl, rawHtml, remoteDom, appsSdk)
|
|
487
489
|
* @param definition.title - Human-readable title for the widget
|
|
488
490
|
* @param definition.description - Description of the widget's functionality
|
|
489
491
|
* @param definition.props - Widget properties configuration with types and defaults
|
|
490
|
-
* @param definition.size - Preferred iframe size [width, height] (e.g., ['
|
|
492
|
+
* @param definition.size - Preferred iframe size [width, height] (e.g., ['900px', '600px'])
|
|
491
493
|
* @param definition.annotations - Resource annotations for discovery
|
|
492
494
|
* @param definition.appsSdkMetadata - Apps SDK specific metadata (CSP, widget description, etc.)
|
|
493
495
|
* @returns The server instance for method chaining
|
|
494
496
|
*
|
|
495
497
|
* @example
|
|
496
498
|
* ```typescript
|
|
499
|
+
* // Simple usage - auto-loads from generated schema
|
|
500
|
+
* server.uiResource('display-weather')
|
|
501
|
+
*
|
|
497
502
|
* // Legacy MCP-UI widget
|
|
498
503
|
* server.uiResource({
|
|
499
504
|
* type: 'externalUrl',
|
|
@@ -533,14 +538,6 @@ var McpServer = class {
|
|
|
533
538
|
* ```
|
|
534
539
|
*/
|
|
535
540
|
uiResource(definition) {
|
|
536
|
-
let toolName;
|
|
537
|
-
if (definition.type === "appsSdk") {
|
|
538
|
-
toolName = definition.name;
|
|
539
|
-
} else if (definition.type === "externalUrl") {
|
|
540
|
-
toolName = `ui_${definition.widget}`;
|
|
541
|
-
} else {
|
|
542
|
-
toolName = `ui_${definition.name}`;
|
|
543
|
-
}
|
|
544
541
|
const displayName = definition.title || definition.name;
|
|
545
542
|
let resourceUri;
|
|
546
543
|
let mimeType;
|
|
@@ -570,6 +567,7 @@ var McpServer = class {
|
|
|
570
567
|
title: definition.title,
|
|
571
568
|
description: definition.description,
|
|
572
569
|
mimeType,
|
|
570
|
+
_meta: definition._meta,
|
|
573
571
|
annotations: definition.annotations,
|
|
574
572
|
readCallback: /* @__PURE__ */ __name(async () => {
|
|
575
573
|
const params = definition.type === "externalUrl" ? this.applyDefaultProps(definition.props) : {};
|
|
@@ -579,7 +577,28 @@ var McpServer = class {
|
|
|
579
577
|
};
|
|
580
578
|
}, "readCallback")
|
|
581
579
|
});
|
|
582
|
-
|
|
580
|
+
if (definition.type === "appsSdk") {
|
|
581
|
+
this.resourceTemplate({
|
|
582
|
+
name: `${definition.name}-dynamic`,
|
|
583
|
+
resourceTemplate: {
|
|
584
|
+
uriTemplate: `ui://widget/${definition.name}-{id}.html`,
|
|
585
|
+
name: definition.title || definition.name,
|
|
586
|
+
description: definition.description,
|
|
587
|
+
mimeType
|
|
588
|
+
},
|
|
589
|
+
_meta: definition._meta,
|
|
590
|
+
title: definition.title,
|
|
591
|
+
description: definition.description,
|
|
592
|
+
annotations: definition.annotations,
|
|
593
|
+
readCallback: /* @__PURE__ */ __name(async (uri, params) => {
|
|
594
|
+
const uiResource = this.createWidgetUIResource(definition, {});
|
|
595
|
+
return {
|
|
596
|
+
contents: [uiResource.resource]
|
|
597
|
+
};
|
|
598
|
+
}, "readCallback")
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
const toolMetadata = definition._meta || {};
|
|
583
602
|
if (definition.type === "appsSdk" && definition.appsSdkMetadata) {
|
|
584
603
|
toolMetadata["openai/outputTemplate"] = resourceUri;
|
|
585
604
|
const toolMetadataFields = [
|
|
@@ -595,17 +614,19 @@ var McpServer = class {
|
|
|
595
614
|
}
|
|
596
615
|
}
|
|
597
616
|
this.tool({
|
|
598
|
-
name:
|
|
617
|
+
name: definition.name,
|
|
599
618
|
title: definition.title,
|
|
600
|
-
|
|
601
|
-
description: definition.type === "appsSdk" && definition.title ? definition.title : definition.description || `Display ${displayName}`,
|
|
619
|
+
description: definition.description,
|
|
602
620
|
inputs: this.convertPropsToInputs(definition.props),
|
|
603
621
|
_meta: Object.keys(toolMetadata).length > 0 ? toolMetadata : void 0,
|
|
604
622
|
cb: /* @__PURE__ */ __name(async (params) => {
|
|
605
623
|
const uiResource = this.createWidgetUIResource(definition, params);
|
|
606
624
|
if (definition.type === "appsSdk") {
|
|
625
|
+
const randomId = Math.random().toString(36).substring(2, 15);
|
|
626
|
+
const uniqueUri = `ui://widget/${definition.name}-${randomId}.html`;
|
|
627
|
+
const uniqueToolMetadata = { ...toolMetadata, "openai/outputTemplate": uniqueUri };
|
|
607
628
|
return {
|
|
608
|
-
_meta:
|
|
629
|
+
_meta: uniqueToolMetadata,
|
|
609
630
|
content: [
|
|
610
631
|
{
|
|
611
632
|
type: "text",
|
|
@@ -643,9 +664,20 @@ var McpServer = class {
|
|
|
643
664
|
* @returns UIResource object compatible with MCP-UI
|
|
644
665
|
*/
|
|
645
666
|
createWidgetUIResource(definition, params) {
|
|
667
|
+
let configBaseUrl = `http://${this.serverHost}`;
|
|
668
|
+
let configPort = this.serverPort || 3001;
|
|
669
|
+
if (this.serverBaseUrl) {
|
|
670
|
+
try {
|
|
671
|
+
const url = new URL(this.serverBaseUrl);
|
|
672
|
+
configBaseUrl = `${url.protocol}//${url.hostname}`;
|
|
673
|
+
configPort = url.port || (url.protocol === "https:" ? 443 : 80);
|
|
674
|
+
} catch (e) {
|
|
675
|
+
console.warn("Failed to parse baseUrl, falling back to host:port", e);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
646
678
|
const urlConfig = {
|
|
647
|
-
baseUrl:
|
|
648
|
-
port:
|
|
679
|
+
baseUrl: configBaseUrl,
|
|
680
|
+
port: configPort
|
|
649
681
|
};
|
|
650
682
|
return createUIResourceFromDefinition(definition, params, urlConfig);
|
|
651
683
|
}
|
|
@@ -662,7 +694,7 @@ var McpServer = class {
|
|
|
662
694
|
* @returns Complete URL with encoded parameters
|
|
663
695
|
*/
|
|
664
696
|
buildWidgetUrl(widget, params) {
|
|
665
|
-
const baseUrl = `http
|
|
697
|
+
const baseUrl = `http://${this.serverHost}:${this.serverPort}/mcp-use/widgets/${widget}`;
|
|
666
698
|
if (Object.keys(params).length === 0) {
|
|
667
699
|
return baseUrl;
|
|
668
700
|
}
|
|
@@ -718,6 +750,324 @@ var McpServer = class {
|
|
|
718
750
|
}
|
|
719
751
|
return defaults;
|
|
720
752
|
}
|
|
753
|
+
/**
|
|
754
|
+
* Check if server is running in production mode
|
|
755
|
+
*
|
|
756
|
+
* @private
|
|
757
|
+
* @returns true if in production mode, false otherwise
|
|
758
|
+
*/
|
|
759
|
+
isProductionMode() {
|
|
760
|
+
return process.env.NODE_ENV === "production";
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Read build manifest file
|
|
764
|
+
*
|
|
765
|
+
* @private
|
|
766
|
+
* @returns Build manifest or null if not found
|
|
767
|
+
*/
|
|
768
|
+
readBuildManifest() {
|
|
769
|
+
try {
|
|
770
|
+
const manifestPath = (0, import_node_path.join)(process.cwd(), "dist", ".mcp-use-manifest.json");
|
|
771
|
+
const content = (0, import_node_fs2.readFileSync)(manifestPath, "utf8");
|
|
772
|
+
return JSON.parse(content);
|
|
773
|
+
} catch {
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Mount widget files - automatically chooses between dev and production mode
|
|
779
|
+
*
|
|
780
|
+
* In development mode: creates Vite dev servers with HMR support
|
|
781
|
+
* In production mode: serves pre-built static widgets
|
|
782
|
+
*
|
|
783
|
+
* @param options - Configuration options
|
|
784
|
+
* @param options.baseRoute - Base route for widgets (defaults to '/mcp-use/widgets')
|
|
785
|
+
* @param options.resourcesDir - Directory containing widget files (defaults to 'resources')
|
|
786
|
+
* @returns Promise that resolves when all widgets are mounted
|
|
787
|
+
*/
|
|
788
|
+
async mountWidgets(options) {
|
|
789
|
+
if (this.isProductionMode()) {
|
|
790
|
+
await this.mountWidgetsProduction(options);
|
|
791
|
+
} else {
|
|
792
|
+
await this.mountWidgetsDev(options);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Mount individual widget files from resources/ directory in development mode
|
|
797
|
+
*
|
|
798
|
+
* Scans the resources/ directory for .tsx/.ts widget files and creates individual
|
|
799
|
+
* Vite dev servers for each widget with HMR support. Each widget is served at its
|
|
800
|
+
* own route: /mcp-use/widgets/{widget-name}
|
|
801
|
+
*
|
|
802
|
+
* @private
|
|
803
|
+
* @param options - Configuration options
|
|
804
|
+
* @param options.baseRoute - Base route for widgets (defaults to '/mcp-use/widgets')
|
|
805
|
+
* @param options.resourcesDir - Directory containing widget files (defaults to 'resources')
|
|
806
|
+
* @returns Promise that resolves when all widgets are mounted
|
|
807
|
+
*/
|
|
808
|
+
async mountWidgetsDev(options) {
|
|
809
|
+
const { promises: fs } = await import("fs");
|
|
810
|
+
const baseRoute = options?.baseRoute || "/mcp-use/widgets";
|
|
811
|
+
const resourcesDir = options?.resourcesDir || "resources";
|
|
812
|
+
const srcDir = (0, import_node_path.join)(process.cwd(), resourcesDir);
|
|
813
|
+
try {
|
|
814
|
+
await fs.access(srcDir);
|
|
815
|
+
} catch (error) {
|
|
816
|
+
console.log(`[WIDGETS] No ${resourcesDir}/ directory found - skipping widget serving`);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
let entries = [];
|
|
820
|
+
try {
|
|
821
|
+
const files = await fs.readdir(srcDir);
|
|
822
|
+
entries = files.filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) => (0, import_node_path.join)(srcDir, f));
|
|
823
|
+
} catch (error) {
|
|
824
|
+
console.log(`[WIDGETS] No widgets found in ${resourcesDir}/ directory`);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
if (entries.length === 0) {
|
|
828
|
+
console.log(`[WIDGETS] No widgets found in ${resourcesDir}/ directory`);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const tempDir = (0, import_node_path.join)(process.cwd(), TMP_MCP_USE_DIR);
|
|
832
|
+
await fs.mkdir(tempDir, { recursive: true }).catch(() => {
|
|
833
|
+
});
|
|
834
|
+
const react = (await import("@vitejs/plugin-react")).default;
|
|
835
|
+
const tailwindcss = (await import("@tailwindcss/vite")).default;
|
|
836
|
+
console.log(react, tailwindcss);
|
|
837
|
+
const widgets = entries.map((entry) => {
|
|
838
|
+
const baseName = entry.split("/").pop()?.replace(/\.tsx?$/, "") || "widget";
|
|
839
|
+
const widgetName = baseName;
|
|
840
|
+
return {
|
|
841
|
+
name: widgetName,
|
|
842
|
+
description: `Widget: ${widgetName}`,
|
|
843
|
+
entry
|
|
844
|
+
};
|
|
845
|
+
});
|
|
846
|
+
for (const widget of widgets) {
|
|
847
|
+
const widgetTempDir = (0, import_node_path.join)(tempDir, widget.name);
|
|
848
|
+
await fs.mkdir(widgetTempDir, { recursive: true });
|
|
849
|
+
const resourcesPath = (0, import_node_path.join)(process.cwd(), resourcesDir);
|
|
850
|
+
const { relative } = await import("path");
|
|
851
|
+
const relativeResourcesPath = relative(widgetTempDir, resourcesPath).replace(/\\/g, "/");
|
|
852
|
+
const cssContent = `@import "tailwindcss";
|
|
853
|
+
|
|
854
|
+
/* Configure Tailwind to scan the resources directory */
|
|
855
|
+
@source "${relativeResourcesPath}";
|
|
856
|
+
`;
|
|
857
|
+
await fs.writeFile((0, import_node_path.join)(widgetTempDir, "styles.css"), cssContent, "utf8");
|
|
858
|
+
const entryContent = `import React from 'react'
|
|
859
|
+
import { createRoot } from 'react-dom/client'
|
|
860
|
+
import './styles.css'
|
|
861
|
+
import Component from '${widget.entry}'
|
|
862
|
+
|
|
863
|
+
const container = document.getElementById('widget-root')
|
|
864
|
+
if (container && Component) {
|
|
865
|
+
const root = createRoot(container)
|
|
866
|
+
root.render(<Component />)
|
|
867
|
+
}
|
|
868
|
+
`;
|
|
869
|
+
const htmlContent = `<!doctype html>
|
|
870
|
+
<html lang="en">
|
|
871
|
+
<head>
|
|
872
|
+
<meta charset="UTF-8" />
|
|
873
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
874
|
+
<title>${widget.name} Widget</title>
|
|
875
|
+
</head>
|
|
876
|
+
<body>
|
|
877
|
+
<div id="widget-root"></div>
|
|
878
|
+
<script type="module" src="${baseRoute}/${widget.name}/entry.tsx"></script>
|
|
879
|
+
</body>
|
|
880
|
+
</html>`;
|
|
881
|
+
await fs.writeFile((0, import_node_path.join)(widgetTempDir, "entry.tsx"), entryContent, "utf8");
|
|
882
|
+
await fs.writeFile((0, import_node_path.join)(widgetTempDir, "index.html"), htmlContent, "utf8");
|
|
883
|
+
}
|
|
884
|
+
const serverOrigin = this.serverBaseUrl || `http://${this.serverHost}:${this.serverPort}`;
|
|
885
|
+
console.log(`[WIDGETS] Serving ${entries.length} widget(s) with shared Vite dev server and HMR`);
|
|
886
|
+
const viteServer = await (0, import_vite.createServer)({
|
|
887
|
+
root: tempDir,
|
|
888
|
+
base: baseRoute + "/",
|
|
889
|
+
plugins: [tailwindcss(), react()],
|
|
890
|
+
resolve: {
|
|
891
|
+
alias: {
|
|
892
|
+
"@": (0, import_node_path.join)(process.cwd(), resourcesDir)
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
server: {
|
|
896
|
+
middlewareMode: true,
|
|
897
|
+
origin: serverOrigin
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
this.app.use(baseRoute, (req, res, next) => {
|
|
901
|
+
const urlPath = req.url || "";
|
|
902
|
+
const [pathname, queryString] = urlPath.split("?");
|
|
903
|
+
const widgetMatch = pathname.match(/^\/([^/]+)/);
|
|
904
|
+
if (widgetMatch) {
|
|
905
|
+
const widgetName = widgetMatch[1];
|
|
906
|
+
const widget = widgets.find((w) => w.name === widgetName);
|
|
907
|
+
if (widget) {
|
|
908
|
+
if (pathname === `/${widgetName}` || pathname === `/${widgetName}/`) {
|
|
909
|
+
req.url = `/${widgetName}/index.html${queryString ? "?" + queryString : ""}`;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
next();
|
|
914
|
+
});
|
|
915
|
+
this.app.use(baseRoute, viteServer.middlewares);
|
|
916
|
+
widgets.forEach((widget) => {
|
|
917
|
+
console.log(`[WIDGET] ${widget.name} mounted at ${baseRoute}/${widget.name}`);
|
|
918
|
+
});
|
|
919
|
+
for (const widget of widgets) {
|
|
920
|
+
const type = "appsSdk";
|
|
921
|
+
let widgetMetadata = {};
|
|
922
|
+
let props = {};
|
|
923
|
+
let description = widget.description;
|
|
924
|
+
try {
|
|
925
|
+
const mod = await viteServer.ssrLoadModule(widget.entry);
|
|
926
|
+
if (mod.widgetMetadata) {
|
|
927
|
+
widgetMetadata = mod.widgetMetadata;
|
|
928
|
+
description = widgetMetadata.description || widget.description;
|
|
929
|
+
if (widgetMetadata.inputs) {
|
|
930
|
+
try {
|
|
931
|
+
props = widgetMetadata.inputs.shape || {};
|
|
932
|
+
} catch (error) {
|
|
933
|
+
console.warn(`[WIDGET] Failed to extract props schema for ${widget.name}:`, error);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
} catch (error) {
|
|
938
|
+
console.warn(`[WIDGET] Failed to load metadata for ${widget.name}:`, error);
|
|
939
|
+
}
|
|
940
|
+
let html = "";
|
|
941
|
+
try {
|
|
942
|
+
html = await (0, import_node_fs2.readFileSync)((0, import_node_path.join)(tempDir, widget.name, "index.html"), "utf8");
|
|
943
|
+
} catch (error) {
|
|
944
|
+
console.error(`Failed to read html template for widget ${widget.name}`, error);
|
|
945
|
+
}
|
|
946
|
+
this.uiResource({
|
|
947
|
+
name: widget.name,
|
|
948
|
+
title: widget.name,
|
|
949
|
+
description,
|
|
950
|
+
type,
|
|
951
|
+
props,
|
|
952
|
+
_meta: {
|
|
953
|
+
"mcp-use/widget": {
|
|
954
|
+
name: widget.name,
|
|
955
|
+
description,
|
|
956
|
+
type,
|
|
957
|
+
props,
|
|
958
|
+
html,
|
|
959
|
+
dev: true
|
|
960
|
+
}
|
|
961
|
+
},
|
|
962
|
+
htmlTemplate: html,
|
|
963
|
+
appsSdkMetadata: {
|
|
964
|
+
"openai/widgetDescription": description,
|
|
965
|
+
"openai/toolInvocation/invoking": "Hand-tossing a map",
|
|
966
|
+
"openai/toolInvocation/invoked": "Served a fresh map",
|
|
967
|
+
"openai/widgetAccessible": true,
|
|
968
|
+
"openai/resultCanProduceWidget": true,
|
|
969
|
+
"openai/widgetCSP": {
|
|
970
|
+
connect_domains: [],
|
|
971
|
+
resource_domains: ["https://persistent.oaistatic.com"]
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Mount pre-built widgets from dist/resources/widgets/ directory in production mode
|
|
979
|
+
*
|
|
980
|
+
* Serves static widget bundles that were built using the build command.
|
|
981
|
+
* Sets up Express routes to serve the HTML and asset files, then registers
|
|
982
|
+
* tools and resources for each widget.
|
|
983
|
+
*
|
|
984
|
+
* @private
|
|
985
|
+
* @param options - Configuration options
|
|
986
|
+
* @param options.baseRoute - Base route for widgets (defaults to '/mcp-use/widgets')
|
|
987
|
+
* @returns Promise that resolves when all widgets are mounted
|
|
988
|
+
*/
|
|
989
|
+
async mountWidgetsProduction(options) {
|
|
990
|
+
const baseRoute = options?.baseRoute || "/mcp-use/widgets";
|
|
991
|
+
const widgetsDir = (0, import_node_path.join)(process.cwd(), "dist", "resources", "widgets");
|
|
992
|
+
if (!(0, import_node_fs.existsSync)(widgetsDir)) {
|
|
993
|
+
console.log("[WIDGETS] No dist/resources/widgets/ directory found - skipping widget serving");
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
this.setupWidgetRoutes();
|
|
997
|
+
const widgets = (0, import_node_fs.readdirSync)(widgetsDir).filter((name) => {
|
|
998
|
+
const widgetPath = (0, import_node_path.join)(widgetsDir, name);
|
|
999
|
+
const indexPath = (0, import_node_path.join)(widgetPath, "index.html");
|
|
1000
|
+
return (0, import_node_fs.existsSync)(indexPath);
|
|
1001
|
+
});
|
|
1002
|
+
if (widgets.length === 0) {
|
|
1003
|
+
console.log("[WIDGETS] No built widgets found in dist/resources/widgets/");
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
console.log(`[WIDGETS] Serving ${widgets.length} pre-built widget(s) from dist/resources/widgets/`);
|
|
1007
|
+
for (const widgetName of widgets) {
|
|
1008
|
+
const widgetPath = (0, import_node_path.join)(widgetsDir, widgetName);
|
|
1009
|
+
const indexPath = (0, import_node_path.join)(widgetPath, "index.html");
|
|
1010
|
+
const metadataPath = (0, import_node_path.join)(widgetPath, "metadata.json");
|
|
1011
|
+
let html = "";
|
|
1012
|
+
try {
|
|
1013
|
+
html = (0, import_node_fs2.readFileSync)(indexPath, "utf8");
|
|
1014
|
+
} catch (error) {
|
|
1015
|
+
console.error(`[WIDGET] Failed to read ${widgetName}/index.html:`, error);
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
let metadata = {};
|
|
1019
|
+
let props = {};
|
|
1020
|
+
let description = `Widget: ${widgetName}`;
|
|
1021
|
+
try {
|
|
1022
|
+
const metadataContent = (0, import_node_fs2.readFileSync)(metadataPath, "utf8");
|
|
1023
|
+
metadata = JSON.parse(metadataContent);
|
|
1024
|
+
if (metadata.description) {
|
|
1025
|
+
description = metadata.description;
|
|
1026
|
+
}
|
|
1027
|
+
if (metadata.inputs) {
|
|
1028
|
+
props = metadata.inputs;
|
|
1029
|
+
}
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
console.log(`[WIDGET] No metadata found for ${widgetName}, using defaults`);
|
|
1032
|
+
}
|
|
1033
|
+
this.uiResource({
|
|
1034
|
+
name: widgetName,
|
|
1035
|
+
title: widgetName,
|
|
1036
|
+
description,
|
|
1037
|
+
type: "appsSdk",
|
|
1038
|
+
props,
|
|
1039
|
+
_meta: {
|
|
1040
|
+
"mcp-use/widget": {
|
|
1041
|
+
name: widgetName,
|
|
1042
|
+
description,
|
|
1043
|
+
type: "appsSdk",
|
|
1044
|
+
props,
|
|
1045
|
+
html,
|
|
1046
|
+
dev: false
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
htmlTemplate: html,
|
|
1050
|
+
appsSdkMetadata: {
|
|
1051
|
+
"openai/widgetDescription": description,
|
|
1052
|
+
"openai/toolInvocation/invoking": `Loading ${widgetName}...`,
|
|
1053
|
+
"openai/toolInvocation/invoked": `${widgetName} ready`,
|
|
1054
|
+
"openai/widgetAccessible": true,
|
|
1055
|
+
"openai/resultCanProduceWidget": true,
|
|
1056
|
+
"openai/widgetCSP": {
|
|
1057
|
+
connect_domains: [],
|
|
1058
|
+
resource_domains: [
|
|
1059
|
+
"https://*.oaistatic.com",
|
|
1060
|
+
"https://*.unsplash.com",
|
|
1061
|
+
"https://*.oaiusercontent.com",
|
|
1062
|
+
// always also add the base url of the server
|
|
1063
|
+
...this.serverBaseUrl ? [this.serverBaseUrl] : []
|
|
1064
|
+
]
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
console.log(`[WIDGET] ${widgetName} mounted at ${baseRoute}/${widgetName}`);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
721
1071
|
/**
|
|
722
1072
|
* Mount MCP server endpoints at /mcp
|
|
723
1073
|
*
|
|
@@ -794,18 +1144,25 @@ var McpServer = class {
|
|
|
794
1144
|
* @example
|
|
795
1145
|
* ```typescript
|
|
796
1146
|
* await server.listen(8080)
|
|
797
|
-
* // Server now running at http://localhost:8080
|
|
1147
|
+
* // Server now running at http://localhost:8080 (or configured host)
|
|
798
1148
|
* // MCP endpoints: http://localhost:8080/mcp
|
|
799
1149
|
* // Inspector UI: http://localhost:8080/inspector
|
|
800
1150
|
* ```
|
|
801
1151
|
*/
|
|
802
1152
|
async listen(port) {
|
|
1153
|
+
this.serverPort = port || (process.env.PORT ? parseInt(process.env.PORT, 10) : 3001);
|
|
1154
|
+
if (process.env.HOST) {
|
|
1155
|
+
this.serverHost = process.env.HOST;
|
|
1156
|
+
}
|
|
1157
|
+
await this.mountWidgets({
|
|
1158
|
+
baseRoute: "/mcp-use/widgets",
|
|
1159
|
+
resourcesDir: "resources"
|
|
1160
|
+
});
|
|
803
1161
|
await this.mountMcp();
|
|
804
|
-
this.serverPort = port || 3001;
|
|
805
1162
|
this.mountInspector();
|
|
806
1163
|
this.app.listen(this.serverPort, () => {
|
|
807
|
-
console.log(`[SERVER] Listening on http
|
|
808
|
-
console.log(`[MCP] Endpoints: http
|
|
1164
|
+
console.log(`[SERVER] Listening on http://${this.serverHost}:${this.serverPort}`);
|
|
1165
|
+
console.log(`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp`);
|
|
809
1166
|
});
|
|
810
1167
|
}
|
|
811
1168
|
/**
|
|
@@ -832,10 +1189,17 @@ var McpServer = class {
|
|
|
832
1189
|
*/
|
|
833
1190
|
mountInspector() {
|
|
834
1191
|
if (this.inspectorMounted) return;
|
|
1192
|
+
if (this.isProductionMode()) {
|
|
1193
|
+
const manifest = this.readBuildManifest();
|
|
1194
|
+
if (!manifest?.includeInspector) {
|
|
1195
|
+
console.log("[INSPECTOR] Skipped in production (use --with-inspector flag during build)");
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
835
1199
|
import("@mcp-use/inspector").then(({ mountInspector }) => {
|
|
836
1200
|
mountInspector(this.app);
|
|
837
1201
|
this.inspectorMounted = true;
|
|
838
|
-
console.log(`[INSPECTOR] UI available at http
|
|
1202
|
+
console.log(`[INSPECTOR] UI available at http://${this.serverHost}:${this.serverPort}/inspector`);
|
|
839
1203
|
}).catch(() => {
|
|
840
1204
|
});
|
|
841
1205
|
}
|
|
@@ -843,7 +1207,7 @@ var McpServer = class {
|
|
|
843
1207
|
* Setup default widget serving routes
|
|
844
1208
|
*
|
|
845
1209
|
* Configures Express routes to serve MCP UI widgets and their static assets.
|
|
846
|
-
* Widgets are served from the dist/resources/
|
|
1210
|
+
* Widgets are served from the dist/resources/widgets directory and can
|
|
847
1211
|
* be accessed via HTTP endpoints for embedding in web applications.
|
|
848
1212
|
*
|
|
849
1213
|
* Routes created:
|
|
@@ -864,12 +1228,12 @@ var McpServer = class {
|
|
|
864
1228
|
this.app.get("/mcp-use/widgets/:widget/assets/*", (req, res, next) => {
|
|
865
1229
|
const widget = req.params.widget;
|
|
866
1230
|
const assetFile = req.params[0];
|
|
867
|
-
const assetPath = (0, import_node_path.join)(process.cwd(), "dist", "resources", "
|
|
1231
|
+
const assetPath = (0, import_node_path.join)(process.cwd(), "dist", "resources", "widgets", widget, "assets", assetFile);
|
|
868
1232
|
res.sendFile(assetPath, (err) => err ? next() : void 0);
|
|
869
1233
|
});
|
|
870
1234
|
this.app.get("/mcp-use/widgets/assets/*", (req, res, next) => {
|
|
871
1235
|
const assetFile = req.params[0];
|
|
872
|
-
const widgetsDir = (0, import_node_path.join)(process.cwd(), "dist", "resources", "
|
|
1236
|
+
const widgetsDir = (0, import_node_path.join)(process.cwd(), "dist", "resources", "widgets");
|
|
873
1237
|
try {
|
|
874
1238
|
const widgets = (0, import_node_fs.readdirSync)(widgetsDir);
|
|
875
1239
|
for (const widget of widgets) {
|
|
@@ -884,7 +1248,7 @@ var McpServer = class {
|
|
|
884
1248
|
}
|
|
885
1249
|
});
|
|
886
1250
|
this.app.get("/mcp-use/widgets/:widget", (req, res, next) => {
|
|
887
|
-
const filePath = (0, import_node_path.join)(process.cwd(), "dist", "resources", "
|
|
1251
|
+
const filePath = (0, import_node_path.join)(process.cwd(), "dist", "resources", "widgets", req.params.widget, "index.html");
|
|
888
1252
|
res.sendFile(filePath, (err) => err ? next() : void 0);
|
|
889
1253
|
});
|
|
890
1254
|
}
|
|
@@ -924,14 +1288,14 @@ var McpServer = class {
|
|
|
924
1288
|
*
|
|
925
1289
|
* @example
|
|
926
1290
|
* ```typescript
|
|
927
|
-
* const schema = this.
|
|
928
|
-
* { name: 'query', type: 'string', required: true },
|
|
1291
|
+
* const schema = this.createParamsSchema([
|
|
1292
|
+
* { name: 'query', type: 'string', required: true, description: 'Search query' },
|
|
929
1293
|
* { name: 'limit', type: 'number', required: false }
|
|
930
1294
|
* ])
|
|
931
|
-
* // Returns: { query: z.string(), limit: z.number().optional() }
|
|
1295
|
+
* // Returns: { query: z.string().describe('Search query'), limit: z.number().optional() }
|
|
932
1296
|
* ```
|
|
933
1297
|
*/
|
|
934
|
-
|
|
1298
|
+
createParamsSchema(inputs) {
|
|
935
1299
|
const schema = {};
|
|
936
1300
|
inputs.forEach((input) => {
|
|
937
1301
|
let zodType;
|
|
@@ -1071,7 +1435,9 @@ function createMCPServer(name, config = {}) {
|
|
|
1071
1435
|
const instance = new McpServer({
|
|
1072
1436
|
name,
|
|
1073
1437
|
version: config.version || "1.0.0",
|
|
1074
|
-
description: config.description
|
|
1438
|
+
description: config.description,
|
|
1439
|
+
host: config.host,
|
|
1440
|
+
baseUrl: config.baseUrl
|
|
1075
1441
|
});
|
|
1076
1442
|
return instance;
|
|
1077
1443
|
}
|