mcp-use 1.1.6-canary.1 → 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
package/dist/src/server/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import express from "express";
|
|
|
9
9
|
import cors from "cors";
|
|
10
10
|
import { existsSync, readdirSync } from "fs";
|
|
11
11
|
import { join } from "path";
|
|
12
|
+
import { readFileSync } from "fs";
|
|
12
13
|
|
|
13
14
|
// src/server/logging.ts
|
|
14
15
|
function requestLogger(req, res, next) {
|
|
@@ -47,13 +48,8 @@ function buildWidgetUrl(widget, props, config) {
|
|
|
47
48
|
`/mcp-use/widgets/${widget}`,
|
|
48
49
|
`${config.baseUrl}:${config.port}`
|
|
49
50
|
);
|
|
50
|
-
if (props) {
|
|
51
|
-
|
|
52
|
-
if (value !== void 0 && value !== null) {
|
|
53
|
-
const stringValue = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
54
|
-
url.searchParams.set(key, stringValue);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
51
|
+
if (props && Object.keys(props).length > 0) {
|
|
52
|
+
url.searchParams.set("props", JSON.stringify(props));
|
|
57
53
|
}
|
|
58
54
|
return url.toString();
|
|
59
55
|
}
|
|
@@ -153,6 +149,8 @@ function createUIResourceFromDefinition(definition, params, config) {
|
|
|
153
149
|
__name(createUIResourceFromDefinition, "createUIResourceFromDefinition");
|
|
154
150
|
|
|
155
151
|
// src/server/mcp-server.ts
|
|
152
|
+
import { createServer } from "vite";
|
|
153
|
+
var TMP_MCP_USE_DIR = ".mcp-use";
|
|
156
154
|
var McpServer = class {
|
|
157
155
|
static {
|
|
158
156
|
__name(this, "McpServer");
|
|
@@ -163,6 +161,8 @@ var McpServer = class {
|
|
|
163
161
|
mcpMounted = false;
|
|
164
162
|
inspectorMounted = false;
|
|
165
163
|
serverPort;
|
|
164
|
+
serverHost;
|
|
165
|
+
serverBaseUrl;
|
|
166
166
|
/**
|
|
167
167
|
* Creates a new MCP server instance with Express integration
|
|
168
168
|
*
|
|
@@ -175,6 +175,8 @@ var McpServer = class {
|
|
|
175
175
|
*/
|
|
176
176
|
constructor(config) {
|
|
177
177
|
this.config = config;
|
|
178
|
+
this.serverHost = config.host || "localhost";
|
|
179
|
+
this.serverBaseUrl = config.baseUrl;
|
|
178
180
|
this.server = new OfficialMcpServer({
|
|
179
181
|
name: config.name,
|
|
180
182
|
version: config.version
|
|
@@ -187,7 +189,6 @@ var McpServer = class {
|
|
|
187
189
|
allowedHeaders: ["Content-Type", "Accept", "Authorization", "mcp-protocol-version", "mcp-session-id", "X-Proxy-Token", "X-Target-URL"]
|
|
188
190
|
}));
|
|
189
191
|
this.app.use(requestLogger);
|
|
190
|
-
this.setupWidgetRoutes();
|
|
191
192
|
return new Proxy(this, {
|
|
192
193
|
get(target, prop) {
|
|
193
194
|
if (prop in target) {
|
|
@@ -246,7 +247,8 @@ var McpServer = class {
|
|
|
246
247
|
title: resourceDefinition.title,
|
|
247
248
|
description: resourceDefinition.description,
|
|
248
249
|
mimeType: resourceDefinition.mimeType,
|
|
249
|
-
annotations: resourceDefinition.annotations
|
|
250
|
+
annotations: resourceDefinition.annotations,
|
|
251
|
+
_meta: resourceDefinition._meta
|
|
250
252
|
},
|
|
251
253
|
async () => {
|
|
252
254
|
return await resourceDefinition.readCallback();
|
|
@@ -365,7 +367,7 @@ var McpServer = class {
|
|
|
365
367
|
* ```
|
|
366
368
|
*/
|
|
367
369
|
tool(toolDefinition) {
|
|
368
|
-
const inputSchema = this.
|
|
370
|
+
const inputSchema = this.createParamsSchema(toolDefinition.inputs || []);
|
|
369
371
|
this.server.registerTool(
|
|
370
372
|
toolDefinition.name,
|
|
371
373
|
{
|
|
@@ -416,7 +418,7 @@ var McpServer = class {
|
|
|
416
418
|
* ```
|
|
417
419
|
*/
|
|
418
420
|
prompt(promptDefinition) {
|
|
419
|
-
const argsSchema = this.
|
|
421
|
+
const argsSchema = this.createParamsSchema(promptDefinition.args || []);
|
|
420
422
|
this.server.registerPrompt(
|
|
421
423
|
promptDefinition.name,
|
|
422
424
|
{
|
|
@@ -443,19 +445,22 @@ var McpServer = class {
|
|
|
443
445
|
* - remoteDom: Legacy MCP-UI Remote DOM scripting
|
|
444
446
|
* - appsSdk: OpenAI Apps SDK compatible widgets (text/html+skybridge)
|
|
445
447
|
*
|
|
446
|
-
* @param
|
|
448
|
+
* @param widgetNameOrDefinition - Widget name (string) for auto-loading schema, or full configuration object
|
|
447
449
|
* @param definition.name - Unique identifier for the resource
|
|
448
450
|
* @param definition.type - Type of UI resource (externalUrl, rawHtml, remoteDom, appsSdk)
|
|
449
451
|
* @param definition.title - Human-readable title for the widget
|
|
450
452
|
* @param definition.description - Description of the widget's functionality
|
|
451
453
|
* @param definition.props - Widget properties configuration with types and defaults
|
|
452
|
-
* @param definition.size - Preferred iframe size [width, height] (e.g., ['
|
|
454
|
+
* @param definition.size - Preferred iframe size [width, height] (e.g., ['900px', '600px'])
|
|
453
455
|
* @param definition.annotations - Resource annotations for discovery
|
|
454
456
|
* @param definition.appsSdkMetadata - Apps SDK specific metadata (CSP, widget description, etc.)
|
|
455
457
|
* @returns The server instance for method chaining
|
|
456
458
|
*
|
|
457
459
|
* @example
|
|
458
460
|
* ```typescript
|
|
461
|
+
* // Simple usage - auto-loads from generated schema
|
|
462
|
+
* server.uiResource('display-weather')
|
|
463
|
+
*
|
|
459
464
|
* // Legacy MCP-UI widget
|
|
460
465
|
* server.uiResource({
|
|
461
466
|
* type: 'externalUrl',
|
|
@@ -495,14 +500,6 @@ var McpServer = class {
|
|
|
495
500
|
* ```
|
|
496
501
|
*/
|
|
497
502
|
uiResource(definition) {
|
|
498
|
-
let toolName;
|
|
499
|
-
if (definition.type === "appsSdk") {
|
|
500
|
-
toolName = definition.name;
|
|
501
|
-
} else if (definition.type === "externalUrl") {
|
|
502
|
-
toolName = `ui_${definition.widget}`;
|
|
503
|
-
} else {
|
|
504
|
-
toolName = `ui_${definition.name}`;
|
|
505
|
-
}
|
|
506
503
|
const displayName = definition.title || definition.name;
|
|
507
504
|
let resourceUri;
|
|
508
505
|
let mimeType;
|
|
@@ -532,6 +529,7 @@ var McpServer = class {
|
|
|
532
529
|
title: definition.title,
|
|
533
530
|
description: definition.description,
|
|
534
531
|
mimeType,
|
|
532
|
+
_meta: definition._meta,
|
|
535
533
|
annotations: definition.annotations,
|
|
536
534
|
readCallback: /* @__PURE__ */ __name(async () => {
|
|
537
535
|
const params = definition.type === "externalUrl" ? this.applyDefaultProps(definition.props) : {};
|
|
@@ -541,7 +539,28 @@ var McpServer = class {
|
|
|
541
539
|
};
|
|
542
540
|
}, "readCallback")
|
|
543
541
|
});
|
|
544
|
-
|
|
542
|
+
if (definition.type === "appsSdk") {
|
|
543
|
+
this.resourceTemplate({
|
|
544
|
+
name: `${definition.name}-dynamic`,
|
|
545
|
+
resourceTemplate: {
|
|
546
|
+
uriTemplate: `ui://widget/${definition.name}-{id}.html`,
|
|
547
|
+
name: definition.title || definition.name,
|
|
548
|
+
description: definition.description,
|
|
549
|
+
mimeType
|
|
550
|
+
},
|
|
551
|
+
_meta: definition._meta,
|
|
552
|
+
title: definition.title,
|
|
553
|
+
description: definition.description,
|
|
554
|
+
annotations: definition.annotations,
|
|
555
|
+
readCallback: /* @__PURE__ */ __name(async (uri, params) => {
|
|
556
|
+
const uiResource = this.createWidgetUIResource(definition, {});
|
|
557
|
+
return {
|
|
558
|
+
contents: [uiResource.resource]
|
|
559
|
+
};
|
|
560
|
+
}, "readCallback")
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
const toolMetadata = definition._meta || {};
|
|
545
564
|
if (definition.type === "appsSdk" && definition.appsSdkMetadata) {
|
|
546
565
|
toolMetadata["openai/outputTemplate"] = resourceUri;
|
|
547
566
|
const toolMetadataFields = [
|
|
@@ -557,17 +576,19 @@ var McpServer = class {
|
|
|
557
576
|
}
|
|
558
577
|
}
|
|
559
578
|
this.tool({
|
|
560
|
-
name:
|
|
579
|
+
name: definition.name,
|
|
561
580
|
title: definition.title,
|
|
562
|
-
|
|
563
|
-
description: definition.type === "appsSdk" && definition.title ? definition.title : definition.description || `Display ${displayName}`,
|
|
581
|
+
description: definition.description,
|
|
564
582
|
inputs: this.convertPropsToInputs(definition.props),
|
|
565
583
|
_meta: Object.keys(toolMetadata).length > 0 ? toolMetadata : void 0,
|
|
566
584
|
cb: /* @__PURE__ */ __name(async (params) => {
|
|
567
585
|
const uiResource = this.createWidgetUIResource(definition, params);
|
|
568
586
|
if (definition.type === "appsSdk") {
|
|
587
|
+
const randomId = Math.random().toString(36).substring(2, 15);
|
|
588
|
+
const uniqueUri = `ui://widget/${definition.name}-${randomId}.html`;
|
|
589
|
+
const uniqueToolMetadata = { ...toolMetadata, "openai/outputTemplate": uniqueUri };
|
|
569
590
|
return {
|
|
570
|
-
_meta:
|
|
591
|
+
_meta: uniqueToolMetadata,
|
|
571
592
|
content: [
|
|
572
593
|
{
|
|
573
594
|
type: "text",
|
|
@@ -605,9 +626,20 @@ var McpServer = class {
|
|
|
605
626
|
* @returns UIResource object compatible with MCP-UI
|
|
606
627
|
*/
|
|
607
628
|
createWidgetUIResource(definition, params) {
|
|
629
|
+
let configBaseUrl = `http://${this.serverHost}`;
|
|
630
|
+
let configPort = this.serverPort || 3001;
|
|
631
|
+
if (this.serverBaseUrl) {
|
|
632
|
+
try {
|
|
633
|
+
const url = new URL(this.serverBaseUrl);
|
|
634
|
+
configBaseUrl = `${url.protocol}//${url.hostname}`;
|
|
635
|
+
configPort = url.port || (url.protocol === "https:" ? 443 : 80);
|
|
636
|
+
} catch (e) {
|
|
637
|
+
console.warn("Failed to parse baseUrl, falling back to host:port", e);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
608
640
|
const urlConfig = {
|
|
609
|
-
baseUrl:
|
|
610
|
-
port:
|
|
641
|
+
baseUrl: configBaseUrl,
|
|
642
|
+
port: configPort
|
|
611
643
|
};
|
|
612
644
|
return createUIResourceFromDefinition(definition, params, urlConfig);
|
|
613
645
|
}
|
|
@@ -624,7 +656,7 @@ var McpServer = class {
|
|
|
624
656
|
* @returns Complete URL with encoded parameters
|
|
625
657
|
*/
|
|
626
658
|
buildWidgetUrl(widget, params) {
|
|
627
|
-
const baseUrl = `http
|
|
659
|
+
const baseUrl = `http://${this.serverHost}:${this.serverPort}/mcp-use/widgets/${widget}`;
|
|
628
660
|
if (Object.keys(params).length === 0) {
|
|
629
661
|
return baseUrl;
|
|
630
662
|
}
|
|
@@ -680,6 +712,324 @@ var McpServer = class {
|
|
|
680
712
|
}
|
|
681
713
|
return defaults;
|
|
682
714
|
}
|
|
715
|
+
/**
|
|
716
|
+
* Check if server is running in production mode
|
|
717
|
+
*
|
|
718
|
+
* @private
|
|
719
|
+
* @returns true if in production mode, false otherwise
|
|
720
|
+
*/
|
|
721
|
+
isProductionMode() {
|
|
722
|
+
return process.env.NODE_ENV === "production";
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Read build manifest file
|
|
726
|
+
*
|
|
727
|
+
* @private
|
|
728
|
+
* @returns Build manifest or null if not found
|
|
729
|
+
*/
|
|
730
|
+
readBuildManifest() {
|
|
731
|
+
try {
|
|
732
|
+
const manifestPath = join(process.cwd(), "dist", ".mcp-use-manifest.json");
|
|
733
|
+
const content = readFileSync(manifestPath, "utf8");
|
|
734
|
+
return JSON.parse(content);
|
|
735
|
+
} catch {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Mount widget files - automatically chooses between dev and production mode
|
|
741
|
+
*
|
|
742
|
+
* In development mode: creates Vite dev servers with HMR support
|
|
743
|
+
* In production mode: serves pre-built static widgets
|
|
744
|
+
*
|
|
745
|
+
* @param options - Configuration options
|
|
746
|
+
* @param options.baseRoute - Base route for widgets (defaults to '/mcp-use/widgets')
|
|
747
|
+
* @param options.resourcesDir - Directory containing widget files (defaults to 'resources')
|
|
748
|
+
* @returns Promise that resolves when all widgets are mounted
|
|
749
|
+
*/
|
|
750
|
+
async mountWidgets(options) {
|
|
751
|
+
if (this.isProductionMode()) {
|
|
752
|
+
await this.mountWidgetsProduction(options);
|
|
753
|
+
} else {
|
|
754
|
+
await this.mountWidgetsDev(options);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Mount individual widget files from resources/ directory in development mode
|
|
759
|
+
*
|
|
760
|
+
* Scans the resources/ directory for .tsx/.ts widget files and creates individual
|
|
761
|
+
* Vite dev servers for each widget with HMR support. Each widget is served at its
|
|
762
|
+
* own route: /mcp-use/widgets/{widget-name}
|
|
763
|
+
*
|
|
764
|
+
* @private
|
|
765
|
+
* @param options - Configuration options
|
|
766
|
+
* @param options.baseRoute - Base route for widgets (defaults to '/mcp-use/widgets')
|
|
767
|
+
* @param options.resourcesDir - Directory containing widget files (defaults to 'resources')
|
|
768
|
+
* @returns Promise that resolves when all widgets are mounted
|
|
769
|
+
*/
|
|
770
|
+
async mountWidgetsDev(options) {
|
|
771
|
+
const { promises: fs } = await import("fs");
|
|
772
|
+
const baseRoute = options?.baseRoute || "/mcp-use/widgets";
|
|
773
|
+
const resourcesDir = options?.resourcesDir || "resources";
|
|
774
|
+
const srcDir = join(process.cwd(), resourcesDir);
|
|
775
|
+
try {
|
|
776
|
+
await fs.access(srcDir);
|
|
777
|
+
} catch (error) {
|
|
778
|
+
console.log(`[WIDGETS] No ${resourcesDir}/ directory found - skipping widget serving`);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
let entries = [];
|
|
782
|
+
try {
|
|
783
|
+
const files = await fs.readdir(srcDir);
|
|
784
|
+
entries = files.filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) => join(srcDir, f));
|
|
785
|
+
} catch (error) {
|
|
786
|
+
console.log(`[WIDGETS] No widgets found in ${resourcesDir}/ directory`);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (entries.length === 0) {
|
|
790
|
+
console.log(`[WIDGETS] No widgets found in ${resourcesDir}/ directory`);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const tempDir = join(process.cwd(), TMP_MCP_USE_DIR);
|
|
794
|
+
await fs.mkdir(tempDir, { recursive: true }).catch(() => {
|
|
795
|
+
});
|
|
796
|
+
const react = (await import("@vitejs/plugin-react")).default;
|
|
797
|
+
const tailwindcss = (await import("@tailwindcss/vite")).default;
|
|
798
|
+
console.log(react, tailwindcss);
|
|
799
|
+
const widgets = entries.map((entry) => {
|
|
800
|
+
const baseName = entry.split("/").pop()?.replace(/\.tsx?$/, "") || "widget";
|
|
801
|
+
const widgetName = baseName;
|
|
802
|
+
return {
|
|
803
|
+
name: widgetName,
|
|
804
|
+
description: `Widget: ${widgetName}`,
|
|
805
|
+
entry
|
|
806
|
+
};
|
|
807
|
+
});
|
|
808
|
+
for (const widget of widgets) {
|
|
809
|
+
const widgetTempDir = join(tempDir, widget.name);
|
|
810
|
+
await fs.mkdir(widgetTempDir, { recursive: true });
|
|
811
|
+
const resourcesPath = join(process.cwd(), resourcesDir);
|
|
812
|
+
const { relative } = await import("path");
|
|
813
|
+
const relativeResourcesPath = relative(widgetTempDir, resourcesPath).replace(/\\/g, "/");
|
|
814
|
+
const cssContent = `@import "tailwindcss";
|
|
815
|
+
|
|
816
|
+
/* Configure Tailwind to scan the resources directory */
|
|
817
|
+
@source "${relativeResourcesPath}";
|
|
818
|
+
`;
|
|
819
|
+
await fs.writeFile(join(widgetTempDir, "styles.css"), cssContent, "utf8");
|
|
820
|
+
const entryContent = `import React from 'react'
|
|
821
|
+
import { createRoot } from 'react-dom/client'
|
|
822
|
+
import './styles.css'
|
|
823
|
+
import Component from '${widget.entry}'
|
|
824
|
+
|
|
825
|
+
const container = document.getElementById('widget-root')
|
|
826
|
+
if (container && Component) {
|
|
827
|
+
const root = createRoot(container)
|
|
828
|
+
root.render(<Component />)
|
|
829
|
+
}
|
|
830
|
+
`;
|
|
831
|
+
const htmlContent = `<!doctype html>
|
|
832
|
+
<html lang="en">
|
|
833
|
+
<head>
|
|
834
|
+
<meta charset="UTF-8" />
|
|
835
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
836
|
+
<title>${widget.name} Widget</title>
|
|
837
|
+
</head>
|
|
838
|
+
<body>
|
|
839
|
+
<div id="widget-root"></div>
|
|
840
|
+
<script type="module" src="${baseRoute}/${widget.name}/entry.tsx"></script>
|
|
841
|
+
</body>
|
|
842
|
+
</html>`;
|
|
843
|
+
await fs.writeFile(join(widgetTempDir, "entry.tsx"), entryContent, "utf8");
|
|
844
|
+
await fs.writeFile(join(widgetTempDir, "index.html"), htmlContent, "utf8");
|
|
845
|
+
}
|
|
846
|
+
const serverOrigin = this.serverBaseUrl || `http://${this.serverHost}:${this.serverPort}`;
|
|
847
|
+
console.log(`[WIDGETS] Serving ${entries.length} widget(s) with shared Vite dev server and HMR`);
|
|
848
|
+
const viteServer = await createServer({
|
|
849
|
+
root: tempDir,
|
|
850
|
+
base: baseRoute + "/",
|
|
851
|
+
plugins: [tailwindcss(), react()],
|
|
852
|
+
resolve: {
|
|
853
|
+
alias: {
|
|
854
|
+
"@": join(process.cwd(), resourcesDir)
|
|
855
|
+
}
|
|
856
|
+
},
|
|
857
|
+
server: {
|
|
858
|
+
middlewareMode: true,
|
|
859
|
+
origin: serverOrigin
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
this.app.use(baseRoute, (req, res, next) => {
|
|
863
|
+
const urlPath = req.url || "";
|
|
864
|
+
const [pathname, queryString] = urlPath.split("?");
|
|
865
|
+
const widgetMatch = pathname.match(/^\/([^/]+)/);
|
|
866
|
+
if (widgetMatch) {
|
|
867
|
+
const widgetName = widgetMatch[1];
|
|
868
|
+
const widget = widgets.find((w) => w.name === widgetName);
|
|
869
|
+
if (widget) {
|
|
870
|
+
if (pathname === `/${widgetName}` || pathname === `/${widgetName}/`) {
|
|
871
|
+
req.url = `/${widgetName}/index.html${queryString ? "?" + queryString : ""}`;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
next();
|
|
876
|
+
});
|
|
877
|
+
this.app.use(baseRoute, viteServer.middlewares);
|
|
878
|
+
widgets.forEach((widget) => {
|
|
879
|
+
console.log(`[WIDGET] ${widget.name} mounted at ${baseRoute}/${widget.name}`);
|
|
880
|
+
});
|
|
881
|
+
for (const widget of widgets) {
|
|
882
|
+
const type = "appsSdk";
|
|
883
|
+
let widgetMetadata = {};
|
|
884
|
+
let props = {};
|
|
885
|
+
let description = widget.description;
|
|
886
|
+
try {
|
|
887
|
+
const mod = await viteServer.ssrLoadModule(widget.entry);
|
|
888
|
+
if (mod.widgetMetadata) {
|
|
889
|
+
widgetMetadata = mod.widgetMetadata;
|
|
890
|
+
description = widgetMetadata.description || widget.description;
|
|
891
|
+
if (widgetMetadata.inputs) {
|
|
892
|
+
try {
|
|
893
|
+
props = widgetMetadata.inputs.shape || {};
|
|
894
|
+
} catch (error) {
|
|
895
|
+
console.warn(`[WIDGET] Failed to extract props schema for ${widget.name}:`, error);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
} catch (error) {
|
|
900
|
+
console.warn(`[WIDGET] Failed to load metadata for ${widget.name}:`, error);
|
|
901
|
+
}
|
|
902
|
+
let html = "";
|
|
903
|
+
try {
|
|
904
|
+
html = await readFileSync(join(tempDir, widget.name, "index.html"), "utf8");
|
|
905
|
+
} catch (error) {
|
|
906
|
+
console.error(`Failed to read html template for widget ${widget.name}`, error);
|
|
907
|
+
}
|
|
908
|
+
this.uiResource({
|
|
909
|
+
name: widget.name,
|
|
910
|
+
title: widget.name,
|
|
911
|
+
description,
|
|
912
|
+
type,
|
|
913
|
+
props,
|
|
914
|
+
_meta: {
|
|
915
|
+
"mcp-use/widget": {
|
|
916
|
+
name: widget.name,
|
|
917
|
+
description,
|
|
918
|
+
type,
|
|
919
|
+
props,
|
|
920
|
+
html,
|
|
921
|
+
dev: true
|
|
922
|
+
}
|
|
923
|
+
},
|
|
924
|
+
htmlTemplate: html,
|
|
925
|
+
appsSdkMetadata: {
|
|
926
|
+
"openai/widgetDescription": description,
|
|
927
|
+
"openai/toolInvocation/invoking": "Hand-tossing a map",
|
|
928
|
+
"openai/toolInvocation/invoked": "Served a fresh map",
|
|
929
|
+
"openai/widgetAccessible": true,
|
|
930
|
+
"openai/resultCanProduceWidget": true,
|
|
931
|
+
"openai/widgetCSP": {
|
|
932
|
+
connect_domains: [],
|
|
933
|
+
resource_domains: ["https://persistent.oaistatic.com"]
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Mount pre-built widgets from dist/resources/widgets/ directory in production mode
|
|
941
|
+
*
|
|
942
|
+
* Serves static widget bundles that were built using the build command.
|
|
943
|
+
* Sets up Express routes to serve the HTML and asset files, then registers
|
|
944
|
+
* tools and resources for each widget.
|
|
945
|
+
*
|
|
946
|
+
* @private
|
|
947
|
+
* @param options - Configuration options
|
|
948
|
+
* @param options.baseRoute - Base route for widgets (defaults to '/mcp-use/widgets')
|
|
949
|
+
* @returns Promise that resolves when all widgets are mounted
|
|
950
|
+
*/
|
|
951
|
+
async mountWidgetsProduction(options) {
|
|
952
|
+
const baseRoute = options?.baseRoute || "/mcp-use/widgets";
|
|
953
|
+
const widgetsDir = join(process.cwd(), "dist", "resources", "widgets");
|
|
954
|
+
if (!existsSync(widgetsDir)) {
|
|
955
|
+
console.log("[WIDGETS] No dist/resources/widgets/ directory found - skipping widget serving");
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
this.setupWidgetRoutes();
|
|
959
|
+
const widgets = readdirSync(widgetsDir).filter((name) => {
|
|
960
|
+
const widgetPath = join(widgetsDir, name);
|
|
961
|
+
const indexPath = join(widgetPath, "index.html");
|
|
962
|
+
return existsSync(indexPath);
|
|
963
|
+
});
|
|
964
|
+
if (widgets.length === 0) {
|
|
965
|
+
console.log("[WIDGETS] No built widgets found in dist/resources/widgets/");
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
console.log(`[WIDGETS] Serving ${widgets.length} pre-built widget(s) from dist/resources/widgets/`);
|
|
969
|
+
for (const widgetName of widgets) {
|
|
970
|
+
const widgetPath = join(widgetsDir, widgetName);
|
|
971
|
+
const indexPath = join(widgetPath, "index.html");
|
|
972
|
+
const metadataPath = join(widgetPath, "metadata.json");
|
|
973
|
+
let html = "";
|
|
974
|
+
try {
|
|
975
|
+
html = readFileSync(indexPath, "utf8");
|
|
976
|
+
} catch (error) {
|
|
977
|
+
console.error(`[WIDGET] Failed to read ${widgetName}/index.html:`, error);
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
let metadata = {};
|
|
981
|
+
let props = {};
|
|
982
|
+
let description = `Widget: ${widgetName}`;
|
|
983
|
+
try {
|
|
984
|
+
const metadataContent = readFileSync(metadataPath, "utf8");
|
|
985
|
+
metadata = JSON.parse(metadataContent);
|
|
986
|
+
if (metadata.description) {
|
|
987
|
+
description = metadata.description;
|
|
988
|
+
}
|
|
989
|
+
if (metadata.inputs) {
|
|
990
|
+
props = metadata.inputs;
|
|
991
|
+
}
|
|
992
|
+
} catch (error) {
|
|
993
|
+
console.log(`[WIDGET] No metadata found for ${widgetName}, using defaults`);
|
|
994
|
+
}
|
|
995
|
+
this.uiResource({
|
|
996
|
+
name: widgetName,
|
|
997
|
+
title: widgetName,
|
|
998
|
+
description,
|
|
999
|
+
type: "appsSdk",
|
|
1000
|
+
props,
|
|
1001
|
+
_meta: {
|
|
1002
|
+
"mcp-use/widget": {
|
|
1003
|
+
name: widgetName,
|
|
1004
|
+
description,
|
|
1005
|
+
type: "appsSdk",
|
|
1006
|
+
props,
|
|
1007
|
+
html,
|
|
1008
|
+
dev: false
|
|
1009
|
+
}
|
|
1010
|
+
},
|
|
1011
|
+
htmlTemplate: html,
|
|
1012
|
+
appsSdkMetadata: {
|
|
1013
|
+
"openai/widgetDescription": description,
|
|
1014
|
+
"openai/toolInvocation/invoking": `Loading ${widgetName}...`,
|
|
1015
|
+
"openai/toolInvocation/invoked": `${widgetName} ready`,
|
|
1016
|
+
"openai/widgetAccessible": true,
|
|
1017
|
+
"openai/resultCanProduceWidget": true,
|
|
1018
|
+
"openai/widgetCSP": {
|
|
1019
|
+
connect_domains: [],
|
|
1020
|
+
resource_domains: [
|
|
1021
|
+
"https://*.oaistatic.com",
|
|
1022
|
+
"https://*.unsplash.com",
|
|
1023
|
+
"https://*.oaiusercontent.com",
|
|
1024
|
+
// always also add the base url of the server
|
|
1025
|
+
...this.serverBaseUrl ? [this.serverBaseUrl] : []
|
|
1026
|
+
]
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
console.log(`[WIDGET] ${widgetName} mounted at ${baseRoute}/${widgetName}`);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
683
1033
|
/**
|
|
684
1034
|
* Mount MCP server endpoints at /mcp
|
|
685
1035
|
*
|
|
@@ -756,18 +1106,25 @@ var McpServer = class {
|
|
|
756
1106
|
* @example
|
|
757
1107
|
* ```typescript
|
|
758
1108
|
* await server.listen(8080)
|
|
759
|
-
* // Server now running at http://localhost:8080
|
|
1109
|
+
* // Server now running at http://localhost:8080 (or configured host)
|
|
760
1110
|
* // MCP endpoints: http://localhost:8080/mcp
|
|
761
1111
|
* // Inspector UI: http://localhost:8080/inspector
|
|
762
1112
|
* ```
|
|
763
1113
|
*/
|
|
764
1114
|
async listen(port) {
|
|
1115
|
+
this.serverPort = port || (process.env.PORT ? parseInt(process.env.PORT, 10) : 3001);
|
|
1116
|
+
if (process.env.HOST) {
|
|
1117
|
+
this.serverHost = process.env.HOST;
|
|
1118
|
+
}
|
|
1119
|
+
await this.mountWidgets({
|
|
1120
|
+
baseRoute: "/mcp-use/widgets",
|
|
1121
|
+
resourcesDir: "resources"
|
|
1122
|
+
});
|
|
765
1123
|
await this.mountMcp();
|
|
766
|
-
this.serverPort = port || 3001;
|
|
767
1124
|
this.mountInspector();
|
|
768
1125
|
this.app.listen(this.serverPort, () => {
|
|
769
|
-
console.log(`[SERVER] Listening on http
|
|
770
|
-
console.log(`[MCP] Endpoints: http
|
|
1126
|
+
console.log(`[SERVER] Listening on http://${this.serverHost}:${this.serverPort}`);
|
|
1127
|
+
console.log(`[MCP] Endpoints: http://${this.serverHost}:${this.serverPort}/mcp`);
|
|
771
1128
|
});
|
|
772
1129
|
}
|
|
773
1130
|
/**
|
|
@@ -794,10 +1151,17 @@ var McpServer = class {
|
|
|
794
1151
|
*/
|
|
795
1152
|
mountInspector() {
|
|
796
1153
|
if (this.inspectorMounted) return;
|
|
1154
|
+
if (this.isProductionMode()) {
|
|
1155
|
+
const manifest = this.readBuildManifest();
|
|
1156
|
+
if (!manifest?.includeInspector) {
|
|
1157
|
+
console.log("[INSPECTOR] Skipped in production (use --with-inspector flag during build)");
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
797
1161
|
import("@mcp-use/inspector").then(({ mountInspector }) => {
|
|
798
1162
|
mountInspector(this.app);
|
|
799
1163
|
this.inspectorMounted = true;
|
|
800
|
-
console.log(`[INSPECTOR] UI available at http
|
|
1164
|
+
console.log(`[INSPECTOR] UI available at http://${this.serverHost}:${this.serverPort}/inspector`);
|
|
801
1165
|
}).catch(() => {
|
|
802
1166
|
});
|
|
803
1167
|
}
|
|
@@ -805,7 +1169,7 @@ var McpServer = class {
|
|
|
805
1169
|
* Setup default widget serving routes
|
|
806
1170
|
*
|
|
807
1171
|
* Configures Express routes to serve MCP UI widgets and their static assets.
|
|
808
|
-
* Widgets are served from the dist/resources/
|
|
1172
|
+
* Widgets are served from the dist/resources/widgets directory and can
|
|
809
1173
|
* be accessed via HTTP endpoints for embedding in web applications.
|
|
810
1174
|
*
|
|
811
1175
|
* Routes created:
|
|
@@ -826,12 +1190,12 @@ var McpServer = class {
|
|
|
826
1190
|
this.app.get("/mcp-use/widgets/:widget/assets/*", (req, res, next) => {
|
|
827
1191
|
const widget = req.params.widget;
|
|
828
1192
|
const assetFile = req.params[0];
|
|
829
|
-
const assetPath = join(process.cwd(), "dist", "resources", "
|
|
1193
|
+
const assetPath = join(process.cwd(), "dist", "resources", "widgets", widget, "assets", assetFile);
|
|
830
1194
|
res.sendFile(assetPath, (err) => err ? next() : void 0);
|
|
831
1195
|
});
|
|
832
1196
|
this.app.get("/mcp-use/widgets/assets/*", (req, res, next) => {
|
|
833
1197
|
const assetFile = req.params[0];
|
|
834
|
-
const widgetsDir = join(process.cwd(), "dist", "resources", "
|
|
1198
|
+
const widgetsDir = join(process.cwd(), "dist", "resources", "widgets");
|
|
835
1199
|
try {
|
|
836
1200
|
const widgets = readdirSync(widgetsDir);
|
|
837
1201
|
for (const widget of widgets) {
|
|
@@ -846,7 +1210,7 @@ var McpServer = class {
|
|
|
846
1210
|
}
|
|
847
1211
|
});
|
|
848
1212
|
this.app.get("/mcp-use/widgets/:widget", (req, res, next) => {
|
|
849
|
-
const filePath = join(process.cwd(), "dist", "resources", "
|
|
1213
|
+
const filePath = join(process.cwd(), "dist", "resources", "widgets", req.params.widget, "index.html");
|
|
850
1214
|
res.sendFile(filePath, (err) => err ? next() : void 0);
|
|
851
1215
|
});
|
|
852
1216
|
}
|
|
@@ -886,14 +1250,14 @@ var McpServer = class {
|
|
|
886
1250
|
*
|
|
887
1251
|
* @example
|
|
888
1252
|
* ```typescript
|
|
889
|
-
* const schema = this.
|
|
890
|
-
* { name: 'query', type: 'string', required: true },
|
|
1253
|
+
* const schema = this.createParamsSchema([
|
|
1254
|
+
* { name: 'query', type: 'string', required: true, description: 'Search query' },
|
|
891
1255
|
* { name: 'limit', type: 'number', required: false }
|
|
892
1256
|
* ])
|
|
893
|
-
* // Returns: { query: z.string(), limit: z.number().optional() }
|
|
1257
|
+
* // Returns: { query: z.string().describe('Search query'), limit: z.number().optional() }
|
|
894
1258
|
* ```
|
|
895
1259
|
*/
|
|
896
|
-
|
|
1260
|
+
createParamsSchema(inputs) {
|
|
897
1261
|
const schema = {};
|
|
898
1262
|
inputs.forEach((input) => {
|
|
899
1263
|
let zodType;
|
|
@@ -1033,7 +1397,9 @@ function createMCPServer(name, config = {}) {
|
|
|
1033
1397
|
const instance = new McpServer({
|
|
1034
1398
|
name,
|
|
1035
1399
|
version: config.version || "1.0.0",
|
|
1036
|
-
description: config.description
|
|
1400
|
+
description: config.description,
|
|
1401
|
+
host: config.host,
|
|
1402
|
+
baseUrl: config.baseUrl
|
|
1037
1403
|
});
|
|
1038
1404
|
return instance;
|
|
1039
1405
|
}
|