Skip to main content
Glama
metehankasapp

safe-strapi-mcp

Safe Strapi MCP

CI npm license

Run content operations against your own Strapi 5 installation from a local MCP client. By default the service creates draft copies, applies ordered component changes, and verifies the result and source hash. An explicit project opt-in also permits previewed patch/insert operations on an existing page draft. It does not publish pages.

Install

Requires Node.js 22.5 or newer. SQLite may need native build tools on platforms without prebuilt binaries.

npm install --global safe-strapi-mcp
safe-strapi-mcp --help

You can also run it without a global install using npx safe-strapi-mcp.

Related MCP server: MCP Strapi Server

Configure your project

1. Create a Strapi API token

In Strapi Admin, open Settings → Global settings → API Tokens → Create new API Token. Choose Custom and grant only:

  • find and findOne for every collection the page populates, including related content;

  • find and findOne for Upload/media when the page contains assets;

  • create and update for the collection types whose drafts the MCP may create.

delete and publish permissions are not required. Save the token when Strapi displays it; Strapi does not show the full value again. A token that can list pages but cannot read populated relations or media may fail during inspect_page with HTTP 403.

2. Store it locally

Create a private env file outside version control:

STRAPI_URL=http://localhost:1337
STRAPI_API_TOKEN=

Paste the token after STRAPI_API_TOKEN=. Use the Strapi origin for STRAPI_URL, without /admin or /api. Never commit this file or paste the token into chat. No hosted account or MCP access key is needed for stdio.

Add the local state and env file to the Strapi project's .gitignore:

.env.safe-strapi
.safe-strapi/

3. Connect your MCP client

Configure your MCP client to run:

safe-strapi-mcp --env-file /absolute/path/strapi/.env --project-root /absolute/path/strapi

Codex CLI can register the published package directly:

codex mcp add safe-strapi -- \
  npm exec --yes --package=safe-strapi-mcp -- safe-strapi-mcp \
  --env-file /absolute/path/strapi/.env.safe-strapi \
  --project-root /absolute/path/strapi

Restart Codex, then begin with a read-only request: Use safe-strapi. List projects, find the homepage, and inspect it. Do not create or update anything. Only continue to preview after this succeeds.

Use the absolute executable path if your desktop client does not inherit your shell PATH. The tool loads the env file directly; do not paste secrets into chat. Environment variables already present in the process take precedence over the file.

The source directory must contain src/api and src/components. Collection types with Draft & Publish, a UID field, and dynamic zones are discovered automatically. Each zone is named content-type:zone in list_projects. Single types are not supported by automatic setup.

Optional env variables: STRAPI_CONTENT_TYPE, STRAPI_BLOCKS_FIELD, STRAPI_SLUG_FIELD, STRAPI_ROUTE_FIELD, STRAPI_TITLE_FIELD, STRAPI_LOCALE (defaults to en). Set STRAPI_ROUTE_FIELD=url only when the frontend routes by a separate top-level field that must track cloned slugs. The field must exist in the content-type schema and be string-like; preview reports the change explicitly. Set STRAPI_TOKEN_ENV=MY_EXISTING_TOKEN to read a differently named token variable. Use HTTPS for remote CMS connections.

Only a remote URL and token? Obtain the schema source from the project owner, or pass --config /absolute/path/projects.json with this structure:

{
  "projects": {
    "my-site": {
      "baseUrl": "https://cms.example.com",
      "tokenEnv": "STRAPI_API_TOKEN",
      "collection": "pages",
      "blocksField": "blocks",
      "slugField": "slug",
      "routeField": "url",
      "titleField": "title",
      "schemaRoot": "/absolute/path/strapi",
      "contentType": "page",
      "populate": "*"
    }
  }
}

For custom config, populate must include every nested component, media and relation required for the full document. Embedded componentSchemas (UID to schema object) and contentTypeSchema can replace source files. Automatic local setup generates nested populate queries from discovered schemas. Custom Strapi routes, permissions and plugins still require integration validation.

