Return 401 instead of 500 when the Kernel API rejects a credential - #156
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Verified against the deployed preview with a credential the Kernel API rejects — same request, both environments: The client now gets a status and a body that name the actual problem, instead of an empty server error. |
yummybomb
left a comment
There was a problem hiding this comment.
the core 401 fix is solid, just a few comments
blockers
- only upstream 401 should become
invalid_token. Kernel returns 403 for a valid credential scoped to another project and 404 for a missing/inactive project. Mapping either to 401 can cause clients to discard a valid credential. Map 403 toinsufficient_scopeand keep 404 distinct. - every statusless exception currently becomes a 503, including unexpected plain errors such as response-parsing bugs. Special-case known SDK transport/timeout errors as unavailable, but classify unknown exceptions as invalid so genuine server bugs still surface as 500s.
non-blocking
unavailabledrops the upstream status, so 408/429/5xx emitupstream_status_code: undefined. PreservestatusCode?: numberon that variant; add a separate error kind if timeout versus abort needs distinction.
test coverage
route.test.tsonly tests the exported response mapper. It doesn’t execute the handler, verify thatinvalidstill throws, or provecaptureMcpConnectionScopeFailurefires. Please add handler-path coverage and a focused analytics capture test.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3f3cc66. Configure here.
|
addressed all four review points:
|
yummybomb
left a comment
There was a problem hiding this comment.
lgtm — the failure classification, response mappings, analytics, and handler-path coverage all look solid.

Summary
Scope resolution collapsed three different outcomes into
null, and the route treated all of them as a server fault:So when
/auth/contextanswers 401, the caller gets a 500. A client with an invalid, revoked, or wrong-project credential is told the server is broken, and their credential problem is counted as our 5xx rate.Over a 3h window,
/auth/contextcalls from the MCP server returned 200 × 8,110 and 401 × 1,017, with no 5xx and no timeouts. In one 30-minute window there were 110 upstream 401s against 110 occurrences ofUnable to resolve Kernel connection scope— one 401 produces one 500. Measured at 20.4% error rate on/[transport]during one alert window.What changed
resolveMcpConnectionContextnow reports why it has no scope instead of returningnull:rejectedinvalid_tokenunavailableRetry-AfterinvalidTransient classification is unchanged — 408, 429, 5xx, and errors with no status are still transient, and still prefer a cached scope when one exists. The only behavior change is what happens when there is nothing to fall back on.
invaliddeliberately still throws. A response we cannot normalize is our bug, and it should keep surfacing as one.Observability
Scope resolution runs before the request reaches the instrumented
McpServer, so these failures emitted no$mcp_*event at all — error rate, tool calls, and session counts looked normal through a spike, and the only signal was a Vercel error-anomaly alert. Addedmcp_connection_scope_failure, following the existingcaptureOAuthTokenExchangepattern, carrying the outcome, credential type, and upstream status. No token, credential, or org identifier is recorded.Tests
bun test— 210 pass. Resolver classification is covered per status (401, 403 → rejected; 408, 429, 500, 502, 503 → unavailable), along with the existing cache and fallback behavior. The route-level mapping is asserted on the response itself: status,WWW-Authenticate,Retry-After, and body. The classification assertions fail against pre-fix code.Note
All rejections answer
invalid_token, including 403. Every rejection observed in production is a 401, so a separateinsufficient_scopemapping would be untested guesswork; worth revisiting if 403s appear.Note
Medium Risk
Changes authentication and scope-resolution error handling on every MCP request; behavior is intentional but affects client-visible status codes and cache invalidation on rejections.
Overview
Fixes MCP transport requests where a Kernel
/auth/contextrejection (especially 401) was turned into a 500 because scope resolution only returnednulland the route always threw.resolveMcpConnectionContextnow returns a discriminated result (ok|rejected|unavailable|invalid) withclassifyAuthContextErrormapping upstream 401/403/404 torejected, retryable statuses and transport errors tounavailable, and malformed/unclassified cases toinvalid. Stale-cache fallback on transient failures is unchanged.The MCP route uses
connectionScopeFailureResponseto answer clients with 401invalid_token, 403insufficient_scope, 404project_not_found, or 503 +Retry-After; onlyinvalidstill throws.captureMcpConnectionScopeFailurerecords outcome, credential type, and upstream status (no secrets).Route and resolver tests cover status mapping, analytics capture, and classification per HTTP status.
Reviewed by Cursor Bugbot for commit 5ca48b7. Bugbot is set up for automated code reviews on this repo. Configure here.