| name | open-mcp-apps-persistent-ui |
| description | Build and manage persistent, reusable AI-generated UI components with the open-mcp-apps MCP engine |
| triggers | ["create a persistent UI component","build an MCP app widget","make a reusable interface for","set up open-mcp-apps","create a kanban board that persists","build a habit tracker with open-mcp-apps","write a custom MCP app","install an app into open-mcp-apps"] |
open-mcp-apps Persistent UI Skill
Skill by ara.so — MCP Skills collection.
Overview
open-mcp-apps is an MCP server that enables AI assistants to create persistent, reusable UI components. When a user asks for a UI (kanban board, habit tracker, reading list), the AI writes a single-file HTML app, saves it to a registry, and binds it to persistent SQLite-backed data. The app persists across conversations and hosts.
Key capabilities:
- App registry: AI can write and save new HTML apps on demand
- Persistent data: Apps bind to versioned SQLite collections with idempotent mutations
- Shell runtime: Serves apps with MCP App bridge, host theming, and data API
- 17 built-in apps: Ready-made components (companion, study cards, family week planner)
Installation
Standard Installation (One Command)
curl -fsSL https://raw.githubusercontent.com/2nd1st/open-mcp-apps/main/install.sh | sh
The installer:
- Prompts for which hosts to register (Claude Desktop, Claude Code, Codex)
- Creates a fixed per-user data store (shared across hosts)
- Registers the MCP server in each host's config
Skip prompts (auto-yes):
curl -fsSL https://raw.githubusercontent.com/2nd1st/open-mcp-apps/main/install.sh | sh -s -- --yes
Target specific host:
curl -fsSL https://raw.githubusercontent.com/2nd1st/open-mcp-apps/main/install.sh | sh -s -- --host codex
Manual Installation (Clone)
git clone https://github.com/2nd1st/open-mcp-apps
cd open-mcp-apps
node install.mjs
Post-Install
- Fully quit and reopen the host (Cmd-Q on macOS, not just close window)
- First-run permissions: When tools appear, click "Always allow" for each
- Batch permissions: Settings → Connectors → open-mcp-apps → Tool permissions
Data Store Location
- macOS:
~/Library/Application Support/open-mcp-apps/open-mcp-apps.db
- Windows:
%APPDATA%\open-mcp-apps\open-mcp-apps.db
- Linux:
$XDG_DATA_HOME/open-mcp-apps/open-mcp-apps.db or ~/.local/share/open-mcp-apps/
Uninstall
node uninstall.mjs
node uninstall.mjs --purge
node uninstall.mjs --check
Full reset: Delete the .db file (and -wal/-shm siblings) while host is fully quit.
Core MCP Tools
The server exposes these tools to the AI:
list_apps
Lists all installed apps with metadata.
{
"name": "list_apps"
}
Returns: Array of apps with name, title, description, version, author, trust_tier.
get_app
Retrieves an app's full source code.
{
"name": "get_app",
"arguments": {
"name": "kanban-board"
}
}
get_app_guide
Retrieves the authoring guide the AI should read before writing a new app.
{
"name": "get_app_guide"
}
Returns: Complete guide with window.oma API, design patterns, constraints.
save_app
Saves a new or updated app to the registry.
{
"name": "save_app",
"arguments": {
"name": "habit-tracker",
"title": "Habit Tracker",
"description": "Track daily habits with streaks",
"html": "<html>...</html>",
"version": "1.0.0",
"collections": ["habits"]
}
}
delete_app
Removes an app from the registry (data persists).
{
"name": "delete_app",
"arguments": {
"name": "old-app"
}
}
open_app (or open_<name>)
Opens an app with optional initial items.
{
"name": "open_kanban",
"arguments": {
"initial_items": {
"tasks": [
{"id": "1", "title": "Review PR", "status": "todo"},
{"id": "2", "title": "Fix bug", "status": "in-progress"}
]
}
}
}
The AI uses this after creating or when reopening an app.
Common Patterns for AI Agents
Pattern 1: Create a New App from User Request
When user says: "make me a reading tracker"
1. Call get_app_guide to read the authoring contract
2. Write single-file HTML app following the guide
3. Call save_app with the HTML and collections list
4. Call open_reading_tracker with sample initial_items
Example HTML structure for an app:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Reading Tracker</title>
</head>
<body>
<div class="container">
<h1>My Reading List</h1>
<ul id="books"></ul>
<form id="add-form">
<input id="title" placeholder="Book title">
<input id="author" placeholder="Author">
<button type="submit">Add</button>
</form>
</div>
<script>
const { items, mutate, subscribe } = .;
books = ();
() {
list = .();
list. = books.(
).();
}
.(). = {
e.();
(, {
: ,
: {
: crypto.(),
: .().,
: .().
}
});
e..();
};
. = {
(, {
: ,
: id
});
};
(, render);
();
Pattern 2: Reuse Existing App
When user says: "show me my reading tracker again"
1. Call list_apps to check if 'reading-tracker' exists
2. If exists: call open_reading_tracker (no initial_items needed)
3. If not: follow Pattern 1 to create it
Pattern 3: Update an App
When user says: "add a rating field to my reading tracker"
1. Call get_app with name="reading-tracker"
2. Modify the HTML to add rating functionality
3. Call save_app with incremented version
4. Call open_reading_tracker to show updated version
Pattern 4: Install from Library
The built-in library app shows 17 ready-made apps. To use them:
1. User opens library (open_library tool)
2. User clicks install in the library UI
3. App is installed and immediately available
Or programmatically:
node install-app.mjs ./my-custom-app.html
node install-app.mjs ./untrusted-app.html --sandboxed
window.oma API Reference
Every app has access to window.oma with these methods:
items(collection_name)
Returns array of all items in the collection.
const tasks = window.oma.items('tasks');
mutate(collection_name, command)
Idempotently mutates the collection.
window.oma.mutate('tasks', {
command: 'add_task',
command_id: crypto.randomUUID(),
expected_version: window.oma.version('tasks'),
item: {
id: crypto.randomUUID(),
title: 'New task',
status: 'todo'
}
});
subscribe(collection_name, callback)
Listens for changes.
window.oma.subscribe('tasks', () => {
console.log('Tasks changed, re-render');
render();
});
version(collection_name)
Returns current version number (for optimistic concurrency control).
const v = window.oma.version('tasks');
theme
Object with mode ('light' or 'dark') and design tokens.
if (window.oma.theme.mode === 'dark') {
document.body.classList.add('dark');
}
File API (for apps with capabilities: ['files'])
window.oma.files.upload(file_object).then(file_id => {
console.log('Uploaded:', file_id);
});
window.oma.files.get(file_id).then(metadata => {
console.log(metadata.name, metadata.size, metadata.mime_type);
});
const url = window.oma.files.url(file_id);
window.oma.files.list().then(files => {
files.forEach(f => console.log(f.name));
});
window.oma.files.delete(file_id);
Configuration
Environment Variables
Set in the env block of your host's MCP server entry:
{
"mcpServers": {
"open-mcp-apps": {
"command": "node",
"args": ["/path/to/open-mcp-apps/src/server.mjs"],
"env": {
"OMA_VIEWER": "0",
"PORT": "9000"
}
}
}
}
OMA_VIEWER: Set to 0 to disable the browser viewer entirely
PORT: Change the viewer port (default: 8787)
Browser Viewer
The viewer runs at http://127.0.0.1:8787 (or custom PORT). It serves:
/view/<app-name> - Individual app pages
/mcp - Stateless HTTP MCP endpoint
Security: Bound to 127.0.0.1 only. No password because any local process can access the SQLite file directly. Treat tunnel URLs as secrets if you expose it.
Advanced: Writing Apps Manually
For apps beyond the AI's context window, write in your own editor:
node install-app.mjs ./my-app.html
node install-app.mjs ./my-app.html --sandboxed
node install-app.mjs --list
Key constraints:
- Single HTML file, ≤200 KB
- No external network requests
- Engine injects CSS, design tokens, and
window.oma
- Provenance (trusted vs sandboxed) cannot be changed after install
See RUNTIME.md for full contract.
Troubleshooting
Apps not appearing after install
- Fully quit the host (Cmd-Q, not just close window)
- Check
~/.config/Claude/claude_desktop_config.json (or equivalent) for server entry
- Verify server process:
ps aux | grep open-mcp-apps
"Permission denied" on first tool use
- Click "Always allow" for each tool
- Or batch-approve: Settings → Connectors → open-mcp-apps → Tool permissions
Data not persisting
- Ensure host is fully quit before deleting/moving the
.db file
- Check for
-wal and -shm siblings (SQLite write-ahead log)
Port 8787 already in use
- Another
open-mcp-apps instance is running (they share data, no issue)
- Another process owns the port: set
PORT in env to use a different port
App doesn't load in viewer
- Check browser console for errors
- Ensure app HTML is valid (no unclosed tags)
- Verify collections exist: the AI should seed initial items
Version conflicts after update
- Fully quit host
- If persists: delete
open-mcp-apps.db and restart (data lost)
"Rate limit" or "Token budget" errors
- The AI is reading large guide/sources. This is expected first time
- Subsequent calls use cached context
Testing
Run the full test suite:
npm test
Individual test files:
node test/server-smoke.mjs
node test/http-smoke.mjs
node test/provenance.mjs
node test/seed-smoke.mjs
node test/files-smoke.mjs
Design Principles
- UI and data persist separately: Apps are views, collections are truth
- Idempotent mutations: Every change is a domain command with
command_id
- Optimistic concurrency:
expected_version prevents conflicts
- Zero config for users: AI handles app creation, no manual setup
- Single-file apps: ≤200 KB HTML, no build step for AI-authored apps
- Provenance matters: Sandboxed apps run in restricted iframe, no file/network access
Example: Full Kanban Board Creation
User says: "make me a kanban board"
AI workflow:
-
Check if exists:
{"name": "list_apps"}
-
Read guide (first time only):
{"name": "get_app_guide"}
-
Write app (HTML with columns: todo, in-progress, done):
<!DOCTYPE html>
<html>
<head><title>Kanban Board</title></head>
<body>
<div class="board">
<div class="column" data-status="todo">
<h2>To Do</h2>
<div class="tasks" id="todo"></div>
</div>
<div class="column" data-status="in-progress">
In Progress
Done
Add to To Do
The board now persists. In any future chat: "open my kanban board" → AI calls open_kanban → all tasks still there.
Repository: https://github.com/2nd1st/open-mcp-apps
Homepage: https://openmcp.app
License: AGPL-3.0