Use

For the default clone workflow, ask your client to list projects, inspect the page, preview operations, and create the reviewed draft with its source hash and a stable idempotency key. Then validate and compare it. Source pages remain read-only in this workflow. Owned-draft tools require registration in this installation's ownership database. modify_owned_draft uses create normalization and may regenerate retained component IDs; its content-preservation behavior differs from direct editing. It has no dedicated persisted preview tool: inspect, review an AI-side plan, then pass the matching draft hash.

Tools: list_projects, find_pages, inspect_page, list_components, get_component_schema, preview_clone_and_modify, clone_page_and_modify, inspect_owned_draft, modify_owned_draft, duplicate_component, replace_component, validate_draft, compare_pages.

Edit an existing page draft without cloning (opt-in)

This feature is available in the current source branch; it is not in the published 0.2.0 package. Build the source to use it before the next release.

For auto-discovered projects, set STRAPI_ALLOW_IN_PLACE_EDITING=true in your private env file or pass --allow-in-place-editing. For a custom projects config, set allowInPlaceEditing: true on each selected project. The CLI flag does not override custom config. list_projects reports whether this is enabled.

Use preview_modify_page with a project, documentId, locale and ordered operations. It records the plan locally and returns pageHash, operationHash and differences. Call modify_page with the same inputs plus expectedPageHash: pageHash, expectedOperationHash: operationHash and a stable idempotencyKey. A matching preview must exist in this installation's audit DB.

Example operations to edit the second block and its nested single component:

[
  {
    "type": "patch",
    "selector": { "index": 1 },
    "changes": { "title": "Updated features", "content": { "description": "Updated description" } }
  }
]

Use field names from get_component_schema; the example's fields must exist in your schema. Natural-language request:

Edit only the features component on the existing model page using preview_modify_page, then apply the reviewed preview with modify_page. Preserve other blocks and their order. Do not clone or publish.

Only patch and insert are accepted. Insertion supports start/end/before/after positions and preserves existing blocks' relative order. Supply every schema attribute of inserted components, including nested components; explicitly use null or empty arrays for unused optional fields. Do not supply component IDs. Existing media/relation references can be supplied as scalar IDs/document IDs.

The first version rejects remove/replace/move/duplicate operations, changes to component identity/type, clearing nonempty fields, replacing arrays (including repeatable components), replacing existing objects and changes to existing media/relation references. Nested single component and JSON object fields can be patched. Existing component IDs are retained and checked after writing.

An existing draft and full collection/component schemas are required. Missing populated fields, private fields, unsupported field types, single types and content types without Draft & Publish are rejected. The write changes only the configured dynamic zone; it does not change slug, route, title or other zones. No ownership record is needed or created for the existing page. A failed or lost write response is reconciled by re-reading; if the result cannot be verified, WRITE_OUTCOME_UNKNOWN blocks another write with the same key. There is no automatic rollback.

REST revision checks cannot prevent an external editor from racing the final GET/PUT. The result includes this limitation in concurrency. A fully atomic guarantee requires a separate Strapi-side conditional-update endpoint, which this version does not implement.

Consistent previews and explicit rollback (current source)

All preview tools return contractVersion: "1" and the same machine-readable fields: target, summary, changes, fieldChanges, safety, revision and nextAction. fieldChanges includes path, before, after, beforeExists and afterExists; missing values are represented as null with the existence flag set to false. Results retain the previous top-level hashes for compatibility. The diff is limited to 500 entries; fieldChangesTruncated signals that the report may be incomplete. AI clients should consistently present the target, old/new values, preservation rules, publication state and rollback eligibility, then request approval. Approval is a client workflow rule, not a server-side human authorization mechanism.

