MCP Server
Plank exposes a read-only MCP endpoint per instance at /mcp.
This guide is primarily intended for agents or automated integrations consuming the MCP endpoint. However, it can also be used manually with HTTP clients such as HTTPie, curl, or similar tools.
This endpoint is intended for schema discovery. It is used to inspect content types, their fields, relations, and enabled locales. It is not intended to read public content entries; use the public API at /api for that.
Local setup
If the endpoint will be consumed by an agent, script, or local integration, the usual approach is to store the token in an environment variable. For example:
PLANK_MCP=your_mcp_tokenThat token is generated from Settings > API Tokens inside the Plank instance.
The variable name is a consumer-side convention, not a server requirement. Plank does not require any specific environment variable name; the only hard requirement is sending the token in Authorization: Bearer <token>.
What it exposes
The list of available content types.
The list of enabled locales and the default locale.
The full schema for each content type.
What it does not expose
Public entry reads.
Mutations.
Editing operations.
Authentication
Requires
Authorization: Bearer <token>.The token must have access type
mcp-server.read-onlyandfull-accesstokens do not work against/mcp.mcp-servertokens do not work against/api.
v1 transport
Base endpoint:
/mcpPOST /mcp: JSON-RPC 2.0 messagesGET /mcp: minimal endpoint metadataDELETE /mcp: not supported in v1
Use POST /mcp for MCP operations. GET /mcp only exposes minimal endpoint metadata and cannot be used to send JSON-RPC messages such as initialize, resources/list, or resources/read.
Supported methods
initializepingresources/listresources/read
Exposed resources
plank://content-typesplank://localesplank://content-types/{slug}/schema
Recommended flow
Call
initialize.Call
resources/listto discover the available resources.Call
resources/readonplank://content-types.Call
resources/readonplank://content-types/{slug}/schemafor each content type you want to inspect.Call
resources/readonplank://localesif you need the locale configuration.
initialize should be called before resources/list and resources/read. Some implementations may tolerate direct calls, but the recommended integration flow is to initialize the JSON-RPC session first.
Manual usage
Although this endpoint is designed for agents or integrations, it can also be used manually.
Important considerations:
Use
POST /mcpto send JSON-RPC messages.Do not use
GET /mcpforinitialize,resources/list, orresources/read.Send
Content-Type: application/json.Send
Authorization: Bearer <token>.
Example with HTTPie:
POST https://your-instance.com/mcpAuth: Bearer token{
"jsonrpc": "2.0",
"id": 1,
"method": "resources/read",
"params": {
"uri": "plank://content-types"
}
}'Request examples
initialize
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {
"name": "example-client",
"version": "0.1.0"
}
}
}resources/list
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/list"
}resources/read for content-types
{
"jsonrpc": "2.0",
"id": 3,
"method": "resources/read",
"params": {
"uri": "plank://content-types"
}
}resources/read for a content type schema
{
"jsonrpc": "2.0",
"id": 4,
"method": "resources/read",
"params": {
"uri": "plank://content-types/post/schema"
}
}resources/read for locales
{
"jsonrpc": "2.0",
"id": 5,
"method": "resources/read",
"params": {
"uri": "plank://locales"
}
}Response examples
initialize response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-11-25",
"capabilities": {
"resources": {}
},
"serverInfo": {
"name": "plank-cms",
"title": "Plank CMS",
"version": "0.30.0"
},
"instructions": "Use resources/list to discover available resources, then resources/read to load Plank content types, per-type schemas, and locales."
}
}resources/list response
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resources": [
{
"name": "content-types",
"title": "Content Types",
"uri": "plank://content-types",
"description": "Lists the content types available in this Plank instance.",
"mimeType": "application/json"
},
{
"name": "locales",
"title": "Locales",
"uri": "plank://locales",
"description": "Lists the enabled locales and the default locale for this Plank instance.",
"mimeType": "application/json"
},
{
"name": "content-type-schema-post",
"title": "Post schema",
"uri": "plank://content-types/post/schema",
"description": "Schema for the \"post\" content type.",
"mimeType": "application/json",
"annotations": {
"lastModified": "2026-05-23T00:00:00.000Z"
}
}
]
}
}resources/read response for plank://content-types
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"contents": [
{
"uri": "plank://content-types",
"mimeType": "application/json",
"text": "{\n \"contentTypes\": [\n {\n \"name\": \"Posts\",\n \"slug\": \"post\",\n \"kind\": \"collection\",\n \"previewEnabled\": true,\n \"isDefault\": true,\n \"schemaUri\": \"plank://content-types/post/schema\",\n \"updatedAt\": \"2026-05-23T00:00:00.000Z\"\n }\n ]\n}"
}
]
}
}resources/read response for plank://locales
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"contents": [
{
"uri": "plank://locales",
"mimeType": "application/json",
"text": "{\n \"defaultLocale\": \"en\",\n \"locales\": [\n \"en\",\n \"es\"\n ]\n}"
}
]
}
}resources/read response for plank://content-types/{slug}/schema
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"contents": [
{
"uri": "plank://content-types/post/schema",
"mimeType": "application/json",
"text": "{\n \"id\": \"c33f35ea-0d98-4a00-95a5-2d8c2d88cf0c\",\n \"name\": \"Posts\",\n \"slug\": \"post\",\n \"kind\": \"collection\",\n \"previewEnabled\": true,\n \"tableName\": \"posts\",\n \"fields\": [\n {\n \"name\": \"title\",\n \"type\": \"string\",\n \"width\": \"half\",\n \"required\": true\n },\n {\n \"name\": \"slug\",\n \"type\": \"uid\",\n \"width\": \"half\",\n \"required\": true,\n \"targetField\": \"title\"\n }\n ],\n \"isDefault\": true,\n \"createdAt\": \"2026-05-01T00:00:00.000Z\",\n \"updatedAt\": \"2026-05-23T00:00:00.000Z\"\n}"
}
]
}
}Useful payloads after parsing contents[0].text
resources/read returns the useful payload inside result.contents[], and the JSON document itself is serialized as a string in text. The client must parse that value before consuming it.
plank://content-types
{
"contentTypes": [
{
"name": "Posts",
"slug": "post",
"kind": "collection",
"previewEnabled": true,
"isDefault": true,
"schemaUri": "plank://content-types/post/schema",
"updatedAt": "2026-05-23T00:00:00.000Z"
}
]
}plank://locales
{
"defaultLocale": "en",
"locales": ["en", "es"]
}plank://content-types/{slug}/schema
Returns the full content type object exactly as Plank persists and uses it internally, including:
idnameslugkindpreviewEnabledtableNamefieldsmetadata such as
isDefault,createdAt, andupdatedAtwhen present
Common errors
Missing token
{
"error": "API token required"
}Invalid token or wrong access type
The server should respond with an authentication error. The exact shape may vary depending on the HTTP layer or middleware, but the usual cause is one of the following:
the token does not exist
the token is invalid
the token does not have access type
mcp-server
Unknown content type
{
"jsonrpc": "2.0",
"id": 7,
"error": {
"code": -32004,
"message": "Content type not found",
"data": {
"slug": "unknown-slug"
}
}
}Implementation notes
The MCP endpoint is intended for schema discovery, not for serving public content.
resources/listenumerates MCP resources; it does not return the schema payload itself.resources/readreturns acontentsarray; the useful payload is incontents[n].text.textcontains serialized JSON, so the client must callJSON.parse(...).Consumers can translate the schema into TypeScript interfaces, validators, internal clients, or custom tooling.
The endpoint can be used by automation or internal integrations without exposing that logic to the public frontend runtime.