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 bycreate_page,update_page_html,str_replace_page_html, andpublish_page. Always use this — never construct a URL yourself from the page id.public_app_key— a separate identifier, returned byget_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.