MCP Server

Manage cmssy content from AI agents over MCP - connect Claude to the hosted server by signing in, or run `@cmssy/mcp-server` over stdio with an API token.

October 9, 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 runs two ways: Claude connects to the hosted server at https://api.cmssy.io/mcp by signing in to cmssy, and any MCP client can launch the npm package over stdio with an API token. Neither needs a long-lived server of your own.

What makes it different from the HTTP API

  • Workspace-scoped — a single token + workspace ID pair, everything enforced server-side
  • High-level tools — add_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

Connect Claude (no token)

Claude on the web, Claude Desktop and Claude Code connect to the hosted server at https://api.cmssy.io/mcp. You sign in to cmssy and pick the workspaces Claude may use - there is no token to copy and nothing to install.

Claude on the web or desktop

  1. Open Settings → Connectors and choose Add custom connector.
  2. Paste https://api.cmssy.io/mcp and add it.
  3. Click Connect, sign in to cmssy, tick the workspaces Claude may use and choose Read only or Read and write.

A connector added on claude.ai is available in Claude Desktop too.

Claude Code

claude mcp add --transport http cmssy https://api.cmssy.io/mcp

Then run /mcp in Claude Code and authenticate: the same sign-in and workspace choice open in your browser.

In the admin, Account → Connected apps → Connect to Claude shows the same URL and command with copy buttons.

What Claude can do

  • Your role, per workspace - Claude acts as you, with the role you hold in each workspace you ticked. Read only keeps it to reading even where your role could write.
  • Several workspaces at once - every tool takes a workspace argument (org-slug/workspace-slug), and list_workspaces lists the ones you granted. A workspace you join later is not added - connect again to include it.
  • Destructive tools ask first - Claude asks you before a tool that deletes or publishes, and the server's own confirmation (below) still applies.
  • Audited as you - a change Claude makes is recorded in the workspace's audit log under your name, like a change you make in the admin.

Disconnecting

Account → Connected apps → Disconnect cuts Claude off from every workspace at once. A workspace admin can remove one workspace from a member's app in Settings → Connected apps; the member's other workspaces keep working. Losing access to a workspace takes it out of your connections.

What reaches Claude

Only the content of the workspaces you ticked, and only what a tool call returns. cmssy stores the connection itself (which app, which workspaces, read or write) and hashed tokens - never a copy of the conversation.

Set up with an API token (stdio)

For CI, scripts or an agent without a browser, run the npm package with an API token instead.

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.

No browser? An agent can create the account, organization, workspace and token through the API - see Creating a token without a browser.

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 register it for every project with claude mcp add --scope user):

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

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.

--api-url (or CMSSY_API_URL) is the API origin and defaults to https://api.cmssy.io; the server appends /graphql itself, so leave it out.

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

The complete list, with the permission each tool needs, is MCP tools - the server generates it on every release, so it always matches what your version exposes. The tools share one naming shape - list_* and get_* to read, create_* / update_* / delete_* to write, plus verbs for state transitions - and fall into these groups:

  • Pages and blocks - the page tree, blocks, layout regions, page types and publishing: get_page, add_block_to_page, patch_block_content, publish_page. Call list_block_types before adding a block: it returns the block types your site actually registers.
  • Models and records - schemas and their entries, including bulk import_records. delete_record refuses while a block still uses the record and names the pages that do; pass force: true to delete anyway.
  • Media - uploads and folders.
  • Forms - forms and their submissions.
  • Commerce - products, orders (manual orders, payments, invoices, refunds, fulfillment, the order pipeline), carts, discounts and customers.
  • Webhooks - subscriptions, deliveries and signing secrets. list_webhook_event_types is the allowlist of subscribable events; a signing secret is returned once, on create and on rotate.
  • Workspace - get_workspace_info (plan, limits, usage), get_site_config, the team and roles.

Full-text search_content is not bound by the MCP server; it is reachable from the in-admin assistant. Nothing in the set touches your code: no tool writes a file, edits a component or opens a pull request - AI edits content, and block schemas stay in your repository under review.

Confirmation before destructive actions

The tools whose effect is hardest to undo - for example delete_page, publish_page, refund_order, bulk_delete_products - do not act on the first call. They answer with needsConfirmation: true, a one-line summary of what would happen and a ref, and nothing changes. The agent shows you the summary; once you approve, it calls the same tool again with the same arguments plus _confirm: "<ref>".

  • A ref works once, for the time given in expiresInSeconds, and only with the arguments it was issued for - change any argument and the server asks again.
  • A tool that needs confirmation has a _confirm parameter in its input schema, so a client can tell from tools/list which ones they are.
  • The same rule applies in the admin's AI assistant, where you approve with a button in the chat.

Dev drafts: a block that is not deployed yet

Every write tool takes an optional target. "draft" (the default) edits the shared page draft; "devDraft" edits your own per-user overlay, which starts from the current page and changes nobody else's preview.

That is what makes it safe to compose a page around a block type that so far exists only on your machine. When the block ships, promote_dev_draft moves your overlay onto the shared draft. get_page with target: "devDraft" returns the overlay next to the shared draft, or null when you have none.

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

  • Every call fails with 404 - --api-url ends in /graphql. Pass the origin only (https://api.cmssy.io, the default) - the server appends the path itself.
  • “Workspace not found” — the user behind the token is not a member of the given --workspace-id, or the id is wrong. A token authenticates a person; what it may do comes from that person's role in that 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

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.

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.

Version

Use @cmssy/mcp-server@latest in .mcp.json to pick up new tools as they ship; npx @cmssy/mcp-server --version prints the version you run. Every release republishes MCP tools from the server itself, so that page always matches the newest version.