Now with AI-powered page building via MCP Server
Documentation

MCP Server

Manage Cmssy workspace content from AI agents with `@cmssy/mcp-server` — an MCP bridge exposing pages, blocks, forms and media tools over stdio.

Last updated: April 24, 2026

Overview

@cmssy/mcp-server is a Model Context Protocol server that bridges AI agents (Claude Code, Claude Desktop, any MCP-aware tool) to a Cmssy workspace. Once configured, the agent can list and edit pages, add or remove blocks, publish drafts, manage forms, and more — without ever leaving its editor.

It ships on npm and talks stdio, so you don't run a long-lived server: the agent launches it on demand via npx.

What makes it different from the HTTP API

  • Workspace-scoped — a single token + workspace ID pair, everything enforced server-side
  • High-level toolsadd_block_to_page, publish_page, patch_block_content, not raw GraphQL
  • Tenant isolation built in — every query is filtered by your workspace; you can't accidentally touch another tenant

Setup

1. Create an API token

Workspace Settings → API Tokens → Create token. Copy the cs_… value immediately — tokens are shown once. Scope is authentication only; what the token can do is determined by your role and isSuperAdmin flag.

2. Find your workspace ID

Workspace Settings → General has a copy button next to the ID. It's the same workspace tied to your API token.

3. Add it to your editor's MCP config

For Claude Code, edit .mcp.json in your project root (or ~/.claude/mcp.json for a global install):

{
  "mcpServers": {
    "cmssy": {
      "command": "npx",
      "args": [
        "-y",
        "@cmssy/mcp-server@latest",
        "--token", "cs_your_token_here",
        "--workspace-id", "507f1f77bcf86cd799439011",
        "--api-url", "https://api.cmssy.io/graphql"
      ]
    }
  }
}

Environment-variable equivalents are also supported: CMSSY_API_TOKEN, CMSSY_WORKSPACE_ID, CMSSY_API_URL. Useful if you don't want the token in a committed config file.

4. Restart the editor

Claude Code picks up the server on the next session start. You should see a cmssy MCP entry with the available tools listed.

Available tools

Pages

  • list_pages — list all pages (optional search filter on name/slug/displayName)
  • get_page — full page with all blocks and i18n content (by slug or id)
  • create_page / update_page_settings / delete_page
  • publish_page / unpublish_page / revert_to_published
  • update_page_blocks / update_page_layout

Blocks

  • list_block_types — enumerate available block types with schemas and defaults
  • get_block_schema — inspect a single type
  • add_block_to_page / remove_block_from_page
  • update_block_content — full content rewrite for any block shape (strings, arrays, nested objects)
  • patch_block_content — surgical HTML patches on string fields (deep dive below)

Forms

  • list_forms / get_form
  • create_form / update_form / delete_form
  • list_form_submissions / get_form_submission
  • update_form_submission_status / delete_form_submission

Custom Data Models

Define schemas (ModelDefinitions) and CRUD their records — AI agents can provision blog post types, product catalogs, directory entries, and more. Schema/fields follow PropertyField from @cmssy/types; records are validated against the model on every write. Requires workspace permissions MODELS_VIEW / MODELS_CREATE / MODELS_EDIT / MODELS_DELETE.

Models

  • list_models — list all ModelDefinitions in the workspace
  • get_model — get a model by id (ObjectId) or slug
  • create_model — create a model (name, slug, fields, optional statusField for record lifecycle)
  • update_model — update any field of a model (changing fields triggers schema migration on the next record write)
  • delete_model — delete a model — cascades to all its records

Records

  • list_records — list records with MongoDB-style filter (JSON), sort, pagination, optional populate for relations
  • get_record — get a single record by id
  • create_record — create a record; data keyed by model field keys
  • update_record — update a record's data and/or transition its status (validated against statusField.transitions)
  • delete_record — delete a record
  • import_records — bulk import up to 1000 records; returns { importedCount, errors }

Templates

  • list_model_templates — list available templates (E-commerce, Blog, etc.)
  • create_model_from_template — install a template; creates all models defined by it and skips existing slugs. Returns { templateId, installedCount, skippedSlugs }

Workspace & media

  • get_workspace_info — name, plan, limits, usage
  • get_site_config — languages, navigation, enabled features
  • list_media — uploaded assets

patch_block_content — surgical edits

For targeted edits on multi-KB content (docs articles, long blog posts), patch_block_content sends only the diff — not the full string. Backed by MongoDB findOneAndUpdate with $set + arrayFilters, tenant-scoped, atomic. Typically ~10× cheaper in tokens than re-sending the whole HTML via update_block_content.

Three operation types

