Skip to content

MCP Server Reference

EmDash exposes a built-in Model Context Protocol (MCP) server at /_emdash/api/mcp. MCP clients use it to read and manage content, bylines, schemas, media, taxonomies, menus, revisions, and settings.

The MCP endpoint requires a Bearer token. EmDash supports these token flows:

MethodUse
OAuth 2.1 Authorization Code with Proof Key for Code Exchange (PKCE)Interactive MCP clients. The user approves the requested scopes in a browser.
Personal access tokenLong-lived access for a client or automation. Tokens use the ec_pat_ prefix and are created in the admin.
OAuth 2.0 Device Authorization GrantCommand-line clients that ask the user to approve a code in a browser. emdash login uses this flow.

Session cookies do not authenticate the MCP endpoint.

OAuth and personal access tokens limit which tools a client can call. The user’s role is checked separately, so a scope never grants a permission the user does not have.

ScopeAccess
content:readRead and search content, bylines, taxonomies, terms, menus, and revisions. Draft-like content also requires the user’s content:read_drafts permission.
content:writeCreate and change content, bylines, and revisions. It also grants taxonomies:manage and menus:manage for compatibility with existing tokens.
media:readRead media records.
media:writeUpload, register, update, and delete media.
schema:readRead collections and fields.
schema:writeCreate, update, and delete collections and fields.
taxonomies:manageCreate, update, and delete taxonomy definitions and terms.
menus:manageCreate, update, and delete menus and menu items.
settings:readRead site settings.
settings:manageUpdate site settings.
mcp:toolsCall MCP tools exposed by any enabled plugin.
mcp:tools:<pluginId>Call MCP tools exposed by one enabled plugin.
adminCall every core tool. Plugin tools still require mcp:tools or the plugin-specific scope.

The authorization-code consent page lets the user remove requested scopes. EmDash also intersects the request with the client’s registered scopes and the user’s role, and refuses an empty grant.

The following table shows the minimum role for the broad capability. Ownership checks can require a higher role when a user acts on another user’s content.

CapabilityMinimum role
Read published content, media, taxonomies, terms, and menusSubscriber
Read drafts, scheduled content, trash, comparisons, and revisionsContributor
Create content or upload mediaContributor
Edit or publish owned content and register mediaAuthor
Manage bylines, taxonomies, menus, or all users’ contentEditor
Read schemas or settingsEditor
Change schemas or settings, permanently delete content, or repair media usageAdmin

See user roles for the complete role definitions.

The server uses stateless Streamable HTTP. Each request is independent; the server does not keep an MCP session or a Server-Sent Events connection.

MethodEndpointBehavior
POST/_emdash/api/mcpAccepts JSON-RPC initialization, tool listing, and tool calls.
GET/_emdash/api/mcpReturns 405 Method Not Allowed.
DELETE/_emdash/api/mcpReturns 405 Method Not Allowed.

Responses use JSON-RPC 2.0. Call tools/list to obtain the current input schemas and MCP annotations before constructing a tool request.

The following inventory matches the static tools returned by tools/list. The registered title is included because clients may display it instead of the tool name.

ToolRegistered titleRequired scope
content_listList Contentcontent:read
content_getGet Contentcontent:read
content_createCreate Contentcontent:write
content_updateUpdate Contentcontent:write
content_deleteDelete Content (Trash)content:write
content_restoreRestore Contentcontent:write
content_permanent_deletePermanently Delete Contentcontent:write
content_publishPublish Contentcontent:write
content_unpublishUnpublish Contentcontent:write
content_scheduleSchedule Contentcontent:write
content_unscheduleCancel Scheduled Publicationcontent:write
content_compareCompare Live vs Draftcontent:read
content_discard_draftDiscard Draftcontent:write
content_list_trashedList Trashed Contentcontent:read
content_duplicateDuplicate Contentcontent:write
content_translationsGet Content Translationscontent:read
ToolRegistered titleRequired scope
byline_listList Bylinescontent:read
byline_getGet Bylinecontent:read
byline_createCreate Bylinecontent:write
byline_updateUpdate Bylinecontent:write
byline_deleteDelete Bylinecontent:write
byline_translationsList Byline Translationscontent:read
ToolRegistered titleRequired scope
schema_list_collectionsList Collectionsschema:read
schema_get_collectionGet Collection Schemaschema:read
schema_create_collectionCreate Collectionschema:write
schema_delete_collectionDelete Collectionschema:write
schema_update_collectionUpdate Collectionschema:write
schema_create_fieldAdd Field to Collectionschema:write
schema_delete_fieldRemove Field from Collectionschema:write
schema_update_fieldUpdate Fieldschema:write
ToolRegistered titleRequired scope
media_listList Mediamedia:read
media_createRegister Uploaded Mediamedia:write
media_uploadUpload Mediamedia:write
media_getGet Media Itemmedia:read
media_updateUpdate Media Metadatamedia:write
media_deleteDelete Mediamedia:write
media_usage_repairRepair Media Usage Indexadmin
ToolRegistered titleRequired scope
searchSearch Contentcontent:read
ToolRegistered titleRequired scope
taxonomy_listList Taxonomiescontent:read
taxonomy_getGet Taxonomy Definitioncontent:read
taxonomy_createCreate Taxonomy Definitiontaxonomies:manage
taxonomy_updateUpdate Taxonomy Definitiontaxonomies:manage
taxonomy_deleteDelete Taxonomy Definitiontaxonomies:manage
taxonomy_list_termsList Taxonomy Termscontent:read
taxonomy_create_termCreate Taxonomy Termtaxonomies:manage
taxonomy_update_termUpdate Taxonomy Termtaxonomies:manage
taxonomy_delete_termDelete Taxonomy Termtaxonomies:manage
taxonomy_term_translationsList Term Translationscontent:read
ToolRegistered titleRequired scope
menu_listList Menuscontent:read
menu_getGet Menu with Itemscontent:read
menu_translationsList Menu Translationscontent:read
menu_createCreate Menumenus:manage
menu_updateUpdate Menumenus:manage
menu_deleteDelete Menumenus:manage
menu_set_itemsSet Menu Itemsmenus:manage
ToolRegistered titleRequired scope
revision_listList Revisionscontent:read
revision_restoreRestore Revisioncontent:write
ToolRegistered titleRequired scope
settings_getGet Site Settingssettings:read
settings_updateUpdate Site Settingssettings:manage

