# CLAUDE.md
## Project Overview
MCP-Strapi provides full CRUD access to Strapi 5.x CMS for AI agents via the Model Context Protocol (MCP). Two implementations exist:
- **Strapi MCP Plugin** (`strapi-plugins/mcp-server/`) — Target architecture. Runs inside Strapi, uses Document Service directly. Streamable HTTP transport.
- **Standalone MCP Server** (`strapi-mcp-server/`) — External process, connects via Strapi REST API. stdio transport. For cases where Strapi can't be modified.
## Repository Structure
```
/
├── strapi-app/ # Strapi 5 application (Docker target)
│ ├── config/
│ │ ├── database.ts # SQLite default, PostgreSQL optional
│ │ ├── server.ts # Host, port, app keys
│ │ ├── admin.ts # Admin JWT, API token salts
│ │ ├── middlewares.ts # Strapi middleware stack
│ │ └── plugins.ts # All plugins configured here
│ └── src/
│ ├── admin/app.ts # Admin panel config
│ ├── api/
│ │ ├── project/ # Demo: software/infra projects
│ │ ├── ticket/ # Demo: bugs, features, tasks
│ │ ├── team/ # Demo: engineering teams
│ │ ├── runbook/ # Demo: infra operational runbooks
│ │ └── standard/ # Demo: SDLC standards & ADRs
│ └── index.ts # Bootstrap: seeds demo users + content
├── strapi-plugins/
│ ├── mcp-server/ # MCP protocol plugin (Streamable HTTP)
│ │ └── server/src/
│ │ ├── controllers/events.controller.ts # POST/GET/DELETE /mcp
│ │ ├── services/auth.service.ts # JWT verification via JWKS
│ │ ├── services/authorization.service.ts # Role matrix + ownership
│ │ ├── middlewares/mcp-auth.ts # MCP client → Strapi admin user
│ │ └── config/index.ts # Plugin configuration
│ ├── secure-documents/ # Document storage + RAG pipeline
│ │ └── server/src/
│ │ ├── controllers/document.controller.ts # CRUD + upload + reindex
│ │ ├── services/
│ │ │ ├── abac.service.ts # ABAC policy engine
│ │ │ ├── storage.service.ts # S3/MinIO abstraction
│ │ │ ├── extractor.service.ts # PDF/DOCX/text extraction
│ │ │ ├── chunker.service.ts # Sliding window chunking
│ │ │ ├── embedding.service.ts # Ollama/Bedrock embeddings
│ │ │ ├── vector.service.ts # pgvector CRUD
│ │ │ └── pipeline.service.ts # Extract→chunk→embed→store
│ │ ├── content-types/document/ # Secure document schema
│ │ ├── middlewares/secure-auth.ts # Auth middleware
│ │ ├── bootstrap.ts # pgvector extension + table
│ │ └── config/index.ts # Storage/embedding/vector config
│ └── secure-search/ # Semantic search + RAG
│ └── server/src/
│ ├── controllers/search.controller.ts # Search/RAG/facets endpoints
│ ├── services/
│ │ ├── search.service.ts # Vector similarity search
│ │ ├── facet.service.ts # ABAC-filtered facets
│ │ └── rag.service.ts # RAG answer generation
│ ├── middlewares/secure-auth.ts # Auth middleware
│ └── config/index.ts # Search/RAG config
├── strapi-mcp-server/ # Standalone server (legacy/reference)
│ └── src/index.ts # Single-file MCP server (~1200 lines)
├── tests/ # Integration tests (11 suites)
├── docs/
│ ├── architecture.md # Full architecture design
│ ├── authentication-flow.md # Auth modes and session lifecycle
│ └── secure-plugins-plan.md # Secure plugins implementation plan
├── Dockerfile # Multi-stage: build 3 plugins → build Strapi
├── docker-compose.yml # Dev: Strapi + PostgreSQL/pgvector + MinIO
├── docker-compose.prod.yml # Prod: + Bedrock + real S3
├── PLAN.md # Project plan and development checklist
└── REVIEW.md # Architecture review and suggestions
```
## Build & Development
```bash
# ─── Docker (recommended for dev) ───
docker compose up -d # Strapi + SQLite, seeds demo data on first boot
docker compose logs -f strapi # View logs (watch for [Seed] messages)
docker compose down # Stop
# Production (PostgreSQL + Redis):
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# ─── Standalone MCP server (root) ───
npm run build # tsc --skipLibCheck && chmod +x build/index.js
npm run watch # tsc --watch
npm run inspector # npx @modelcontextprotocol/inspector build/index.js
# ─── Integration tests ───
npm run test:integration # Run all 8 test suites against running Strapi
npm run test:integration:list # List available test suites
```
There is no test runner configured. There is no linter configured.
## Demo Data (seeded on first boot)
### Users (all password: `Admin1234!`)
| Email | Strapi Role | MCP Role | Persona |
|---|---|---|---|
| admin@example.com | Super Admin | Admin | IT Director |
| editor@example.com | Editor | Publisher | Tech Lead |
| author@example.com | Author | Author | Developer |
### Content (IT company theme)
- **3 Teams**: Frontend Engineering, Backend Engineering, Infrastructure & DevOps
- **5 Projects**: Customer Portal, API Gateway, Data Pipeline v2, Cloud Migration, CI/CD Platform
- **8 Tickets**: bugs, features, tasks across projects with various priorities
- **5 Runbooks**: K8s CrashLoopBackOff, DB Failover, SSL Renewal, Prod Rollback, Alert Triage (Infrastructure)
- **6 Standards**: TypeScript Coding, ADR-001 Streamable HTTP, Git Branching, REST API Design, Security Checklist, Testing Standards (Dev teams)
MCP endpoint: `http://localhost:1337/api/mcp-server/mcp`
## TypeScript Configuration
- **Standalone server** (root): Target ES2022, Module Node16 (ESM), strict, `./src` → `./build`
- **Strapi app**: extends `@strapi/typescript-utils/tsconfigs/server`
- **Plugin**: extends `@strapi/typescript-utils/tsconfigs/server`, source in `server/src/`
## Key Architecture Decisions
- **Streamable HTTP transport** (MCP spec 2025-03-26), not deprecated SSE
- **Server-per-session**: each MCP connection gets its own `McpServer` instance (MCP SDK requires one transport per server)
- **MCP client = Strapi admin user**: each client maps 1:1 to a real Strapi admin user via JWT email claim — no shared service account
- **Native ownership**: `createdBy`/`updatedBy` tracks actual user, enabling Strapi's built-in "own entries" restriction
- **Koa integration**: Strapi uses Koa; MCP SDK used with raw `ctx.req`/`ctx.res`
- **PostgreSQL + pgvector**: Vector search requires PostgreSQL with pgvector extension (dev and prod)
- **ABAC (Attribute-Based Access Control)**: Document-level policy enforcement with classification, department, region, clearance, and tenant isolation
- **Dual embedding providers**: Ollama (dev, nomic-embed-text, 768 dims) / AWS Bedrock (prod, titan-embed-text-v2, 1024 dims)
- **S3/MinIO storage**: Documents stored in S3-compatible storage with presigned download URLs
## Authentication Modes
| Mode | Config | Identity Source | Use Case |
|------|--------|----------------|----------|
| External JWT | `auth: 'jwt'` | `Authorization: Bearer <jwt>` → email → admin lookup | Production with IdP |
| Strapi admin | `auth: 'admin'` (default) | `Authorization: Bearer <strapi-admin-jwt>` → decode → admin user | Direct admin access |
| No auth | `auth: false` | First Super Admin user | Development only |
Two-layer security in JWT mode: `X-MCP-Internal-Secret` (trusted orchestrator) + JWT (user identity).
## Authorization Model
Role matrix enforced before Document Service calls:
| Role | create | read | update | delete | publish |
|------|--------|------|--------|--------|---------|
| Admin | all | all | all | all | all |
| Publisher | yes | yes | yes | no | yes |
| Author | yes | yes | own | own | no |
| Reader | no | yes | no | no | no |
Role derived from: JWT `role` claim (if present) → Strapi admin role mapping → default `Reader`.
Strapi admin role → MCP role mapping:
- Super Admin → Admin
- Editor → Publisher
- Author → Author
- (any other / no role) → Reader
Ownership enforcement: Authors get `createdBy = user.id` filter injected on update/delete.
Multi-tenant: `tenantId` filter injected if JWT has `tenant` claim and content type has `tenantId` attribute.
## MCP Tools (20 production, 25 with dev mode)
Content (collection types): `list_content_types`, `get_entries`, `get_entry`, `create_entry`, `update_entry`, `delete_entry`, `publish_entry`, `unpublish_entry`
Content (single types): `get_single_type`, `update_single_type`, `delete_single_type`, `publish_single_type`, `unpublish_single_type`
Media: `upload_media` (base64, ~750KB), `upload_media_from_path` (file path, 10MB)
Schema & Relations: `get_content_type_schema`, `connect_relation`, `disconnect_relation`, `list_components`, `get_component_schema`
Dev mode only: `create_content_type`, `update_content_type`, `delete_content_type`, `create_component`, `update_component`
## Key Dependencies
- `@strapi/strapi` ^5.12.6 — Strapi 5 framework (in strapi-app)
- `@modelcontextprotocol/sdk` — MCP protocol implementation
- `jose` — JWT verification (plugin dependency)
- `@aws-sdk/client-s3`, `@aws-sdk/s3-request-presigner` — S3 storage (secure-documents)
- `@aws-sdk/client-bedrock-runtime` — Bedrock embeddings + RAG (prod)
- `pdf-parse` — PDF text extraction
- `mammoth` — DOCX text extraction
- `pgvector` — pgvector support utilities
- `pg` — PostgreSQL driver
- `axios` — HTTP client for standalone server
## Docker Services
### Dev (docker-compose.yml)
- **strapi**: Strapi 5 + 3 plugins on port 1337, PostgreSQL/pgvector
- **postgres-db**: PostgreSQL 16 with pgvector extension
- **minio**: S3-compatible storage (ports 9000/9001)
- **minio-init**: Creates `secure-documents` bucket on startup
- Ollama runs on host machine via `host.docker.internal:11434`
### Production (docker-compose.prod.yml overlay)
- **strapi**: Same, with Bedrock embeddings/RAG + real AWS S3
- **postgres-db**: pgvector/pgvector:pg16 (data persisted in volume)
- **redis-cache**: Redis 7 Alpine (session store for multi-instance MCP)
## Environment Variables
### Strapi App
`DATABASE_CLIENT` (postgres), `APP_KEYS`, `ADMIN_JWT_SECRET`, `API_TOKEN_SALT`, `TRANSFER_TOKEN_SALT`, `JWT_SECRET`
### MCP Plugin
`MCP_AUTH_MODE` (false/admin/jwt), `JWT_ISSUER`, `JWT_AUDIENCE`, `JWT_JWKS_URI`, `MCP_SECRET_KEY`
### Secure Documents Plugin
`S3_BUCKET`, `S3_REGION`, `S3_ENDPOINT`, `S3_FORCE_PATH_STYLE`, `S3_PUBLIC_ENDPOINT`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `EMBEDDING_PROVIDER` (ollama/bedrock), `EMBEDDING_MODEL`, `EMBEDDING_DIMENSIONS`, `OLLAMA_ENDPOINT`
### Secure Search Plugin
`RAG_ENABLED`, `RAG_PROVIDER` (ollama/bedrock), `RAG_MODEL`, `AWS_REGION`
### Standalone Server
`STRAPI_URL`, `STRAPI_ADMIN_EMAIL`, `STRAPI_ADMIN_PASSWORD`, `STRAPI_API_TOKEN`, `STRAPI_DEV_MODE`, `LOG_LEVEL`, `STRAPI_CACHE_TTL`
## Secure Document Endpoints
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/secure-documents/documents` | Upload document (multipart) |
| GET | `/api/secure-documents/documents` | List documents (ABAC-filtered) |
| GET | `/api/secure-documents/documents/:id` | Get document metadata |
| PUT | `/api/secure-documents/documents/:id` | Update metadata/policy |
| DELETE | `/api/secure-documents/documents/:id` | Delete document + S3 + vectors |
| GET | `/api/secure-documents/documents/:id/download` | Get presigned download URL |
| POST | `/api/secure-documents/documents/:id/reindex` | Trigger re-indexing |
| GET | `/api/secure-documents/documents/:id/status` | Get indexing status |
## Secure Search Endpoints
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/secure-search/search` | Semantic vector search |
| POST | `/api/secure-search/search/rag` | RAG answer generation |
| GET | `/api/secure-search/search/facets` | Faceted counts (ABAC-filtered) |
## ABAC Policy Model
Documents have a JSON `policy` field controlling access:
```json
{
"allowed_departments": ["engineering", "security"],
"allowed_regions": ["us", "eu"],
"excluded_employmentTypes": ["contractor"],
"min_clearance": 2,
"classification": "confidential"
}
```
Classification hierarchy: public (0) → internal (1) → confidential (2) → restricted (3). User clearance must >= classification level.
## RAG Pipeline
Upload flow: S3 storage → text extraction (PDF/DOCX/text) → sliding window chunking (800 tokens, 100 overlap) → embedding generation → pgvector storage with ABAC metadata per chunk.
Search flow: embed query → pgvector cosine similarity with ABAC pre-filter → Strapi document lookup → server-side ABAC re-check → return scored results.
RAG flow: search chunks → build grounded prompt → call LLM (Ollama/Bedrock) → return answer with source references.
## Development Status
Completed: Strapi app scaffold, demo content types, bootstrap seed, MCP plugin (Streamable HTTP, auth, authorization, ownership), secure-documents plugin (ABAC, S3 storage, RAG pipeline with extraction/chunking/embedding/vector services), secure-search plugin (vector search, facets, RAG answer generation), Docker setup with PostgreSQL/pgvector + MinIO, integration tests (11 suites: 01-08 MCP plugin, 09 document CRUD, 10 ABAC enforcement, 11 search + RAG).
Remaining: production hardening.
## Conventions
- All source in TypeScript with strict mode
- ESM modules throughout (`"type": "module"`) for standalone server; CJS for Strapi plugin
- Plugin follows Strapi 5 plugin SDK structure (`server/src/` convention)
- Content type UIDs follow Strapi convention: `api::singular-name.singular-name`
- Logging via `strapi.log.*` in plugin, custom `log` object in standalone server
- Error handling: tool calls return `{ isError: true, content: [{ type: 'text', text: errorMessage }] }`