hyphertext/docs

Conventions & Gotchas

Things that are easy to get wrong, mostly because they're not obvious from a tool's name alone.

Three different IDs for a page — don't mix them up

  • id (the page's UUID) — used in tool arguments (page_id) and appears in its public URL.
  • publish_path — the actual URL path, e.g. /u/alice/p/8f2c... or /p/8f2c.... Returned by create_page, update_page_html, str_replace_page_html, and publish_page. Always use this — never construct a URL yourself from the page id.
  • public_app_key — a separate identifier, returned by get_page, used only by a published page's own JS calling the runtime endpoints (see Runtime Endpoints). Not a URL, not interchangeable with the page id.

This server does not generate HTML

AI page generation is off platform-wide right now. create_page and update_page_html mirror the dashboard's “Import HTML” flow — html is a required argument you supply. Write the page yourself (in your own reasoning/tool-use loop), then call these tools to host what you wrote.

Secrets: never invent a value

For any real third-party credential (an API key, a token) that the user hasn't explicitly pasted into the conversation themselves, use declare_project_secret — it registers the key name with no value, and the human enters the real value directly in Studio → Secrets, so it never passes through the conversation at all. Reach for set_project_secret only when the user has explicitly typed or pasted the actual value themselves.

Use exposure: "secret" (the default) for anything that must not be readable in the page's own source — the page's JS reaches it only through /api/proxy, never directly. Use exposure: "public" only for values explicitly designed to be client-visible (a publishable key, a domain-restricted key) — it gets inlined straight into the page.

After declaring a secret, tell the user which key(s) they still need to fill in and where (Studio → Secrets) — the page can't use it until they do.

Prefer targeted edits over full rewrites

str_replace_page_html / str_replace_project_file (exact-substring find/replace, like an editor) are cheaper and less error-prone than resending an entire document through update_page_html / update_project_file for a small change. Call get_page / get_project_file first if you need to see current content — old_str must match exactly, including whitespace.

Version snapshots are automatic, not agent-revertible (yet)

Every HTML edit (via update_page_html or str_replace_page_html) saves a snapshot automatically before applying the change, so a human can revert in Studio. There is currently no MCP tool to read or revert to a past version — if a user asks to undo a change you just made and you still have the prior HTML in context, the practical path is to write it back yourself with update_page_html.

Visibility: private means private to the owner, specifically

publish_page({ visibility: "private" }) restricts a page to its own owner, signed into their account. Everyone else — including other signed-in Hyphertext accounts — is redirected to sign in. It is not “any signed-in user can see it.”

Fixed limits (not yet tier-based)

These are flat safety caps today, not a tuned per-plan quota system — see Pricing & Limits for the plan-based site/page limits, which are the ones that actually vary by tier:

  • Project database: 5,000 rows per (page_id, collection).
  • Assets: 20 total per page (max 15 images, 5 documents), 5 MB per file.
  • Auxiliary project files: 2 MB of text per file.

Every tool call is scoped to the connecting user

The MCP server never uses a service-role key — every database and storage call runs with a client scoped to your session's own JWT, so Postgres row-level security applies exactly as it does for the website. You can only ever see or modify pages, files, secrets, and database records owned by the account you're connected as.