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",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "NODE_ENV=development npm run build && concurrently 'npm run start:server'",
|
"start": "vite dev",
|
||||||
"start:server": "bun serve.ts",
|
"start:server": "vite preview",
|
||||||
"build": "concurrently 'INPUT=example-host-vanilla.html vite build' 'INPUT=example-host-react.html vite build' 'INPUT=sandbox.html vite build'"
|
"build": "vite build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/ext-apps": "../..",
|
"@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 { AppRenderer, AppRendererProps } from "../src/AppRenderer";
|
||||||
import { AppBridge } from "../../../dist/src/app-bridge";
|
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.
|
* Example React application demonstrating the AppRenderer component.
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,11 @@ import {
|
||||||
McpUiSizeChangeNotificationSchema,
|
McpUiSizeChangeNotificationSchema,
|
||||||
} from "@modelcontextprotocol/ext-apps";
|
} 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 () => {
|
window.addEventListener("load", async () => {
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import { viteSingleFile } from "vite-plugin-singlefile";
|
|
||||||
|
|
||||||
const INPUT = process.env.INPUT;
|
export default defineConfig(({ mode }) => {
|
||||||
if (!INPUT) {
|
const isDevelopment = mode === "development";
|
||||||
throw new Error("INPUT environment variable is not set");
|
return {
|
||||||
}
|
plugins: [react()],
|
||||||
|
build: {
|
||||||
const isDevelopment = process.env.NODE_ENV === "development";
|
sourcemap: isDevelopment ? "inline" : undefined,
|
||||||
|
cssMinify: !isDevelopment,
|
||||||
export default defineConfig({
|
minify: !isDevelopment,
|
||||||
plugins: [react(), viteSingleFile()],
|
rollupOptions: {
|
||||||
build: {
|
input: [
|
||||||
sourcemap: isDevelopment ? "inline" : undefined,
|
"index.html",
|
||||||
cssMinify: !isDevelopment,
|
"example-host-vanilla.html",
|
||||||
minify: !isDevelopment,
|
"example-host-react.html",
|
||||||
rollupOptions: {
|
"sandbox.html",
|
||||||
input: INPUT,
|
],
|
||||||
|
},
|
||||||
|
outDir: `dist`,
|
||||||
|
emptyOutDir: false,
|
||||||
},
|
},
|
||||||
outDir: `dist`,
|
};
|
||||||
emptyOutDir: false,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue