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

@waleedlatif1 waleedlatif1 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_error clearState 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

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 22, 2026 8:02am

Request Review

@cursor

cursor Bot 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-apps Bot 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

Filename Overview
apps/sim/hooks/queries/mcp.ts Centralizes 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.tsx Removes the redundant direct SSE subscription now provided by the MCP tools query.
apps/sim/hooks/queries/mcp.test.tsx Adds EventSource test support and resets shared SSE state between tests.
apps/sim/app/api/mcp/oauth/callback/route.ts Adds labeled timeout bounds around OAuth callback reads and cleanup.
apps/sim/lib/mcp/domain-check.ts Clarifies 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 thread apps/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
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/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
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/hooks/queries/mcp.ts

@cursor cursor Bot 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
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot 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.
@waleedlatif1 waleedlatif1 changed the title improvement(mcp): subscribe settings page to the live push, lean on it over re-probing improvement(mcp): push-driven settings freshness + lifecycle-audit fixes Jul 22, 2026
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/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
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot 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
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/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
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/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
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot 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 staging Jul 22, 2026
20 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/mcp-settings-push-subscription branch July 22, 2026 17:20
Sign up for free to 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