Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 3.8k
improvement(mcp): push-driven settings freshness + lifecycle-audit fixes#5842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
da8ede3289c81df8fc2ed85318ac82d32c4b3e6b240b0658cced941c55ba0151815e21File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -42,7 +42,13 @@ const logger = createLogger('McpQueries') | ||
| export type { McpServerStatusConfig, McpTool, StoredMcpTool } | ||
| export const MCP_SERVER_LIST_STALE_TIME = 60 * 1000 | ||
| export const MCP_SERVER_TOOLS_STALE_TIME = 30 * 1000 | ||
| /** | ||
| * Tool discovery is kept fresh by the `list_changed` → SSE push (see `useMcpToolsEvents`), | ||
| * so the query only needs a re-probe-on-visit fallback for servers without push. Matches the | ||
| * server-side cache TTL (`MCP_CONSTANTS.CACHE_TIMEOUT`) — no reference MCP client re-probes | ||
| * more often than its cache; real changes arrive via push regardless of this value. | ||
| */ | ||
| export const MCP_SERVER_TOOLS_STALE_TIME = 5 * 60 * 1000 | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| export const MCP_STORED_TOOL_LIST_STALE_TIME = 60 * 1000 | ||
| export const MCP_ALLOWED_DOMAINS_STALE_TIME = 5 * 60 * 1000 | ||
| @@ -140,6 +146,11 @@ function isServerEligibleForDiscovery(server: McpServer, workspaceId: string): b | ||
| export function useMcpToolsQuery(workspaceId: string) { | ||
| const queryClient = useQueryClient() | ||
| const { data: servers, isLoading: serversLoading } = useMcpServers(workspaceId) | ||
| // Push is intrinsic to consuming the tools query: every surface that reads tools (settings, | ||
| // tool picker, dynamic args, tool selector, canvas block) gets real-time `list_changed` | ||
| // refresh via the shared, reference-counted subscription — so the 5-min stale time is always | ||
| // push-backed and no consumer is left re-probing. | ||
| useMcpToolsEvents(workspaceId) | ||
| /** | ||
| * Skip disabled rows, rows retained from a previous workspace, and OAuth rows | ||
| @@ -192,10 +203,10 @@ export function useMcpToolsQuery(workspaceId: string) { | ||
| const serverId = serverIds[index] | ||
| const status = serverId ? statusById.get(serverId) : undefined | ||
| const persistentlyFailed = status === 'error' || status === 'disconnected' | ||
| // Keep last-known-good tools through a transient failure (React Query retains `data`, the | ||
| // stored status is still healthy) so a populated server doesn't blank — but drop them once | ||
| // the stored status crosses its failure threshold, so the workflow editor stops offering a | ||
| // dead server's stale tools. Matches how reference MCP clients treat transient vs. closed. | ||
| // Keep last-known-good tools while the stored status is still `connected` (React Query | ||
| // retains `data` across a failed refetch, so a populated server doesn't blank on a | ||
| // transient probe error) — but drop them once the stored status leaves `connected` | ||
| // (disconnected/error), so the workflow editor stops offering a dead server's stale tools. | ||
| if (result.data && (!result.isError || !persistentlyFailed)) { | ||
| tools.push(...result.data) | ||
| hasData = true | ||
| @@ -527,6 +538,12 @@ const sseConnections: Map<string, SseEntry> = | ||
| ((globalThis as Record<string, unknown>)[SSE_KEY] as Map<string, SseEntry>) ?? | ||
| ((globalThis as Record<string, unknown>)[SSE_KEY] = new Map<string, SseEntry>()) | ||
| /** Per-workspace flag: has this session ever held a live SSE subscription for it? */ | ||
| const SSE_SUBSCRIBED_KEY = '__mcp_sse_subscribed' as const | ||
| const sseEverSubscribed: Set<string> = | ||
| ((globalThis as Record<string, unknown>)[SSE_SUBSCRIBED_KEY] as Set<string>) ?? | ||
| ((globalThis as Record<string, unknown>)[SSE_SUBSCRIBED_KEY] = new Set<string>()) | ||
| /** Subscribes to `tools_changed` SSE events and invalidates the affected query keys. */ | ||
| export function useMcpToolsEvents(workspaceId: string) { | ||
| const queryClient = useQueryClient() | ||
| @@ -563,7 +580,23 @@ export function useMcpToolsEvents(workspaceId: string) { | ||
| invalidate(serverId) | ||
| }) | ||
| // EventSource fires `onopen` on the initial connect and on every auto-reconnect. Re-sync | ||
| // the workspace whenever we could have missed a `tools_changed` event: on any reconnect, | ||
| // on the first open of a RE-subscription (leaving the tab tears the connection down), and | ||
| // on a first open that only succeeded after an earlier connection error (the initial tools | ||
| // query may have failed during that gap and won't retry itself). Skip only a clean first | ||
| // subscription — the queries fetch fresh on their own initial mount. | ||
| const isResubscribe = sseEverSubscribed.has(workspaceId) | ||
| sseEverSubscribed.add(workspaceId) | ||
| let opened = false | ||
| let erroredBeforeOpen = false | ||
| source.onopen = () => { | ||
| if (opened || isResubscribe || erroredBeforeOpen) invalidate() | ||
| opened = true | ||
| } | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| source.onerror = () => { | ||
| if (!opened) erroredBeforeOpen = true | ||
| logger.warn(`SSE connection error for workspace ${workspaceId}`) | ||
| } | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.