diff --git a/examples/simple-host/index.html b/examples/simple-host/index.html new file mode 100644 index 00000000..1bae39df --- /dev/null +++ b/examples/simple-host/index.html @@ -0,0 +1 @@ + diff --git a/examples/simple-host/package.json b/examples/simple-host/package.json index 741e6484..6c4dc329 100644 --- a/examples/simple-host/package.json +++ b/examples/simple-host/package.json @@ -4,9 +4,9 @@ "version": "1.0.0", "type": "module", "scripts": { - "start": "NODE_ENV=development npm run build && concurrently 'npm run start:server'", - "start:server": "bun serve.ts", - "build": "concurrently 'INPUT=example-host-vanilla.html vite build' 'INPUT=example-host-react.html vite build' 'INPUT=sandbox.html vite build'" + "start": "vite dev", + "start:server": "vite preview", + "build": "vite build" }, "dependencies": { "@modelcontextprotocol/ext-apps": "../..", diff --git a/examples/simple-host/serve.ts b/examples/simple-host/serve.ts deleted file mode 100644 index d02809f5..00000000 --- a/examples/simple-host/serve.ts +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env npx tsx -/** - * HTTP servers for the MCP UI example: - * - Host server (port 8080): serves host HTML files (React and Vanilla examples) - * - Sandbox server (port 8081): serves sandbox.html with permissive CSP - * - * Running on separate ports ensures proper origin isolation for security. - */ - -import express from "express"; -import cors from "cors"; -import { fileURLToPath } from "url"; -import { dirname, join } from "path"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const HOST_PORT = parseInt(process.env.HOST_PORT || "8080", 10); -const SANDBOX_PORT = parseInt(process.env.SANDBOX_PORT || "8081", 10); -const DIRECTORY = join(__dirname, "dist"); - -// ============ Host Server (port 8080) ============ -const hostApp = express(); -hostApp.use(cors()); - -// Exclude sandbox.html from host server -hostApp.use((req, res, next) => { - if (req.path === "/sandbox.html") { - res.status(404).send("Sandbox is served on a different port"); - return; - } - next(); -}); - -hostApp.use(express.static(DIRECTORY)); - -hostApp.get("/", (_req, res) => { - res.redirect("/example-host-react.html"); -}); - -// ============ Sandbox Server (port 8081) ============ -const sandboxApp = express(); -sandboxApp.use(cors()); - -// Permissive CSP for sandbox content -sandboxApp.use((_req, res, next) => { - const csp = [ - "default-src 'self'", - "img-src * data: blob: 'unsafe-inline'", - "style-src * blob: data: 'unsafe-inline'", - "script-src * blob: data: 'unsafe-inline' 'unsafe-eval'", - "connect-src *", - "font-src * blob: data:", - "media-src * blob: data:", - "frame-src * blob: data:", - ].join("; "); - res.setHeader("Content-Security-Policy", csp); - res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - res.setHeader("Pragma", "no-cache"); - res.setHeader("Expires", "0"); - next(); -}); - -sandboxApp.get(["/", "/sandbox.html"], (_req, res) => { - res.sendFile(join(DIRECTORY, "sandbox.html")); -}); - -sandboxApp.use((_req, res) => { - res.status(404).send("Only sandbox.html is served on this port"); -}); - -// ============ Start both servers ============ -hostApp.listen(HOST_PORT, () => { - console.log(`Host server: http://localhost:${HOST_PORT}`); -}); - -sandboxApp.listen(SANDBOX_PORT, () => { - console.log(`Sandbox server: http://localhost:${SANDBOX_PORT}`); - console.log("\nPress Ctrl+C to stop\n"); -}); diff --git a/examples/simple-host/src/example-host-react.tsx b/examples/simple-host/src/example-host-react.tsx index 7e8be2cd..4156ce7b 100644 --- a/examples/simple-host/src/example-host-react.tsx +++ b/examples/simple-host/src/example-host-react.tsx @@ -8,7 +8,11 @@ import { Tool } from "@modelcontextprotocol/sdk/types.js"; import { AppRenderer, AppRendererProps } from "../src/AppRenderer"; import { AppBridge } from "../../../dist/src/app-bridge"; -const SANDBOX_PROXY_URL = new URL("http://localhost:8081/sandbox.html"); +// We use '[::1]' for the sandbox to ensure it's a different origin from 'localhost'. +const SANDBOX_PROXY_URL = new URL( + "/sandbox.html", + location.href.replace("localhost:", "[::1]:"), +); /** * Example React application demonstrating the AppRenderer component. diff --git a/examples/simple-host/src/example-host-vanilla.ts b/examples/simple-host/src/example-host-vanilla.ts index dbd921c9..249aabf8 100644 --- a/examples/simple-host/src/example-host-vanilla.ts +++ b/examples/simple-host/src/example-host-vanilla.ts @@ -18,7 +18,11 @@ import { McpUiSizeChangeNotificationSchema, } from "@modelcontextprotocol/ext-apps"; -const SANDBOX_PROXY_URL = new URL("http://localhost:8081/sandbox.html"); +// We use '[::1]' for the sandbox to ensure it's a different origin from 'localhost'. +const SANDBOX_PROXY_URL = new URL( + "/sandbox.html", + location.href.replace("localhost:", "[::1]:"), +); window.addEventListener("load", async () => { const client = new Client({ diff --git a/examples/simple-host/vite.config.ts b/examples/simple-host/vite.config.ts index e2f69582..14a4709c 100644 --- a/examples/simple-host/vite.config.ts +++ b/examples/simple-host/vite.config.ts @@ -1,24 +1,24 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; -import { viteSingleFile } from "vite-plugin-singlefile"; -const INPUT = process.env.INPUT; -if (!INPUT) { - throw new Error("INPUT environment variable is not set"); -} - -const isDevelopment = process.env.NODE_ENV === "development"; - -export default defineConfig({ - plugins: [react(), viteSingleFile()], - build: { - sourcemap: isDevelopment ? "inline" : undefined, - cssMinify: !isDevelopment, - minify: !isDevelopment, - rollupOptions: { - input: INPUT, +export default defineConfig(({ mode }) => { + const isDevelopment = mode === "development"; + return { + plugins: [react()], + build: { + sourcemap: isDevelopment ? "inline" : undefined, + cssMinify: !isDevelopment, + minify: !isDevelopment, + rollupOptions: { + input: [ + "index.html", + "example-host-vanilla.html", + "example-host-react.html", + "sandbox.html", + ], + }, + outDir: `dist`, + emptyOutDir: false, }, - outDir: `dist`, - emptyOutDir: false, - }, + }; });