Complete reference for Claude, Codex, ChatGPT, and any MCP-compatible client. Use the publishwith.ai MCP tool to create, update, and manage hosted apps.
Add the MCP server to your client once. Use the credentials from your dashboard.
claude mcp add publishwith --transport http "https://publishwith.ai/mcp" \ -- --header "Authorization: Bearer YOUR_API_KEY"
codex mcp add publishwith --transport http "https://publishwith.ai/mcp" \ -- --header "Authorization: Bearer YOUR_API_KEY"
{
"publishwith": {
"type": "http",
"url": "https://publishwith.ai/mcp",
"headers": { "Authorization": "Bearer YOUR_API_KEY" }
}
}
publishwith action-based tool. All operations use action="..." as the first parameter.Static apps serve files directly. Supported formats: HTML, Markdown (.md), CSS, JS, JSON, images, and any text format. Markdown files are automatically rendered to HTML with GFM support (tables, code blocks, task lists).
create_app(name="my-report")
→ { app_id: "abc123", url: "https://publishwith.ai/a/my-report/" }
publish_static(app_id="abc123", files={
"index.html": "<h1>Q3 Revenue</h1><p>$1.2M</p>"
})
→ { url: "https://publishwith.ai/a/my-report/", version: 1 }
publish_static(app_id="abc123", files={
"index.md": "# Q3 Report\n\n| Month | Revenue |\n|---|---|\n| Jul | $380K |\n| Aug | $420K |\n"
})
Markdown is rendered server-side using GitHub Flavored Markdown. Tables, fenced code blocks, task lists, and inline HTML are all supported.
publish_static(app_id="abc123", files={
"index.html": "<!DOCTYPE html>...",
"style.css": "body { font-family: sans-serif; }",
"data.json": '{"revenue": 1200000}'
})
Dynamic apps have an isolated SQLite database and Liquid templates that render data server-side. Routes can have parameters, queries, and POST handlers.
create_app(name="inventory", mode="dynamic")
→ { app_id: "xyz789" }
execute_sql(app_id="xyz789",
sql="CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT, qty INTEGER)")
execute_sql(app_id="xyz789",
sql="INSERT INTO items (name, qty) VALUES ('Widget A', 42), ('Widget B', 7)")
publish_dynamic(app_id="xyz789",
manifest="""
kind: mcp-html-app
version: 1
routes:
- path: /
template: index.html
queries:
items:
sql: "SELECT * FROM items ORDER BY name"
""",
templates={
"index.html": """
<!DOCTYPE html>
<html><body>
<h1>Inventory</h1>
<ul>{% for item in items %}
<li>{{ item.name }} — {{ item.qty }} units</li>
{% endfor %}</ul>
</body></html>
"""
}
)
→ { url: "...", version: 1, routes: ["/"] }
| Field | Required | Description |
|---|---|---|
kind | Yes | Must be mcp-html-app |
version | Yes | Must be 1 |
routes[].path | Yes | URL path, supports {param} placeholders |
routes[].template | GET only | Liquid template filename |
routes[].method | No | POST for mutation routes (default: GET) |
routes[].queries | No | Named SQL SELECT queries injected into template context |
For large apps, upload files in batches using the token returned by the first call. All calls with the same token update the same version atomically.
# Start a new version — no token
publish_static(app_id="abc123", files={"index.html": "..."})
→ { token: "tok_abc...", version: 2 }
# Add more files to the same version
publish_static(app_id="abc123", token="tok_abc...", files={"style.css": "..."})
publish_static(app_id="abc123", token="tok_abc...", files={"app.js": "..."})
publish_static without a token always creates a new version.Share links give unauthenticated access to a specific app. Access is tracked (anonymised — no IP or user agent stored).
create_share_link(app_id="abc123", ttl_hours=72)
→ { share_url: "https://publishwith.ai/p/abc...", expires_at: "..." }
list_share_links(app_id="abc123")
→ { links: [{ id: "lnk_...", share_url: "...", view_count: 14 }] }
revoke_share_link(link_id="lnk_...")
→ { revoked: true }
list_access_log(link_id="lnk_...")
→ { view_count: 14, events: [{ timestamp: "...", action: "view", path: "/" }] }
| Action | Description |
|---|---|
create_app | Create a new app. Returns app_id and URL. |
list_apps | List all apps in your tenant. |
delete_app | Delete an app and all its data. |
publish_static | Upload static files (HTML, MD, CSS, JS…). Returns URL and version. |
publish_dynamic | Deploy a dynamic app with manifest + Liquid templates. |
validate_app | Validate a manifest before publishing. Returns errors if any. |
execute_sql | Run SQL on an app's isolated SQLite database. |
preview | Fetch the rendered HTML of an app (dry-run for dynamic APIs). |
create_share_link | Create a public share link with optional TTL. |
list_share_links | List share links for an app. |
revoke_share_link | Revoke a share link immediately. |
list_access_log | View anonymised access events for a share link. |
create_user | Invite a user to your tenant. |
list_groups | List access groups in your tenant. |
docs | Fetch inline documentation. Use topic=X for a specific section. |
Every action returns a hint field on error. Always surface it to the user or use it to self-correct before retrying.
action="docs" to fetch the latest in-context documentation at any time. Use topic="manifest", topic="sql", or topic="share" for filtered sections.| Error | Cause | Fix |
|---|---|---|
| app not found | Wrong app_id | Call list_apps to get valid IDs |
| token expired | Upload session > 30 min | Start new session with publish_static (no token) |
| mode mismatch | publish_static on a dynamic app | Use publish_dynamic |
| quota exceeded | Plan limit reached | Delete unused apps or upgrade plan |
| manifest invalid | YAML/schema error | Use validate_app first to get field-level errors |