Skip to content

Commit c90fcca

Browse files
fix(local-mcp): don't submit before local MCP servers finish loading
useTaskCreation read useLocalMcpCloudServers's data at submit time without checking it had resolved. Submitting a cloud task before the (async, ~/.claude.json-backed) query settled silently shipped importedMcpServers and relayedMcpServers empty, dropping the user's local servers with no error. useLocalMcpCloudServers now returns { servers, isLoading } instead of a bare array; isLoading tracks the query's own isLoading (false while disabled, so it never blocks a caller that isn't waiting on it). useTaskCreation blocks submit with a toast while loading; McpServersView takes the servers field unchanged.
1 parent cf5bff5 commit c90fcca

3 files changed

Lines changed: 23 additions & 4 deletions

File tree

packages/ui/src/features/local-mcp/useLocalMcpCloudServers.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,21 @@ import { useQuery } from "@tanstack/react-query";
99
// Stable identity so consumers' memo deps don't churn while data is absent.
1010
const NO_SERVERS: LocalMcpCloudClassification[] = [];
1111

12+
export interface LocalMcpCloudServersResult {
13+
servers: LocalMcpCloudClassification[];
14+
/** True only during the initial fetch while enabled — false once resolved,
15+
* and false while disabled, so a disabled query never blocks a caller. */
16+
isLoading: boolean;
17+
}
18+
1219
/**
1320
* The user's local (~/.claude.json) MCP servers classified by cloud
1421
* availability. Empty on hosts without a local workspace (web/mobile — the
1522
* service is only bound on desktop).
1623
*/
1724
export function useLocalMcpCloudServers(
1825
enabled: boolean,
19-
): LocalMcpCloudClassification[] {
26+
): LocalMcpCloudServersResult {
2027
const service = useServiceOptional<LocalMcpImportService>(
2128
LOCAL_MCP_IMPORT_SERVICE,
2229
);
@@ -28,5 +35,5 @@ export function useLocalMcpCloudServers(
2835
staleTime: 30_000,
2936
});
3037

31-
return query.data ?? NO_SERVERS;
38+
return { servers: query.data ?? NO_SERVERS, isLoading: query.isLoading };
3239
}

packages/ui/src/features/mcp-servers/components/McpServersView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export function McpServersView() {
7777
reauthorizePending,
7878
} = useMcpServers();
7979

80-
const localServers = useLocalMcpCloudServers(true);
80+
const { servers: localServers } = useLocalMcpCloudServers(true);
8181

8282
useEffect(() => {
8383
const refreshMcpState = () => {

packages/ui/src/features/task-detail/hooks/useTaskCreation.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,8 @@ export function useTaskCreation({
195195
additionalDirectoriesOverride ?? defaultAdditionalDirectories;
196196
// Importable local MCP servers for cloud runs, self-fetched like the
197197
// additional-directory defaults above rather than threaded in by callers.
198-
const localMcpServers = useLocalMcpCloudServers(workspaceMode === "cloud");
198+
const { servers: localMcpServers, isLoading: localMcpServersLoading } =
199+
useLocalMcpCloudServers(workspaceMode === "cloud");
199200
const taskService = useService<TaskService>(TASK_SERVICE);
200201
const clearTaskInputReportAssociation = useTaskInputPrefillStore(
201202
(s) => s.clearReportAssociation,
@@ -240,6 +241,16 @@ export function useTaskCreation({
240241
return false;
241242
}
242243

244+
// The local MCP server classification is fetched lazily on entering cloud
245+
// mode; submitting before it resolves would silently drop importedMcpServers/
246+
// relayedMcpServers below instead of including the user's local servers.
247+
if (workspaceMode === "cloud" && localMcpServersLoading) {
248+
toast.error("Still checking your local MCP servers", {
249+
description: "Try again in a moment.",
250+
});
251+
return false;
252+
}
253+
243254
// Confirm a couple of worktree branch situations before starting the
244255
// task. Done before the pending view so a dialog (and a cancel) don't
245256
// leave a half-started task on screen. Reusing an existing worktree takes
@@ -534,6 +545,7 @@ export function useTaskCreation({
534545
bluebirdEnabled,
535546
personalChannel?.id,
536547
localMcpServers,
548+
localMcpServersLoading,
537549
clearTaskInputReportAssociation,
538550
invalidateTasks,
539551
onTaskCreated,

0 commit comments

Comments
 (0)