Skip to content
fmrlyour agent's outbox

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.

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.