tools/list returns each tool’s description, JSON input schema, and annotations. Read that metadata before constructing a call so your client uses the fields, allowed values, and limits supported by the installed EmDash version.

For example, a client updating an article first calls content_get and keeps the returned _rev. It can then send this JSON-RPC request:

{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "content_update",
"arguments": {
"collection": "articles",
"id": "01JARTICLE0000000000000000",
"data": { "title": "Updated title" },
"_rev": "opaque-revision-token"
}
}
}

The result is returned as JSON text in the first content block. A tool with an output schema may also return the same value in structuredContent.

content_get returns an opaque _rev value. Pass it to content_update, content_publish, content_unpublish, or content_discard_draft. A stale value returns a conflict, so read the item again before retrying.

content_update is a partial update: omitted fields keep their current values. Updating a published item stages a draft while the live version remains unchanged. Use content_compare to review the live and draft values, then call content_publish to make the draft live or content_discard_draft to remove it. content_delete moves an item to trash; only content_permanent_delete removes a trashed item permanently.

Bylines are reusable author or contributor credits. byline_create can create a guest credit or link a byline to a CMS user. Pass the returned byline ID in the bylines input accepted by content_create and content_update. Deleting a byline removes that credit from content and clears it as the primary byline.

MCP writes do not participate in the admin’s entry edit lock. The _rev check protects the operations that accept it, but other write tools can change an entry while an editor has it open.

Content, byline, taxonomy term, and menu translation tools return every locale variant in the relevant translation group. Use the creation tool’s translationOf input when its schema provides one; tools/list is authoritative for the required fields.

content_translations accepts a collection and content ID or slug. The byline, taxonomy-term, and menu translation tools accept either one record’s ID or the shared translation-group ID. A user without draft access sees only published content translations.

Schema tools change the database structure. Use schema_get_collection before creating content or changing fields; it returns the available field names, types, constraints, and validation rules. Collection and field deletion remove stored content or field values and cannot be undone.

Use media_upload to send base64-encoded bytes or fetch a public HTTP or HTTPS URL. URL uploads reject private-network destinations and re-check redirects. Use media_create only when the file already exists at the supplied storage key and you need to register its metadata. Uploads are subject to the configured size and MIME-type limits, and identical bytes may return an existing media item with deduplicated: true.

Taxonomy definitions describe the classification and the collections it applies to; terms are the individual values assigned to content. Hierarchical terms can use parentId, but a parent must belong to the same taxonomy and cannot create a cycle. A term with children must have those children removed or moved before deletion.

menu_set_items replaces a menu’s complete item list in one atomic operation. The array order becomes the menu order. A nested item’s parentIndex points to an earlier item in the same array, so place every parent before its children.

media_usage_repair can process one collection or every collection and may run for a long time on a large site. Its complete, partial, failed, and stale statuses are successful tool responses. Inspect the returned status and counts instead of relying on isError; authentication, validation, and unexpected execution failures set isError: true.

An administrator must enable each plugin’s MCP surface. Enabled tools appear in tools/list as <pluginId>__<localName> and require mcp:tools or mcp:tools:<pluginId> for token-authenticated calls. EmDash also checks the permission declared by the plugin route and records the plugin, tool, route, and actor in the audit log.

Because plugin tools are installation-specific, they are not part of the static inventory above.

MCP clients discover the authorization server from the protected-resource metadata:

GET /.well-known/oauth-protected-resource

The response identifies /_emdash/api/mcp as the protected resource and links to the authorization server. Clients then read its metadata at:

GET /.well-known/oauth-authorization-server/_emdash

That document supplies the current authorization, token, registration, and device-authorization endpoints, supported scopes, grant types, and the S256 PKCE method. Use the discovered values instead of hard-coding the OAuth protocol routes.

An unauthenticated MCP request returns a 401 response with the discovery URL:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://example.com/.well-known/oauth-protected-resource"

A tool failure has isError: true. The first text block starts with a stable code, and _meta.code repeats it for clients that read structured metadata:

{
"content": [{ "type": "text", "text": "[NOT_FOUND] Collection 'articles' not found" }],
"isError": true,
"_meta": { "code": "NOT_FOUND" }
}

Authentication failures use codes such as INSUFFICIENT_SCOPE and INSUFFICIENT_PERMISSIONS. Transport failures use the JSON-RPC internal-error code -32603 and do not expose the underlying exception.