- 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](https://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)
```bash
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):
```bash
curl -fsSL https://raw.githubusercontent.com/2nd1st/open-mcp-apps/main/install.sh | sh -s -- --yes
```
**Target specific host**:
```bash
curl -fsSL https://raw.githubusercontent.com/2nd1st/open-mcp-apps/main/install.sh | sh -s -- --host codex
```
### Manual Installation (Clone)
```bash
git clone https://github.com/2nd1st/open-mcp-apps
cd open-mcp-apps
node install.mjs
```
### Post-Install
1. **Fully quit and reopen** the host (Cmd-Q on macOS, not just close window)
2. **First-run permissions**: When tools appear, click "Always allow" for each
3. **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
```bash
node uninstall.mjs # Unregister, keep data
node uninstall.mjs --purge # Delete everything (irreversible)
node uninstall.mjs --check # Preview changes
```
**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.
```json
{
"name": "list_apps"
}
```
Returns: Array of apps with `name`, `title`, `description`, `version`, `author`, `trust_tier`.
### `get_app`
Retrieves an app's full source code.
```json
{
"name": "get_app",
"arguments": {
"name": "kanban-board"
}
}
```
### `get_app_guide`
Retrieves the authoring guide the AI should read before writing a new app.
```json
{
"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.
```json
{
"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).
```json
{
"name": "delete_app",
"arguments": {
"name": "old-app"
}
}
```
### `open_app` (or `open_<name>`)
Opens an app with optional initial items.
```json
{
"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:
```html
<!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>
// window.oma is injected by the engine
const { items, mutate, subscribe } = window.oma;
// Load collection
const books = items('books');
// Render
function render() {
const list = document.getElementById('books');
list.innerHTML = books.map(b =>
`<li>${b.title} by ${b.author} <button onclick="remove('${b.id}')">✕</button></li>`
).join('');
}
// Add book
document.getElementById('add-form').onsubmit = (e) => {
e.preventDefault();
mutate('books', {
command: 'add_book',
item: {
id: crypto.randomUUID(),
title: document.getElementById('title').value,
author: document.getElementById('author').value
}
});
e.target.reset();
};
// Remove book
window.remove = (id) => {
mutate('books', {
command: 'remove_book',
item_id: id
});
};
// Subscribe to changes
subscribe('books', render);
render();
</script>
</body>
</html>
```
### 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:
```bash
# Install a custom app from file
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.
```javascript
const tasks = window.oma.items('tasks');
// Returns: [{id: '1', title: 'Task 1', ...}, ...]
```
### `mutate(collection_name, command)`
Idempotently mutates the collection.
```javascript
window.oma.mutate('tasks', {
command: 'add_task',
command_id: crypto.randomUUID(), // Optional, auto-generated
expected_version: window.oma.version('tasks'), // OCC
item: {
id: crypto.randomUUID(),
title: 'New task',
status: 'todo'
}
});
```
### `subscribe(collection_name, callback)`
Listens for changes.
```javascript
window.oma.subscribe('tasks', () => {
console.log('Tasks changed, re-render');
render();
});
```
### `version(collection_name)`
Returns current version number (for optimistic concurrency control).
```javascript
const v = window.oma.version('tasks'); // e.g., 42
```
### `theme`
Object with `mode` ('light' or 'dark') and design tokens.
```javascript
if (window.oma.theme.mode === 'dark') {
document.body.classList.add('dark');
}
```
### File API (for apps with `capabilities: ['files']`)
```javascript
// Upload file
window.oma.files.upload(file_object).then(file_id => {
console.log('Uploaded:', file_id);
});
// Get file metadata
window.oma.files.get(file_id).then(metadata => {
console.log(metadata.name, metadata.size, metadata.mime_type);
});
// Download URL
const url = window.oma.files.url(file_id);
// Returns: /files/<file_id>
// List all files
window.oma.files.list().then(files => {
files.forEach(f => console.log(f.name));
});
// Delete
window.oma.files.delete(file_id);
```
## Configuration
### Environment Variables
Set in the `env` block of your host's MCP server entry:
```json
{
"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:
```bash
# Create my-app.html with full bundling/tooling
# Keep it ≤200 KB, self-contained, no network requests
# Install as trusted (full capabilities)
node install-app.mjs ./my-app.html
# Install as sandboxed (no capabilities)
node install-app.mjs ./my-app.html --sandboxed
# List provenance
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
Ver no GitHub