Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 202% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 120% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 91% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 96% | 0% |
Configure Vite with vite-plugin-singlefile to produce a single self-contained HTML file for MCP Apps running in sandboxed iframes.
MCP Apps run in sandboxed iframes with no same-origin server. This means:
<script src="./main.js"> will not resolve<link href="./styles.css"> will not loadvite-plugin-singlefile handles this by inlining all assets into one HTML file during the Vite build. This is mandatory for MCP Apps -- without it, the app will show a blank iframe.
vite-plugin-singlefilebase, build.outDir, and entry point<script> tags<style> tagsdist/mcp-app.htmldist/server.jsmcp-app.html vs index.html)typescript// vite.config.ts import { defineConfig } from 'vite'; import { viteSingleFile } from 'vite-plugin-singlefile'; export default defineConfig({ plugins: [viteSingleFile()], build: { outDir: 'dist', // Entry point for the MCP App UI rollupOptions: { input: 'mcp-app.html', }, }, });
typescript// vite.config.ts 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', rollupOptions: { input: 'mcp-app.html', }, }, });
typescript// vite.config.ts import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import { viteSingleFile } from 'vite-plugin-singlefile'; export default defineConfig({ plugins: [vue(), viteSingleFile()], build: { outDir: 'dist', rollupOptions: { input: 'mcp-app.html', }, }, });
typescript// vite.config.ts import { defineConfig } from 'vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; import { viteSingleFile } from 'vite-plugin-singlefile'; export default defineConfig({ plugins: [svelte(), viteSingleFile()], build: { outDir: 'dist', rollupOptions: { input: 'mcp-app.html', }, }, });
html<!-- mcp-app.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>My MCP App</title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.ts"></script> </body> </html>
For React (.tsx entry):
html<!-- mcp-app.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>My MCP App</title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body> </html>
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" } }
typescriptimport 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, }], }; }, });
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" } }
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
rollupOptions.input must point to the MCP App HTML file, not the standalone index.html.resourceDomains for large external resources.type="module": The <script> tag in the HTML entry must have type="module" for Vite to process it.vite-plugin-singlefile in devDependenciesvite.config.ts imports and uses viteSingleFile()rollupOptions.input points to mcp-app.html (not index.html)mcp-app.html entry point exists with <script type="module">npm run build:ui produces dist/mcp-app.htmldist/mcp-app.html is self-contained (no external src= or href= to files)build:ui, build:server, builddist/mcp-app.html at runtimejavascriptconst singleFileBundlingTask = defineTask({ name: 'single-file-bundling', description: 'Configure Vite with vite-plugin-singlefile for MCP App', inputs: { framework: { type: 'string', required: true }, entryPoint: { type: 'string', default: 'mcp-app.html' }, outDir: { type: 'string', default: 'dist' }, hybrid: { type: 'boolean', default: false } }, outputs: { viteConfigCreated: { type: 'boolean' }, entryPointCreated: { type: 'boolean' }, buildScriptsAdded: { type: 'boolean' }, artifacts: { type: 'array' } }, async run(inputs, taskCtx) { return { kind: 'skill', title: `Configure single-file bundling (${inputs.framework})`, skill: { name: 'single-file-bundling', context: { framework: inputs.framework, entryPoint: inputs.entryPoint, outDir: inputs.outDir, hybrid: inputs.hybrid, instructions: [ 'Install vite and vite-plugin-singlefile', 'Create vite.config.ts with framework plugin and singlefile', 'Create mcp-app.html entry point', 'Add build scripts to package.json', inputs.hybrid ? 'Create separate MCP Vite config alongside existing build' : null, 'Build and verify single-file output' ].filter(Boolean) } }, io: { inputJsonPath: `tasks/${taskCtx.effectId}/input.json`, outputJsonPath: `tasks/${taskCtx.effectId}/result.json` } }; } });
Other measured skills in the registry, with their headline benchmark lift.