Skip to content

improvement(mcp): push-driven settings freshness + lifecycle-audit fixes - #5842

Merged
waleedlatif1 merged 10 commits into
stagingfrom
fix/mcp-settings-push-subscription
Jul 22, 2026
Merged

improvement(mcp): push-driven settings freshness + lifecycle-audit fixes#5842
waleedlatif1 merged 10 commits into
stagingfrom
fix/mcp-settings-push-subscription

Conversation

@waleedlatif1

@waleedlatif1waleedlatif1 commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Aligns the MCP settings surface with the reference-client refresh model and folds in the fixes from a full end-to-end lifecycle audit. One clean PR covering the settings freshness work plus every audit finding worth acting on.

Push-driven freshness (settings page)

  • Subscribe the settings page to the live list_changed → SSE push (useMcpToolsEvents) — the pipeline (connection-manager → pubsub → /api/mcp/events → browser EventSource) already existed but was only mounted in the workflow-editor tool picker. Same shared, reference-counted connection. Real-time tool updates on the settings page.
  • Raise MCP_SERVER_TOOLS_STALE_TIME 30s → 5min (matching the server-side cache TTL) so revisiting leans on push + stored state instead of re-probing. There is no background poll (no refetchInterval); this only affects refetch-on-visit-if-stale — real changes arrive instantly via push.
  • Resync on reconnect AND on re-subscribe — a dropped/reconnected EventSource, or leaving and returning to the tab (which tears the connection down), could miss a tools_changed event; a per-workspace sseEverSubscribed flag re-syncs on the first open of a re-subscription and on every reconnect, while skipping only the session's first subscribe (queries fetch fresh then).

Lifecycle-audit fixes

  • Recovered-server status reconciliation. The per-server tools queryFn refreshed the server list only on error; a server recovering via the stale re-probe showed "tools present but row red" until the list independently refetched. A successful probe against a still-failed server now reconciles the status. (+ corrected an overstated keep/drop comment.)
  • Stale SSRF docstring.validateMcpServerSsrf's docstring described "pin subsequent connections," which no longer holds post validate-at-connect — the returned IP is a policy signal, and redirect/rebind safety comes from per-connect validation + followRedirectsGuarded, not pinning (only the self-hosted private carve-out still pins). Corrected so no one reintroduces a real pin from the text. Doc-only.
  • OAuth callback timeout consistency. The callback bounded only its post-auth steps; loadOauthRowByState, getSession, the server SELECT, and the provider_errorclearState were unbounded. Now wrapped in timedStep so a wedged DB read surfaces as a labeled timeout.

Validated against Claude Code / LibreChat / OpenCode / VS Code + the MCP spec: discover-once + list_changed push, no timer polling, resync-on-reconnect. Three-pass adversarial lifecycle audit found no security escapes, count leaks, or double-release; the findings above are everything worth acting on.

Type of Change

  • Improvement + bug fixes (UX freshness, robustness, doc correctness)

Testing

  • 109 tests green across the MCP query, settings, domain-check, and callback-route suites; tsc + biome clean.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…t over re-probing
The list_changed → SSE push pipeline was already wired end-to-end but only
mounted in the workflow-editor tool picker. Subscribe the settings MCP page to
the same shared, reference-counted EventSource so tool changes reflect in real
time there too — the reference-client model (discover once, refresh on push).
With push now active on the settings page, raise MCP_SERVER_TOOLS_STALE_TIME
from 30s to 5min (matching the server-side cache TTL) so revisiting the page
leans on push + stored state instead of re-probing every connected server.
There is no background poll (no refetchInterval) — this only affects
refetch-on-visit-if-stale; real changes still arrive instantly via push.
@vercel

vercelBot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
docsSkippedSkippedJul 22, 2026 8:02am

Request Review

@cursor

cursorBot commented Jul 22, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Mostly client caching/SSE behavior and OAuth callback timeouts; SSRF doc is comment-only. OAuth timeouts could surface failures earlier on slow DB but improve hang behavior rather than weakening auth.

Overview
MCP tool lists now lean on SSE push instead of frequent re-discovery: useMcpToolsEvents is mounted inside useMcpToolsQuery, so every consumer (settings, tool picker, etc.) shares one reference-counted tools_changed subscription without duplicating useMcpToolsEvents in components like tool-input. MCP_SERVER_TOOLS_STALE_TIME goes from 30s to 5 minutes (aligned with server cache TTL); live updates still come from push, not polling.

useMcpToolsEvents gains reconnect/resubscribe safety: a per-workspace sseEverSubscribed flag plus onopen / pre-open error tracking trigger a workspace invalidate when the client could have missed events (reconnect, tab return, or first connect after an initial SSE error), while skipping a clean first subscription.

The MCP OAuth callback wraps previously unbounded steps (loadOauthRowByState, getSession, server load, provider-error clearState) in timedStep so wedged DB/session work fails with labeled timeouts instead of hanging.

validateMcpServerSsrf docstring is updated to describe the return value as an SSRF policy signal and validate-at-connect guarding, not legacy connection pinning.

Tests add a FakeEventSource, clear shared SSE globals between runs, and count discovery calls explicitly now that the tools query also opens SSE.

Reviewed by Cursor Bugbot for commit 1815e21. Configure here.

@greptile-apps

greptile-appsBot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves MCP settings freshness and lifecycle handling. The main changes are:

  • Moves the shared SSE subscription into the MCP tools query.
  • Resyncs tools after reconnects, re-subscriptions, and pre-open connection errors.
  • Aligns the client tools stale time with the server cache.
  • Adds timeout bounds to OAuth callback operations.
  • Updates MCP query tests and clarifies SSRF documentation.

Confidence Score: 5/5

This looks safe to merge.

  • The initial connection-error path now triggers a workspace resync after recovery.
  • Reconnect and re-subscription paths invalidate the relevant MCP queries.
  • The workflow editor retains SSE updates through its existing MCP tools query.
  • No blocking issues were found in the changed code.

Important Files Changed

FilenameOverview
apps/sim/hooks/queries/mcp.tsCentralizes SSE freshness handling and covers reconnect, re-subscription, and initial connection-error recovery.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsxRemoves the redundant direct SSE subscription now provided by the MCP tools query.
apps/sim/hooks/queries/mcp.test.tsxAdds EventSource test support and resets shared SSE state between tests.
apps/sim/app/api/mcp/oauth/callback/route.tsAdds labeled timeout bounds around OAuth callback reads and cleanup.
apps/sim/lib/mcp/domain-check.tsClarifies how MCP SSRF validation, guarded connections, and IP pinning interact.

Reviews (9): Last reviewed commit: "fix(mcp): drop the success-path status r..." | Re-trigger Greptile

Comment threadapps/sim/hooks/queries/mcp.ts
…'t strand stale tools
A dropped/reconnected EventSource may miss a tools_changed event during the gap,
which — with a longer stale time — could leave the page showing old tools until
a remount or manual refresh. Invalidate the workspace tools on reconnect (never
on the first open), the standard resync-on-reconnect pattern for push clients.
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/hooks/queries/mcp.ts
…losed reconcile
Leaving the settings tab tears down the shared EventSource; remounting created a
new connection whose first open was skipped, so a tools_changed fired while
unsubscribed wasn't reconciled (the 5min stale time also won't refetch on
remount). Track a per-workspace 'ever subscribed' flag: skip resync only on the
session's first subscription (queries fetch fresh then); resync on the first
open of any re-subscription and on every reconnect.
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/hooks/queries/mcp.ts

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit f8fc2ed. Configure here.

…ry + fix stale comment
Lifecycle-audit findings:
- The per-server tools queryFn invalidated the server list only on error. A server
that recovered via the stale re-probe returned fresh tools while the cached status
still showed failed → 'tools present but row red' until the list independently
refetched (a window the 5min stale-time widened). Now a successful probe against a
server the cache still shows non-connected refreshes the list so the row clears.
- Correct the keep/drop comment: tools drop once the stored status leaves 'connected'
(disconnected/error), not at a 3-failure threshold.
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 82d32c4. Configure here.

…uth callback steps
Lifecycle-audit follow-ups (merged-code):
- validateMcpServerSsrf's return docstring described 'pin subsequent connections',
which no longer holds for the public path — the returned IP is a policy signal
selecting the validate-at-connect guarded fetch; redirect/rebind safety comes
from per-connect validation + followRedirectsGuarded, not pinning (only the
self-hosted private carve-out still literally pins). Corrected so a future
reader can't reintroduce a real pin from the misleading text.
- The callback wrapped only the five post-auth steps in timedStep; the earlier
loadOauthRowByState, getSession, and server SELECT (plus the provider_error
clearState) were unbounded, contradicting the 'every awaited step bounded'
invariant. Wrapped them so a wedged DB read surfaces as a labeled timeout.
@waleedlatif1waleedlatif1 changed the title improvement(mcp): subscribe settings page to the live push, lean on it over re-probingimprovement(mcp): push-driven settings freshness + lifecycle-audit fixesJul 22, 2026
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/hooks/queries/mcp.ts
…onnection error
If the initial EventSource connection errors before opening (and the initial tools
query fails with retry:false), the first successful onopen was skipped, leaving the
query stale. Track erroredBeforeOpen so a first open that followed a connection
error also resyncs — only a clean first subscription still skips.
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

1 issue from previous review remains unresolved.

Fix All in Cursor

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0b0658c. Configure here.

The 5min stale time assumed push, but useMcpToolsEvents was only mounted in the
settings page and the tool picker — other consumers of the tools query (dynamic
args, tool selector, canvas block via useMcpToolsQuery/useMcpTools) had no
subscription, so list_changed updates could stay invisible for the full window.
Mount useMcpToolsEvents inside useMcpToolsQuery so every tools consumer gets
real-time push from the shared, reference-counted connection — no consumer is
left re-probing. Removed the now-redundant explicit mounts from the settings
page and tool-input.
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/hooks/queries/mcp.test.tsx Outdated
…obalThis
mcp.ts captures the connections Map and subscribed Set in module consts at import,
so setting the globalThis property to undefined didn't reset what the module uses —
subscription state leaked across tests. Clear the shared instances instead.
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

Comment threadapps/sim/hooks/queries/mcp.ts Outdated
The client-side serversList invalidation on a successful probe assumed the probe
updated the stored status, but a server-side cache hit returns tools without
touching status — so in the failed-cache-delete edge it fired a pointless
refetch. The benefit (instant vs the 60s serversList stale-time for status
recovery) doesn't justify the incorrect assumption; rely on the existing 60s
stale-time + SSE push for status recovery instead. Keeps the corrected keep/drop
comment.
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@cursor review

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 1815e21. Configure here.

@waleedlatif1
waleedlatif1 merged commit fd0d08a into stagingJul 22, 2026
20 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/mcp-settings-push-subscription branch July 22, 2026 17:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@waleedlatif1