| 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 --
<script src="./main.js"> will not resolve
- No CSS file imports --
<link href="./styles.css"> will not load
- No image/font paths -- relative paths to assets will fail
- The entire UI must be a single HTML file with all JS, CSS, images, and fonts inlined
vite-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.
Capabilities
Vite Configuration
- Configure Vite with
vite-plugin-singlefile
- Set correct
base, build.outDir, and entry point
- Handle framework-specific Vite plugins (React, Vue, Svelte, Preact, Solid)
Single HTML File Output
- All JavaScript inlined as
<script> tags
- All CSS inlined as
<style> tags
- All images converted to data URIs
- All fonts converted to base64
Two-Phase Build Setup
- Phase 1: Vite bundles UI into
dist/mcp-app.html
- Phase 2: TypeScript compiles server into
dist/server.js
- Combined build script orchestrates both phases
Hybrid Build Pipelines
- Add MCP build alongside existing standalone build
- Separate Vite configs for MCP and standalone if needed
- Separate HTML entry points (
mcp-app.html vs index.html)
Usage
Basic Vite Configuration
import { defineConfig } from 'vite';
import { viteSingleFile } from 'vite-plugin-singlefile';
export default defineConfig({
plugins: [viteSingleFile()],
build: {
outDir: 'dist',
rollupOptions: {
input: 'mcp-app.html',
},
},
});
React Vite Configuration
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',
},
},
});
Vue Vite Configuration
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',
},
},
});
Svelte Vite Configuration
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 Entry Point
<!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):
<!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>
Package.json Build Scripts
{
"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
import fs from 'fs';
import path from 'path';
import { registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps';
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:
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',
},
},
});
{
"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
npm install -D vite vite-plugin-singlefile
npm install -D @vitejs/plugin-react
npm install -D @vitejs/plugin-vue
npm install -D @sveltejs/vite-plugin-svelte
Common Pitfalls
- Forgetting vite-plugin-singlefile: Without it, Vite produces separate JS/CSS files that won't load in the sandboxed iframe.
- Wrong entry point: The
rollupOptions.input must point to the MCP App HTML file, not the standalone index.html.
- Large bundle size: Inline images and fonts increase HTML file size. Consider optimizing assets or using CSP
resourceDomains for large external resources.
- TypeScript server in Vite output: The server should be compiled separately (Phase 2), not included in the Vite bundle.
- Missing
type="module": The <script> tag in the HTML entry must have type="module" for Vite to process it.
Verification Checklist
Task Definition
const 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',
: {
: inputs.,
: inputs.,
: inputs.,
: inputs.,
: [
,
,
,
,
inputs. ? : ,
].()
}
},
: {
: ,
:
}
};
}
});
Applicable Processes
- create-mcp-app.js
- add-app-to-mcp-server.js
- convert-web-app-to-mcp.js
External Dependencies
- Vite (build tool)
- vite-plugin-singlefile (asset inlining)
- Framework-specific Vite plugins (@vitejs/plugin-react, @vitejs/plugin-vue, etc.)
References
Related Skills
- mcp-app-scaffolding
- mcp-tool-resource-pattern
- mcp-app-verification
Related Agents
- mcp-app-architect
- mcp-ui-developer