Troubleshooting
This page is organized by symptom. Three distinctions do most of the diagnostic work: errors (the request failed — a typed code like unauthorized or query_parse_failed) versus answer states (the request succeeded and the typed answer reports what published state can support); HTTP-layer failures (401/429/503 before any tool runs) versus tool-layer errors (returned inside the tool result); and a governance outcome (review required, purpose not covered, no exception) versus a malfunction — most surprises in this section are the first. The full code table is at the bottom of this page.
Connection and authentication failuresDirect link to Connection and authentication failures
Every invalid-token case — missing header, malformed header, mistyped token, unknown token, revoked token, expired token — returns the same unauthorized response (HTTP 401), by design. The response never says which case you hit, so work the checklist instead:
- The
Authorizationheader isBearerfollowed by the token:Authorization: Bearer mtt_…— no quotes, no line breaks, nothing else in the value. - The token is intact:
mtt_followed by 64 hex characters. Truncation and stray whitespace are the most common copy-paste failures. - The token shows Active in the Tokens tab of your workspace's MCP module — not Revoked (revocation applies immediately to new requests) or Expired.
- Token values cannot be retrieved after issue — only a hash is stored. If there is any doubt about what your client is sending, issue a fresh token from the Tokens tab (admin or owner role required) and re-register the client.
auth_unavailable (HTTP 503) is different: token verification infrastructure was temporarily unreachable, so your token was never judged. Retry shortly — do not reissue a token over a 503.
To separate network and URL problems from auth problems, hit the health check, which requires no token:
curl -sS https://<workspace-mcp-host>/health
If health fails, the problem is the endpoint URL or your network path, not the token. Your real endpoint is shown on the Connect tab; see Connect an agent.
Empty or "not enough published state" answersDirect link to Empty or "not enough published state" answers
not_enough_published_state is not an error — it is a successful, typed answer saying your workspace's published state cannot support a decision for that asset or scenario. Zero applicable published rows is reported this way on purpose: it is never an empty pass and never an invented decision. Each reason code names its own fix:
| Reason code | What it means | Fix |
|---|---|---|
no_current_publication | The workspace has no current deployment publication at all. | Publish a deployment — see Deployments. |
no_published_instruction_state | A publication exists, but no published instruction rows apply to this asset and scenario. | Write a policy that targets the asset (or extend one to cover the scenario), then deploy and publish — see Policies. |
unresolved_published_target | A published instruction targets something that could not be resolved in the catalog. | Review unresolved targets in Deployments, fix the target reference or catalog state, and republish. |
scenario_unresolved | A free-text use could not be deterministically mapped to a canonical scenario. | Pass a canonical scenario_key explicitly, or align your workspace's scenario labels so the mapper can resolve your phrasing — see Scenarios. |
not_currently_effective | Published rules exist for this asset and scenario, but none is inside its validity window at the evaluation instant. | Check the policy version's spec.validity window — correct it and republish, or wait until it opens. Compare evaluated_at in the answer with the effective_from / effective_until on rules returned by inspect_governance_rules. |
Its sibling state review_required (reason codes decision_requires_review, conflicted_published_state) is likewise a governance outcome, not a malfunction: published rows exist but a human must decide. See Typed answer states for the full model.
An answer that used to be allow is now review_requiredDirect link to an-answer-that-used-to-be-allow-is-now-review_required
Nothing broke; the most likely cause is a missing purpose. On an asset governed by permitted-uses rules, a call that states no purpose_key fails closed — a purpose-blind call never rides a permitted-uses allow. Check the answer's evaluated_purpose: if it is null, add a purpose_key.
Work through these in order:
evaluated_purposeisnull→ add a registrypurpose_key. See Purpose keys.evaluated_purposeis set but the reason says the permitted-uses list does not name it → the stated purpose genuinely is not covered. Either state a purpose the policy names, or extend the policy'spermittedUses.- The reason mentions an entry "outside the purpose registry" → the policy's uses list contains a legacy free string that cannot be resolved. An unmapped entry can never prove coverage. Rewrite it as registry vocabulary if the intent is unambiguous — the policy editor flags these and offers a one-click swap where a canonical key exists.
evaluated_actor_roleisnullon a role-gated asset → the token has no bound role, so role-addressed allows resolve to review. Issue or rotate a token with the role bound; see Connect an agent.
A useful diagnostic: inspect_governance_rules serves rules as authored, with no purpose or actor substitution. Compare it against authorize_use to see exactly which rule the evaluation moved and why.
insufficient_scopeDirect link to insufficient_scope
The token does not carry the scope the tool requires. Today that means one thing: something called request_access with a {read} token.
The refusal happens before the input is parsed, so it tells you nothing about whether your arguments were valid, and it is byte-identical for every tool and token. It is also not metered.
Fix it by rotating the token with the request-lane option enabled (scopes are immutable, so there is no way to add the scope in place) — or by issuing a new token with it. Both are admin/owner actions in the Tokens tab. Remember that rotation mints a new subject identity: the new token cannot see the old one's access requests or use its exceptions.
cursor_expiredDirect link to cursor_expired
A discover_context pagination cursor is no longer usable. One error covers three causes deliberately — the cursor is unknown, it belongs to another workspace, or its pinned publication is no longer current (including because a deployment published mid-walk, or because there is no current publication at all).
Restart discovery without a cursor. Two related mistakes to rule out:
- Not resending your filters. The cursor pins the publication and your position, not the filter set. Resend the same filters with every page.
- Constructing or editing a cursor. It is opaque; pass
next_cursorback verbatim. A shape violation isinvalid_parametersrather thancursor_expired.
Request-lane problemsDirect link to Request-lane problems
| Symptom | Cause | Fix |
|---|---|---|
request_access returns not_eligible / use_not_blocked | The server re-evaluated the cited authorization against current published state and the use is now proceedable. Nothing was written. | Re-run authorize_use and proceed. |
request_access returns not_eligible / open_request_limit | This token already holds 100 live open requests. | Have stewards resolve or withdraw some; then re-file. |
request_access returns asset_not_found | The cited authorization_id does not resolve for this token — unknown, another workspace, or another token's record. All three are indistinguishable. | Cite an authorization_id from a call made with this same token. After a rotation, re-run authorize_use first. |
submitted came back with resubmitted: true | An identical live open request already existed for this token, asset, scenario, and purpose. Yours was absorbed. | Nothing to do; poll the returned request_id. |
note_recorded: false | The workspace's raw-text privacy switch is off, so the note was dropped. | Expected. Enable the switch if your team needs notes, or put the context somewhere else. |
check_request returns asset_not_found for a request you filed | The token was rotated, so the subject changed. | Re-file with the current token, or have a steward resolve the old request from the inbox. |
A retry with satisfied_conditions echoes not_verified | One uniform result covering forged, expired, revoked, foreign-subject, and out-of-scope ids. | Check exception.state with check_request, and confirm the retry uses the same asset, scenario, and purpose as the request. |
can_proceed_now is still false even though an exception verified | The answer carries a role_restricted or ai_restriction condition, or a restriction from another rule family. Exceptions never satisfy those. | Fix the underlying rule or the token's bound role. Never gate on holding an exception_id. |
An unknown purpose_key is an error, not a reviewDirect link to an-unknown-purpose_key-is-an-error-not-a-review
A purpose_key outside the registry — including a case variant, a padded value, or an underscore instead of a dot — is invalid_parameters. The call never runs and nothing is recorded. This is deliberate and is not the same rule as the review outcome for unmapped authored strings in your policies. See Purpose keys.
Quota and rate limitsDirect link to Quota and rate limits
Every tool call — regardless of tool — consumes exactly one metered MCP call against your workspace's plan quota, checked before the tool does any work. Two distinct limits apply:
rate_limited(HTTP 429): the per-token, per-minute request limit was hit. The response carries aRetry-Afterheader; back off and retry. If one shared token serves many agents, issue separate tokens per agent.quota_exceeded(tool-layer): the workspace has used its MCP call quota for the current billing period. Calls resume next period, or on a higher plan — see Plans.
Metering never fails open: if the quota check itself cannot complete cleanly, the call is rejected (as read_failed) rather than allowed through unmetered.
Query validation errorsDirect link to Query validation errors
Both of these mean validate_query_context did not validate the query — treat them as gate failures, never as a pass:
query_parse_failed: the SQL could not be parsed for governed-table extraction. Simplify the statement or split multi-statement input; validate the statement that actually reads governed tables.query_unresolved_identifier: a table identifier could not be fully qualified. Either use three-part names (database.schema.table) in the SQL, or passdefault_databaseanddefault_schemain the tool input so one- and two-part names resolve.
asset_not_foundDirect link to asset_not_found
The referenced asset, column, or decision does not resolve in this workspace. Check, in order:
- Spelling and normalization. Identifiers are lowercase normalized names — match what
discover_contextreturns rather than what your SQL editor displays. - Catalog membership. The asset must exist in the workspace's synced catalog.
asset_not_foundis about resolution, not governance: an asset that resolves but has no published rows returnsnot_enough_published_stateinstead. - The token's workspace. The endpoint is shared; your token selects the workspace. A token from workspace A asking about workspace B's assets gets
asset_not_found— cross-workspace references are indistinguishable from not-found, by design. Confirm the token was issued in the workspace you think you are querying.
Claude Code plugin issuesDirect link to Claude Code plugin issues
The plugin supplies slash commands and workflows; the MCP connection is registered separately with your token. So if /metatate: commands exist but tool calls fail with unauthorized, the plugin is fine — the registration or token is the problem.
- Check the connection from Claude Code with
/mcp— themetatateserver should show as connected. - If the token was revoked, expired, or mistyped at registration, issue a fresh one (Tokens tab), then re-register:
claude mcp remove metatate, then re-run the bundled helper. With--runit reads the token from theMETATATE_MCP_TOKENenvironment variable or prompts with input hidden, and it accepts the base URL or the full/mcpendpoint:
export METATATE_MCP_TOKEN=mtt_...
./plugins/metatate/bin/metatate-cloud-mcp-add \
--url https://<workspace-mcp-host> \
--config-scope user \
--run
- To refresh the plugin itself, run
/plugin marketplace update metatate-claude-plugins, then update or reinstall the plugin. See the Claude Code plugin page for the full install flow.
Where to look in the appDirect link to Where to look in the app
- MCP page, Connect tab — the Metatate Runtime Status card shows whether the canonical tool inventory is registered for your workspace and how many governed tables the current publication serves ("N/A" means the count could not be read or there is no current publication — a useful hint when answers come back as
not_enough_published_state). - MCP page, Tokens tab — each token's Active / Revoked / Expired status and its scopes badge (
ReadorRead + request), admins and owners only. This is the fastest check for aninsufficient_scopefailure. - MCP page, Requests tab (
/[tenantSlug]/mcp?tab=requests) — the per-token MCP call log; useful for confirming a client is reaching the endpoint at all. This is call telemetry inside the MCP module, not the steward queue below. - Activity, Review requests tab (
/[tenantSlug]/activity/review-requests) — the steward inbox for access requests, with the stored evaluation snapshot beside a fresh preview of current state. Admins and owners only; other members do not get the tab. See Review requests. - MCP page, Tools tab — reproduce a failing call. "Live endpoint" mode sends a real request to the deployed endpoint with your pasted token, the exact path your client uses. See MCP tools in the app.
- Activity, Activity log tab (
/[tenantSlug]/activity/log) — token issue and revoke events are recorded in the workspace activity log, which answers "who revoked the CI token, and when". - Activity, Audit trail tab (
/[tenantSlug]/activity/audit-trail) — the durable record of what eachauthorize_useorvalidate_query_contextcall was answered, when you need the receipt rather than the live answer.
Error code referenceDirect link to Error code reference
The server emits errors only from this closed set. Messages are fixed and redacted: they never echo your SQL, token material, or workspace identifiers.
| Code | Layer | Meaning | What to do |
|---|---|---|---|
unauthorized | HTTP 401 | Invalid or missing token; all cases are indistinguishable. | Work the auth checklist; reissue if in doubt. |
auth_unavailable | HTTP 503 | Token verification was temporarily unavailable; not a token verdict. | Retry shortly. |
rate_limited | HTTP 429 | Per-token, per-minute limit exceeded. | Honor Retry-After; split tokens per agent. |
quota_exceeded | Tool result | Workspace MCP call quota exhausted for the billing period. | Wait for the period reset or upgrade — Plans. |
asset_not_found | Tool result | Asset, column, or decision not resolvable in this workspace (including cross-workspace references). | See asset_not_found. |
invalid_parameters | Tool result | Input rejected by the tool schema — a non-canonical scenario_key, a non-registry purpose_key, an unknown key, a malformed discovery cursor, or an explain_why identifier that does not match its kind. | Fix the input against the tool reference. |
insufficient_scope | Tool result | The token lacks the scope the tool requires; refused before input parsing, and not metered. | See insufficient_scope. |
cursor_expired | Tool result | A well-formed discover_context cursor that is unknown, from another workspace, or pinned to a publication that is no longer current. | Restart discovery without a cursor — see cursor_expired. |
not_supported | Tool result | An intentionally unsupported input mode — today, get_decision_context with proposed_use and no asset. | Use a supported input mode. |
query_parse_failed | Tool result | SQL could not be parsed; the query was not validated. | See Query validation errors. |
query_unresolved_identifier | Tool result | A table identifier could not be fully qualified; the query was not validated. | Qualify the name or pass default_database / default_schema. |
read_failed | Tool result | A read or infrastructure failure (redacted), including a metering check that did not complete cleanly. | Retry; contact support if persistent. |
output_validation_failed | Tool result | The tool produced output that failed schema validation; no partial result is returned. | Retry; report if persistent. |
internal_error | Tool result | An internal server error; already logged server-side. It also covers the deliberate durability failures: a decision-evidence write that did not complete on authorize_use / validate_query_context, and a request write that did not complete on request_access. In each case no answer is returned rather than an uncitable or unrecorded one. | Retry; report if persistent. |