fix(mcp): stabilize authenticated tool discovery#5754
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview Tool discovery and refresh are reworked around a shared monotonic cache mutation id (memory + Redis) that also drives database Supporting changes: HTTP/2 on pinned MCP fetch, redacted structural error diagnostics in logs, allowlisted user-facing failure messages, and workspace cache invalidation when auth type or transport changes on server update. Reviewed by Cursor Bugbot for commit b9dfe48. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryThis PR stabilizes authenticated MCP tool discovery and publication. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (4): Last reviewed commit: "Address PR review feedback (#5754)" | Re-trigger Greptile |
- preserve typed static-header authentication failures\n- order discovery publication by start time\n- atomically publish tools and failure cache state
Match configuration generations within the JavaScript millisecond window so PostgreSQL microseconds do not suppress valid publication.
Addressed in 89dce25: both status-publication predicates now match the JavaScript millisecond window, accepting PostgreSQL sub-millisecond precision while still rejecting the next generation. A boundary regression covers the behavior. |
- fall back to best-effort cache writes when mutation ordering is unavailable\n- share bounded redacted MCP error diagnostics across client and service
Retry cache mutation ownership before publishing discovery state, avoid unordered publication that older writers can supersede, and keep explicit invalidation best effort.
Use timestamp-based mutation tokens for both cache ownership and database CAS ordering, and invalidate Redis mutation owners before deleting entries during a full clear.
Recompute consecutive failures after status CAS conflicts, reload winning cached tools for superseded discoveries, and return live tools without unordered publication when cache ordering is unavailable.
Expose discovery publication metadata to refresh callers, report live tools during cache degradation, and compare lastToolsRefresh mutation tokens instead of wall clocks when preserving a newer success.
Probe mutation ownership after database CAS misses, keep valid live tools for metadata-only edits, and invalidate ownership for transport and auth-type changes.
- Clear MCP caches when implicit OAuth promotion changes the resolved auth type - Cover unchanged-client-ID promotion with a lifecycle regression test
- Fall back safely when ordered MCP cache invalidation is unavailable - Surface sanitized concrete refresh errors without masking OAuth-pending state
- Retry failure and OAuth status publication across metadata-only races - Keep losing winner-cache refreshes from syncing workflows
- Retry successful status publication across metadata-only races - Preserve superseded and unavailable discovery semantics
- Preserve OAuth-pending state when a status reread is stale - Keep concrete refresh errors sanitized and visible
- Make cache invalidation retries ownership-safe - Preserve proven newer successes for superseded refreshes
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default 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 0fb9a4e. Configure here.

Summary
Authenticated MCP servers that advertise OAuth can now be added and refreshed with an explicitly configured static
Authorization: Bearer ...credential without being diverted into OAuth. The previous fix made the pre-save connection test try the configured header, but runtime discovery still mapped every SDKUnauthorizedErrorto “authorization required.” This PR makes that classification depend on the server's configured auth type, so static-header servers stay on the normal credential path and only OAuth-configured servers enter OAuth.OAuth-pending connections now remain actionable in Workspace Settings: the persistent server status says
OAuth authorization required, and a refresh that cleanly returns to that state saysAuthorization requiredinstead of the genericFailed/Not Connected. Concrete connection errors still take precedence, so timeouts and other failures are not mislabeled as an OAuth prompt.Discovery publication is ordered end to end. Redis and memory cache adapters allocate a monotonic millisecond mutation token; conditional cache publication and the database
lastToolsRefreshcompare-and-swap use that same token, so retries, failures, successes, and invalidations choose one winner across both layers. A credential-safe discovery revision covers every connection-affecting field, preserving valid tools across metadata-only edits while failing closed after URL, credential, auth-type, or transport changes.Each retry acquires fresh mutation ownership. Successful, failed, and OAuth-pending status publication share a bounded retry for metadata-only edit races: every attempt rechecks cache ownership and the discovery revision before publishing against the latest row token. If ordering or an atomic cache transition is unavailable, freshly fetched tools may be returned to the current caller but unordered cache/database state is not persisted. Successful discovery returns its exact publication token, so workflow schemas sync only while the post-discovery reread still belongs to that winning
publishedrefresh; later success, failure, or invalidation barriers take precedence, andwinner-cache,superseded, and degradedunavailableresults cannot propagate as winners. Cache invalidation retries reacquire fresh ownership, publish a database barrier only after an atomic delete wins, and never fall back to an unordered delete that could erase a newer discovery.Refresh responses now prove that a newer successful discovery actually won before preserving connected state after a failure: both
lastToolsRefreshandlastConnectedmust advance beyond the request's original snapshot. Invalidation advances the publication token but notlastConnected, so it can no longer hide a real discovery failure behind a stale connected row. Failure counters also recompute through an optimistic JSONB compare-and-swap when success races them.Security-sensitive paths remain bounded: MCP targets still pass domain and SSRF validation, public targets are DNS-pinned when policy permits, and HTTP/2 negotiation does not relax that pinning. Logs contain header names and structural diagnostics but never values, session IDs, or raw upstream bodies, while UI/API errors come from an allowlist. OAuth servers without dynamic client registration return an actionable 422 that directs users to configure a token. The settings copy change consumes only the existing auth type and sanitized error state; it does not alter or expose credentials, headers, tokens, or upstream responses.
Related: #5665 and #5595.
Type of Change
Testing
bun run --cwd apps/sim test -- lib/mcp app/api/mcp 'app/workspace/[workspaceId]/settings/components/mcp'— 454 tests passed across all 29 MCP suites, including static Bearer classification, concurrent publication, repeated metadata-only races, ownership-safe invalidation retries, degraded-cache behavior, publication-token-gated workflow sync, secret-safe diagnostics, SSRF pinning, invalidation-only refresh races, and OAuth-specific settings status behavior.bun run check:api-validation:strict— passed with all 964 routes Zod-backed and no boundary-policy drift.git diff --check— passed.tools/listwith 47 tools using both normal and SSRF-pinned transports; the SDK transport carried the session ID without logging it.bun run --cwd apps/sim type-checkreaches only unrelated shared-worktree dependency errors in fork audit events,recordAuditBatch, and@google-cloud/storage; no MCP TypeScript errors are reported.Reviewer focus: auth-type-aware handling of typed 401s, OAuth-pending UI status with concrete-error precedence, unified cache/database mutation ownership, bounded same-revision status retries, fresh-token invalidation retries, Redis conditional-mutation scripts, winner-only workflow synchronization, fail-closed degraded-cache behavior, and the two-timestamp proof for a racing successful refresh.
Checklist
Screenshots/Videos
N/A — status-copy-only UI change with no layout change.