Your CMS is an MCP server
Claude, Cursor, or a script you wrote this morning connects to one endpoint with an API token and works with your content directly — through the same tool registry, and the same permission check, as the assistant inside the product.
For engineers, and for anyone curious what this actually means
A Tuesday
Somebody points Claude Desktop at the CMS and says: find every article still tagged with last year’s campaign, and draft a replacement summary for each. It reads with a token you minted, writes drafts, and cannot publish any of them — because the token you gave it does not carry that permission, and no amount of asking changes that.
One registry, two front doors
The Model Context Protocol is how an agent outside a system finds out what that system can do and then does it. Apograph speaks it at POST /api/v1/mcp — stateless Streamable HTTP, so every request authenticates from scratch and there is no session store and no sticky routing.
What the endpoint exposes is not a second API written for agents. It is the same tool registry the in-product assistant runs against. A tool declares the permission keys a caller must hold, and the registry checks them before it dispatches — one place authorises a call, whichever side it arrived from. Filtering the list an agent is shown is a courtesy; the check at call time is the boundary, because a model can invoke a name it was never shown and sometimes invents one.
A bearer API token is the only way in. A session cookie is refused outright: a cookie rides along ambiently, and this endpoint writes content.
An outside agent and the in-product assistant both arrive at one tool registry, which checks permissions before dispatching to the services behind it.
The token decides what the agent can see
You mint tokens on the admin’s API Tokens page, scoped read or full, and granted to specific workspaces. The scope is what decides which tools appear in the agent’s list at all: a read-scoped token is offered the reads and nothing else, so a retrieval agent is structurally incapable of writing rather than merely instructed not to.
A refusal names the permission it wanted — "content_delete" requires content:delete, which this token does not hold — and that sentence goes back to the model verbatim. It is deliberate: the tool set is derived from the caller’s own scope and reveals nothing about anyone else, and an undiagnosable permission error costs more than naming the key.
Workspace comes from an X-Workspace-Id header or a ?workspaceId= on the URL. Both spellings exist because MCP client configuration is URL-shaped and several clients make custom headers awkward. Naming two workspaces at once is an error rather than a silent pick.
What an agent can actually do
Twenty-five tools, contributed by the plugins that own the data behind them.
| Tool group | What it covers |
|---|---|
| Schema | content_types_list, content_type_get — the shape of everything the token’s workspace was granted. |
| Reads | content_list, content_get, content_relations, content_media, content_translations — published entries, with search, filter, sort and sparse fieldsets. |
| Writes | content_create, content_update, content_delete — a create yields a draft; an update merges. |
| Publish lifecycle | content_publish, content_unpublish — separate calls, and separate permissions, from the writes. |
| Batches | content_bulk_save, content_bulk_publish, content_bulk_unpublish, content_bulk_delete — because an agent syncing a list should not make one round trip per record. |
| Media, read-only | media_assets_search, media_folders_list, media_asset_read — shared with the in-product assistant rather than written twice. An agent can find and read an asset; it cannot upload one. |
| Review | protection_review_status, protection_review_diff, protection_request_review — where an entry stands against its content type’s approval rule, what changed since a given approval, and asking named people to look. There is no approve tool, on this surface or any other. |
| Segments | segments_list, content_access_get, content_access_set — who may read an entry. The last needs a full token; the segment vocabulary itself is not reachable with a token at all. |
Every tool delegates to the service its own HTTP controller calls, which is what stops the two surfaces drifting apart. Some tools are deliberately absent: translation and alarm findings are offered to the in-product assistant only, because an API token scope cannot carry the permissions they require, and a tool that appears in the list and is refused on every call is worse than one that is absent.
Connecting a client
The endpoint speaks Streamable HTTP directly. For a client that only speaks stdio, mcp-remote bridges it in one line of config.
- Turn the endpoint on — it is off unless you set MCP_ENABLED
- Mint a token on the API Tokens page, read or full
- Grant it the workspaces it may reach
- Point the client at /api/v1/mcp with the token as a bearer header
- Name the workspace by header or query string
- Ask the agent what content types exist
What one exchange is allowed to cost
Three ceilings bound a request, and all three exist because an agent on the other end of a network has obligations an in-process loop does not. A request body is capped at 1 MB. One tool call gets 30 seconds. One result gets 4 MiB.
The result ceiling is the interesting one: a result is serialised twice here — once as the text a model reads, once as structured content for clients that parse it — and a payload that will not fit under the ceiling would not have fit in the model’s context either. So the refusal names both sizes and tells the agent to ask for less, which is something a model can act on.
The deadline abandons rather than cancels. A handler is given a signal that aborts when the clock runs out or the caller hangs up, but a tool that ignores it runs to completion with nobody reading the answer. Bounding the caller’s wait is the guarantee; ending the work is not one this layer can make, and we would rather say so than imply otherwise.
Questions worth asking before you turn it on
Is this on by default?
No. It needs MCP_ENABLED. Turning it on lets any holder of a full-scope token drive content changes from outside, which is a decision an operator should make deliberately rather than inherit from an upgrade.
Does the agent see drafts?
No. The content tools served here read the published-only services, the same ones the public API uses, and attribute their writes to the token. Drafts belong to the people working in the admin.
What stops a prompt injection in an entry body?
Nothing stops the text existing — entry bodies are written by people and can say anything. What bounds it is that the agent holds exactly the token’s permissions and no others, so the worst an injection achieves is something the token could already do. That is a smaller problem than an agent with its own account.
Do I need to write an MCP server?
No. This is the server. You point a client at it. If you are building your own agent instead, it is an HTTP endpoint speaking JSON-RPC and any MCP client library will talk to it.
Next
Every feature is included, free
The core is MIT licensed and every feature is in it — none of them is paid-only. Community runs free in production; a plan buys room and governance, not a different product.