MCP content API token rotation: a runbook I actually follow
Short answer: Rotate MCP Sanctum tokens by issuing a new token with the same abilities, updating .cursor/mcp.json + CI secrets, verifying content_upsert + media_upload, then revoking the old token—total cutover ~15 minutes.
Foundation: How I secure the MCP content API.
Abilities I grant automation
| Ability | Route |
|---|---|
mcp:cms.write |
content_upsert |
mcp:media.upload |
featured images |
mcp:db.read |
read-only SQL for audits |
mcp:cms.read |
schema introspection |
Never grant mcp:db.pii to content agents unless redaction is verified.
Rotation checklist
php artisan mcp:token --abilities=...on production (or local against prod—prefer SSH)- Duplicate token into Cursor MCP config locally first
- Test: upsert draft post → delete via Filament
- Test:
media/uploadon that draft - Revoke old token in
personal_access_tokens - Grep git for leaked old token (should be none—env only)
Audit what agents changed
MCP middleware logs X-Mcp-Tool per request. When AdSense prep bulk-published posts, I could trace which tool wrote each row—important when undoing a bad batch.
Pair with application rate limits
Sanctum token ≠ DDoS protection. nginx limit_req on /api/v1/mcp/* is still warranted.
FAQ
How often rotate?
90 days or immediately if a token appears in a screenshot/chat.
Separate tokens per agent?
Yes in ideal world—Aviwebsquad uses one editorial token + one read-only CI token.
Related reading
LLMs and your privacy policy if agents process subscriber data (they should not here).
Incident response: leaked token
- Revoke in Sanctum immediately
- Review
mcp_audit_logsforcontent_upsertpayloads - Restore DB if destructive (
postgres-backupsrunbook) - Rotate nginx rate limit if abused
Staging tokens
I do not point agents at production from local .env by default—APP_BASE_URL in MCP config is explicit so mistakes are visible in config review.
FAQ (extended)
OAuth instead of PAT?
Sanctum PAT fits machine agents; OAuth is overkill until third-party integrations exist.
Filament vs API writes?
Humans win conflicts—last write by updated_at; agents should upsert known slugs only.