UNPKG

mcp-use

Version:

Opinionated MCP Framework for TypeScript (@modelcontextprotocol/sdk compatible) - Build MCP Agents, Clients and Servers with support for ChatGPT Apps, Code Mode, OAuth, Notifications, Sampling, Observability and more.

1,617 lines (1,605 loc) 177 kB
import { getRequestContext, hasRequestContext, runWithContext } from "../../chunk-LWVK6RXA.js"; import { createEnhancedContext, findSessionContext, isValidLogLevel } from "../../chunk-YV6CCR64.js"; import { convertToolResultToResourceResult } from "../../chunk-362PI25Z.js"; import { convertToolResultToPromptResult } from "../../chunk-2EYAMIT3.js"; import { createRequest, sendNotificationToAll, sendNotificationToSession } from "../../chunk-UWWLWLS2.js"; import "../../chunk-KUEVOU4M.js"; import { Telemetry, VERSION, fsHelpers, generateUUID, getCwd, getEnv, getPackageVersion, isDeno, pathHelpers } from "../../chunk-CN263ZGG.js"; import "../../chunk-FRUZDWXH.js"; import { __name } from "../../chunk-3GQAWCBQ.js"; // src/server/mcp-server.ts import { McpServer as OfficialMcpServer, ResourceTemplate as ResourceTemplate2 } from "@mcp-use/modelcontextprotocol-sdk/server/mcp.js"; import { McpError, ErrorCode } from "@mcp-use/modelcontextprotocol-sdk/types.js"; import { z as z3 } from "zod"; // src/server/utils/response-helpers.ts function text(content) { return { content: [ { type: "text", text: content } ], _meta: { mimeType: "text/plain" } }; } __name(text, "text"); function image(data, mimeType = "image/png") { return { content: [ { type: "image", data, mimeType } ], _meta: { mimeType, isImage: true } }; } __name(image, "image"); function getAudioMimeType(filename) { const ext = filename.split(".").pop()?.toLowerCase(); switch (ext) { case "wav": return "audio/wav"; case "mp3": return "audio/mpeg"; case "ogg": return "audio/ogg"; case "m4a": return "audio/mp4"; case "webm": return "audio/webm"; case "flac": return "audio/flac"; case "aac": return "audio/aac"; default: return "audio/wav"; } } __name(getAudioMimeType, "getAudioMimeType"); function arrayBufferToBase64(buffer) { if (isDeno) { const bytes = new Uint8Array(buffer); let binary2 = ""; for (let i = 0; i < bytes.length; i++) { binary2 += String.fromCharCode(bytes[i]); } return btoa(binary2); } else { return Buffer.from(buffer).toString("base64"); } } __name(arrayBufferToBase64, "arrayBufferToBase64"); function audio(dataOrPath, mimeType) { const isFilePath = dataOrPath.includes("/") || dataOrPath.includes("\\") || dataOrPath.includes("."); if (isFilePath && dataOrPath.length < 1e3) { return (async () => { const buffer = await fsHelpers.readFile(dataOrPath); const base64Data = arrayBufferToBase64(buffer); const inferredMimeType = mimeType || getAudioMimeType(dataOrPath); return { content: [ { type: "audio", data: base64Data, mimeType: inferredMimeType } ], _meta: { mimeType: inferredMimeType, isAudio: true } }; })(); } const finalMimeType = mimeType || "audio/wav"; return { content: [ { type: "audio", data: dataOrPath, mimeType: finalMimeType } ], _meta: { mimeType: finalMimeType, isAudio: true } }; } __name(audio, "audio"); function resource(uri, mimeTypeOrContent, text2) { if (typeof mimeTypeOrContent === "object" && mimeTypeOrContent !== null && "content" in mimeTypeOrContent) { const contentResult = mimeTypeOrContent; let extractedText; let extractedMimeType; if (contentResult._meta && typeof contentResult._meta === "object") { const meta = contentResult._meta; if (meta.mimeType && typeof meta.mimeType === "string") { extractedMimeType = meta.mimeType; } } if (contentResult.content && contentResult.content.length > 0) { const firstContent = contentResult.content[0]; if (firstContent.type === "text" && "text" in firstContent) { extractedText = firstContent.text; } } const resourceContent2 = { type: "resource", resource: { uri, ...extractedMimeType && { mimeType: extractedMimeType }, ...extractedText && { text: extractedText } } }; return { content: [resourceContent2] }; } const mimeType = mimeTypeOrContent; const resourceContent = { type: "resource", resource: { uri, ...mimeType && { mimeType }, ...text2 && { text: text2 } } }; return { content: [resourceContent] }; } __name(resource, "resource"); function error(message) { return { isError: true, content: [ { type: "text", text: message } ] }; } __name(error, "error"); function object(data) { return Array.isArray(data) ? array(data) : { content: [ { type: "text", text: JSON.stringify(data, null, 2) } ], structuredContent: data, _meta: { mimeType: "application/json" } }; } __name(object, "object"); function array(data) { return { content: [ { type: "text", text: JSON.stringify(data, null, 2) } ], structuredContent: { data } }; } __name(array, "array"); function html(content) { return { content: [ { type: "text", text: content } ], _meta: { mimeType: "text/html" } }; } __name(html, "html"); function markdown(content) { return { content: [ { type: "text", text: content } ], _meta: { mimeType: "text/markdown" } }; } __name(markdown, "markdown"); function xml(content) { return { content: [ { type: "text", text: content } ], _meta: { mimeType: "text/xml" } }; } __name(xml, "xml"); function css(content) { return { content: [ { type: "text", text: content } ], _meta: { mimeType: "text/css" } }; } __name(css, "css"); function javascript(content) { return { content: [ { type: "text", text: content } ], _meta: { mimeType: "text/javascript" } }; } __name(javascript, "javascript"); function binary(base64Data, mimeType) { return { content: [ { type: "text", text: base64Data } ], _meta: { mimeType, isBinary: true } }; } __name(binary, "binary"); function widget(config) { const props = config.props || config.data || {}; const { output, message } = config; const finalContent = message ? [{ type: "text", text: message }] : Array.isArray(output?.content) && output.content.length > 0 ? output.content : [{ type: "text", text: "" }]; const meta = { ...output?._meta || {}, "mcp-use/props": props }; const result = { content: finalContent, _meta: meta }; if (output?.structuredContent) { result.structuredContent = output.structuredContent; } else if (Object.keys(props).length > 0) { result.structuredContent = props; } return result; } __name(widget, "widget"); function mix(...results) { const structuredContent = results.find((result) => result.structuredContent) && results.filter((result) => result.structuredContent).map((result) => result.structuredContent).reduce( (acc, result) => { return { ...acc, ...result }; }, {} ); const _meta = results.find((result) => result._meta) && results.filter((result) => result._meta).map((result) => result._meta).reduce( (acc, result) => { return { ...acc, ...result }; }, {} ); return { content: results.flatMap((result) => result.content), ...structuredContent && { structuredContent }, ..._meta && { _meta } }; } __name(mix, "mix"); // src/server/utils/server-helpers.ts import { Hono } from "hono"; import { cors } from "hono/cors"; function getDefaultCorsOptions() { return { origin: "*", allowMethods: ["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"], allowHeaders: [ "Content-Type", "Accept", "Authorization", "mcp-protocol-version", "mcp-session-id", "X-Proxy-Token", "X-Target-URL" ], // Expose mcp-session-id so browser clients can read it from responses exposeHeaders: ["mcp-session-id"] }; } __name(getDefaultCorsOptions, "getDefaultCorsOptions"); function createHonoApp(requestLogger2) { const app = new Hono(); app.use("*", cors(getDefaultCorsOptions())); app.use("*", requestLogger2); return app; } __name(createHonoApp, "createHonoApp"); function getServerBaseUrl(serverBaseUrl, serverHost, serverPort) { if (serverBaseUrl) { return serverBaseUrl; } const mcpUrl = getEnv("MCP_URL"); if (mcpUrl) { return mcpUrl; } return `http://${serverHost}:${serverPort}`; } __name(getServerBaseUrl, "getServerBaseUrl"); function getCSPUrls() { const cspUrlsEnv = getEnv("CSP_URLS"); if (!cspUrlsEnv) { console.log("[CSP] No CSP_URLS environment variable found"); return []; } const urls = cspUrlsEnv.split(",").map((url) => url.trim()).filter((url) => url.length > 0); console.log("[CSP] Parsed CSP URLs:", urls); return urls; } __name(getCSPUrls, "getCSPUrls"); function logRegisteredItems(registeredTools, registeredPrompts, registeredResources) { console.log("\n\u{1F4CB} Server exposes:"); console.log(` Tools: ${registeredTools.length}`); if (registeredTools.length > 0) { registeredTools.forEach((name) => { console.log(` - ${name}`); }); } console.log(` Prompts: ${registeredPrompts.length}`); if (registeredPrompts.length > 0) { registeredPrompts.forEach((name) => { console.log(` - ${name}`); }); } console.log(` Resources: ${registeredResources.length}`); if (registeredResources.length > 0) { registeredResources.forEach((name) => { console.log(` - ${name}`); }); } console.log(""); } __name(logRegisteredItems, "logRegisteredItems"); function parseTemplateUri(template, uri) { const params = {}; let regexPattern = template.replace(/[.*+?^$()[\]\\|]/g, "\\$&"); const paramNames = []; regexPattern = regexPattern.replace(/\{([^}]+)\}/g, (_, paramName) => { paramNames.push(paramName); return "([^/]+)"; }); const regex = new RegExp(`^${regexPattern}$`); const match = uri.match(regex); if (match) { paramNames.forEach((paramName, index) => { params[paramName] = match[index + 1]; }); } return params; } __name(parseTemplateUri, "parseTemplateUri"); // src/server/utils/server-lifecycle.ts function isProductionMode() { return getEnv("NODE_ENV") === "production"; } __name(isProductionMode, "isProductionMode"); function getDenoCorsHeaders() { return { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type" }; } __name(getDenoCorsHeaders, "getDenoCorsHeaders"); function applyDenoCorsHeaders(response) { const corsHeaders = getDenoCorsHeaders(); const newHeaders = new Headers(response.headers); Object.entries(corsHeaders).forEach(([key, value]) => { newHeaders.set(key, value); }); return new Response(response.body, { status: response.status, statusText: response.statusText, headers: newHeaders }); } __name(applyDenoCorsHeaders, "applyDenoCorsHeaders"); function createSupabasePathRewriter() { return (pathname) => { let newPathname = pathname; const functionsMatch = pathname.match(/^\/functions\/v1\/[^/]+(\/.*)?$/); if (functionsMatch) { newPathname = functionsMatch[1] || "/"; } else { const functionNameMatch = pathname.match(/^\/([^/]+)(\/.*)?$/); if (functionNameMatch && functionNameMatch[2]) { newPathname = functionNameMatch[2] || "/"; } } return newPathname; }; } __name(createSupabasePathRewriter, "createSupabasePathRewriter"); function rewriteSupabaseRequest(req) { const url = new URL(req.url); const pathname = url.pathname; const rewriter = createSupabasePathRewriter(); const newPathname = rewriter(pathname); if (newPathname !== pathname) { const newUrl = new URL(newPathname + url.search, url.origin); return new Request(newUrl, { method: req.method, headers: req.headers, body: req.body, redirect: req.redirect }); } return req; } __name(rewriteSupabaseRequest, "rewriteSupabaseRequest"); async function startServer(app, port, host, options) { if (isDeno) { const corsHeaders = getDenoCorsHeaders(); globalThis.Deno.serve( { port, hostname: host }, async (req) => { if (req.method === "OPTIONS") { return new Response("ok", { headers: corsHeaders }); } let finalReq = req; if (options?.onDenoRequest) { finalReq = await options.onDenoRequest(req); } let response = await app.fetch(finalReq); if (options?.onDenoResponse) { response = await options.onDenoResponse(response); } else { response = applyDenoCorsHeaders(response); } return response; } ); console.log(`[SERVER] Listening`); } else { const { serve } = await import("@hono/node-server"); serve( { fetch: app.fetch, port, hostname: host }, (_info) => { console.log(`[SERVER] Listening on http://${host}:${port}`); console.log( `[MCP] Endpoints: http://${host}:${port}/mcp and http://${host}:${port}/sse` ); } ); } } __name(startServer, "startServer"); // src/server/connect-adapter.ts function isExpressMiddleware(middleware) { if (!middleware || typeof middleware !== "function") { return false; } const paramCount = middleware.length; if (paramCount === 3 || paramCount === 4) { return true; } if (paramCount === 2) { const fnString = middleware.toString(); const expressPatterns = [ /\bres\.(send|json|status|end|redirect|render|sendFile|download)\b/, /\breq\.(body|params|query|cookies|session)\b/, /\breq\.get\s*\(/, /\bres\.set\s*\(/ ]; const hasExpressPattern = expressPatterns.some( (pattern) => pattern.test(fnString) ); if (hasExpressPattern) { return true; } return false; } return false; } __name(isExpressMiddleware, "isExpressMiddleware"); async function adaptMiddleware(middleware, middlewarePath = "*") { if (isExpressMiddleware(middleware)) { return adaptConnectMiddleware(middleware, middlewarePath); } return middleware; } __name(adaptMiddleware, "adaptMiddleware"); async function adaptConnectMiddleware(connectMiddleware, middlewarePath) { let createRequest2; let createResponse; try { const { createRequire } = await import("module"); const { pathToFileURL } = await import("url"); const userProjectRequire = createRequire( pathToFileURL( // Use process.cwd() since this is a runtime utility that should work from user's project process.cwd() + "/package.json" ).href ); const httpMocksPath = userProjectRequire.resolve("node-mocks-http"); const httpMocks = await import(httpMocksPath); createRequest2 = httpMocks.createRequest; createResponse = httpMocks.createResponse; } catch (error2) { throw new Error( "\u274C Widget middleware dependencies not installed!\n\nTo use Connect middleware adapters with MCP widgets, you need to install:\n\n npm install node-mocks-http\n # or\n pnpm add node-mocks-http\n\nThis dependency is automatically included in projects created with 'create-mcp-use-app'." ); } let normalizedPath = middlewarePath; if (normalizedPath.endsWith("*")) { normalizedPath = normalizedPath.slice(0, -1); } if (normalizedPath.endsWith("/")) { normalizedPath = normalizedPath.slice(0, -1); } const honoMiddleware = /* @__PURE__ */ __name(async (c, next) => { const request = c.req.raw; const parsedURL = new URL(request.url, "http://localhost"); const query = {}; for (const [key, value] of parsedURL.searchParams.entries()) { query[key] = value; } let middlewarePathname = parsedURL.pathname; if (normalizedPath && middlewarePathname.startsWith(normalizedPath)) { middlewarePathname = middlewarePathname.substring(normalizedPath.length); if (middlewarePathname === "") { middlewarePathname = "/"; } else if (!middlewarePathname.startsWith("/")) { middlewarePathname = "/" + middlewarePathname; } } const mockRequest = createRequest2({ method: request.method.toUpperCase(), url: middlewarePathname + parsedURL.search, headers: request.headers && typeof request.headers.entries === "function" ? Object.fromEntries(request.headers.entries()) : request.headers, query, ...request.body && { body: request.body } }); const mockResponse = createResponse(); let responseResolved = false; const res = await new Promise((resolve) => { const originalEnd = mockResponse.end.bind(mockResponse); mockResponse.end = (...args) => { const result = originalEnd(...args); if (!responseResolved && mockResponse.writableEnded) { responseResolved = true; const statusCode = mockResponse.statusCode; const noBodyStatuses = [204, 304]; const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null; const connectResponse = new Response(responseBody, { status: statusCode, statusText: mockResponse.statusMessage, headers: mockResponse.getHeaders() }); resolve(connectResponse); } return result; }; connectMiddleware(mockRequest, mockResponse, () => { if (!responseResolved && !mockResponse.writableEnded) { responseResolved = true; const statusCode = mockResponse.statusCode; const noBodyStatuses = [204, 304]; const responseBody = noBodyStatuses.includes(statusCode) ? null : mockResponse._getData() || mockResponse._getBuffer() || null; const preparedHeaders = c.newResponse(null, 204, {}).headers; for (const key of [...preparedHeaders.keys()]) { if (preparedHeaders.has(key)) { c.header(key, void 0); } if (c.res && c.res.headers.has(key)) { c.res.headers.delete(key); } } const connectHeaders = mockResponse.getHeaders(); for (const [key, value] of Object.entries(connectHeaders)) { if (value !== void 0) { c.header( key, Array.isArray(value) ? value.join(", ") : String(value) ); } } c.status(statusCode); if (noBodyStatuses.includes(statusCode)) { resolve(c.newResponse(null, statusCode)); } else if (responseBody) { resolve(c.body(responseBody)); } else { resolve(void 0); } } }); }); if (res) { c.res = res; return res; } await next(); }, "honoMiddleware"); return honoMiddleware; } __name(adaptConnectMiddleware, "adaptConnectMiddleware"); // src/server/utils/hono-proxy.ts function createHonoProxy(target, app) { return new Proxy(target, { get(target2, prop) { if (prop === "use") { return async (...args) => { const hasPath = typeof args[0] === "string"; const path = hasPath ? args[0] : "*"; const handlers = hasPath ? args.slice(1) : args; const adaptedHandlers = handlers.map((handler) => { if (isExpressMiddleware(handler)) { return { __isExpressMiddleware: true, handler, path }; } return handler; }); const hasExpressMiddleware = adaptedHandlers.some( (h) => h.__isExpressMiddleware ); if (hasExpressMiddleware) { await Promise.all( adaptedHandlers.map(async (h) => { if (h.__isExpressMiddleware) { const adapted = await adaptConnectMiddleware( h.handler, h.path ); if (hasPath) { app.use(path, adapted); } else { app.use(adapted); } } else { if (hasPath) { app.use(path, h); } else { app.use(h); } } }) ); return target2; } return app.use(...args); }; } if (prop in target2) { return target2[prop]; } const value = app[prop]; return typeof value === "function" ? value.bind(app) : value; } }); } __name(createHonoProxy, "createHonoProxy"); // src/server/widgets/mcp-ui-adapter.ts import { createUIResource } from "@mcp-ui/server"; function buildWidgetUrl(widget2, props, config) { const url = new URL( `/mcp-use/widgets/${widget2}`, `${config.baseUrl}:${config.port}` ); if (props && Object.keys(props).length > 0) { url.searchParams.set("props", JSON.stringify(props)); } return url.toString(); } __name(buildWidgetUrl, "buildWidgetUrl"); async function createExternalUrlResource(uri, iframeUrl, encoding = "text", adapters, metadata) { return await createUIResource({ uri, content: { type: "externalUrl", iframeUrl }, encoding, adapters, metadata }); } __name(createExternalUrlResource, "createExternalUrlResource"); async function createRawHtmlResource(uri, htmlString, encoding = "text", adapters, metadata) { return await createUIResource({ uri, content: { type: "rawHtml", htmlString }, encoding, adapters, metadata }); } __name(createRawHtmlResource, "createRawHtmlResource"); async function createRemoteDomResource(uri, script, framework = "react", encoding = "text", adapters, metadata) { return await createUIResource({ uri, content: { type: "remoteDom", script, framework }, encoding, adapters, metadata }); } __name(createRemoteDomResource, "createRemoteDomResource"); function createAppsSdkResource(uri, htmlTemplate, metadata) { const resource2 = { uri, mimeType: "text/html+skybridge", text: htmlTemplate }; if (metadata && Object.keys(metadata).length > 0) { resource2._meta = metadata; } return { type: "resource", resource: resource2 }; } __name(createAppsSdkResource, "createAppsSdkResource"); async function createUIResourceFromDefinition(definition, params, config) { const buildIdPart = config.buildId ? `-${config.buildId}` : ""; const uri = definition.type === "appsSdk" ? `ui://widget/${definition.name}${buildIdPart}.html` : `ui://widget/${definition.name}${buildIdPart}`; const encoding = definition.encoding || "text"; switch (definition.type) { case "externalUrl": { const widgetUrl = buildWidgetUrl(definition.widget, params, config); return await createExternalUrlResource( uri, widgetUrl, encoding, definition.adapters, definition.appsSdkMetadata ); } case "rawHtml": { return await createRawHtmlResource( uri, definition.htmlContent, encoding, definition.adapters, definition.appsSdkMetadata ); } case "remoteDom": { const framework = definition.framework || "react"; return await createRemoteDomResource( uri, definition.script, framework, encoding, definition.adapters, definition.appsSdkMetadata ); } case "appsSdk": { return createAppsSdkResource( uri, definition.htmlTemplate, definition.appsSdkMetadata ); } default: { const _exhaustive = definition; throw new Error(`Unknown UI resource type: ${_exhaustive.type}`); } } } __name(createUIResourceFromDefinition, "createUIResourceFromDefinition"); // src/server/widgets/widget-helpers.ts function generateWidgetUri(widgetName, buildId, extension = "", suffix = "") { const parts = [widgetName]; if (buildId) { parts.push(buildId); } if (suffix) { parts.push(suffix); } return `ui://widget/${parts.join("-")}${extension}`; } __name(generateWidgetUri, "generateWidgetUri"); function convertPropsToInputs(props) { if (!props) return []; return Object.entries(props).map(([name, prop]) => ({ name, type: prop.type, description: prop.description, required: prop.required, default: prop.default })); } __name(convertPropsToInputs, "convertPropsToInputs"); function applyDefaultProps(props) { if (!props) return {}; const defaults = {}; for (const [key, prop] of Object.entries(props)) { if (prop.default !== void 0) { defaults[key] = prop.default; } } return defaults; } __name(applyDefaultProps, "applyDefaultProps"); async function readBuildManifest() { try { const manifestPath = pathHelpers.join( isDeno ? "." : getCwd(), "dist", "mcp-use.json" ); const content = await fsHelpers.readFileSync(manifestPath, "utf8"); return JSON.parse(content); } catch { return null; } } __name(readBuildManifest, "readBuildManifest"); function getContentType(filename) { const ext = filename.split(".").pop()?.toLowerCase(); switch (ext) { case "js": return "application/javascript"; case "css": return "text/css"; case "png": return "image/png"; case "jpg": case "jpeg": return "image/jpeg"; case "svg": return "image/svg+xml"; case "gif": return "image/gif"; case "webp": return "image/webp"; case "ico": return "image/x-icon"; case "woff": return "font/woff"; case "woff2": return "font/woff2"; case "ttf": return "font/ttf"; case "otf": return "font/otf"; case "json": return "application/json"; case "pdf": return "application/pdf"; default: return "application/octet-stream"; } } __name(getContentType, "getContentType"); function processWidgetHtml(html2, widgetName, baseUrl) { let processedHtml = html2; if (baseUrl && processedHtml) { let htmlWithoutComments = processedHtml; let prevHtmlWithoutComments; do { prevHtmlWithoutComments = htmlWithoutComments; htmlWithoutComments = htmlWithoutComments.replace(/<!--[\s\S]*?-->/g, ""); } while (prevHtmlWithoutComments !== htmlWithoutComments); const baseTagRegex = /<base\s+[^>]*\/?>/i; if (baseTagRegex.test(htmlWithoutComments)) { const actualBaseTagMatch = processedHtml.match(/<base\s+[^>]*\/?>/i); if (actualBaseTagMatch) { processedHtml = processedHtml.replace( actualBaseTagMatch[0], `<base href="${baseUrl}" />` ); } } else { const headTagRegex = /<head[^>]*>/i; if (headTagRegex.test(processedHtml)) { processedHtml = processedHtml.replace( headTagRegex, (match) => `${match} <base href="${baseUrl}" />` ); } } processedHtml = processedHtml.replace( /src="\/mcp-use\/widgets\/([^"]+)"/g, `src="${baseUrl}/mcp-use/widgets/$1"` ); processedHtml = processedHtml.replace( /href="\/mcp-use\/widgets\/([^"]+)"/g, `href="${baseUrl}/mcp-use/widgets/$1"` ); processedHtml = processedHtml.replace( /<head[^>]*>/i, `<head> <script>window.__getFile = (filename) => { return "${baseUrl}/mcp-use/widgets/${widgetName}/"+filename }; window.__mcpPublicUrl = "${baseUrl}/mcp-use/public";</script>` ); } return processedHtml; } __name(processWidgetHtml, "processWidgetHtml"); function createWidgetRegistration(widgetName, metadata, html2, serverConfig, isDev = false) { const props = metadata.props || metadata.inputs || metadata.schema || {}; const description = metadata.description || `Widget: ${widgetName}`; const title = metadata.title || widgetName; const exposeAsTool = metadata.exposeAsTool !== void 0 ? metadata.exposeAsTool : true; const mcp_connect_domain = serverConfig.serverBaseUrl ? new URL(serverConfig.serverBaseUrl || "").origin : null; return { name: widgetName, title, description, type: "appsSdk", props, _meta: { "mcp-use/widget": { name: widgetName, title, description, type: "appsSdk", props, html: html2, dev: isDev, exposeAsTool }, ...metadata._meta || {} }, htmlTemplate: html2, appsSdkMetadata: { "openai/widgetDescription": description, "openai/toolInvocation/invoking": `Loading ${widgetName}...`, "openai/toolInvocation/invoked": `${widgetName} ready`, "openai/widgetAccessible": true, "openai/resultCanProduceWidget": true, ...metadata.appsSdkMetadata || {}, "openai/widgetCSP": { connect_domains: [ // always also add the base url of the server ...mcp_connect_domain ? [mcp_connect_domain] : [], ...metadata.appsSdkMetadata?.["openai/widgetCSP"]?.connect_domains || [] ], resource_domains: [ "https://*.oaistatic.com", "https://*.oaiusercontent.com", ...isDev ? [] : ["https://*.openai.com"], // always also add the base url of the server ...mcp_connect_domain ? [mcp_connect_domain] : [], // add additional CSP URLs from environment variable ...serverConfig.cspUrls, ...metadata.appsSdkMetadata?.["openai/widgetCSP"]?.resource_domains || [] ] } } }; } __name(createWidgetRegistration, "createWidgetRegistration"); async function createWidgetUIResource(definition, params, serverConfig) { let configBaseUrl = `http://${serverConfig.serverHost}`; let configPort = serverConfig.serverPort || 3e3; if (serverConfig.serverBaseUrl) { try { const url = new URL(serverConfig.serverBaseUrl); configBaseUrl = `${url.protocol}//${url.hostname}`; configPort = url.port || (url.protocol === "https:" ? 443 : 80); } catch (e) { console.warn("Failed to parse baseUrl, falling back to host:port", e); } } const urlConfig = { baseUrl: configBaseUrl, port: configPort, buildId: serverConfig.buildId }; const uiResource = await createUIResourceFromDefinition( definition, params, urlConfig ); if (definition._meta && Object.keys(definition._meta).length > 0) { uiResource.resource._meta = { ...uiResource.resource._meta, ...definition._meta }; } return uiResource; } __name(createWidgetUIResource, "createWidgetUIResource"); function ensureWidgetMetadata(metadata, widgetName, widgetDescription) { const result = { ...metadata }; if (!result.description) { result.description = widgetDescription || `Widget: ${widgetName}`; } return result; } __name(ensureWidgetMetadata, "ensureWidgetMetadata"); async function readWidgetHtml(filePath, widgetName) { try { return await fsHelpers.readFileSync(filePath, "utf8"); } catch (error2) { console.error( `[WIDGET] Failed to read html template for widget ${widgetName}:`, error2 ); return ""; } } __name(readWidgetHtml, "readWidgetHtml"); async function registerWidgetFromTemplate(widgetName, htmlPath, metadata, serverConfig, registerWidget, isDev = false) { let html2 = await readWidgetHtml(htmlPath, widgetName); if (!html2) { return; } html2 = processWidgetHtml(html2, widgetName, serverConfig.serverBaseUrl); const processedMetadata = ensureWidgetMetadata(metadata, widgetName); const widgetRegistration = createWidgetRegistration( widgetName, processedMetadata, html2, serverConfig, isDev ); registerWidget(widgetRegistration); } __name(registerWidgetFromTemplate, "registerWidgetFromTemplate"); function setupPublicRoutes(app, useDistDirectory = false) { app.get("/mcp-use/public/*", async (c) => { const filePath = c.req.path.replace("/mcp-use/public/", ""); const basePath = useDistDirectory ? "dist/public" : "public"; const fullPath = pathHelpers.join(getCwd(), basePath, filePath); try { if (await fsHelpers.existsSync(fullPath)) { const content = await fsHelpers.readFile(fullPath); const contentType = getContentType(filePath); return new Response(content, { status: 200, headers: { "Content-Type": contentType } }); } return c.notFound(); } catch { return c.notFound(); } }); } __name(setupPublicRoutes, "setupPublicRoutes"); function setupFaviconRoute(app, faviconPath, useDistDirectory = false) { if (!faviconPath) { return; } app.get("/favicon.ico", async (c) => { const basePath = useDistDirectory ? "dist/public" : "public"; const fullPath = pathHelpers.join(getCwd(), basePath, faviconPath); try { if (await fsHelpers.existsSync(fullPath)) { const content = await fsHelpers.readFile(fullPath); const contentType = getContentType(faviconPath); return new Response(content, { status: 200, headers: { "Content-Type": contentType, "Cache-Control": "public, max-age=31536000" // Cache for 1 year } }); } return c.notFound(); } catch { return c.notFound(); } }); } __name(setupFaviconRoute, "setupFaviconRoute"); // src/server/widgets/mount-widgets-dev.ts var TMP_MCP_USE_DIR = ".mcp-use"; async function mountWidgetsDev(app, serverConfig, registerWidget, options) { const { promises: fs } = await import("fs"); const baseRoute = options?.baseRoute || "/mcp-use/widgets"; const resourcesDir = options?.resourcesDir || "resources"; const srcDir = pathHelpers.join(getCwd(), resourcesDir); try { await fs.access(srcDir); } catch (error2) { console.log( `[WIDGETS] No ${resourcesDir}/ directory found - skipping widget serving` ); return; } const entries = []; try { const files = await fs.readdir(srcDir, { withFileTypes: true }); for (const dirent of files) { if (dirent.name.startsWith("._") || dirent.name.startsWith(".DS_Store")) { continue; } if (dirent.isFile() && (dirent.name.endsWith(".tsx") || dirent.name.endsWith(".ts"))) { entries.push({ name: dirent.name.replace(/\.tsx?$/, ""), path: pathHelpers.join(srcDir, dirent.name) }); } else if (dirent.isDirectory()) { const widgetPath = pathHelpers.join(srcDir, dirent.name, "widget.tsx"); try { await fs.access(widgetPath); entries.push({ name: dirent.name, path: widgetPath }); } catch { } } } } catch (error2) { console.log(`[WIDGETS] No widgets found in ${resourcesDir}/ directory`); return; } if (entries.length === 0) { console.log(`[WIDGETS] No widgets found in ${resourcesDir}/ directory`); return; } const tempDir = pathHelpers.join(getCwd(), TMP_MCP_USE_DIR); try { await fs.access(tempDir); const currentWidgetNames = new Set(entries.map((e) => e.name)); const existingDirs = await fs.readdir(tempDir, { withFileTypes: true }); for (const dirent of existingDirs) { if (dirent.isDirectory() && !currentWidgetNames.has(dirent.name)) { const staleDir = pathHelpers.join(tempDir, dirent.name); await fs.rm(staleDir, { recursive: true, force: true }); console.log(`[WIDGETS] Cleaned up stale widget: ${dirent.name}`); } } } catch { } await fs.mkdir(tempDir, { recursive: true }).catch(() => { }); let createServer; let react; let tailwindcss; try { const { createRequire } = await import("module"); const { pathToFileURL } = await import("url"); const userProjectRequire = createRequire( pathToFileURL(pathHelpers.join(getCwd(), "package.json")).href ); const vitePath = userProjectRequire.resolve("vite"); const reactPluginPath = userProjectRequire.resolve("@vitejs/plugin-react"); const tailwindPath = userProjectRequire.resolve("@tailwindcss/vite"); const viteModule = await import(vitePath); createServer = viteModule.createServer; const reactModule = await import(reactPluginPath); react = reactModule.default; const tailwindModule = await import(tailwindPath); tailwindcss = tailwindModule.default; } catch (error2) { throw new Error( "\u274C Widget dependencies not installed!\n\nTo use MCP widgets with resources folder, you need to install the required dependencies:\n\n npm install vite @vitejs/plugin-react @tailwindcss/vite\n # or\n pnpm add vite @vitejs/plugin-react @tailwindcss/vite\n\nThese dependencies are automatically included in projects created with 'create-mcp-use-app'.\nFor production, pre-build your widgets using 'mcp-use build'." ); } const widgets = entries.map((entry) => { return { name: entry.name, description: `Widget: ${entry.name}`, entry: entry.path }; }); for (const widget2 of widgets) { const widgetTempDir = pathHelpers.join(tempDir, widget2.name); await fs.mkdir(widgetTempDir, { recursive: true }); const resourcesPath = pathHelpers.join(getCwd(), resourcesDir); const relativeResourcesPath = pathHelpers.relative(widgetTempDir, resourcesPath).replace(/\\/g, "/"); const mcpUsePath = pathHelpers.join(getCwd(), "node_modules", "mcp-use"); const relativeMcpUsePath = pathHelpers.relative(widgetTempDir, mcpUsePath).replace(/\\/g, "/"); const cssContent = `@import "tailwindcss"; /* Configure Tailwind to scan the resources directory and mcp-use package */ @source "${relativeResourcesPath}"; @source "${relativeMcpUsePath}/**/*.{ts,tsx,js,jsx}"; `; await fs.writeFile( pathHelpers.join(widgetTempDir, "styles.css"), cssContent, "utf8" ); const entryContent = `import React from 'react' import { createRoot } from 'react-dom/client' import './styles.css' import Component from '${widget2.entry}' const container = document.getElementById('widget-root') if (container && Component) { const root = createRoot(container) root.render(<Component />) } `; const htmlContent = `<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>${widget2.name} Widget</title>${serverConfig.favicon ? ` <link rel="icon" href="/mcp-use/public/${serverConfig.favicon}" />` : ""} </head> <body> <div id="widget-root"></div> <script type="module" src="${baseRoute}/${widget2.name}/entry.tsx"></script> </body> </html>`; await fs.writeFile( pathHelpers.join(widgetTempDir, "entry.tsx"), entryContent, "utf8" ); await fs.writeFile( pathHelpers.join(widgetTempDir, "index.html"), htmlContent, "utf8" ); } const serverOrigin = serverConfig.serverBaseUrl; console.log( `[WIDGETS] Serving ${entries.length} widget(s) with shared Vite dev server and HMR` ); const ssrCssPlugin = { name: "ssr-css-handler", enforce: "pre", resolveId(id, importer, options2) { if (options2 && options2.ssr === true && (id.endsWith(".css") || id.endsWith(".module.css"))) { return "\0ssr-css:" + id; } return null; }, load(id, options2) { if (options2 && options2.ssr === true && id.startsWith("\0ssr-css:")) { return "export default {}"; } return null; } }; const watchResourcesPlugin = { name: "watch-resources", configureServer(server) { const resourcesPath = pathHelpers.join(getCwd(), resourcesDir); server.watcher.add(resourcesPath); console.log(`[WIDGETS] Watching resources directory: ${resourcesPath}`); server.watcher.on("unlink", async (filePath) => { const relativePath = pathHelpers.relative(resourcesPath, filePath); if ((relativePath.endsWith(".tsx") || relativePath.endsWith(".ts")) && !relativePath.includes("/")) { const widgetName = relativePath.replace(/\.tsx?$/, ""); const widgetDir = pathHelpers.join(tempDir, widgetName); try { await fs.access(widgetDir); await fs.rm(widgetDir, { recursive: true, force: true }); console.log( `[WIDGETS] Cleaned up stale widget (file removed): ${widgetName}` ); } catch { } } else if (relativePath.endsWith("widget.tsx")) { const parts = relativePath.split("/"); if (parts.length === 2) { const widgetName = parts[0]; const widgetDir = pathHelpers.join(tempDir, widgetName); try { await fs.access(widgetDir); await fs.rm(widgetDir, { recursive: true, force: true }); console.log( `[WIDGETS] Cleaned up stale widget (file removed): ${widgetName}` ); } catch { } } } }); server.watcher.on("unlinkDir", async (dirPath) => { const relativePath = pathHelpers.relative(resourcesPath, dirPath); if (relativePath && !relativePath.includes("/")) { const widgetName = relativePath; const widgetDir = pathHelpers.join(tempDir, widgetName); try { await fs.access(widgetDir); await fs.rm(widgetDir, { recursive: true, force: true }); console.log( `[WIDGETS] Cleaned up stale widget (directory removed): ${widgetName}` ); } catch { } } }); } }; const nodeStubsPlugin = { name: "node-stubs", enforce: "pre", resolveId(id) { if (id === "posthog-node" || id.startsWith("posthog-node/")) { return "\0virtual:posthog-node-stub"; } return null; }, load(id) { if (id === "\0virtual:posthog-node-stub") { return ` export class PostHog { constructor() {} capture() {} identify() {} alias() {} flush() { return Promise.resolve(); } shutdown() { return Promise.resolve(); } } export default PostHog; `; } return null; } }; const viteServer = await createServer({ root: tempDir, base: baseRoute + "/", plugins: [ nodeStubsPlugin, ssrCssPlugin, watchResourcesPlugin, tailwindcss(), react() ], resolve: { alias: { "@": pathHelpers.join(getCwd(), resourcesDir) } }, server: { middlewareMode: true, origin: serverOrigin, watch: { // Watch the resources directory for HMR to work // This ensures changes to widget source files trigger hot reload ignored: ["**/node_modules/**", "**/.git/**"], // Include the resources directory in watch list // Vite will watch files imported from outside root usePolling: false } }, // Explicitly tell Vite to watch files outside root // This is needed because widget entry files import from resources directory optimizeDeps: { // Exclude Node.js-only packages from browser bundling // posthog-node is for server-side telemetry and doesn't work in browser exclude: ["posthog-node"] }, ssr: { // Force Vite to transform these packages in SSR instead of using external requires noExternal: ["@openai/apps-sdk-ui", "react-router"], // Mark Node.js-only packages as external in SSR mode external: ["posthog-node"] }, define: { // Define process.env for SSR context "process.env.NODE_ENV": JSON.stringify( process.env.NODE_ENV || "development" ), "import.meta.env.DEV": true, "import.meta.env.PROD": false, "import.meta.env.MODE": JSON.stringify("development"), "import.meta.env.SSR": true } }); app.use(`${baseRoute}/*`, async (c, next) => { const url = new URL(c.req.url); const pathname = url.pathname; const widgetMatch = pathname.replace(baseRoute, "").match(/^\/([^/]+)/); if (widgetMatch) { const widgetName = widgetMatch[1]; const widget2 = widgets.find((w) => w.name === widgetName); if (widget2) { const relativePath = pathname.replace(baseRoute, ""); if (relativePath === `/${widgetName}` || relativePath === `/${widgetName}/`) { const newUrl = new URL(c.req.url); newUrl.pathname = `${baseRoute}/${widgetName}/index.html`; const newRequest = new Request(newUrl.toString(), c.req.raw); Object.defineProperty(c, "req", { value: { ...c.req, url: newUrl.toString(), raw: newRequest }, writable: false, configurable: true }); } } } await next(); }); const viteMiddleware = await adaptConnectMiddleware( viteServer.middlewares, `${baseRoute}/*` ); app.use(`${baseRoute}/*`, viteMiddleware); setupPublicRoutes(app, false); setupFaviconRoute(app, serverConfig.favicon, false); app.use(`${baseRoute}/*`, async (c) => { const url = new URL(c.req.url); const isAsset = url.pathname.match( /\.(js|css|png|jpg|jpeg|svg|json|ico|woff2?|tsx?)$/i ); const message = isAsset ? "Widget asset not found" : "Widget not found"; return c.text(message, 404); }); widgets.forEach((widget2) => { console.log( `[WIDGET] ${widget2.name} mounted at ${baseRoute}/${widget2.name}` ); }); for (const widget2 of widgets) { let metadata = {}; try { const mod = await viteServer.ssrLoadModule(widget2.entry); if (mod.widgetMetadata) { metadata = mod.widgetMetadata; const schemaField = metadata.props || metadata.inputs; if (schemaField) { try { metadata.props = schemaField; if (!metadata.inputs) { metadata.inputs = schemaField; } } catch (error2) { console.warn( `[WIDGET] Failed to extract schema for ${widget2.name}:`, error2 ); } } } } catch (error2) { console.warn( `[WIDGET] Failed to load metadata for ${widget2.name}:`, error2 ); } await registerWidgetFromTemplate( widget2.name, pathHelpers.join(tempDir, widget2.name, "index.html"), metadata.description ? metadata : { ...metadata, description: widget2.description }, serverConfig, registerWidget, true // isDev ); } } __name(mountWidgetsDev, "mountWidgetsDev"); // src/server/widgets/mount-widgets-production.ts async function mountWidgetsProduction(app, serverConfig, registerWidget, options) { const baseRoute = options?.baseRoute || "/mcp-use/widgets"; const widgetsDir = pathHelpers.join( isDeno ? "." : getCwd(), "dist", "resources", "widgets" ); console.log("widgetsDir", widgetsDir); const manifestPath = "./dist/mcp-use.json"; let widgets = []; let widgetsMetadata = {}; try { const manifestContent = await fsHelpers.readFileSync(manifestPath, "utf8"); const manifest = JSON.parse(manifestContent); if (manifest.buildId && typeof manifest.buildId === "string") { serverConfig.buildId = manifest.buildId; console.log(`[WIDGETS] Build ID: ${manifest.buildId}`); } if (manifest.widgets && typeof manifest.widgets === "object" && !Array.isArray(manifest.widgets)) { widgets = Object.keys(manifest.widgets); widgetsMetadata = manifest.widgets; console.log(`[WIDGETS] Loaded ${widgets.length} widget(s) from manifest`); } else if (manifest.widgets && Array.isArray(manifest.widgets)) { widgets = manifest.widgets; console.log( `[WIDGETS] Loaded ${widgets.length} widget(s) from manifest (legacy format)` ); } else { console.log("[WIDGETS] No widgets found in manifest"); } } catch (error2) { console.log( "[WIDGETS] Could not read manifest file, falling back to directory listing:", error2 ); try { const allEntries = await fsHelpers.readdirSync(widgetsDir); for (const name of allEntries) { const widgetPath = pathHelpers.join(widgetsDir, name); const indexPath = pathHelpers.join(widgetPath, "index.html"); if (await fsHelpers.existsSync(indexPath)) { widgets.push(name); } } } catch (dirError) { console.log("[WIDGETS] Directory listing also failed:", dirError); } } if (widgets.length === 0) { console.log("[WIDGETS] No built widgets found"); return; } console.log( `[WIDGETS] Serving ${widgets.length} pre-built widget(s) from dist/resources/widgets/` ); for (const widgetName of widgets) { const widgetPath = pathHelpers.join(widgetsDir, widgetName); const indexPath = pathHelpers.join(widgetPath, "index.html"); const metadata = widgetsMetadata[widgetName] || {}; const mcp_connect_domain = serverConfig.serverBaseUrl ? new URL(serverConfig.serverBaseUrl || "").origin : null; console.log("[CSP] mcp_connect_domain", mcp_connect_domain); console.log("[CSP] cspUrls", serverConfig.cspUrls); console.log("[CSP] metadata.appsSdkMetadata", metadata.appsSdkMetadata); console.log("[CSP] metadata._meta", metadata._meta); await registerWidgetFromT