Direct-edit results include an operationId (the caller's idempotency key). list_page_operations lists local operations for a project, document and locale without exposing snapshots. For an approved rollback:

  1. Call preview_rollback_page with that target and operationId.

  2. Review the old/new field values and obtain explicit approval.

  3. Call rollback_page with the same target and operation ID, both returned hashes as expectedPageHash / expectedOperationHash, and a new stable idempotency key.

  4. Re-inspect the draft and report the verified result; never publish.

Rollback only restores a completed, verified patch-only operation while the page still has its exact post-operation revision. Later changes, uncertain operations, missing snapshots, changed target configuration and insert operations are rejected. It preserves component count, order, IDs and media/relation references. Recorded scalar values may return to their previous empty/null value; ordinary patches still forbid clearing content. rollbackSupported is eligibility, not a guarantee that the current page revision permits rollback. There is no automatic rollback and no component deletion. The REST concurrency limitation also applies to rollback. These tools require the current source build.

Use alongside the official Strapi MCP

Safe Strapi MCP uses a Content API Token and REST. The official MCP uses an Admin Token and /mcp; keep their credentials separate. Use this service's opt-in tools for protected dynamic-zone edits and the official MCP for general content operations. Where supported, restrict the official token's update permissions on protected fields: writing through its tools bypasses these preservation checks.

Copy-ready workflows for discovery, JSON insertion, component reordering, long pages, team review drafts, and conflict recovery are available in the prompt cookbook. AI clients can read the same guide directly as Markdown or discover key resources through llms.txt.

Indexes are zero-based. Operations run sequentially, so later indexes refer to the current order. Ambiguous selectors must include an occurrence or index. Writes require revision hashes and idempotency keys; retries with a different operation hash are rejected.

If a create response is lost before its document ID can be recorded, the retry fails closed with WRITE_OUTCOME_UNKNOWN; it never submits another create or adopts a page by slug. Inspect the CMS through its API and use a new operation only after resolving the uncertain result. A lost update response is safely reconciled from the recorded intended content without a second PUT.

Concurrent MCP writes sharing the same audit database are serialized. Draft and source hashes are checked immediately before writes and results are fetched and verified afterward. Owned-draft updates send only the configured dynamic-zone field. Strapi REST does not expose an atomic revision precondition, so a writer outside this MCP can still race inside the final GET/PUT interval; preventing that last race requires a Strapi-side conditional-update endpoint.

Local state

The audit database defaults to .safe-strapi/audit.sqlite inside the selected project; set AUDIT_DB to override it. Preserve this database between sessions. Add .safe-strapi/ and env files to your project's gitignore. Protect them with local filesystem permissions.

Preview and write records contain content snapshots for verification and rollback. Treat the database as private content; losing it also loses recorded rollback history.

Each installation uses its own local credentials and state. The package supports stdio only and does not require or accept an MCP access key.

Development checks

npm install
npm run typecheck
npm run build
npm test
npm pack --dry-run

Tests include a 100-component preservation scenario, owned-draft checks, idempotency conflicts, lost-response recovery, cross-instance serialization, and env-file onboarding through the real stdio protocol against mocked Strapi REST. Local acceptance additionally exercises a real Strapi 5 API without using the admin UI. These tests do not certify every Strapi plugin or version.

To verify in-place editing with an isolated real Strapi instance, run after build:

node scripts/in-place-acceptance.mjs /absolute/path/to/strapi-project-with-node_modules

The script reuses installed dependencies but creates a temporary app, SQLite DB and test token; it never loads that project's config, env or customer data. It checks middle/nested patches, component IDs, media/relations, insertion over MCP stdio and preservation of the published version, then removes the temporary app.

Independent tooling; not affiliated with or endorsed by Strapi.

Community and security

Bug reports and contributions are welcome on GitHub. Read CONTRIBUTING.md before opening a pull request. Report vulnerabilities privately as described in SECURITY.md.

Released under the MIT License.

Related MCP Connectors

Related MCP Servers