How do you secure an MCP content API in Laravel with Sanctum?
How do you secure an MCP content API in Laravel with Sanctum?
Short answer: give agents Sanctum personal access tokens with narrow abilities (not user passwords), require a tool header on every write, throttle endpoints, audit log mutations, and keep read-only SQL behind a separate ability with row limits. Never expose Filament admin credentials to MCP.
Aviwebsquad uses this model so Cursor can content_upsert blog posts while production stays defensible if a token leaks.
Threat model (honest)
Agents can:
- Publish or alter public content (reputation harm, SEO spam)
- Upload media (storage cost, malware optics)
- Run read-only SQL (data exfiltration if too broad)
Agents should not be able to:
- Change user passwords or roles
- Run arbitrary
UPDATE/DELETESQL - Bypass moderation on destructive deletes
Design for leaked token and over-eager agent scenarios—not only malicious humans.
Token design
Separate tokens by capability
| Ability | Allows |
|---|---|
mcp:cms.read |
Read helpers, schema resources |
mcp:cms.write |
content_upsert create/update |
mcp:media.upload |
Spatie media attach |
mcp:model.write |
Fillable-safe Eloquent mutator |
mcp:db.read |
SELECT / EXPLAIN only |
Generate with your project command (e.g. php artisan mcp:token) and label tokens in Sanctum: cursor-prod-june, local-dev.
Rotate after travel, contractor access, or any repo leak.
Never reuse the web session
Personal access tokens are for automation. Filament stays cookie-session + 2FA for humans.
Route middleware stack
Typical write route:
auth:sanctum
CheckAbilities:mcp:cms.write
throttle:30,1
RecordMcpAuditLog
Reads throttle tighter if needed. Fail closed: missing ability → 403, not degraded guest access.
X-Mcp-Tool header
Every request includes which logical tool ran (content_upsert, db_query, …). The audit log stores it.
Benefits:
- Forensics: “who bulk-deleted categories at 2am?”
- Policy: block unknown tool strings at the edge
- Training: reviewers see agent behavior patterns
content_upsert vs raw SQL
Prefer content_upsert modules (blog_post, page, blog_category) with validation per module. Raw model_mutate is for edge cases with a fillable allowlist.
db_query is SELECT-only server-side with:
- Statement timeout (e.g. 3000ms)
- Row cap (e.g. 200)
- Optional PII redaction unless
mcp:db.pii
Never grant mcp:db.read to a token that also has broad write if you can avoid it—split read/write tokens.
Publishing guardrails
Even with write access, agents should:
- Default new posts to
draftunless explicitly scheduling - Set
published_atfor future organic cadence—not instant flood - Avoid deleting categories with posts (server returns 422)
Humans review in Filament before high-risk changes when unsure.
Media uploads
- Validate MIME and size server-side
- Allowlist remote image fetch domains if agents pull stock photos
- Store under Spatie collections (
featured,og) with conversions
Logging and alerting
RecordMcpAuditLog should capture: user id, token id, tool, module, slug/id, IP, timestamp.
Alert on:
- Spike in
deleteoperations - First use of
mcp:db.readfrom a new IP - Repeated 422 validation failures (agent loop)
FAQ
Is Sanctum enough for production?
For single-user/solo automation, yes—with abilities, throttling, and rotation. Multi-tenant or compliance-heavy apps may add OAuth clients, IP allowlists, or HMAC request signing.
Should agents use the same token locally and in prod?
No. Separate tokens, separate abilities. Local dev tokens get mcp:db.read; prod writer tokens often omit DB access.
Can I disable MCP entirely in production?
Yes—simply do not create tokens. The site runs fine with Filament-only editing.
What if an agent publishes spam?
Revoke token in Sanctum, revert post via Filament activity log / soft deletes, file Search Console URL removal if indexed.
Does MCP replace Fortify auth?
No. MCP is machine-to-machine. Public readers still use Fortify/session; admins use Filament.
Implementation checklist
- Sanctum abilities documented (
REF_AUTH_ABILITIESin MCP server) - Write routes require ability + audit middleware
-
db_queryrejects non-SELECT - Tokens rotatable without deploy
- Filament shows human actions separately from MCP audit table
-
.envnever committed; tokens not inmcp.jsonin public repos
Bottom line
MCP content automation is worth it when treated like API keys, not magic. Sanctum abilities + tool auditing + disciplined publish fields let Aviwebsquad ship agent-assisted articles without handing agents the keys to the whole kingdom.