insert_before / insert_after

Insert HTML directly before/after a unique marker. The marker MUST match exactly one location — zero or multiple occurrences reject the op with BAD_USER_INPUT.

{
  "op": "insert_after",
  "marker": "<h2>Pricing</h2>",
  "html": "<p>Plans start at $0/month.</p>"
}

replace_section

Replace everything from startMarker (inclusive) to endMarker (exclusive). Both markers must resolve uniquely.

{
  "op": "replace_section",
  "startMarker": "<h2>Pricing</h2>",
  "endMarker": "<h2>FAQ</h2>",
  "html": "<h2>Pricing</h2><p>New plans here.</p>"
}

Multiple ops in one call

Operations apply in order on the running result. Any failure (missing marker, ambiguous, etc.) aborts the whole patch — no half-applied state.

{
  "pageId": "...",
  "blockId": "...",
  "locale": "en",
  "operations": [
    {
      "op": "insert_before",
      "marker": "<h2>Appendix</h2>",
      "html": "<h2>New Section</h2><p>…</p>"
    },
    {
      "op": "replace_section",
      "startMarker": "<h2>Pricing</h2>",
      "endMarker": "<h2>FAQ</h2>",
      "html": "<h2>Pricing</h2><p>Updated.</p>"
    }
  ]
}

When an operation fails

  • 0 matches — marker not found. Check for an exact character-level match; whitespace and attribute order matter.
  • 2+ matches — marker isn't unique. Add surrounding HTML to disambiguate. Overlapping matches count ("aa" in "aaa" counts as 2), so avoid ultra-short markers.
  • Field not a string — the targeted fieldPath resolves to an array or object. Use update_block_content for structured fields.
  • Layout blocks not supported — header/footer and other layout blocks go through update_block_content.

patch_block_content vs update_block_content

 patch_block_contentupdate_block_content
When to useTargeted edits on an HTML stringFull content rewrite, any shape
Token costProportional to the diffProportional to the full content
Typical savings~10× on multi-KB content
Field typesString only (via fieldPath)Any (strings, arrays, nested objects)
Partial failureWhole patch aborts — no half-applied stateWhole write applies or fails
Layout blocksNot supportedSupported

Rule of thumb: if you'd previously re-send the entire HTML to change one paragraph, use patch_block_content. Otherwise stick with update_block_content.

Troubleshooting

  • “Workspace not found” — the token doesn't belong to the given --workspace-id. Tokens are scoped to a single workspace; check the pair in Workspace Settings.
  • “Not authenticated” — token expired or revoked. Create a new one from Workspace Settings → API Tokens.
  • MCP server not visible in the editor — restart the editor after config changes. In Claude Code, check Settings → MCP → cmssy for startup logs.
  • Rate limits / plan limits — the MCP server respects the workspace's plan limits (max pages, storage, AI tokens). get_workspace_info shows current usage vs limits.

Response mode (0.6.0)

Since 0.6.0, every write tool accepts an optional response: "minimal" | "full" param (default "minimal"). Minimal returns a small compact-JSON ack (~100-200 bytes) with just the IDs and state you need to chain the next call — not the full mutated resource.

Typical bulk edit sessions (agents touching the same docs page ~6 times) were burning ~170kB of echoed HTML per page pre-0.6. Minimal mode cuts that to ~1kB total — ~95% reduction in response token cost.

Minimal ack shapes

  • Page tools (create_page, update_page_blocks, update_page_settings, publish_page, unpublish_page, revert_to_published, update_page_layout) — {id, slug, hasUnpublishedChanges, updatedAt} (+published on publish/unpublish)
  • Block-on-page tools (add_block_to_page, update_block_content, remove_block_from_page) — {pageId, blockId, hasUnpublishedChanges, updatedAt}
  • Form tools (create_form, update_form) — {id, slug, status, updatedAt}
  • Model tools (create_model, update_model) — {id, slug, updatedAt}
  • Record tools (create_record, update_record) — {id, status, updatedAt}

When to opt into full

Pass response: "full" when you actually need the full mutated resource in the same call — e.g. to verify a complete transformation, read a server-generated field, or debug. Otherwise chain a follow-up read tool (get_page / get_form / get_model / get_record).

Tools that don't take response

Already return a compact ack and are unchanged: patch_block_content, all delete_*, update_form_submission_status, import_records, create_model_from_template.

Version

These docs describe @cmssy/mcp-server 0.6.0. Minimal response mode (see above) shipped in 0.6.0; patch_block_content shipped in 0.5.0; earlier versions only have update_block_content. Pin @cmssy/mcp-server@latest in .mcp.json to always get the newest tools.