Integrations
Hosted MCP
Connect supported chat apps to public-page workflows, where hosted MCP is enabled. Private content stays with the local plugin.
On this page
Hosted MCP
A remote MCP server for chat apps, behind an OAuth 2.1 authorization server of its own. A person connects an app by pasting https://fmrl.site/mcp where it takes a custom connector — setup and troubleshooting for Claude and for ChatGPT — and the app does the rest: it finds the authorization server through the discovery documents below, identifies itself by its Client ID Metadata Document (Claude and ChatGPT do) or by registering (POST /oauth/register), and sends the person to the consent page, where the connection is bound to exactly one named API key, chosen or minted there. From then on every tool call is that key, spending that key's limits; an API key is not an MCP token, and an MCP token is not an API key. Public pages only: a chat app cannot encrypt on the person's machine, so private pages stay with the local plugin and the browser, and this server reads a private page as its envelope and nothing more — a refusal never falls back to a public page. Client names are self-declared. Every route in this section answers 404 unless the deployment enables hosted MCP, and then only on its one configured origin (the resource is <origin>/mcp; the bare <origin> names the same server and is what the root discovery document describes; nothing is derived from a request's Host). A connection is bound to exactly one named API key, chosen or minted on the consent page; an API key is not an MCP token, and an MCP token is not an API key. Client names are self-declared. Every answer is no-store. A request whose Origin is not that origin is refused (403 invalid_origin) and no CORS headers are sent, so an MCP client must be native or run server-side, not in a browser page. The OAuth and discovery routes are limited per network per minute; /mcp is limited per key per minute instead, since a chat app calls it for all its users from shared servers (429 rate_limited). Registrations, key minting at consent and metadata-document fetches are budgeted per network per day in the store, and failed key or token checks per address per hour; past a budget the answer is 429 rate_limited with Retry-After to the end of the window.
Call the hosted MCP server
POST /mcp
Stateless MCP over Streamable HTTP (JSON responses, no Mcp-Session-Id): any instance answers any request.
Send Authorization: Bearer <access token> on every request; without a live one the answer is 401 with a WWW-Authenticate challenge naming /.well-known/oauth-protected-resource/mcp. The token's key is re-read on every request, and a key that is gone fails closed. Past the key's per-minute budget the answer is 429 rate_limited with Retry-After: 60. Never send a page key (#p=), a passphrase or a private page's plaintext here.
tools/list names eight tools. Each is the API route it stands for, called as the connection's key and spending that route's limits; page URLs are on the hosted origin. A refusal is a tool result with isError, its text meant for the model.
fmrl_publish— publish a public HTML or Markdown page (POST /api/v1/publish): owned and watched by the key, counted against its monthly quota. The result carries nomanage_urland norecovery: no manage link goes into a chat. The page still has its recovery link, which its person can rotate from the browser.fmrl_get— a page's metadata and one revision's source (GET /api/v1/docs/{id}with…/revisions/{rev}'s content); reading marks nothing seen, unlike the API's read, so the tool is read-only;fmrl_watchwithseen_revmoves a watch's place.fmrl_watch— watch a page (PUT /api/v1/docs/{id}/watch, optionalseen_rev).fmrl_inbox— the key's watched pages with revisions by other editors it has not seen (GET /api/v1/inbox), each revision without its time.fmrl_edit— a new revision of a public page (PUT /api/v1/docs/{id}): a page the connection's key may edit (one it owns, holds a role on, or, with theallscope, its person's). It takes no manage token: amanage_tokenargument is an unknown argument and is refused, andFmrl-Manage-Tokenstays the API's.base_revis taken as the API takes it, and a conflict answers the revision the page is at now. The revision names the connection's key, and the edit watches the page for the key; the result'swatchingisfalse, and its text says so, when the page could not be added to the key's watches, usually because the key already watches the most pages it may; the edit still succeeds.fmrl_list— the key's pages, newest first, 50 at most (GET /api/v1/docs); a private page is listed as private, with no sealed record; no row carries a title.fmrl_whoami— the key (GET /api/v1/me): prefix,label,display_name, quota andprincipal, withoutattach_urloraccount_vault, since the consent already attached the key's agent to its person, and withoutcreated_at,attached_atorrotated_at: a chat app needs none of those times, and whether the agent is attached isprincipal.attached_to. The quota keepsresets_at, when the key can publish again.fmrl_delete— remove a page the key published (DELETE /api/v1/docs/{id}); a page it does not own answers as an unknown id, and one already removed says so.
Every tool carries a title, annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint; fmrl_edit and fmrl_delete are destructive, and fmrl_publish and fmrl_edit are open-world, since they write to the public web) and an output schema, a closed object its structuredContent satisfies.
Private pages never pass through here. A private flag with any value but false or null (true, but also a mistyped "true" or 1), any argument a tool does not name (an encryption switch, a key), an argument named twice (names compare case-insensitively, so private and Private are one name) and any id or URL with a fragment (#p=) are refused, and a private page is read as its encrypted envelope, unopened (which does not mark it seen). A private page cannot be edited here: fmrl_edit refuses one (including one another key published, which is otherwise answered as an unknown id), and a private flag on it, with the same kind of refusal a refused private publish gets (stop, do not retry, do not publish it as a public page, go back to your person). A private page can still be removed with fmrl_delete, which needs no key. No hosted response carries sealed: that record is ciphertext under the owner's ring, which this connector never holds. A refused private publish never falls back to public: the refusal tells the model not to retry without the flag or as a public page, and to stop and go back to its person, who can publish or open a private page with the local plugin or in the browser. Tool descriptions say only that the connector handles public pages and that a private request is refused; the stop text lives in the refusals, which are tool output. A client must never retry a refused private publish as a public one.
Open a stream (not offered)
GET /mcp
Always 405 once authorized: a stateless server holds no stream.