@corpusctl/mcp
v0.3.0
Published
Model Context Protocol server for Corpusctl — let an assistant read your content types, draft entries and publish, bounded by your API token's scopes
Downloads
595
Maintainers
Readme
@corpusctl/mcp
Model Context Protocol server for Corpusctl. Point an MCP client at it and an assistant can set up your schema, manage locales, search your content, draft entries and publish them — a space can be built end-to-end without opening the panel, bounded by whatever your API token is allowed to do.
Setup
Create an API token in the panel (Settings → Tokens) with the scopes you want the assistant to have, then add this to your MCP client config:
{
"mcpServers": {
"corpusctl": {
"command": "npx",
"args": ["-y", "@corpusctl/mcp"],
"env": {
"CORPUSCTL_URL": "https://api.corpusctl.com",
"CORPUSCTL_TOKEN": "cctl_api.…"
}
}
}
}Both variables are required. CORPUSCTL_URL must be https — the only
exception is a loopback address for local development.
CORPUSCTL_TOKEN accepts two kinds of token:
- Space token (
cctl_api.…) — bound to exactly one space. The assistant works in that space and nowhere else. - Account token (
cctl_acc.…) — spans the owner's memberships. Every space-bound tool takes an optionalspaceargument (a space UUID or slug) to pick the target; without it, requests fall back to the owner's first membership.list_spacesshows what is available, and with thetenants:managescopecreate_spacecan open new spaces.
Tools
| Tool | Scope required |
|---|---|
| list_content_types | schema:read |
| get_content_type | schema:read |
| upsert_content_type | schema:write |
| delete_content_type | schema:write |
| get_locales | content:read |
| set_locales | schema:write |
| list_documents | content:read |
| get_document | content:read |
| search_content | search:read |
| create_document | content:write |
| update_draft | content:write |
| publish_document | content:publish |
| unpublish_document | content:publish |
| list_spaces | account token (cctl_acc.…) |
| create_space | account token + tenants:manage |
Every tool except the two account-level ones also takes an optional space
argument — a space UUID or slug sent as the x-corpusctl-tenant header. It
only matters with an account token; a space token is pinned to its space on
the server regardless.
Give the token only the scopes you want used. A token without
content:publish produces an assistant that can draft but cannot publish, and
the refusal comes from the server — not from this package.
Markdown in, blocks out
Body fields use Corpusctl's block format. The assistant does not need to
generate block JSON: create_document and update_draft accept a
markdownFields object ({ "body": "# markdown here" }) and the server
converts each entry to blocks before sending — headings, lists, task lists,
tables, code fences, images, links and inline formatting included. A field
present in both data and markdownFields is an error; nothing is silently
overwritten.
What it deliberately cannot do
There are no tools for deleting documents, purging trash, managing members or
issuing tokens. An assistant produces content; destroying content stays a
human decision. The one delete that exists — delete_content_type — cannot
touch data: the server refuses to drop a type that still has documents.
Member and token management is session-only on the API anyway, so an API
token could not perform it even if a tool existed.
Security model
The token is the boundary, and it is enforced on the server.
- A space token belongs to exactly one space. The space is resolved from
the token record in the database — the
x-corpusctl-tenantheader cannot move it, so this package cannot reach another space even if asked to. - An account token selects a space per request, but only among its
owner's memberships — the server answers
403for anything else, and the effective scopes are intersected with the owner's role in the selected space (a viewer membership cannot write, whatever the token's scopes say). - Scopes are enforced by the API. This package invents no authorization of its
own; a
403is surfaced to the assistant as a readable error. - A token is also bound to its owner's current membership. If the person who created it is removed from the space, or their role is reduced, the token dies or narrows with them.
- The token is only ever sent to the configured
CORPUSCTL_URL. Redirects are refused rather than followed, and no tool takes a base URL argument. - Session keys (
cctl_ses.…) are rejected. They represent a whole person across every space and do not belong in a config file.
Responses are capped so a large query cannot flood the assistant's context; a truncated result says so rather than looking complete.
Requirements
Node 20 or newer.
Licence
MIT
