fix(mcp): stabilize authenticated tool discovery#5754
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview Cache adapters (memory and Redis) gain Client and errors: header-auth Reviewed by Cursor Bugbot for commit d4bdbe8. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryThis PR orders authenticated MCP discovery results so older requests cannot replace newer state. The main changes are:
Confidence Score: 4/5The database publication predicate needs a precision-safe configuration generation before merging.
apps/sim/lib/mcp/service.ts Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant DB as PostgreSQL
participant Discovery as MCP discovery
participant Cache as MCP cache
DB-->>Discovery: updatedAt .123456 becomes JS Date .123
Discovery->>Discovery: tools/list succeeds
Discovery->>DB: "UPDATE WHERE updatedAt = .123000"
DB-->>Discovery: zero rows updated
Discovery-->>Cache: publication skipped
Discovery-->>Discovery: discovered tools discarded
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant DB as PostgreSQL
participant Discovery as MCP discovery
participant Cache as MCP cache
DB-->>Discovery: updatedAt .123456 becomes JS Date .123
Discovery->>Discovery: tools/list succeeds
Discovery->>DB: "UPDATE WHERE updatedAt = .123000"
DB-->>Discovery: zero rows updated
Discovery-->>Cache: publication skipped
Discovery-->>Discovery: discovered tools discarded
Reviews (1): Last reviewed commit: "fix(mcp): stabilize authenticated tool d..." | 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.
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 c1764a1. Configure here.
Hash connection-affecting server fields and recheck the fresh revision after database CAS misses so metadata edits retain valid tools while URL, credential, and transport changes fail closed.

Summary
Authenticated MCP servers now keep the newest valid discovery result instead of resurfacing stale tools, stale status, or an OAuth prompt from an older request. Static-header authorization failures remain header-auth failures, while OAuth servers that do not advertise dynamic client registration return an actionable 422 directing the user to configure a token.
Discovery publication is ordered end to end. Redis and memory cache adapters allocate a monotonic millisecond mutation token; conditional cache publication and the database
lastToolsRefreshCAS use that exact same token, so retries cannot make the two layers choose different winners. A credential-safe SHA-256 discovery revision covers every connection-affecting field; after a status CAS race, discovery re-reads that revision so metadata-only edits keep valid live tools while connection changes invalidate stale results. Failure counters recompute through an optimistic JSONB CAS when a success races them, and a full cache clear invalidates existing mutation owners before deleting entries.Mutation ownership is retried once. If ordering or an atomic cache transition remains unavailable, freshly fetched tools are returned to the caller but unordered cache/database state is not persisted. If another mutation supersedes the request, discovery reloads the winning positive-cache entry; stale-configuration results with no valid winner still fail closed. Explicit configuration and list-change invalidation remains best-effort during cache degradation.
Bulk discovery awaits each publication before returning, failures persist a zero tool count, and refresh consumes the discovery publication metadata. It re-reads current server metadata, reports unavailable or winner-cache results with the live tool count, and compares the published
lastToolsRefreshtoken instead of a wall-clock timestamp before applying a failure. Raw upstream errors are replaced with allowlisted UI messages plus shared credential-free structural diagnostics.Investigation confirmed
@modelcontextprotocol/sdk1.29 already capturesMcp-Session-Idfrom initialize and replays it ontools/list. The client records the request phase and whether a session id was present without logging the identifier or credentials.Related: #5665 and #5595.
Type of Change
Testing
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/sim bun run --cwd apps/sim test -- lib/mcp 'app/api/mcp/servers/[id]/refresh/route.test.ts'— 357 tests passed across 19 files.bun run check:api-validation:strict— passed with no boundary-policy drift.git diff --check— passed.tools/listwith 47 tools using both the normal and SSRF-pinned transports; the SDK transport carried the session id.bun run --cwd apps/sim type-checkcurrently reaches only unrelated shared-worktree dependency errors in fork audit events,recordAuditBatch, and@google-cloud/storage; no MCP TypeScript errors are reported.Reviewer focus: the unified cache/database publication token, explicit discovery revision, status JSONB CAS loop, Redis conditional-mutation scripts, and degraded-cache response path are the highest-value concurrency review points.
Checklist
Screenshots/Videos
N/A — API and backend behavior only.