File size: 2,966 Bytes
3d700dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useSettings } from "#/hooks/query/use-settings";
import SettingsService from "#/api/settings-service/settings-service.api";
import { MCPServerConfig } from "#/types/mcp-server";
import {
  buildMcpServerPatch,
  buildRenameMcpConfigPatch,
  parseMcpConfig,
} from "#/utils/mcp-config";
import { SETTINGS_QUERY_KEYS } from "#/hooks/query/query-keys";
import { clearMcpServerHealth } from "#/api/mcp-health/mcp-health-store";
import { getMcpServerHealthKey } from "#/utils/mcp-server-health-key";
import { toMcpServerName } from "#/utils/mcp-server-name";

// @spec MCP-001 — Sparse mutations preserve sibling servers
export function useUpdateMcpServer() {
  const queryClient = useQueryClient();
  const { data: settings } = useSettings();

  return useMutation({
    mutationFn: async ({
      serverId,
      server,
    }: {
      serverId: string;
      server: MCPServerConfig;
    }): Promise<string> => {
      const currentConfig =
        settings?.mcp_config ??
        parseMcpConfig(settings?.agent_settings?.mcp_config);
      const previous = currentConfig[serverId];
      if (!previous) {
        throw new Error(`MCP server "${serverId}" no longer exists.`);
      }
      const previousHealthKey = getMcpServerHealthKey({
        id: serverId,
        type:
          previous.transport === "stdio"
            ? "stdio"
            : previous.transport === "sse"
              ? "sse"
              : "shttp",
        name: serverId,
        ...(previous.transport === "stdio"
          ? {
              command: previous.command,
              args: previous.args ?? undefined,
              env: previous.env ?? undefined,
            }
          : {
              url: previous.url,
              headers: previous.headers ?? undefined,
              auth: previous.auth ?? undefined,
            }),
      });

      const nextKey = toMcpServerName(server.name || serverId);
      if (nextKey !== serverId) {
        if (nextKey in currentConfig) {
          throw new Error(`MCP server "${nextKey}" already exists.`);
        }
        await SettingsService.patchMcpConfig(
          buildRenameMcpConfigPatch(serverId, nextKey, previous, server),
        );
        return previousHealthKey;
      }

      await SettingsService.patchMcpServer(
        serverId,
        buildMcpServerPatch(previous, server),
      );
      return previousHealthKey;
    },
    onSuccess: (previousHealthKey, variables) => {
      // The stored config changed, so any prior health verdict for it is
      // stale. This hook-level reset runs before the caller's onSuccess,
      // letting save flows re-seed from their fresh pre-save test result.
      clearMcpServerHealth(previousHealthKey);
      clearMcpServerHealth(getMcpServerHealthKey(variables.server));
      queryClient.invalidateQueries({
        queryKey: SETTINGS_QUERY_KEYS.personal(),
      });
    },
  });
}