--- name: single-file-bundling description: Configure Vite with vite-plugin-singlefile for mandatory single-file HTML bundling of MCP Apps. All assets (JS, CSS, images, fonts) must be inlined into a single HTML file for sandboxed iframe compatibility. allowed-tools: Read, Write, Edit, Bash graph: domains: [domain:software-engineering] specializations: [specialization:ai-agents-conversational] skillAreas: [skill-area:mcp-server-implementation, skill-area:mcp-tool-design] roles: [role:backend-engineer, role:fullstack-engineer] workflows: [workflow:feature-development] --- # single-file-bundling Configure Vite with vite-plugin-singlefile to produce a single self-contained HTML file for MCP Apps running in sandboxed iframes. ## Overview MCP Apps run in sandboxed iframes with no same-origin server. This means: - **No relative asset URLs** -- ` ``` For React (`.tsx` entry): ```html My MCP App
``` ### Package.json Build Scripts ```json { "scripts": { "build:ui": "vite build", "build:server": "tsc --project tsconfig.server.json", "build": "npm run build:ui && npm run build:server", "dev": "concurrently \"vite\" \"tsx watch src/server.ts\"", "serve": "tsx src/server.ts" } } ``` ### Server Reading the Bundled HTML ```typescript import fs from 'fs'; import path from 'path'; import { registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps'; // Read the single-file bundle produced by Vite const bundledHtml = fs.readFileSync( path.join(__dirname, '../dist/mcp-app.html'), 'utf-8' ); registerAppResource(server, { uri: 'app:///my-app', name: 'My App', mimeType: RESOURCE_MIME_TYPE, async read() { return { contents: [{ uri: 'app:///my-app', mimeType: RESOURCE_MIME_TYPE, text: bundledHtml, }], }; }, }); ``` ### Hybrid Build Pipeline (MCP + Standalone) When converting a web app that already has its own build: ```typescript // vite.config.mcp.ts -- MCP-specific build config import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { viteSingleFile } from 'vite-plugin-singlefile'; export default defineConfig({ plugins: [react(), viteSingleFile()], build: { outDir: 'dist/mcp', rollupOptions: { input: 'mcp-app.html', // Separate entry from index.html }, }, }); ``` ```json { "scripts": { "build:standalone": "vite build", "build:mcp:ui": "vite build --config vite.config.mcp.ts", "build:mcp:server": "tsc --project tsconfig.server.json", "build:mcp": "npm run build:mcp:ui && npm run build:mcp:server", "build:all": "npm run build:standalone && npm run build:mcp" } } ``` ### Installing Dependencies ```bash # Required dev dependencies npm install -D vite vite-plugin-singlefile # Framework-specific (pick one) npm install -D @vitejs/plugin-react # React npm install -D @vitejs/plugin-vue # Vue npm install -D @sveltejs/vite-plugin-svelte # Svelte ``` ## Common Pitfalls 1. **Forgetting vite-plugin-singlefile**: Without it, Vite produces separate JS/CSS files that won't load in the sandboxed iframe. 2. **Wrong entry point**: The `rollupOptions.input` must point to the MCP App HTML file, not the standalone `index.html`. 3. **Large bundle size**: Inline images and fonts increase HTML file size. Consider optimizing assets or using CSP `resourceDomains` for large external resources. 4. **TypeScript server in Vite output**: The server should be compiled separately (Phase 2), not included in the Vite bundle. 5. **Missing `type="module"`**: The `