Internal HTTP MCP server for MCP Cloud integration with Contentrain Studio. Provides tools for content management, model operations, branching, and media handling within a Nuxt-based content management platform.
Strong tool definitions with comprehensive descriptions and proper schemas. All 5 tools have clear verb-noun naming (list_, get_, save_, replace_, delete_) and detailed descriptions (194 - 1024 chars, well above baseline). Input schemas are present with typed properties. However, output schemas are not documented, and error handling lacks recovery guidance. Tool annotations (destructiveHint, readOnlyHint) are declared but not visible in schema inspection. Parameter descriptions are thorough but some lack explicit format/constraint details (e.g., locale code format, hex id length for collections).
Delete entries from a model. For collections: pass entry IDs. For documents: pass slugs (the whole document is removed, every locale together — locale narrowing is not supported for documents). For dictionaries: pass keys as entryIds. An entry is one translation unit: for an i18n collection or dictionary, deleting it removes EVERY configured locale's copy by default, in one commit — not just the locale you're working in. Pass `locales` (a subset) to delete only those locales and leave the rest; the result's `remainingLocales` lists which locales still hold the entry afterward. Omit `locales` for a non-i18n model. The result always reports `touchedLocales` (what this call actually deleted) and `files` (the exact paths removed) — never guess either from the request.
Read content entries for a model and locale. Returns object-map for collections ({entryId: {fields}}), flat object for singletons ({field: value}), flat key-value for dictionaries ({key: "string value"}). Use this to look up existing entry IDs before updating, or to find valid relation targets. Publish status is NOT a content field — it comes back in the `meta` block: `meta[entryId].status` for collections and documents, `meta.status` for singletons and dictionaries.
List all content models with their full field schemas, kind, domain, and i18n settings.
Fix a word, a typo, a link or a URL INSIDE a text field without re-sending the field. The server finds `find` in the current value and replaces it with `replace`; nothing else in the field can change. Use this — not save_content — whenever the change is smaller than the field: a spelling fix, a changed link target, a renamed product, one sentence in a long markdown body. Re-sending a long field to change a few words corrupts text you did not mean to touch. `find` is an EXACT, case-sensitive match. Copy it verbatim from the current value (brain_query / get_content), with enough context to be unique. Every occurrence is replaced unless `occurrence` (1-based) names one. The result reports `replacements`: how many were replaced per edit. A `find` that does not occur is an ERROR and nothing is written — read the value again, never report it as fixed. All edits (up to 50, across entries of one model and locale) land in ONE commit, all-or-nothing. Documents: `field: "body"` edits the markdown body; any other field is frontmatter. Dictionaries: `field` is the key. Singletons and dictionaries take no `entry`.
Output schemas not documented. LLMs cannot plan downstream calls or extract required fields (e.g., created/updated entry IDs, status values, remaining locales after deletion). Responses are inferred from descriptions only.
Error handling lacks recovery guidance. Descriptions mention 'Refused if...' conditions but do not tell LLMs what to do next (e.g., 'Check the content index first' or 'Try a different locale'). No actionable error messages.
Parameter constraints not fully formalized. 'locale' accepts any string but should document valid locale codes. 'id' for collections must be '12-char lowercase hex', this is stated in description but not in schema pattern/minLength/maxLength. LLMs cannot validate without formal constraints.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | B | 70 | 2026-07-28+ | v2 |
Create or update content. State which with `mode`: "update": change entries that already exist in this locale. Use the EXISTING id (collection) or slug (document) from brain_query / get_content. Refused if the entry does not exist in this locale. "create": add NEW entries. For a collection, pick a fresh 12-char lowercase hex id that no entry uses yet (check the content index / brain_query); for a document, a new slug. Refused if that id or slug already exists in this locale — never reuse an id to "create" over another entry. An i18n entry keeps ONE id across locales: create it in the first locale, then create it in the next locale with that same id. Updates MERGE with existing data — only send changed fields. Inside an object field, only the sub-keys you send change; set a sub-key to null to remove it. Arrays are replaced as sent. FORMAT BY KIND: collection: { "entryId": { field: value, ... } }, document: slug + data (frontmatter) + body — or, to write several documents of one model in ONE commit, `documents: [{ slug, data, body }]` (up to 20; all or nothing: one invalid document writes none). Use it whenever you change more than one section of a guide. singleton: { field: value, ... } — always mode "update", dictionary: { "key": "string value", ... } — ALL values must be strings; mode "update". STATUS: pass `status: "published"` to create-and-publish (or `"draft"` to unpublish) in the same commit — no separate update_status needed. Applies ONLY to the locale this call writes to, not the entry's other locales (use update_status for that). Without it, new entries are saved as draft and existing ones keep their status. The result lists which entries were `created` and which `updated`, and `statuses` — the status each entry has once merged. "merged: true" does NOT mean published: say an entry is live only if its status is "published". RELATION FIELDS: relation (single): set value to target entry ID (collection) or slug (document), relations (array): set value to string[] of IDs/slugs, polymorphic (model is string[]): set value to { "model": "target-model", "ref": "id-or-slug" }. SCHEDULING: publish_at / expire_at go NEXT TO data, never inside it. They live in meta only and are a delivery gate on top of status — a published entry is served once publish_at has passed and until expire_at. They NEVER change status: a past publish_at does not publish a draft; publishing is update_status. null clears a date; omit to leave it unchanged. IMPORTANT: Never include system fields (id, slug, status, source) in data.
save_content combines multiple responsibilities: create, update, status change, and scheduling in one tool. While descriptions are clear, splitting into save_content + update_status + schedule_content would improve composability and reduce parameter complexity.
Pagination not supported. get_content returns all entries for a model/locale. Large content sets will blow context windows. No limit, offset, or cursor parameters documented.