ocyris-strapi-builder-mcp
v1.0.4
Published
MCP plugin that exposes Strapi content types and data to LLMs for agentic UI building
Downloads
553
Maintainers
Readme
Strapi Builder MCP
A Strapi v5 plugin that exposes your content types, components, and data via the Model Context Protocol (MCP) — enabling AI tools like Claude Code, Claude Desktop, and Cursor to understand, query, and manage your Strapi backend.
How It Works
sequenceDiagram
participant AI as AI Tool (Claude Code / Cursor)
participant MCP as MCP Endpoint
participant Auth as Auth Middleware
participant Tools as Tool Registry
participant Strapi as Strapi Internal APIs
AI->>MCP: POST /api/strapi-builder-mcp/mcp
MCP->>Auth: Check Bearer token
Auth-->>MCP: Authenticated
MCP->>Tools: Route to tool handler
Tools->>Strapi: Query content types / documents
Strapi-->>Tools: Data
Tools-->>AI: JSON response (sanitized)The plugin adds a single HTTP endpoint to your Strapi server. Any MCP-compatible client connects to it and discovers available tools automatically. The AI can then explore your API, query data, and (in development mode) create content and manage permissions.
Architecture
graph TB
subgraph "MCP Endpoint"
Controller["Controller<br/>POST /api/strapi-builder-mcp/mcp"]
Transport["StreamableHTTPServerTransport<br/>Session management"]
Server["MCP Server<br/>Tool routing"]
end
subgraph "Auth"
Middleware["Bearer Token Middleware"]
end
subgraph "Tool Registry"
direction TB
Read["Read Tools (11)<br/>Always available"]
Write["Write Tools (4)<br/>Dev mode only"]
end
subgraph "Services"
CT["ContentType Service<br/>Schema introspection"]
Doc["Document Service<br/>CRUD operations"]
Comp["Component Service<br/>Component listing"]
Perm["Permission Service<br/>Public access control"]
end
subgraph "Utilities"
Sanitize["Output Sanitizer<br/>Strip large fields"]
Errors["Error Formatter<br/>Actionable messages"]
Knowledge["Knowledge Base<br/>Strapi v5 patterns"]
end
Controller --> Middleware
Middleware --> Transport
Transport --> Server
Server --> Read
Server --> Write
Read --> CT
Read --> Doc
Read --> Comp
Read --> Perm
Read --> Knowledge
Write --> Doc
Write --> Perm
Doc --> Sanitize
Server --> ErrorsDirectory Structure
server/src/
├── index.ts # Entry point (register, bootstrap, destroy)
├── bootstrap.ts # Auth middleware + session management
├── destroy.ts # Graceful shutdown
├── register.ts # Registration phase
├── config/index.ts # Plugin configuration
├── routes/content-api.ts # HTTP route definitions
├── controllers/mcp.ts # Request handler (StreamableHTTPServerTransport)
├── services/
│ ├── content-type.ts # Schema introspection (getAll, isValidUid)
│ ├── document.ts # Document CRUD (findMany, findOne)
│ ├── component.ts # Component listing with pagination
│ └── permission.ts # Public permission queries + updates
└── mcp/
├── server.ts # MCP Server setup + tool registration
├── schemas/index.ts # Zod validation for tool inputs
├── utils/
│ ├── sanitize.ts # Output sanitization + large field stripping
│ └── errors.ts # Actionable error message formatting
└── tools/
├── index.ts # Tool registry + dispatcher
├── explore-api.ts # Complete API map with permissions
├── list-content-types.ts
├── list-components.ts
├── find-many.ts # Query documents with filters
├── find-one.ts # Fetch single document
├── get-page-structure.ts
├── get-api-response.ts # Test real REST API response
├── verify-endpoint.ts # Endpoint health check
├── health-check.ts # Server status + diagnostics
├── get-media.ts # Media library browser
├── search-knowledge.ts # Strapi v5 knowledge base
├── create-entry.ts # Create document (dev only)
├── update-entry.ts # Update document (dev only)
├── delete-entry.ts # Delete document (dev only)
└── set-permissions.ts # Manage public access (dev only)Installation
npm install ocyris-strapi-builder-mcpAdd to your Strapi config:
// config/plugins.ts
export default () => ({
'strapi-builder-mcp': {
enabled: true,
},
});Rebuild and restart:
npm run build
npm run developThe endpoint is available at POST /api/strapi-builder-mcp/mcp.
Authentication
All requests require a Bearer token:
- Go to Settings > API Tokens in Strapi admin
- Create a new token (Full Access recommended)
- Use it in requests:
Authorization: Bearer your-token
flowchart LR
A[MCP Client] -->|"Authorization: Bearer token"| B[Auth Middleware]
B -->|Valid| C[MCP Server]
B -->|Missing/Invalid| D[401 Unauthorized]Connecting AI Tools
Claude Code
claude mcp add strapi \
--transport http \
http://localhost:1337/api/strapi-builder-mcp/mcp \
-H "Authorization: Bearer your-token"Verify: claude mcp list
Then use it:
"Use the strapi MCP to explore my API and build a blog page"
Claude Desktop
// claude_desktop_config.json
{
"mcpServers": {
"strapi": {
"url": "http://localhost:1337/api/strapi-builder-mcp/mcp",
"headers": {
"Authorization": "Bearer your-token"
}
}
}
}Cursor
// .cursor/mcp.json
{
"mcpServers": {
"strapi": {
"url": "http://localhost:1337/api/strapi-builder-mcp/mcp",
"headers": {
"Authorization": "Bearer your-token"
}
}
}
}Custom Agents (Vercel AI SDK)
import { createMCPClient } from '@ai-sdk/mcp'
const client = await createMCPClient({
transport: {
type: 'http',
url: 'http://localhost:1337/api/strapi-builder-mcp/mcp',
headers: {
Authorization: 'Bearer your-token',
},
},
})
const tools = await client.tools() // Auto-discovers all available toolsTools Reference
Read Tools (always available)
explore_api
Returns a complete API map — like a dynamic OpenAPI spec. Shows every endpoint with fields, relations, components, dynamic zones, and public access status.
{ "name": "explore_api", "arguments": { "verbose": true } }Response includes isPublic and publicActions per endpoint:
{
"endpoints": [
{
"name": "Article",
"uid": "api::article.article",
"endpoint": "/api/articles",
"isPublic": true,
"publicActions": ["find", "findOne"],
"fields": ["title", "slug", "content"],
"relations": [{ "field": "author", "target": "api::author.author" }]
}
]
}health_check
Server diagnostics: environment, uptime, plugins, content type counts, public permissions, and whether write tools are enabled.
{ "name": "health_check" }find_many
Query documents with filtering, sorting, pagination, and population.
{
"name": "find_many",
"arguments": {
"uid": "api::article.article",
"filters": { "title": { "$containsi": "hello" } },
"populate": "*",
"sort": "createdAt:desc",
"pagination": { "page": 1, "pageSize": 10 },
"status": "published"
}
}Large content fields are stripped by default. Use includeContent: true to include them.
find_one
Fetch a single document by ID.
{
"name": "find_one",
"arguments": {
"uid": "api::article.article",
"documentId": "abc123",
"populate": "*"
}
}get_page_structure
Structured summary of a content type — fields, dynamic zones, components, relations, and sample data. Great for generating TypeScript types.
{
"name": "get_page_structure",
"arguments": { "contentType": "landing-page" }
}get_api_response
Test the actual REST API response exactly as the frontend would receive it. Detects auto-population middleware.
{
"name": "get_api_response",
"arguments": { "contentType": "article", "limit": 2 }
}verify_endpoint
Health check for a specific endpoint: does it exist, is it published, is it auto-populated, what block types are available.
{
"name": "verify_endpoint",
"arguments": { "contentType": "article" }
}get_media
Browse the media library with pagination and MIME type filtering.
{
"name": "get_media",
"arguments": { "mime": "image", "page": 1, "pageSize": 10 }
}search_strapi_knowledge_sources
Search a bundled Strapi v5 knowledge base covering data fetching, population, filtering, dynamic zones, permissions, media handling, i18n, and more.
{
"name": "search_strapi_knowledge_sources",
"arguments": { "query": "how to populate dynamic zones" }
}list_content_types / list_components
List all content types or components with full schema definitions.
Write Tools (development mode only)
flowchart TD
A[Tool Request] --> B{NODE_ENV?}
B -->|development| C[Execute Write Tool]
B -->|production| D["Blocked: 'Only available in development mode'"]Write tools are automatically hidden from the tool list in production. In development, they appear alongside read tools.
create_entry
{
"name": "create_entry",
"arguments": {
"uid": "api::article.article",
"data": { "title": "New Article", "slug": "new-article" },
"status": "published"
}
}update_entry
{
"name": "update_entry",
"arguments": {
"uid": "api::article.article",
"documentId": "abc123",
"data": { "title": "Updated Title" }
}
}delete_entry
{
"name": "delete_entry",
"arguments": {
"uid": "api::article.article",
"documentId": "abc123"
}
}set_permissions
Configure which actions are publicly accessible without authentication.
{
"name": "set_permissions",
"arguments": {
"contentType": "article",
"actions": ["find", "findOne"]
}
}Request Flow
flowchart TB
A[MCP Client Request] --> B[Koa Middleware]
B --> C{Bearer Token?}
C -->|No| D[401 Unauthorized]
C -->|Yes| E[Controller]
E --> F{Session exists?}
F -->|No| G[Create new session<br/>+ MCP Server + Transport]
F -->|Yes| H[Reuse session]
G --> I[StreamableHTTPServerTransport]
H --> I
I --> J[MCP Server]
J --> K{Tool type?}
K -->|ListTools| L[Return tool list<br/>filtered by environment]
K -->|CallTool| M{Write tool in prod?}
M -->|Yes| N[Blocked with error]
M -->|No| O[Validate input with Zod]
O --> P[Execute handler]
P --> Q[Sanitize output]
Q --> R[Return JSON response]Session Management
stateDiagram-v2
[*] --> Active: New request (no session ID)
Active --> Active: Subsequent requests (same session ID)
Active --> Expired: 4 hour timeout
Expired --> [*]: Cleanup on next request
Active --> Closed: Plugin destroy / server shutdown
Closed --> [*]: Graceful cleanup- Sessions stored in-memory (
Map<string, { server, transport }>) - Session ID via
mcp-session-idHTTP header - 4-hour timeout per session
- Expired sessions cleaned up probabilistically (~1% of requests)
- All sessions closed gracefully on plugin destroy
Context Optimization
The plugin is designed to minimize token usage for LLM agents:
- Large field stripping: Rich text, markdown, HTML, and long descriptions are automatically truncated to
[N chars stripped]infind_manyresults - Truncation threshold: Strings over 300 characters are truncated
- Override: Pass
includeContent: trueto get full content - Depth limit: Recursive stripping stops at depth 10
- Output sanitization: Uses Strapi's built-in content API sanitizer to respect permissions (fail-closed)
Error Handling
All errors include actionable guidance:
| Error | Message | |-------|---------| | 403 Forbidden | "Use set_permissions tool to enable public access, or check API token permissions" | | 401 Unauthorized | "Verify Bearer token is valid and hasn't expired" | | 404 Not Found | "Use list_content_types to see available types" | | Connection refused | "Verify Strapi is running and URL is correct" | | Write in production | "Tool X is only available in development mode" |
CORS Configuration
The MCP protocol requires the mcp-session-id header. Update config/middlewares.ts:
export default [
'strapi::logger',
'strapi::errors',
'strapi::security',
{
name: 'strapi::cors',
config: {
origin: '*',
headers: ['Content-Type', 'Authorization', 'Accept', 'mcp-session-id'],
expose: ['mcp-session-id'],
},
},
'strapi::poweredBy',
'strapi::query',
'strapi::body',
'strapi::session',
'strapi::favicon',
'strapi::public',
];If using ngrok, also add ngrok-skip-browser-warning to headers.
Configuration
// config/plugins.ts
export default () => ({
'strapi-builder-mcp': {
enabled: true, // Set to false to disable entirely
},
});Requirements
- Strapi v5.x
- Node.js 18+
License
MIT
