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

  1. php artisan mcp:token --abilities=... on production (or local against prod—prefer SSH)
  2. Duplicate token into Cursor MCP config locally first
  3. Test: upsert draft post → delete via Filament
  4. Test: media/upload on that draft
  5. Revoke old token in personal_access_tokens
  6. 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

  1. Revoke in Sanctum immediately
  2. Review mcp_audit_logs for content_upsert payloads
  3. Restore DB if destructive (postgres-backups runbook)
  4. 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.