Skip to main content
Glama
andychoi
by andychoi
README.md
# Strapi MCP Server (Strapi 5.x Only)

An MCP server for Strapi 5.x CMS that gives AI agents full access to content types, entries, media, and schema management through the [Model Context Protocol](https://modelcontextprotocol.io/).

## Features

- **Full CRUD** on collection types and single types
- **Media uploads** — base64 or local file path
- **Relation management** — connect/disconnect related entries
- **Schema management** — create, update, delete content types and components (dev mode)
- **Per-request auth** — override server-level credentials with a JWT or API token per tool call
- **Structured audit logging** — JSON audit trail for all write operations
- **Request correlation** — optional `requestId` for multi-agent tracing
- **Input validation** — content type UIDs, entry IDs, file paths validated against injection (OWASP MCP02)
- **Structured logging** — `LOG_LEVEL` env var controls verbosity (`error` / `warn` / `info` / `debug`)
- **Dev-mode gating** — schema management tools hidden unless `STRAPI_DEV_MODE=true` (20 tools in production, 25 in dev)
- **TTL-based cache** — content types cached with configurable `STRAPI_CACHE_TTL`

## Quick Start

### 1. Install

```bash
npm install mcp-strapi
```

Or from source:

```bash
git clone https://github.com/andychoi/mcp-strapi.git
cd mcp-strapi
npm install
npm run build
```

### 2. Configure

Create a `.env` file (add to `.gitignore`):

```dotenv
STRAPI_URL=http://localhost:1337
STRAPI_ADMIN_EMAIL=admin@example.com
STRAPI_ADMIN_PASSWORD=your_password
# STRAPI_API_TOKEN=your_api_token     # Optional fallback
# STRAPI_DEV_MODE=true                # Enable schema management tools
# LOG_LEVEL=debug                     # error | warn | info (default) | debug
# STRAPI_CACHE_TTL=300000             # Content type cache TTL in ms (default: 5 min)
```

### 3. Add to your MCP client

**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%/Claude/claude_desktop_config.json` on Windows):

```json
{
  "mcpServers": {
    "mcp-strapi": {
      "command": "npx",
      "args": ["mcp-strapi"],
      "env": {
        "STRAPI_URL": "http://localhost:1337",
        "STRAPI_ADMIN_EMAIL": "admin@example.com",
        "STRAPI_ADMIN_PASSWORD": "your_password"
      }
    }
  }
}
```

**Cursor** (`~/.cursor/mcp.json`):

```json
{
  "strapi-mcp": {
    "command": "npx",
    "args": ["mcp-strapi"],
    "env": {
      "STRAPI_URL": "http://localhost:1337",
      "STRAPI_ADMIN_EMAIL": "admin@example.com",
      "STRAPI_ADMIN_PASSWORD": "your_password"
    }
  }
}
```

If installed from source, replace `"npx"` / `["mcp-strapi"]` with `"node"` / `["/path/to/mcp-strapi/build/index.js"]`.

**Alternative — run directly:**

```bash
node --env-file=.env build/index.js
```

## Environment Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `STRAPI_URL` | No | `http://localhost:1337` | Strapi instance URL |
| `STRAPI_ADMIN_EMAIL` | Recommended | — | Admin email for full functionality |
| `STRAPI_ADMIN_PASSWORD` | Recommended | — | Admin password |
| `STRAPI_API_TOKEN` | No | — | API token fallback (limited permissions) |
| `STRAPI_DEV_MODE` | No | `false` | Enable schema management tools |
| `LOG_LEVEL` | No | `info` | Log verbosity: `error`, `warn`, `info`, `debug` |
| `STRAPI_CACHE_TTL` | No | `300000` | Content type cache TTL in milliseconds (5 min) |

### Authentication Priority

Per request: **`authToken` parameter** > **admin JWT** > **API token**

Admin credentials are recommended for full functionality (schema access, publish/unpublish, content type management).

## Tools

All 20 production tools accept an optional `authToken` parameter for per-request auth override.

### Content (Collection Types)

| Tool | Description |
|------|-------------|
| `list_content_types` | List all content types with `kind` (collectionType/singleType) and `singularName` |
| `get_entries` | Get entries with filtering, pagination, sorting, population |
| `get_entry` | Get a specific entry by ID |
| `create_entry` | Create a new entry |
| `update_entry` | Update an existing entry |
| `delete_entry` | Delete an entry |
| `publish_entry` | Publish an entry (admin required) |
| `unpublish_entry` | Unpublish an entry (admin required) |

### Content (Single Types)

| Tool | Description |
|------|-------------|
| `get_single_type` | Get a single type entry (e.g., homepage, site-settings) |
| `update_single_type` | Update a single type entry |
| `delete_single_type` | Delete a single type entry |
| `publish_single_type` | Publish a single type (admin required) |
| `unpublish_single_type` | Unpublish a single type (admin required) |

### Media

| Tool | Description |
|------|-------------|
| `upload_media` | Upload a file via base64 (max ~750KB file / 1MB base64) |
| `upload_media_from_path` | Upload from local file path (max 10MB) |

### Schema & Relations

| Tool | Description |
|------|-------------|
| `get_content_type_schema` | Get schema (fields, types, relations) |
| `connect_relation` | Connect related entries to a relation field |
| `disconnect_relation` | Disconnect related entries from a relation field |
| `list_components` | List all Strapi components |
| `get_component_schema` | Get a component's schema |

### Dev Mode Only (`STRAPI_DEV_MODE=true`)

| Tool | Description |
|------|-------------|
| `create_content_type` | Create a new content type (admin required) |
| `update_content_type` | Update a content type's attributes (admin required) |
| `delete_content_type` | Delete a content type (admin required) |
| `create_component` | Create a new component (admin required) |
| `update_component` | Update a component (admin required) |

## Usage Examples

### List content types

```json
{
  "tool": "list_content_types",
  "arguments": {}
}
```

### Get entries with filtering and pagination

```json
{
  "tool": "get_entries",
  "arguments": {
    "contentType": "api::article.article",
    "filters": { "title": { "$contains": "hello" } },
    "pagination": { "page": 1, "pageSize": 10 },
    "sort": ["title:asc", "createdAt:desc"],
    "populate": ["author", "categories"]
  }
}
```

### Create an entry

```json
{
  "tool": "create_entry",
  "arguments": {
    "contentType": "api::article.article",
    "data": {
      "title": "My New Article",
      "content": "Article body text."
    }
  }
}
```

### Per-request auth token

Override server credentials with a role-specific JWT or API token:

```json
{
  "tool": "get_entries",
  "arguments": {
    "contentType": "api::article.article",
    "authToken": "eyJhbGciOiJIUzI1NiIs..."
  }
}
```

Use cases:
- Operate as a specific role (admin / author / reader)
- Different content types need different permission levels
- Test access with a scoped API token

### Single type operations

```json
{
  "tool": "get_single_type",
  "arguments": {
    "contentType": "api::homepage.homepage",
    "options": "{\"populate\": [\"hero\", \"seo\"]}"
  }
}
```

```json
{
  "tool": "update_single_type",
  "arguments": {
    "contentType": "api::homepage.homepage",
    "data": { "title": "Welcome", "description": "Updated" }
  }
}
```

### Upload media

**Base64 (small files):**
```json
{
  "tool": "upload_media",
  "arguments": {
    "fileData": "<base64-encoded-data>",
    "fileName": "image.jpg",
    "fileType": "image/jpeg"
  }
}
```

**File path (recommended for larger files):**
```json
{
  "tool": "upload_media_from_path",
  "arguments": {
    "filePath": "/path/to/image.jpg"
  }
}
```

### Connect/disconnect relations

```json
{
  "tool": "connect_relation",
  "arguments": {
    "contentType": "api::article.article",
    "id": "1",
    "relationField": "authors",
    "relatedIds": [2, 3]
  }
}
```

### Create a content type (dev mode)

```json
{
  "tool": "create_content_type",
  "arguments": {
    "displayName": "Product",
    "singularName": "product",
    "pluralName": "products",
    "kind": "collectionType",
    "description": "Store products",
    "draftAndPublish": true,
    "attributes": {
      "name": { "type": "string", "required": true },
      "price": { "type": "decimal", "required": true },
      "stock": { "type": "integer" }
    }
  }
}
```

### Request correlation ID

Pass a `requestId` to trace requests across multi-agent systems. It appears in audit log entries:

```json
{
  "tool": "create_entry",
  "arguments": {
    "contentType": "api::article.article",
    "data": { "title": "Traced Article" },
    "requestId": "agent-abc-req-123"
  }
}
```

## Enterprise Features

### Audit Logging

All write operations emit structured JSON audit events to stderr:

```json
{"audit":true,"timestamp":"2026-02-22T03:00:00.000Z","action":"create_entry","contentType":"api::article.article","entryId":"42","authMethod":"adminJwt","status":"success","requestId":"agent-abc-req-123"}
```

Fields: `timestamp`, `action`, `contentType`, `entryId`, `authMethod` (`authToken` | `adminJwt` | `apiToken` | `none`), `status` (`success` | `error`), `requestId`, `error`.

Audit events are always emitted regardless of `LOG_LEVEL`.

### Request Correlation IDs

Every tool accepts an optional `requestId` parameter. When provided, the ID is included in all audit log entries for that request, enabling end-to-end tracing in multi-agent deployments.

### Content Type Caching

Content types are cached with a configurable TTL (default: 5 minutes). Set `STRAPI_CACHE_TTL` in milliseconds to adjust. The cache is automatically invalidated when schema management tools modify content types.

### Security

- **No token logging** — tokens never appear in logs (OWASP MCP01)
- **Input validation** — regex validation for content type UIDs, entry IDs, component UIDs, file paths (OWASP MCP02)
- **Sanitized errors** — raw Strapi error details only logged at debug level
- **Dev-mode gating** — destructive schema tools hidden in production

## Resource URIs

Content types are exposed as MCP resources:

- `strapi://content-type/api::article.article` — all articles
- `strapi://content-type/api::article.article/1` — article with ID 1
- `strapi://content-type/api::article.article?filters={"title":{"$contains":"hello"}}` — filtered

## Troubleshooting

### Placeholder API Token Error
```
[Error] STRAPI_API_TOKEN appears to be a placeholder value...
```
Replace `"strapi_token"` or `"your-api-token-here"` with a real API token from Strapi admin > Settings > API Tokens.

### Connection Refused
```
Cannot connect to Strapi instance: Connection refused
```
Ensure Strapi is running (`npm run develop`), the URL is correct, and the database is up.

### Authentication Failed
```
Cannot connect to Strapi instance: Authentication failed
```
Verify admin email/password or API token permissions. Ensure the admin user is active.

### Context Window Overflow with Uploads
Use `upload_media_from_path` instead of `upload_media` for files larger than ~500KB. The base64 tool has a 1MB limit (~750KB file).

### Permission Errors (403)
Use admin credentials for full access. If using an API token, ensure it has "Full access" permissions.

## Debugging

```bash
npm run inspector
```

Opens the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) for debugging tool calls in your browser.

## Development

```bash
npm install          # Install dependencies
npm run build        # Build
npm run watch        # Build with auto-rebuild
```

For deployment details, see [DEPLOYMENT.md](./DEPLOYMENT.md).


## License

MIT