MCP server
Page summary:
Strapi includes a built-in Model Context Protocol (MCP) server. Once enabled, it lets AI clients create, read, update, delete, publish, and unpublish content directly through Strapi's Content Manager. All operations are gated by Admin token permissions.
The MCP server exposes a set of content management tools to AI clients such as Claude Desktop, Claude Code, Cursor, or any MCP-compatible tool. An AI client connected to the MCP server can, for example, create a blog article, list recent entries, or publish a page. Which tools are available depends on the permissions granted to the Admin token used for authentication.
Configuration
Before first use, the Strapi MCP server must be:
- enabled through the server configuration file and authenticated with Admin tokens created in the admin panel
- connected to your AI client.
Strapi code-based configuration
Enable the MCP server by adding the mcp object to the server configuration file:
- JavaScript
- TypeScript
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
mcp: {
enabled: true,
},
});
import type { Core } from '@strapi/strapi';
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Server => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
mcp: {
enabled: true,
},
});
export default config;
Once the setting is in place, restart Strapi. The MCP endpoint becomes available at /mcp on your Strapi server (e.g., http://localhost:1337/mcp).
Advanced options
The following optional keys can be added to the mcp configuration object:
| Option | Type | Default | Description |
|---|---|---|---|
enabled | Boolean | false | Enable or disable the MCP server. |
connectTimeoutMs | Number | 5000 | Maximum time (in ms) for the internal MCP transport to connect before the request is aborted. |
requestTimeoutMs | Number | 60000 | Maximum time (in ms) for a single MCP request to complete before it times out. |
mcp: {
enabled: true,
connectTimeoutMs: 10000, // 10 seconds
requestTimeoutMs: 120000, // 2 minutes
},
Strapi admin panel configuration
The MCP server authenticates requests using Admin tokens. Each MCP session is scoped to the permissions of the token used to connect:
- Create a new Admin token (see Creating an Admin token on the Admin tokens feature page).
- Copy the token value. You will need it when configuring the AI client.
The token's permissions determine which MCP tools are exposed to the AI client. For instance, if the token only grants read on an Article content-type, the AI client will only see listing and reading tools for articles.
AI client configuration
Once you have enabled the MCP server through the server configuration file and created an Admin token in the admin panel, connect your AI client to the Strapi MCP server.
http://localhost:1337/ is used in configuration examples on this page. If your Strapi server is hosted on another URL or port, please update the code accordingly.
Connecting Claude Desktop
Open Claude Desktop's configuration file. The location varies depending on your system:
| OS | File location |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
You can also open the configuration file for Claude Desktop from Claude's settings: go to Settings > Desktop app > Developer, then click on the Edit config button.
Add the Strapi MCP server to Claude's configuration file, as in the following example, replacing YOUR_ADMIN_TOKEN with the Admin token value copied from the Strapi admin panel configuration:
{
"mcpServers": {
"strapi-mcp": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://localhost:1337/mcp",
"--header",
"Authorization: Bearer YOUR_ADMIN_TOKEN"
]
}
}
}
Restart Claude Desktop for the changes to take effect.
Connecting Claude Code
Run the following command, replacing YOUR_ADMIN_TOKEN with the Admin token value copied from the Strapi admin panel configuration:
claude mcp add strapi-mcp --transport http http://localhost:1337/mcp -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
Restart Claude Code, then run /mcp to confirm strapi-mcp reports as connected.
Connecting Cursor
Add the server to your .cursor/mcp.json file:
{
"mcpServers": {
"strapi-mcp": {
"type": "streamable-http",
"url": "http://localhost:1337/mcp",
"headers": {
"Authorization": "Bearer YOUR_ADMIN_TOKEN"
}
}
}
}
Connecting Windsurf
Add the server to your ~/.codeium/windsurf/mcp_config.json file:
{
"mcpServers": {
"strapi-mcp": {
"serverUrl": "http://localhost:1337/mcp",
"headers": {
"Authorization": "Bearer YOUR_ADMIN_TOKEN"
}
}
}
}
Connecting other MCP clients
Any client that supports the MCP Streamable HTTP transport can connect. The generic configuration is as follows:
| Setting | Value |
|---|---|
| Transport type | streamable-http |
| URL | http://localhost:1337/mcp (adjust host and port to your Strapi instance) |
| Authorization header | Bearer YOUR_ADMIN_TOKEN |
Usage
The MCP server uses the Streamable HTTP transport protocol. Any MCP-compatible client can connect by pointing to the /mcp endpoint with a Bearer token in the Authorization header. Once connected, the AI client can interact with your Strapi content using natural language prompts.
Available tools
The MCP server exposes 2 categories of tools: content-type tools generated from your schema, and built-in utility tools.
Content-type tools
The tools generated differ depending on whether the content type is a collection type or a single type.
Collection types generate up to 8 tools — 5 for CRUD operations and 3 for Draft & Publish actions:
| Tool | Action | Permission required | Description |
|---|---|---|---|
list | Read | read | List entries with pagination, sorting, and filtering |
get | Read | read | Get a single entry by document ID |
create | Create | create | Create a new entry (created as draft if Draft & Publish is enabled) |
update | Update | update | Update an existing entry by document ID |
delete | Delete | delete | Delete an entry by document ID |
publish | Publish | publish | Publish a draft entry |
unpublish | Unpublish | publish | Unpublish a published entry |
discard_draft | Discard draft | publish | Discard draft changes and revert to the published version |
Single types generate up to 6 tools. Because a single type always represents exactly one document, there is no list tool and create/update are merged into a single write tool:
| Tool | Action | Permission required | Description |
|---|---|---|---|
get | Read | read | Get the single-type document |
write | Create or update | create and/or update | Create the document if none exists; update the existing draft otherwise |
delete | Delete | delete | Delete the single-type document |
publish | Publish | publish | Publish the document |
unpublish | Unpublish | publish | Unpublish the published document |
discard_draft | Discard draft | publish | Discard draft changes and revert to the published version |
The publish, unpublish, and discard_draft tools are only generated when Draft & Publish is enabled on the content type.
Built-in utility tools
In addition to content-type tools, Strapi registers the following built-in tools:
| Tool | Availability | Description |
|---|---|---|
log | Development mode only | Logs a message to the Strapi server console at a specified level (info, warn, error, http, log). Useful for debugging MCP interactions. |
Built-in utility tools are only available in development mode (when autoReload is enabled) and do not require specific admin permissions.
Content management through prompts
Once connected, you can interact with your Strapi content using natural language:
| Prompt | What happens |
|---|---|
| "Create a new article titled 'Hello World' with body 'First post'." | Creates a draft article entry |
| "List the 5 most recent articles." | Returns paginated list, newest first |
| "Show me article with ID abc123." | Returns the full entry |
| "Update article abc123, change the title to 'Hello Strapi'." | Updates the title, other fields untouched |
| "Publish article abc123." | Changes the entry status to published |
| "Delete article abc123." | Removes the entry |
| "Create an article in French with the title 'Bonjour le monde'." | Creates a draft article with locale set to fr |
Internationalization (i18n)
When Internationalization (i18n) is enabled on a content type, MCP tools accept an optional locale parameter (e.g., "en", "fr"). If omitted, the default locale is used.
The AI client sees which locales are available in each tool's schema, so you can ask it to create or update content in a specific language. For example, asking "Create an article in French titled 'Bonjour'" passes locale: "fr" to the create tool. Which locales are available depends on the Admin token's permissions (see Permission boundaries).
When working with localized content, explicitly mention the target language in your prompt so the AI client passes the correct locale value. For instance, prefer "Create an article in French" over "Create an article titled 'Bonjour'" to avoid ambiguity.
Sorting
The list tool accepts a sort parameter that supports 4 notations:
| Notation | Example |
|---|---|
| String | "title:asc" |
| Array of strings | ["title:asc", "createdAt:desc"] |
| Object | { "title": "asc" } |
| Array of objects | [{ "title": "asc" }, { "createdAt": "desc" }] |
Sort field names are constrained to the content type's scalar attributes (strings, numbers, booleans, dates, enumerations). Relation, component, dynamic zone, media, and JSON fields cannot be sorted on.
Filtering
The list tool accepts a filters parameter using Strapi's filter syntax:
- Field operators:
$eq,$ne,$in,$notIn,$lt,$lte,$gt,$gte,$between,$contains,$notContains,$startsWith,$endsWith,$null,$notNull, and their case-insensitive variants ($eqi,$nei,$containsi,$notContainsi,$startsWithi,$endsWithi). - Logical operators:
$and,$or(accept arrays of filter objects),$not(wraps a single filter object). - Implicit equality: Passing a value directly (e.g.,
{ "title": "Hello" }) is equivalent to{ "title": { "$eq": "Hello" } }.
Like sort fields, filter fields are constrained to scalar attributes only.
Pagination
The list tool also accepts page (1-indexed, default: 1) and pageSize (default: 25, max: 100) parameters.
Relations
Relation fields support both a shorthand document ID string and a full relation object.
To-one relations (oneToOne, manyToOne) accept:
- A document ID string:
"z7v8zma53x01r6oceimv922b" - A relation object:
{ "documentId": "z7v8zma53x01r6oceimv922b", "locale": "en", "status": "draft" }(localeandstatusare optional) nullto clear the relation
To-many relations (oneToMany, manyToMany) accept a relation object with one or more of the following keys:
| Key | Description |
|---|---|
connect | Add relations. Accepts an array of document ID strings or { documentId, locale?, status?, position? } objects. The optional position key supports { before?, after?, start?, end? } ordering hints (default: { end: true }). |
disconnect | Remove relations. Accepts an array of document ID strings or { documentId, locale?, status? } objects. |
set | Replace all existing relations with the provided array. Pass null to clear all relations. Mutually exclusive with connect/disconnect. |
Permission boundaries
The MCP server enforces the same permission model as the Strapi admin panel. Permissions are checked at multiple levels:
-
Tool visibility: When an AI client connects, Strapi checks the Admin token's permissions and only exposes tools the token has access to. If the token does not grant
deleteonArticle, the AI client will not see a delete tool for articles at all. -
Field filtering: Even within an exposed tool, input and output schemas are narrowed to the fields the token can access. If the token grants
readonArticlebut excludes thebodyfield, the AI client will not see or receivebodycontent. Field restrictions are applied independently per action. Write schemas (create,update) only include fields permitted for the corresponding action. -
Locale filtering: When the Internationalization (i18n) feature is enabled and locale-level permissions are configured, the
localeparameter is narrowed per action. For example, a token might allow reading content inenandfrbut only creating content inen. If the default locale is permitted for a given action, that locale is applied as the Zod schema default, so the AI client does not need to specify a locale explicitly. -
Runtime enforcement: Beyond schema-level narrowing, each handler calls Strapi's permission checker at runtime to verify access on the specific document being read, written, or published. Condition-based permissions (e.g., "only update entries you own") are enforced at this level.
This means you can create tokens with fine-grained access:
- A "read-only" token that only exposes listing and reading tools
- A token scoped to specific content types (e.g., articles but not categories)
- A token restricted to specific fields or locales
- A token with condition-based permissions (e.g., only update entries you own)
Create dedicated Admin tokens for each AI client or use case. Use the most restrictive permissions that still allow the AI to accomplish its task.
Stateless architecture
The MCP server uses a stateless architecture. Each POST request to the /mcp endpoint creates a fresh, ephemeral MCP server instance scoped to the authenticated token's permissions. There is no session persistence between requests: every request is independently authenticated and authorized. Because there is no session state, the AI client does not need to manage session IDs, and permission changes (such as revoking a token or updating its permissions) take effect on the next request.
GET and DELETE HTTP methods on the /mcp endpoint return a 405 Method Not Allowed JSON-RPC error, as the MCP server only accepts POST requests.
Known limitations
The MCP server has the following limitations:
- Dynamic zones: Dynamic zone fields are passed as untyped arrays in tool schemas. The internal structure of each component within a dynamic zone is not described.
- Nested population parameters: The
listandgettools do not support nested population parameters for relations. - Media upload: Media fields accept existing media asset references but the MCP server cannot upload new files. Use Strapi's media library or upload API to add files first, then reference them in MCP tool calls.
- Custom fields: Custom fields registered via plugins are mapped to their underlying Strapi type. If the custom field registry is not populated when MCP tools are registered, the custom field falls back to an
unknowntype. - Circular component references: Components that reference themselves (directly or indirectly) fall back to an open
record<string, unknown>schema at the point of the cycle, rather than an infinite recursive structure.
Plugin API
Strapi plugins can register additional MCP tools, prompts, and resources through the strapi.ai.mcp service. All registrations must happen during the plugin's register() lifecycle phase, before the MCP server starts.
Registering a custom tool
Use strapi.ai.mcp.registerTool() to expose a custom tool to AI clients:
- JavaScript
- TypeScript
const { z } = require('@strapi/utils');
module.exports = {
register({ strapi }) {
if (strapi.ai.mcp.isEnabled()) {
strapi.ai.mcp.registerTool({
name: 'my_custom_tool',
title: 'My Custom Tool',
description: 'A short description shown to the AI client.',
auth: {
// The session gate passes when the token satisfies ANY policy in the array.
policies: [{ action: 'plugin::my-plugin.my-action' }],
},
// resolveInputSchema and resolveOutputSchema are called per request,
// so they can narrow schemas based on the token's permissions.
resolveInputSchema: (context) =>
z.object({
message: z.string().describe('The message to echo.'),
}),
resolveOutputSchema: (context) =>
z.object({
result: z.string(),
}),
createHandler: (strapi, context) => async ({ args }) => ({
content: [{ type: 'text', text: args.message }],
structuredContent: { result: args.message },
}),
});
}
},
};
import { z } from '@strapi/utils';
export default {
register({ strapi }) {
if (strapi.ai.mcp.isEnabled()) {
strapi.ai.mcp.registerTool({
name: 'my_custom_tool',
title: 'My Custom Tool',
description: 'A short description shown to the AI client.',
auth: {
// The session gate passes when the token satisfies ANY policy in the array.
policies: [{ action: 'plugin::my-plugin.my-action' }],
},
resolveInputSchema: (context) =>
z.object({
message: z.string().describe('The message to echo.'),
}),
resolveOutputSchema: (context) =>
z.object({
result: z.string(),
}),
createHandler: (strapi, context) => async ({ args }) => ({
content: [{ type: 'text', text: args.message }],
structuredContent: { result: args.message },
}),
});
}
},
};
Tool definition options
| Option | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Unique tool name. Must be unique across all registered MCP tools. |
title | String | Yes | Human-readable title shown to the AI client. |
description | String | Yes | Short description of what the tool does. |
auth | Object | Yes (or devModeOnly) | Auth requirement. The session gate passes when the token satisfies any policy in the policies array. Each policy is { action, subject? }. |
devModeOnly | Boolean | Yes (or auth) | Set to true to restrict the tool to development mode only (equivalent to the built-in log tool). |
resolveInputSchema | Function | No | Returns a Zod schema for the tool's input arguments. Called per request so RBAC constraints can be applied dynamically. Omit for tools with no input. |
resolveOutputSchema | Function | Yes | Returns a Zod schema for the tool's structured output. Called per request. |
createHandler | Function | Yes | Factory that returns the async tool handler. Receives the Strapi instance and per-request context (including userAbility and user). |
telemetry | Object | No | Optional analytics metadata: { source?: string; name?: string }. Use source to identify the plugin and name to override the raw tool name in analytics events. |
resolveInputSchema and resolveOutputSchema are called once per incoming MCP request, so you can narrow schemas dynamically based on the token's permissions (via context.userAbility).