Going further

Troubleshooting

Look up a response code, read mcp:doctor, and fix the failures people actually hit.

On this page

Start here, every time.

php please mcp:doctor

It prints the endpoint and auth mode, then runs every check without stopping, so all problems are named at once. Each [FAIL] carries the same remedy text the endpoint answers with at runtime.

Look up a response#

Pick what the endpoint answered.

What did the endpoint answer?
HTTP 401 WWW-Authenticate: Bearer …
Unauthenticated

The token is missing, malformed, expired, or revoked, or its user was deleted. Every case answers the same way, so nobody can probe which tokens exist. In OAuth mode, Passport rejected the bearer or its user no longer resolves.

{
  "error": "Unauthenticated."
}
What to do
  • Check the header is Authorization: Bearer mcp_… and the token is complete.
  • Run php please mcp:tokens to see whether it still exists and when it expires.
  • Issue a new one with php please mcp:token [email protected].

What the doctor checks#

CheckWhat it looks at
Kill switch and mountingWhether MCP is enabled and the route is actually mounted. “Enabled but not mounted” means the route, server, or middleware config has the wrong shape. The addon keeps your site up and reports Statamic MCP failed to mount; run php please mcp:doctor with the cause, and until the config is fixed, requests to the endpoint reach Statamic’s frontend route.
Route cacheWhether a cached route was built for another auth mode, or holds no MCP route at all. It can’t tell a cache from v1.0 apart from a current one, so rebuild the cache after updating.
Server classWhether server names a subclass of the MCP server.
MiddlewareEvery entry has to be a string that resolves to a class, alias, or group. A typo mounts fine and then returns a 500 on every request.
APP_URLWarns on the Laravel default of http://localhost, and on plain http://, because bearer tokens over http travel unencrypted.
Leftover migrationsWarns about a second *_statamic_auth_tables.php, left by an interrupted OAuth setup, which would crash php artisan migrate.

In token mode it also checks these.

CheckWhat it looks at
Token storeThat it’s writable, including the case where tokens.yaml was created by a different user, such as root.
Corrupt fileA corrupt tokens.yaml is reported with a recovery path instead of crashing. Meanwhile every request to the endpoint answers 500, so nobody gets in.
Token countsSeparates active tokens from expired and orphaned-user ones. “5 tokens issued but none are active” is a locked door that looks configured.

In OAuth mode it checks these instead.

CheckWhat it looks at
PassportWhether it’s installed.
DatabaseWhether it’s reachable. If it isn’t, that’s the only OAuth check reported.
Encryption keysWhether they’re available, naming the source: environment config, database, key files, or pending provision. There’s a dedicated failure when the stored key can’t be decrypted because APP_KEY changed.
TablesPassport’s tables are migrated, with string user_id columns.
Consent viewA consent view is bound.

There’s no user-repository check, because file users work in OAuth mode.

Common problems#

The agent can’t see a collection#

Three things can hide it. Check them in this order.

  1. Is the handle in resources? A hidden handle reads as not found.
  2. Does the user’s role have view {collection} entries? statamic_overview lists only what the user may view.
  3. On multi-site, does the role have access {site} site?

The agent says a tool doesn’t exist#

Tool [entries_delete] not found. means the tool isn’t registered. Delete tools need deletes, write tools disappear under read_only, and entries_localize only exists on multi-site. A client can also be holding an old tool list, so reconnect it.

Edits aren’t showing on the site#

On a revision-enabled collection, an edit to a live entry is a working copy. Look at result in the response, or has_working_copy on entries_get. It goes live with entries_publish.

A write was refused with an unknown field#

The key isn’t in the entry’s blueprint. The error lists the valid handles. slug, and date on dated collections, are top-level parameters and don’t go inside data.

A scheduled entry isn’t going live#

Check date_behavior in statamic_overview. The entry goes live without cron, because Statamic checks the date on every request, but the static cache and search index need schedule:run and a queue worker to refresh at that minute.

Every OAuth client has to reconnect#

The signing key changed. That happens when APP_KEY was rotated or when someone regenerated keys. See If APP_KEY changes.

Concurrent token writes#

Token writes from the command line and the Control Panel are serialized behind one of Laravel’s cache locks, so the default cache store must support atomic locks and be shared by every writer. Array and null cache stores, or per-server caches on multi-server deploys, degrade to no cross-process locking.

Use the MCP Inspector#

You can point the MCP Inspector at your server. This command comes from laravel/mcp.

php artisan mcp:inspector mcp/statamic