Strapi MCP Suite
Provides tools to introspect the GraphQL schema, execute queries and mutations, and generate queries from content-type UIDs against the Strapi GraphQL endpoint.
Provides tools for content management (CRUD operations on content-types), schema authoring (creating, modifying content-types and components), media upload, visual layout configuration, and diagnostics on a Strapi CMS instance.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Strapi MCP Suiteshow me the latest 5 blog posts"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
strapi-plugin-mcp-suite
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, anti-impersonation and multi-layer rate limiting.
πͺπΈ Leer en espaΓ±ol
TL;DR
Drop this plugin into any Strapi v5 project, create an API token, point your MCP client at /api/strapi-mcp/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), token anti-impersonation, rate limiting in 3 layers (per-token / per-user / per-IP) and a fail-closed production mode for schema authoring.
Features
Built-in tools (31 total, organized by capability)
Category | Tools | Notes |
Content ops |
| Generic CRUD on any content-type. Delegates to |
Visual layout |
| Modifies the Content Manager UI config (widths, labels, ordering). Stored in |
Schema authoring |
| Writes |
Media / upload |
| URL-based uploads. Works with any Strapi provider (local, S3, Cloudinary, R2, etc.). SSRF-protected. |
GraphQL |
| Test GraphQL queries, introspect the schema, generate queries from a content-type UID. Mutations require explicit |
Diagnostics |
| Health ping (use after schema-authoring to confirm Strapi restarted) + registry inventory. Always available. |
Extensibility
Custom tools registered from your project bootstrap appear alongside the built-ins:
strapi.plugin('strapi-mcp').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
Currently distributed via git only (npm publish coming soon):
# Clone the plugin into your Strapi project
cd <your-strapi-project>/src/plugins
git clone https://github.com/amilcarex/strapi-plugin-mcp-suite.git strapi-mcp
# Build the plugin's dist
cd strapi-mcp
npm install
npm run buildThen enable it in config/plugins.ts:
export default {
'strapi-mcp': {
enabled: true,
resolve: './src/plugins/strapi-mcp',
},
};Restart Strapi. You should see:
[strapi-mcp] plugin loaded β endpoint /api/strapi-mcp/stream | strapi=5.46.0 | env=development | schema_authoring=disabled | upload=disabled | graphql=disabledQuick start
1. Create an API token
In the Strapi admin: Settings β API Tokens β Create new API Token.
Name: include your email, e.g.
youremail@example.com - 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/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/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 |
|
| Exposes the 7 schema-authoring tools (writes |
|
| Exposes the 6 media library tools. Requires an upload provider configured. |
|
| Exposes the 3 GraphQL tools. Requires |
|
| Per-token rate limit (sliding window). |
|
| Per-admin-user rate limit (sums all tokens of the same owner). Requires Strapi 5.45+. |
|
| Per-origin-IP rate limit. Requires |
|
| Sliding window size (milliseconds). |
| (empty) | Strict allowlist for |
| (empty) | Same as above but matches domain suffixes (e.g. |
| (empty) | Additional hosts to block (extends the hardcoded blocklist). |
| (empty) | Additional IPv4 CIDR ranges to block. |
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 & attribution
Native Strapi API tokens β no custom auth scheme to break. Reuses Strapi's hashing and storage.
Anti-impersonation (Strapi 5.45+) β if the token name contains an email, it must match the email of the
adminUserOwnerof the token. Prevents low-priv users from naming their tokenceo@company.com - ...to attribute writes to the CEO.Graceful degradation on Strapi <5.45 β
adminUserOwnerdoesn't exist; the plugin doesn't attribute (no false trust) and logs a warning on boot suggesting upgrade.
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.
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.
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').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 test145+ 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.shWindows:
$env:STRAPI_MCP_TOKEN = "<your-token>"
pwsh src/plugins/strapi-mcp/scripts/security-test.ps1The 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 | Fully quit MCP client (system tray β Quit), reopen |
| Strapi runs the old plugin dist | Restart Strapi ( |
| Token name contains an email different from | Rename token to match the owner's email, or remove email from name |
| URL caught by SSRF blocklist | Add to |
| Rate limit hit | Wait 60s, or raise |
Schema authoring fails with |
| Explicitly set |
|
| Enable + install the plugin |
Behind a CDN/proxy, per-IP rate limit triggers immediately | All requests appear to come from the proxy IP | Set |
Roadmap
npm publish under
@<scope>/strapi-plugin-mcp-suiteStrapi marketplace submission
Redis backend for rate limiting (multi-instance support)
Token rotation hooks
More i18n-specific tools (
clone_entry_to_locale,list_locales)delete_content_typewith multi-step confirmation
Contributing
See CONTRIBUTING.md.
License
MIT β Amilcar Coronado, 2026.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/amilcarex/strapi-plugin-mcp-suite'
If you have feedback or need assistance with the MCP directory API, please join our Discord server