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,
- },
+ };
});