@lapage/strapi-mcp
# @lapage/strapi-mcp
MCP server for Strapi CMS content and media management. Built by LaPage Digital.
## Install
Run with `npx`:
```sh
npx -y @lapage/strapi-mcp
```
Or install globally:
```sh
npm install -g @lapage/strapi-mcp
```
## Environment
| Variable | Required | Description |
| --- | --- | --- |
| `STRAPI_URL` | Yes | Strapi base URL, for example `https://cms.example.com`. |
| `STRAPI_API_TOKEN` | Yes | Strapi API token for REST content and media operations. |
| `STRAPI_ADMIN_TOKEN` | Optional | Token for content-manager/admin schema discovery. Falls back to `STRAPI_API_TOKEN`. |
`STRAPI_TOKEN` is also accepted as an alias for `STRAPI_API_TOKEN`.
`STRAPI_API_URL` is also accepted as an alias for `STRAPI_URL`.
## Agent Config
### Codex
`~/.codex/config.toml` or project-local `.codex/config.toml`:
```toml
[mcp_servers.lapage-strapi-mcp]
command = "npx"
args = ["-y", "@lapage/strapi-mcp"]
env = {
"STRAPI_URL" = "https://cms.example.com",
"STRAPI_API_TOKEN" = "your-strapi-api-token",
"STRAPI_ADMIN_TOKEN" = "optional-admin-token"
}
[mcp_servers.lapage-strapi-mcp.tools.strapi_create_entry]
approval_mode = "approve"
[mcp_servers.lapage-strapi-mcp.tools.strapi_update_entry]
approval_mode = "approve"
[mcp_servers.lapage-strapi-mcp.tools.strapi_delete_entry]
approval_mode = "approve"
[mcp_servers.lapage-strapi-mcp.tools.strapi_request]
approval_mode = "approve"
[mcp_servers.lapage-strapi-mcp.tools.strapi_upload_media_from_url]
approval_mode = "approve"
[mcp_servers.lapage-strapi-mcp.tools.strapi_upload_media_from_file]
approval_mode = "approve"
[mcp_servers.lapage-strapi-mcp.tools.strapi_update_media_info]
approval_mode = "approve"
[mcp_servers.lapage-strapi-mcp.tools.strapi_delete_media]
approval_mode = "approve"
```
### Claude Desktop
`claude_desktop_config.json`:
```json
{
"mcpServers": {
"lapage-strapi-mcp": {
"command": "npx",
"args": ["-y", "@lapage/strapi-mcp"],
"env": {
"STRAPI_URL": "https://cms.example.com",
"STRAPI_API_TOKEN": "your-strapi-api-token",
"STRAPI_ADMIN_TOKEN": "optional-admin-token"
}
}
}
}
```
### Cursor
`.cursor/mcp.json`:
```json
{
"mcpServers": {
"lapage-strapi-mcp": {
"command": "npx",
"args": ["-y", "@lapage/strapi-mcp"],
"env": {
"STRAPI_URL": "https://cms.example.com",
"STRAPI_API_TOKEN": "your-strapi-api-token",
"STRAPI_ADMIN_TOKEN": "optional-admin-token"
}
}
}
}
```
### Windsurf
MCP config:
```json
{
"mcpServers": {
"lapage-strapi-mcp": {
"command": "npx",
"args": ["-y", "@lapage/strapi-mcp"],
"env": {
"STRAPI_URL": "https://cms.example.com",
"STRAPI_API_TOKEN": "your-strapi-api-token",
"STRAPI_ADMIN_TOKEN": "optional-admin-token"
}
}
}
}
```
## Tools
### Server
- `strapi_health` — checks Strapi reachability.
- `strapi_request` — performs an arbitrary authenticated Strapi HTTP request.
### Content Types
- `strapi_list_content_types` — lists available Strapi content types.
- `strapi_get_content_type_schema` — returns one content type schema by UID.
### Entries
- `strapi_list_entries` — lists entries for a collection type.
- `strapi_get_entry` — gets one entry by Strapi 5 `documentId` or Strapi 4 `id`.
- `strapi_create_entry` — creates an entry.
- `strapi_update_entry` — updates an entry.
- `strapi_delete_entry` — deletes an entry.
## Strapi 5 i18n notes
### `locale: "all"` list queries
For `strapi_list_entries`, this server treats `locale: "all"` as a request to
fetch every configured Strapi locale separately and merge the results. It first
calls `/api/i18n/locales`, then queries the collection once per locale, for
example `locale=en` and `locale=vi`.
This is intentional. Some Strapi 5 REST configurations can return a bogus empty
array for filtered queries such as:
```json
{
"filters": { "projectId": { "$eq": "lp_homepage" } },
"locale": "all",
"pagination": { "pageSize": 100 },
"populate": "*"
}
```
while the equivalent per-locale queries return data. The merged response includes
`meta.localeAll.resolvedLocales` so callers can see which locales were queried.
Pagination metadata for merged `locale: "all"` responses is synthetic. The tool
applies the requested pagination to each locale request, merges the returned
rows, and reports a single combined `meta.pagination.total`. If you need a strict
cross-locale audit, query each locale explicitly and compare entries by
`documentId`.
### Localized relation updates
When updating localized entries, relation fields that point to localized content
must use the numeric `id` for the target entry in the same locale. This matters
especially for post categories. Do not attach Vietnamese posts to English
category IDs just because the categories share the same `documentId`.
Concrete LaPage category examples:
- EN `n8n Tutorials` numeric id `105` maps to VI `Hướng Dẫn n8n` numeric id `111`.
- EN `Archived` numeric id `113` maps to VI `Archived` numeric id `115`.
For a VI post update, use `[111, 115]` rather than `[105, 113]` for those
categories.
### Media Library
- `strapi_list_media` — lists Media Library files.
- `strapi_get_media` — gets one Media Library file by id.
- `strapi_upload_media_from_url` — uploads media from a URL.
- `strapi_upload_media_from_file` — uploads media from a local file path.
- `strapi_update_media_info` — updates media metadata.
- `strapi_delete_media` — deletes a Media Library file.
TDQS
Scored across 15 tools
Each tool targets a distinct resource and action: health check, generic request, content types (list/schema), entries CRUD, and media operations. The overlapping upload tools are differentiated by source (URL vs local file), and the generic strapi_request is clearly scoped to custom endpoints.
All tools follow a consistent snake_case pattern starting with 'strapi_', followed by a verb (list, get, create, update, delete, upload) and an object (content_types, entries, media). Even compound names like upload_media_from_url maintain a regular structure.
With 15 tools, the server covers health, content type introspection, entry lifecycle, and media management without being bloated. Each tool serves a clear purpose within the Strapi domain, and the count falls well within the ideal 3-15 range.
The server provides comprehensive CRUD for both entries and media, plus schema access and a generic request fallback for custom endpoints. Minor gaps exist, such as no explicit publish/unpublish tool (though update_entry may handle it) and no dedicated filtering/search tool, but these are workable via list_entries parameters or strapi_request.