Make vite work out-of-the-box for simple-host (#33)
This commit is contained in:
parent
9d8b705598
commit
a7d73c72e0
6 changed files with 33 additions and 104 deletions
1
examples/simple-host/index.html
Normal file
1
examples/simple-host/index.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<meta http-equiv="refresh" content="0; url=/example-host-react.html">
|
||||
|
|
@ -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": "../..",
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue