Debugging checklist
TL;DR — When something is broken and you don’t know where, work top-down through this checklist. Each step is a 30-second check. Nine of ten broken deployments turn out to be discovery misconfig, resource-URI mismatch, or a missing env var — this page catches those fast.
Step 1 — Is AuthPlane reachable?
$ curl -s http://localhost:9000/health
{"status":"ok","time":"2026-07-01T00:00:00Z","db":"ok"}
- Response OK → AuthPlane is running and can reach its DB. Next step.
- Connection refused → AuthPlane isn’t running. Check
docker ps/systemctl status authserver/ pod status. "status":"degraded","db":"error"→ DB unreachable. For Postgres check the DSN + network reachability; for SQLite check the file exists and is writable by the AuthPlane user.
Step 2 — Does discovery work?
AS metadata:
$ curl -s http://localhost:9000/.well-known/oauth-authorization-server | jq .issuer
"http://localhost:9000"
issuermatches yourAUTHPLANE_SERVER_ISSUER→ OK.issuerdoesn’t match → wrongAUTHPLANE_SERVER_ISSUERat boot; every downstream check (JWTissvalidation) will fail.- 404 → wrong URL. Discovery is on port 9000 (public), not 9001.
JWKS:
$ curl -s http://localhost:9000/.well-known/jwks.json | jq '.keys | length'
2
- ≥ 1 keys → OK.
current+ optionallyprevious. - 0 keys → key store misconfig. Check
signing.key_storeand the key path/DB/Vault backing it.
PRM (on your MCP server, not AuthPlane):
$ curl -s https://mcp.example.com/.well-known/oauth-protected-resource | jq .
- Full JSON with
resource,authorization_servers,scopes_supported→ OK. - 404 → SDK didn’t mount PRM handler (Go:
http.Handle(adapter.WellKnownPRMPath(), adapter.ProtectedResourceMetadataHandler()); Python/TS auto-mount). authorization_serversempty → wrongissuerpassed to your SDK.
Step 3 — Do scopes advertise correctly?
$ curl -s http://localhost:9000/.well-known/oauth-authorization-server | jq .scopes_supported
["tools/read","tools/write"]
Should list every scope from every resource you’ve registered. Empty = no resources registered, or scopes not declared on any resource. Check: authserver admin resource list.
Step 4 — Is your token signed by the expected key?
Decode the JWT (never do this in production without redacting logs):
$ echo "<access_token>" | cut -d. -f1 | base64 -d | jq .
{
"alg": "ES256",
"typ": "at+jwt",
"kid": "kid_current_abc"
}
kidshould be in the JWKS from step 2. If not → JWKS is stale on the SDK side (rare; refresh happens every 5 min).typ: at+jwt→ RFC 9068 compliant (AuthPlane’s default).
Step 5 — Does the token’s aud match your resource?
$ echo "<access_token>" | cut -d. -f2 | base64 -d | jq '{iss, aud, sub, scope, exp}'
{
"iss": "http://localhost:9000",
"aud": ["http://mcp.example.com/mcp"],
"sub": "user-42",
"scope": "tools/read",
"exp": 1708762500
}
auddoesn’t contain your resource URI → RFC 8707 mismatch. Client didn’t sendresource=<your-uri>on/oauth/authorize. The fix is on the client (or your PRM advertisement) —AUTHPLANE_OAUTH_REQUIRE_SCOPEonly affects scope defaulting, not audience enforcement, and toggling it will not repair an audience mismatch.issdoesn’t match AuthPlane’s issuer → wrong AuthPlane URL in the SDK’s config.scope— check it contains whatrequire_scope()demands in your tool handler.exp— is the token expired? Check clock skew (default tolerance is 30 s).
Step 6 — Is your MCP server’s SDK reading the token?
Structured slog logs (JSON format) from your SDK:
level=INFO msg="verified" sub=user-42 scope=tools/read aud=http://mcp.example.com/mcp
- Log present → SDK is reading + verifying tokens.
- No log at all → SDK isn’t wired into the request path. Check middleware ordering. Ensure
bearerAuth(or equivalent) runs BEFORE your tool handlers. - Log with error → follow the error code — likely one of the top-20 in Common errors.
Step 7 — Are metrics telling you a story?
$ curl -s http://localhost:9001/metrics | grep -E "^(authserver|authplane)_" | head -20
authserver_tokens_issued_total 42
authserver_auth_denied_total{reason="invalid_pkce"} 3
authplane_dpop_proofs_validated_total 15
authplane_dpop_proofs_rejected_total{reason="replay"} 0
auth_denied_total{reason="…"}growing → clients failing for a reason you can query. Filter byreasonlabel to narrow.dpop_proofs_rejected_total> 0 → DPoP clients hitting issues (proof format, replay, htu mismatch). See Guides: Enable DPoP → troubleshooting.upstream_token_refresh_total{outcome="failed"}> 0 → broker refresh failing (user revoked upstream, or provider is down).
Step 8 — Correlate a specific failing request end-to-end
Every AuthPlane log line carries request_id, trace_id, span_id. Grab one from a failure:
level=ERROR msg="token issuance failed" error=ErrInvalidPKCE code=invalid_grant
client_id=my-client resource=http://mcp.example.com/mcp
request_id=r_abc123 trace_id=t_def456 span_id=s_ghi789
Query all logs with that trace_id:
$ kubectl logs -l app=authplane --tail=1000 | grep trace_id=t_def456
Or if you have OTEL traces wired (Guides: Monitoring → OpenTelemetry), find the trace in your tracing backend — every span from HTTP handler → service → storage → crypto is there.
Step 9 — Reproduce with MCP Inspector
If nothing above turns up the issue, run MCP Inspector against your MCP server:
$ npx @modelcontextprotocol/inspector https://mcp.example.com/mcp
Inspector shows every request/response of the OAuth flow inline — see Guides: Testing with MCP Inspector. If Inspector works but your target client (Claude Code, Cursor, etc.) doesn’t, it’s a client-specific quirk — see Guides: Connect an MCP client → Known Claude Code quirks and authserver/docs/compatibility.md.
Step 10 — Check the audit log
Every security-relevant event writes an audit_events row. There’s no admin audit … CLI subcommand — query via the Admin REST API:
$ curl -s "http://localhost:9001/admin/audit?since=2026-07-01T00:00:00Z&user_id=user-42&limit=20" \
-H "Authorization: Bearer $AUTHPLANE_ADMIN_API_KEY"
You’ll see token_issued, token_revoked, consent_granted, broker_grant_updated, etc. — the full sequence of what actually happened for that user in AuthPlane.
When to escalate
If you’ve gone through all ten steps and can’t pin the failure — file a bug at github.com/authplane/authserver/issues with:
- Version (
authserver versionor the container tag) - Redacted config
- Redacted request/response of the failing OAuth call
- Log lines with
trace_id - Metric values from
/metricsif relevant
See Getting help.
Related
- Common errors — top-20 with fixes
- Reference: Errors — complete error catalog
- Guides: Monitoring — logs + metrics wiring
- Guides: Testing with MCP Inspector — step-9 in depth
- Getting help