open-sites-mcp
v0.9.0
Published
MCP server that lets any AI agent (Claude Desktop, Claude Code, Cursor, …) create, publish, and share websites on a self-hosted open-sites hub
Maintainers
Readme
open-sites-mcp
MCP server that gives any AI agent — Claude Desktop, Claude Code, Cowork, Cursor, or anything else that speaks the Model Context Protocol — the tools to create, version, publish, and share websites on a self-hosted open-sites hub: an open equivalent of ChatGPT Sites you run yourself.
Setup
You need a hub: deploy one in a few minutes with the Vercel guide or Docker.
Sign in once from a terminal (stores an API token in
~/.config/open-sites/config.json):npx open-sites-mcp login https://your-hub.example.com…or create a token in the hub's Settings page and pass it as environment variables (see below).
Add the server to your MCP client:
{ "mcpServers": { "open-sites": { "command": "npx", "args": ["-y", "open-sites-mcp"] } } }Claude Desktop: Settings → Developer → Edit Config. Cursor:
.cursor/mcp.json. Claude Code:claude mcp add open-sites -- npx -y open-sites-mcp(or install the plugin, which also ships a site-building skill).To use environment variables instead of
login, add"env": { "OPEN_SITES_URL": "https://your-hub.example.com", "OPEN_SITES_TOKEN": "ost_…" }.
Without configuration the server still starts; every tool then explains how to connect. npx open-sites-mcp status shows what it would connect as, logout removes the stored token.
Tools
| Tool | What it does |
|---|---|
| list_sites (q, owner, sort, limit, offset) | Sites you can access, with owner (ownerKind: personal | team), visibility, deployed version, version count, URL, hasServer (the deployed version runs server code) |
| create_site · get_site · update_site · delete_site | Lifecycle; create_site takes an optional org, update_site renames, changes the slug, or transfers to another user/organization |
| save_version | Save a complete set of files as a new version (optionally deploy); add _server/index.js for a full-stack site (server syntax errors are rejected at save time) |
| update_files | Change some files / remove paths on top of the latest (or any) version |
| get_version_files | List files of latest / deployed / n, optionally with contents in one call |
| deploy · undeploy · list_versions · delete_version | Publish, roll back, take offline, tidy up (list_versions paginates and reports file counts/sizes and hasServer) |
| diff_versions | Added / removed / changed / moved files between two versions (from/to accept latest/deployed); include_patch adds unified patches |
| list_kv · get_kv · delete_kv · list_objects · delete_object | Inspect and tidy the runtime data a site stored via _api/kv / _api/objects (editors) |
| set_secrets · list_secrets · delete_secret | Secrets a full-stack site's server code reads as env.SECRETS.NAME — set/replace/delete by name (owner), list names only (values are never returned) |
| get_logs (limit, offset, since, kind, level, requestId) | Server logs of a full-stack site, newest first: request lines, console output, uncaught errors with stacks — pass the request id from a 500 page to see one request end to end |
| get_build (slug, version?, wait?) · rebuild (slug, version?) · get_container (slug) · restart_container (slug) | Container apps (a Dockerfile in the version, run by the hub's runner): build state — wait polls until ready/failed —, re-run a build, the deployed container's status, recreate it |
| run_sql (slug, query, params?, maxRows?) | Run SQL in the site's own Postgres schema as the site's role (owner): one statement with params, or a ;-script in one transaction — create/migrate tables before deploying, inspect and repair data. Errors: sql_error with details.sqlstate/position |
| list_tables (slug) | The site's tables with columns, row counts and size, plus usage against the per-site quota and whether SQL is on/off/unavailable on the hub (editors) |
| get_usage | Storage a site uses (incl. server-log rows and secret count); admins get hub-wide totals with a per-owner breakdown when slug is omitted |
| get_preview_url | Temporary sandboxed link to review any version before deploying |
| set_access · list_users · create_user | Private / password / public, per-user grants, anonymous-write toggle |
| list_orgs · get_org · create_org · set_org_members | Organizations: sites owned by a team rather than a person; members need no per-site grant |
| get_analytics | Views, uniques, per-day series, top pages |
| whoami | Which hub/user the server is connected as, which organizations they belong to, whether the hub allows server code (hub.functions) and per-site SQL (hub.sql) |
Sites are static HTML/CSS/JS with a built-in runtime API for visitor identity and per-site / per-visitor storage — see the hub docs. A version that contains _server/index.js (export default { async fetch(request, env, ctx) }) is full-stack: static files win for GET/HEAD, everything else runs the sandboxed handler with env.KV, env.OBJECTS, env.SQL (a Postgres schema of its own — run_sql/list_tables manage it), env.SECRETS, env.visitor and outbound fetch; get_logs shows what happened. On hubs with a runner (whoami → hub.containers: "on") a version with a root Dockerfile is a container app in any language — the hub builds and runs it (get_build, get_container), still versioned and access-controlled. Errors come back as Hub error <status> (<code>): <message> with stable codes so agents can react — validation_failed, bad_request, unauthorized, forbidden, not_found, conflict, payload_too_large, rate_limited (wait Retry-After seconds, then retry), sql_error (a Postgres error from run_sql; details.sqlstate), unavailable (SQL or containers off/unavailable on the hub), build_pending / build_failed / container_start_failed (container apps), quota_exceeded (don't retry; details.quota is sitesPerOrg | bytesPerOrg | versionsPerSite | kvScopesPerSite | orgsPerUser | sqlBytesPerSite | containerAppsPerOrg — the message says what to delete or that the hub admin must raise the quota), internal_error. Hubs enforce per-owner quotas (QUOTA_*) and hub-wide rate limits (RATE_LIMIT_*); get_usage shows a site's storage. MIT.
