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:

Agents should not be able to:

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:

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:

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:

  1. Default new posts to draft unless explicitly scheduling
  2. Set published_at for future organic cadence—not instant flood
  3. Avoid deleting categories with posts (server returns 422)

Humans review in Filament before high-risk changes when unsure.

Media uploads

Logging and alerting

RecordMcpAuditLog should capture: user id, token id, tool, module, slug/id, IP, timestamp.

Alert on:

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

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.