Strapi Suite
БесплатноНе проверенMCP server that exposes Strapi v5 content management, visual layout configuration, schema authoring, media uploads, and GraphQL testing to LLM clients via nativ
Описание
MCP server that exposes Strapi v5 content management, visual layout configuration, schema authoring, media uploads, and GraphQL testing to LLM clients via native Strapi API tokens with security hardening.
README
Model Context Protocol server for Strapi v5 Expose your Strapi instance to LLM clients (Claude, Cursor, any MCP-compatible) for generic content management, visual layout configuration, schema authoring, media uploads and GraphQL testing — with native Strapi API token auth, multi-layer rate limiting and a forensic audit trail for every operation.
TL;DR
Drop this plugin into any Strapi v5 project, create an API token, point your MCP client at
/api/strapi-mcp-suite/stream. Your LLM can now read/write entries, reorganize admin UI layouts, generate
components and content-types (opt-in), upload media (opt-in) and execute GraphQL queries (opt-in) —
all through native Strapi APIs (strapi.documents(), lifecycle hooks, validation, draft & publish).
The plugin ships with hardened defaults: path traversal blocking, SSRF protection (AWS IMDS / RFC1918 / DNS rebinding), rate limiting in 3 layers (per-token / per-user / per-IP), a fail-closed production mode for schema authoring, and a forensic audit trail (token lifecycle + every operation) with delete-permission enforcement on tokens.
Coexistence with Strapi's native MCP server (5.47+)
Strapi 5.47 introduced a native MCP server (/mcp) that generates content CRUD tools from your
schema. This plugin is designed to complement, not compete with it: the native server owns the
commodity CRUD, while this plugin focuses on what the native server does not do — schema
authoring, visual layout configuration, media uploads, GraphQL, and a forensic audit trail.
Control how they coexist with the coexistence setting (or the MCP_COEXISTENCE env override):
| Mode | Behavior |
|---|---|
auto (default) |
If the native MCP is detected serving (Strapi ≥ 5.47 and server.mcp.enabled === true), the plugin auto-suppresses its CRUD tools (find_entries, create_entry, …) so the LLM never sees duplicates. If there's no native MCP, the plugin serves everything. |
standalone |
Ignores the native server; the plugin serves all enabled tools on its own endpoint (use this if you want the plugin's CRUD even alongside the native one). |
extend-native |
Registers the plugin's differentiators into the native server (strapi.ai.mcp), so a single /mcp endpoint serves native CRUD + schema authoring + layout + media/graphql. One endpoint, one auth (the native admin tokens). |
Note on auth: the native MCP authenticates with admin tokens (created in Settings → Admin Tokens), while this plugin's standalone endpoint uses content API tokens. They are not interchangeable. In
extend-nativemode the bridged tools run under the native admin-token auth.
// config/plugins.ts
export default {
'strapi-mcp-suite': {
enabled: true,
config: {
coexistence: 'auto', // 'auto' | 'standalone' | 'extend-native'
contentOps: true, // CRUD tools (auto-suppressed when coexisting)
schemaAuthoring: false,
upload: false,
graphql: false,
customTokenWrite: 'strict', // 'strict' | 'endpoint' — see below
},
},
};
customTokenWrite controls how a custom API token is authorized to write via
content-ops:
strict(default): the token must hold the matching${uid}.${action}Strapi permission. Strapi freezes a token's permissions at creation time, so content-types added later aren't covered — and if a content-type doesn't expose content-api routes, the permission isn't even grantable.endpoint: any custom token that reached the endpoint (it already hasstream.handle) may write to any content-type — scalable, no per-type re-granting. The plugin writes throughstrapi.documents(), below the route layer, so it works even for content-types without content-api routes.read-onlytokens stay blocked in both modes;full-accessis unchanged.
Gating is config-driven (precedence: built-in defaults → config/plugins.ts → env override).
The env vars (CONTENT_OPS_ENABLED, SCHEMA_AUTHORING_ENABLED, UPLOAD_ENABLED, GRAPHQL_ENABLED,
MCP_COEXISTENCE, MCP_CUSTOM_TOKEN_WRITE) still work and win over config.
Features
Built-in tools (33 total, organized by capability)
| Category | Tools | Notes |
|---|---|---|
| Content ops | list_content_types, get_content_type_schema, find_entries, get_entry, create_entry, update_entry, delete_entry, publish_entry, unpublish_entry |
Generic CRUD on any content-type. Delegates to strapi.documents(). Always available. |
| Visual layout | get_visual_layout, set_field_layout, set_field_metadata, set_view_settings |
Modifies the Content Manager UI config (widths, labels, ordering). Stored in strapi_core_store_settings. No restart required. |
| Schema authoring | list_existing_schemas, read_schema, validate_schema_proposal, create_component, create_content_type, add_field_to_schema, delete_field_from_schema |
Writes .json schemas and .ts stubs to filesystem. Requires Strapi restart for new schemas to load. Gated by SCHEMA_AUTHORING_ENABLED=true. Refused in production. |
| Media / upload | list_media, get_media, upload_media_from_url, update_media_metadata, delete_media, link_media_to_entry |
URL-based uploads. Works with any Strapi provider (local, S3, Cloudinary, R2, etc.). Gated by UPLOAD_ENABLED=true. SSRF-protected. |
| GraphQL | graphql_introspect, graphql_query, graphql_generate_query |
Test GraphQL queries, introspect the schema, generate queries from a content-type UID. Mutations require explicit allow_mutations:true. Gated by GRAPHQL_ENABLED=true and requires @strapi/plugin-graphql installed. |
| Diagnostics | __health, __list_registered_tools |
Health ping (use after schema-authoring to confirm Strapi restarted) + registry inventory. Always available. |
| Audit | __audit_token_creators, __audit_log_query |
Read-only views over the forensic audit tables (who created each token, every tool invocation with redacted args). Requires super-admin. Always exposed but always denies non-super-admin callers. |
Extensibility
Custom tools registered from your project bootstrap appear alongside the built-ins:
strapi.plugin('strapi-mcp-suite').service('registry').registerTool({
name: 'my_custom_tool',
description: '...',
inputSchema: { ... },
handler: async (ctx, args) => { ... },
testCases: [ ... ], // optional, run automatically in dev bootstrap
tags: ['read'],
});
The registry validates structure (snake_case naming, JSON Schema validity, no built-in name
collision) and optionally runs testCases to give you confidence before exposing the tool to the
LLM.
Requirements
- Strapi: 5.0.0+ (5.45+ recommended for full anti-impersonation)
- Node.js: 20+ (uses built-in
fetch,crypto.subtle, etc.) - MCP client: Claude Desktop, Claude Code CLI, or any MCP-compatible client supporting streamable HTTP transport
Installation
pnpm add strapi-plugin-mcp-suite
Strapi v5 auto-discovers the plugin — no config/plugins entry is required.
Restart Strapi. You should see:
[strapi-mcp] plugin loaded — endpoint /api/strapi-mcp-suite/stream | strapi=5.46.0 | env=development | schema_authoring=disabled | upload=disabled | graphql=disabled
Developing the plugin from source instead? See CONTRIBUTING.md for the local-clone setup.
Quick start
1. Create an API token
In the Strapi admin: Settings → API Tokens → Create new API Token.
- Name: include your email, e.g.
[email protected] - mcp client. The plugin uses the email in the name (combined withadminUserOwner) for anti-impersonation and to attributecreatedBy/updatedByin entries created via MCP. - Token type:
Full access(recommended) orCustomwith the content-types you want exposed. - Lifespan: as your team requires.
Copy the token — it's shown only once.
2. Configure your MCP client
Claude Code (CLI)
Edit ~/.claude.json or your client's config:
{
"mcpServers": {
"strapi-local": {
"url": "http://localhost:1337/api/strapi-mcp-suite/stream",
"headers": {
"Authorization": "Bearer YOUR_TOKEN_HERE"
}
}
}
}
Claude Desktop (Windows / macOS)
Claude Desktop only supports stdio transport, so you need
mcp-remote as a bridge. In
%APPDATA%\Claude\claude_desktop_config.json (Windows) or ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"strapi-local": {
"command": "npx.cmd",
"args": [
"-y",
"mcp-remote",
"http://localhost:1337/api/strapi-mcp-suite/stream",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer YOUR_TOKEN_HERE"
}
}
}
}
On Windows, use npx.cmd (not npx). After editing, fully quit Claude Desktop (system tray → Quit)
and reopen.
3. Try a first tool call
In Claude:
"List the content types in my Strapi instance and show me the fields of each."
This invokes list_content_types and you should see your CTs (article, author, etc.) with their
attributes.
Configuration
All configuration is via environment variables. See .env.example in the repo root for the complete
annotated list. Quick reference:
| Variable | Default | Purpose |
|---|---|---|
SCHEMA_AUTHORING_ENABLED |
false |
Exposes the 7 schema-authoring tools (writes .json schemas to filesystem). Refused if NODE_ENV is production-ish. |
UPLOAD_ENABLED |
false |
Exposes the 6 media library tools. Requires an upload provider configured. |
GRAPHQL_ENABLED |
false |
Exposes the 3 GraphQL tools. Requires @strapi/plugin-graphql installed. |
MCP_RATE_LIMIT_PER_MIN |
60 |
Per-token rate limit (sliding window). |
MCP_RATE_LIMIT_PER_USER_PER_MIN |
120 |
Per-admin-user rate limit (sums all tokens of the same owner). Requires Strapi 5.45+. |
MCP_RATE_LIMIT_PER_IP_PER_MIN |
300 |
Per-origin-IP rate limit. Requires proxy: true in config/server.ts if behind a reverse proxy. |
MCP_RATE_LIMIT_WINDOW_MS |
60000 |
Sliding window size (milliseconds). |
UPLOAD_URL_ALLOWED_HOSTS |
(empty) | Strict allowlist for upload_media_from_url. If set, only these hosts can be downloaded. |
UPLOAD_URL_ALLOWED_DOMAIN_SUFFIXES |
(empty) | Same as above but matches domain suffixes (e.g. .amazonaws.com). |
UPLOAD_URL_EXTRA_BLOCKED_HOSTS |
(empty) | Additional hosts to block (extends the hardcoded blocklist). |
UPLOAD_URL_EXTRA_BLOCKED_CIDRS |
(empty) | Additional IPv4 CIDR ranges to block. |
MCP_AUDIT_RETENTION_DAYS |
90 |
op-log rows older than this are deleted. 0 disables the age pass. |
MCP_AUDIT_MAX_ROWS |
100000 |
Cap on op-log rows. Oldest are trimmed first. 0 disables the cap. |
MCP_AUDIT_CLEANUP_INTERVAL_HOURS |
24 |
How often the cleanup job runs. Minimum 1. |
Security model
The plugin is designed assuming the LLM is untrusted input — prompt injection, poisoning, or jailbreak could turn it into an adversary. Defenses:
Authentication & granular permissions
- Native Strapi API tokens — no custom auth scheme to break. Reuses Strapi's hashing and storage.
- Granular permission enforcement — Custom tokens must have
plugin::strapi-mcp-suite.stream.handleexplicitly marked. Tokens of typeCustomwithout the MCP permission marked are rejected with401 Custom token missing MCP permission.Full AccessandRead Onlytokens pass by design (broader scope). - Best-effort attribution — if the token has
adminUserOwnerpopulated (only happens forkind='admin'tokens with the experimentalfeatures.future.adminTokensflag), the plugin attributescreatedBy/updatedByon entries. For standardcontent-apitokens, attribution is null (see Known limitations).
Path traversal (schema authoring)
- All UID segments validated against
^[a-z][a-z0-9-]*$before being used inpath.join. - Defense in depth:
assertWithinAllowedRoot()ensures the resolved absolute path is undersrc/api/orsrc/components/. writeFilesperforms a final containment check before any disk write.- Backups go to
.strapi-mcp-backups/(gitignored by default), preserving relative paths.
SSRF (upload_media_from_url)
- Protocol allowlist: only
http://andhttps://. Blocked:file://,gopher://,javascript:,data:, etc. - IPv4 blocklist: loopback, RFC1918, CGNAT, link-local (including AWS IMDS
169.254.169.254), Alibaba metadata (100.100.100.0/24), reserved ranges. - IPv6 blocklist:
::1,fc00::/7(ULA),fe80::/10(link-local), multicast, IPv4-mapped variants. - DNS rebinding defense: hostnames are resolved and all returned IPs validated.
- Redirect chasing:
fetchusesredirect: 'manual'and re-validates each hop (max 3 redirects). - Per-environment override via
UPLOAD_URL_ALLOWED_HOSTS(strict mode) orUPLOAD_URL_EXTRA_BLOCKED_HOSTS/_CIDRS.
Rate limiting (3 layers)
| Layer | Default | Defends against |
|---|---|---|
| Per-token (SHA-256 of bearer) | 60 req/min | Leaked token abuse |
| Per-admin-user | 120 req/min | A user creating N tokens to bypass per-token |
| Per-IP | 300 req/min | Independent secondary layer; handles NAT'd teams |
Each layer is a sliding window. Any layer hitting its limit returns 429 with Retry-After and
details.layer identifying which limit fired.
Production guardrails
isProduction()is fail-closed: ifNODE_ENVis not explicitlydevelopment,testordev, schema authoring is refused. Docker containers withoutNODE_ENVget safe defaults.- Schema authoring tools are hidden from
tools/listunlessSCHEMA_AUTHORING_ENABLED=true. Even if enabled, writers refuse in production. - GraphQL mutations require explicit
allow_mutations: trueper call. - Destructive operations (
delete_*) requireconfirm: true.
Audit trail (v0.4.0)
The plugin maintains two internal tables (hidden from Content Manager and Content-Type Builder, not exposed via REST/GraphQL):
mcp_token_audits— one row per API token. Capturescreator_id,creator_email,created_at_real, and on deletiondeleter_id,deleter_email,deleted_at. Tokens that existed before the plugin was installed are backfilled withcreator_email='unknown'andis_legacy=true.mcp_op_logs— one row pertools/callover the MCP endpoint. Captures:tool_name,status(ok/error),duration_ms,token_id,admin_user_id,admin_email,ip,user_agent,args_redacted(args with secret-shaped keys replaced by[REDACTED]),result_summary(small extraction —documentId,count,uid— never the full payload), anderror_messagefor failures.
Delete-permission enforcement on admin::api-token: a beforeDelete lifecycle hook blocks
deletion unless the caller is the original creator OR a super-admin. Legacy tokens require
super-admin. The deletion itself is recorded by afterDelete, so even authorized deletions leave a
trace.
Retention: op-log is bounded by both an age window (MCP_AUDIT_RETENTION_DAYS, default 90)
and a row cap (MCP_AUDIT_MAX_ROWS, default 100k). A cleanup job runs every
MCP_AUDIT_CLEANUP_INTERVAL_HOURS (default 24) in batches of 1000. Setting either limit to 0
disables that pass — useful for tests, not recommended in production.
Querying the audit:
// Tool: __audit_token_creators
// Args: { include_deleted?: boolean = true, limit?: number (cap 500) }
// Returns: { count, tokens: [{token_id, token_name, token_type, creator_id, creator_email, created_at, deleter_id?, deleter_email?, deleted_at?, is_legacy}] }
// Tool: __audit_log_query
// Args: { token_id?, admin_user_id?, tool_name?, status?, since? (ISO), until? (ISO), limit?, include_payloads?: boolean = false }
// Returns: { count, filters, include_payloads, rows: [...] }
Both tools require a super-admin caller. Since standard content-api tokens have no admin user
resolved (see Known limitations), invoking these tools in practice requires:
- Strapi 5.45+ with
features.future.adminTokens: trueinconfig/admin.ts. - A token created from a super-admin session (so
adminUserOwnerpopulates with that user).
If your setup doesn't meet those conditions, you can still query the tables directly via SQL — the data is captured regardless of whether the introspection tools are usable. Example:
SELECT tool_name, status, duration_ms, admin_email, ip, ts
FROM mcp_op_logs
WHERE ts > datetime('now', '-1 day')
ORDER BY ts DESC
LIMIT 100;
What the audit does NOT do: it does not prevent impersonation — that's structurally impossible in standard Strapi 5.x (see Known limitations). It provides forensic evidence so an incident can be reconstructed after the fact, and it raises the cost of "delete the evidence then deny" since the delete itself is logged.
What this plugin does NOT protect against
- Compromise of the Strapi admin user that creates tokens — out of scope; if the admin is compromised, the attacker can create tokens anyway. The audit will record the creation under that user, which helps post-incident.
- Egress firewall bypass — if your server can reach
169.254.169.254, the plugin blocks but ideally your VPC also blocks. Defense in depth. - Distributed attacks across multiple instances — rate limit is in-memory per instance. Use a CDN/proxy or Redis backend for cluster-wide limits.
Extensibility: registerTool
Custom tools live in your project's src/index.ts bootstrap:
export default {
register() {},
bootstrap({ strapi }) {
strapi.plugin('strapi-mcp-suite').service('registry').registerTool({
name: 'feature_article',
description: 'Marks an article as featured (sets is_featured=true and featured_at=now). Useful when an editor wants to highlight content without opening the admin.',
inputSchema: {
type: 'object',
properties: {
documentId: { type: 'string' },
unfeature: { type: 'boolean', default: false },
},
required: ['documentId'],
additionalProperties: false,
},
handler: async ({ strapi }, args) => {
const uid = 'api::article.article';
const current = await strapi.documents(uid).findOne({ documentId: args.documentId });
if (!current) throw new Error(`Article ${args.documentId} not found`);
return strapi.documents(uid).update({
documentId: args.documentId,
data: args.unfeature
? { is_featured: false, featured_at: null }
: { is_featured: true, featured_at: new Date().toISOString() },
});
},
testCases: [
{ name: 'rejects unknown', args: { documentId: 'does-not-exist' }, expect: { errorMatches: /not found/ } },
],
tags: ['write'],
});
},
};
The registry enforces:
nameis snake_case, 3-64 chars, doesn't collide with a built-indescriptionis ≥30 chars (helps the LLM choose when to invoke)inputSchemais a valid JSON Schema withadditionalProperties: falserequiredreferences only fields present inpropertieshandleris an async functiontestCases(optional) follow the expected shape
If validation fails, registerTool throws on boot with a detailed error message.
Use __list_registered_tools
Call this tool from your MCP client to see what's registered and the results of the last self-test run for each custom tool. Useful for debugging.
Testing
Unit tests (Node built-in test runner)
cd src/plugins/strapi-mcp
npm test
145+ tests covering: URL safety (SSRF), schema validator (9 rules), path-lock (concurrency), writer (path traversal defenses), registry (tool definition validation), rate limiting (sliding window, multi-layer), schema derivation, content-ops handlers.
Security smoke test (script against running Strapi)
export STRAPI_MCP_TOKEN=<your-token>
bash src/plugins/strapi-mcp/scripts/smoke-test.sh
Windows:
$env:STRAPI_MCP_TOKEN = "<your-token>"
pwsh src/plugins/strapi-mcp/scripts/security-test.ps1
The security test exercises 18+ regression cases for C1 (path traversal), C3 (SSRF), H1 (GraphQL auth), M1 (find_entries cap, GraphQL query bombs), rate limit, plus manual instructions for C2 (token impersonation) and H3 (backups location).
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Tool not appearing in client | Client cached tools/list from a previous session |
Fully quit MCP client (system tray → Quit), reopen |
Tool "X" no encontrada from new tool |
Strapi runs the old plugin dist | Restart Strapi (Ctrl+C + pnpm dev); the plugin doesn't hot-reload |
401 Token name email mismatch |
Token name contains an email different from adminUserOwner email |
Rename token to match the owner's email, or remove email from name |
URL_BLOCKED on a legitimate URL |
URL caught by SSRF blocklist | Add to UPLOAD_URL_EXTRA_BLOCKED_HOSTS exception list, or switch to allowlist mode |
429 Too Many Requests |
Rate limit hit | Wait 60s, or raise MCP_RATE_LIMIT_PER_MIN in dev. Restart Strapi to wipe counters |
Schema authoring fails with SCHEMA_AUTHORING_DISABLED_IN_PRODUCTION even in dev |
NODE_ENV not set |
Explicitly set NODE_ENV=development in your .env |
Tool "graphql_query" no encontrada |
GRAPHQL_ENABLED=false or @strapi/plugin-graphql not installed |
Enable + install the plugin |
| Behind a CDN/proxy, per-IP rate limit triggers immediately | All requests appear to come from the proxy IP | Set proxy: true in config/server.ts |
403 [strapi-mcp audit] Delete bloqueado when deleting an API token |
Caller is not the original creator and not super-admin | Log in as the creator or as a super-admin. If the token is legacy (created before v0.4.0), only super-admin can delete it. |
__audit_token_creators returns AUDIT_REQUIRES_SUPER_ADMIN |
Token has no admin user resolved (standard content-api tokens) | Activate features.future.adminTokens: true in config/admin.ts and create the audit-query token from a super-admin session; or query the mcp_token_audits / mcp_op_logs tables directly via SQL. |
Deep population on reads (v0.5.0)
find_entries and get_entry accept two extra args to materialize a recursive populate tree
without you having to hand-craft it:
{
"uid": "api::page.page",
"populate_deep": true,
"populate_depth": 4 // default 4, hard cap 6
}
When populate_deep: true, the plugin walks the live schema and builds a populate object that
expands every relation, component, dynamiczone and media field, recursing up to populate_depth
levels. Cycles are protected by a visited Set — bidirectional relations don't spin forever.
Trade-offs:
- Queries become larger and slower. Use only when you genuinely need the full context (e.g. rendering a page with all its dynzone sections expanded).
- The
pageSizecap of 200 still applies, so worst-case is ~200 entries × the branching at each depth level. populate(the explicit object) is ignored whenpopulate_deep: true. The response carries awarningfield if you accidentally pass both.
System models (admin::user, plugin::users-permissions.*) are treated as shallow — large trees,
rarely useful from an MCP client.
Schema strategies on writes (v0.5.0)
Strapi's Content-Type Builder UI doesn't allow editing a component that nests another component more than 1 level deep. Before v0.5.0, the validator caught proposals exceeding this and returned an error. Now it returns strategies — concrete alternatives the LLM can pick from.
When create_component receives a proposal triggering NESTED_COMPONENT_DEPTH_EXCEEDED, the
response shape is:
{
"success": false,
"validation": { ... },
"strategies": [
{ "name": "flat", "available": true, "schema": { ... }, "trade_offs": [...] },
{ "name": "modular", "available": true, "schema": { ... }, "wiring_instructions": "...", "trade_offs": [...] },
{ "name": "dynamiczone", "available": false, "unavailable_reason": "..." }
],
"hint": "Elige una estrategia (flat | modular | dynamiczone) y vuelve a llamar con `strategy: '<nombre>'`."
}
The three strategies:
| Strategy | What it does | When it's unavailable |
|---|---|---|
flat |
Inlines the nested component's attributes into the parent with a ${attrName}_ prefix. One file, no manual wiring. |
Parent attr is repeatable: true, nested component doesn't exist, or prefixed names would collide. |
modular |
Writes the parent without the nested ref. Returns wiring_instructions with the JSON snippet the user must paste into the parent's schema manually. Maximum reusability. |
Always available. |
dynamiczone |
Converts the offending attribute to a dynamiczone (resets Strapi's depth counter). |
Not applicable when the proposal is a component (dynzones only live in content-types). |
as-proposed (escape hatch) |
Writes the schema EXACTLY as proposed, preserving the depth. The CTB UI rejects opening this component for editing, but Strapi's backend (DB, REST, GraphQL, lifecycle, populate) handles deeper nesting fine. | Always available — for users who know the limitation and prefer JSON-only editing. |
To materialize, re-call create_component with strategy: 'flat' | 'modular' | 'dynamiczone' | 'as-proposed'. The plugin applies the strategy, re-validates, and writes.
For a pure dry-run analysis without committing, use propose_schema_strategy — same input, no
disk writes, returns the same strategy list.
Batch field additions: add_fields_to_schema
The singular add_field_to_schema triggers a Strapi restart per call (~12s downtime each). When
adding 2+ fields to the same schema, use the new add_fields_to_schema (plural) tool: it reads
the schema once, merges all fields, validates, and writes once → one restart total.
{
"uid": "api::page.page",
"fields": [
{ "field_name": "subtitle", "field": { "type": "string" } },
{ "field_name": "slug", "field": { "type": "uid", "targetField": "subtitle" } },
{ "field_name": "cover", "field": { "type": "media" } }
]
}
Atomic: if any field collides (within the batch or against existing attributes), the entire operation aborts without writing. No partial states.
Note: Strategy support currently lives in create_component only. create_content_type and
add_field_to_schema will get the same fork in a future release.
Full schema mutation in one restart: modify_schema (v0.6.0)
modify_schema is the most powerful schema tool — it combines remove + add + update into one
atomic write → a single Strapi restart instead of N:
{
"uid": "molecules.feature-item",
"remove": ["legacy_field"],
"update": [{ "field_name": "count", "field": { "type": "biginteger" } }],
"add": [{ "field_name": "slug", "field": { "type": "uid", "targetField": "title" } }]
}
remove[]— field names to delete (refuses if a relation in another schema depends on them viainversedBy/mappedBy)update[]— replace a field's full definition. The way to change a field'stype(e.g.text → string) without orchestrating delete-then-add.add[]— new fields (collision-checked)
Applied in order remove → update → add, validated as a whole, written once. Any failure aborts
everything — no partial states. Cross-list conflicts (a name in both remove and add, duplicates,
etc.) are caught before the filesystem is touched.
Proactive atomization: suggest_reusable_atoms (v0.6.0)
Read-only analysis tool. Walks every component and content-type, counts repeated (fieldName, type)
patterns, and flags scalar fields worth promoting to reusable atom components — the classic case of
title: string copy-pasted into 8 sections.
{ "scope": "all", "min_occurrences": 3 }
For each strong candidate it returns the used_in list, a starter atom schema (with built-in
enrichment for known names — title → atoms.heading with tag/align, icon → atoms.icon with
size/color), and an execution_plan of concrete create_component + modify_schema calls you can
run after review. It also surfaces depth warnings (when a consumer is itself nested) and a
data-migration note. Never writes.
Known limitations
Schema-authoring chains can hang Claude Desktop via mcp-remote
When you call add_field_to_schema (or any schema-authoring tool), Strapi restarts in dev mode and
the MCP endpoint is unreachable for ~10-15s. Two compounding issues make this fragile when chaining
multiple operations from Claude Desktop:
- The LLM frequently ignores
restart_info.estimated_downtime_seconds. Observed in live testing: Claude receivedestimated_downtime_seconds: 12and called__healthjust 2 seconds later, hitting Strapi mid-restart. The plugin can only emit hints in the tool response — the protocol has no mechanism to block the next call for N seconds. - The
mcp-remotebridge gives up after 2 reconnection attempts. Claude Desktop uses mcp-remote as an stdio↔HTTP bridge. When the endpoint returnsECONNREFUSEDduring the restart window, mcp-remote tries twice and then throwsMaximum reconnection attempts (2) exceeded. Even when Strapi comes back, the session stays dead until Claude Desktop is fully restarted.
Workarounds:
- Prefer batch operations. Use
add_fields_to_schema(plural) to apply N fields in one restart instead of N restarts. Same logic applies tocreate_content_typewith all attributes defined up front. Each restart is one chance to lose the session — minimize the count. - Restart Claude Desktop completely (system tray → Quit, NOT just close the window) if a chain fails mid-way. Reopening clears the dead bridge.
- For projects with slow boot (TypeScript types + many plugins + WSL/VMs),
restart_infomay underestimate. After a schema operation, wait ~25s manually before any next MCP interaction. - Consider Claude Code instead of Claude Desktop for heavy schema-authoring sessions. Claude
Code talks to the MCP server over HTTP directly (no stdio bridge) and handles
ECONNREFUSEDmore gracefully.
This is a Claude Desktop + mcp-remote compatibility issue, not a plugin bug — the endpoint behaves identically to any other HTTP service during restart. The audit log shows the operation succeeded server-side even when the client sees a hung session.
Anti-impersonation via adminUserOwner is not implemented
An earlier security audit identified a scenario: if a user with permission to create API tokens
names their token "[email protected] - mcp", all writes via that token would be attributed to the
CEO via the email-in-token-name convention. The initial fix in 0.3.0 tried to mitigate this by
verifying the token's adminUserOwner field against the email in the name.
Investigation in 0.3.1 revealed the mitigation is not feasible in standard Strapi 5.x. The
adminUserOwner field is only populated for tokens of kind='admin' (a feature gated behind
features.future.adminTokens: true in config/admin.ts — experimental, not enabled by default).
Standard content-api tokens (the ones created from Settings → API Tokens) have adminUserOwner
forced to null by Strapi's admin service (see
source).
The check was removed in 0.3.1 because keeping it would generate false confidence — for the vast majority of users, the policy would degrade to "no attribution" anyway without rejecting impostor tokens.
Workarounds if you need strict attribution per user:
Activate the experimental flag in
config/admin.ts:features: { future: { adminTokens: true } }Then create tokens via the
/admin/admin-tokensREST endpoint (not the UI). Those tokens haveadminUserOwnerpopulated and the plugin attributes correctly.Enforce token naming convention via process: have a policy where developers MUST include their own email in tokens they create. Audit token names periodically against admin user roster. This is process-based, not technical.
Wait for Strapi to stabilize the feature: when
adminTokensgraduates from "future" to a stable API, the plugin can rely on it for all installations.
Other known limitations
- Rate limit is in-memory per instance. Multi-instance setups behind a load balancer don't share counters. Use a CDN/proxy rate limit or Redis backend (planned for future release).
- Schema authoring requires Strapi restart to take effect. The plugin tells the LLM to wait via
restart_info, but cannot eliminate the restart itself (Strapi loads schemas in boot). - GraphQL tools depend on
@strapi/plugin-graphqlbeing installed andGRAPHQL_ENABLED=true. If absent, tools are not exposed.
Roadmap
- npm publish: strapi-plugin-mcp-suite
- Forensic audit trail (token lifecycle + operations) — v0.4.0
- Deep populate on reads + progressive schema strategies on writes — v0.5.0
- Strapi marketplace approval
- Redis backend for rate limiting (multi-instance support)
- Admin UI panel for browsing the audit log
- Token rotation hooks
- More i18n-specific tools (
clone_entry_to_locale,list_locales) -
delete_content_typewith multi-step confirmation - Re-implement anti-impersonation when Strapi stabilizes
adminTokensfuture flag
Contributing
See CONTRIBUTING.md.
License
MIT — Amilcar Coronado, 2026.
Установка Strapi Suite
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/amilcarex/strapi-plugin-mcp-suiteFAQ
Strapi Suite MCP бесплатный?
Да, Strapi Suite MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Strapi Suite?
Нет, Strapi Suite работает без API-ключей и переменных окружения.
Strapi Suite — hosted или self-hosted?
Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.
Как установить Strapi Suite в Claude Desktop, Claude Code или Cursor?
Открой Strapi Suite на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
Omni Video
An MCP server that transforms LLM-enabled IDEs into professional video editors by pre-processing footage into text proxies, generating motion graphics via HTML/
автор: buildwithtazaARA
Generate images, video and audio from any AI agent — one connector.
автор: ARAYouTube
Transcripts, channel stats, search
автор: YouTubeEverArt
AI image generation using various models.
автор: modelcontextprotocolCompare Strapi Suite with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории media
