Skip to content

Commit 503f8e6

Browse files
committed
Use the host-binary cli
Signed-off-by: David Gageot <[email protected]>
1 parent 6203309 commit 503f8e6

File tree

10 files changed

+58
-385
lines changed

10 files changed

+58
-385
lines changed

src/extension/ui/src/Constants.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ export const CATALOG_LAYOUT_SX = {
2121

2222
export const ASSIGNED_SECRET_PLACEHOLDER = "********";
2323

24-
export const BUSYBOX = 'busybox@sha256:37f7b378a29ceb4c551b1b5582e27747b855bbfaa73fa11914fe0df028dc581f';
25-
2624
// Filenames in docker-prompts volume
2725
export const REGISTRY_YAML = 'registry.yaml'
2826
export const CONFIG_YAML = 'config.yaml'

src/extension/ui/src/MCPClients.ts

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,48 @@
11
import { v1 } from "@docker/extension-api-client-types";
2-
import { SUPPORTED_MCP_CLIENTS } from "./mcp-clients";
2+
import ClaudeDesktop from "./mcp-clients/ClaudeDesktop";
3+
import ContinueDotDev from "./mcp-clients/ContinueDotDev";
4+
import Cursor from "./mcp-clients/Cursor";
5+
import Gordon from "./mcp-clients/Gordon";
36
import { MCPClient } from "./types/mcp";
47

58
export type MCPClientState = {
69
client: MCPClient;
710
exists: boolean;
811
configured: boolean;
9-
path: string;
10-
preventAutoConnectMessage?: string;
1112
}
1213

1314
export const getMCPClientStates = async (ddClient: v1.DockerDesktopClient) => {
1415
const mcpClientStates: { [name: string]: MCPClientState } = {};
15-
for (const mcpClient of SUPPORTED_MCP_CLIENTS) {
16-
const { path, content } = await mcpClient.readConfig(ddClient);
17-
if (content === null) {
18-
mcpClientStates[mcpClient.name] = { exists: false, configured: false, path, client: mcpClient };
19-
} else if (content === undefined) {
20-
mcpClientStates[mcpClient.name] = { exists: true, configured: false, path, client: mcpClient };
21-
} else {
22-
mcpClientStates[mcpClient.name] = { exists: true, configured: mcpClient.validateConfig(content), path: path, client: mcpClient };
16+
17+
try {
18+
const result = await ddClient.extension.host?.cli.exec("host-binary", ["client", "ls", "--global", "--json"]);
19+
if (result) {
20+
const fromCLI = JSON.parse(result.stdout);
21+
22+
if (fromCLI["gordon"]) {
23+
mcpClientStates[Gordon.name] = toState(Gordon, fromCLI["gordon"]);
24+
}
25+
if (fromCLI["claude-desktop"]) {
26+
mcpClientStates[ClaudeDesktop.name] = toState(ClaudeDesktop, fromCLI["claude-desktop"]);
27+
}
28+
if (fromCLI["cursor"]) {
29+
mcpClientStates[Cursor.name] = toState(Cursor, fromCLI["cursor"]);
30+
}
31+
if (fromCLI["continue"]) {
32+
mcpClientStates[ContinueDotDev.name] = toState(ContinueDotDev, fromCLI["continue"]);
33+
}
2334
}
35+
} catch (e) {
36+
ddClient.desktopUI.toast.error("Unable to connect Claude Desktop");
2437
}
2538
return mcpClientStates;
26-
}
39+
}
40+
41+
function toState(client: MCPClient, config: any): MCPClientState {
42+
return {
43+
client: client,
44+
exists: config["isInstalled"],
45+
configured: config["dockerMCPCatalogConnected"],
46+
};
47+
}
48+

src/extension/ui/src/components/tabs/YourClients.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
Stack,
1818
Typography
1919
} from '@mui/material';
20-
import { useState } from 'react';
20+
import { useEffect, useState } from 'react';
2121

2222
import ClaudeIcon from '../../assets/claude-ai-icon.svg';
2323
import ContinueIcon from '../../assets/continue.svg';
@@ -41,7 +41,7 @@ const iconMap = {
4141

4242
const MCPClientSettings = ({ appProps }: MCPClientSettingsProps) => {
4343
// Extract all the values we need from appProps
44-
const { mcpClientStates } = appProps;
44+
const { mcpClientStates, updateMCPClientStates } = appProps;
4545

4646
if (!mcpClientStates) {
4747
return (
@@ -53,6 +53,9 @@ const MCPClientSettings = ({ appProps }: MCPClientSettingsProps) => {
5353
}
5454

5555
const [copyButtonText, setCopyButtonText] = useState('Copy');
56+
useEffect(() => {
57+
updateMCPClientStates();
58+
}, []);
5659

5760
return (
5861
<Stack sx={CATALOG_LAYOUT_SX} spacing={2}>
@@ -195,10 +198,7 @@ function ClientSetting({
195198
});
196199
}
197200
}}
198-
disabled={
199-
buttonsLoading[name] ||
200-
Boolean(mcpClientState.preventAutoConnectMessage)
201-
}
201+
disabled={buttonsLoading[name]}
202202
color="warning"
203203
size="small"
204204
>
@@ -235,10 +235,7 @@ function ClientSetting({
235235
});
236236
}
237237
}}
238-
disabled={
239-
buttonsLoading[name] ||
240-
Boolean(mcpClientState.preventAutoConnectMessage)
241-
}
238+
disabled={buttonsLoading[name]}
242239
color="primary"
243240
size="small"
244241
>
Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import { v1 } from "@docker/extension-api-client-types";
2-
import { BUSYBOX } from "../Constants";
3-
import { getUser } from "../utils/Files";
42
import { MCPClient, SAMPLE_MCP_CONFIG } from "./MCPTypes";
53

64
class ClaudeDesktopClient implements MCPClient {
@@ -22,39 +20,6 @@ class ClaudeDesktopClient implements MCPClient {
2220
linux: "/home/$USER/.config/claude/claude_desktop_config.json",
2321
win32: "%APPDATA%\\Claude\\claude_desktop_config.json",
2422
};
25-
readConfig = async (client: v1.DockerDesktopClient) => {
26-
const platform = client.host.platform;
27-
let path = "";
28-
switch (platform) {
29-
case "darwin":
30-
path =
31-
"/Users/$USER/Library/Application Support/Claude/claude_desktop_config.json";
32-
break;
33-
case "linux":
34-
path = "/home/$USER/.config/claude/claude_desktop_config.json";
35-
break;
36-
case "win32":
37-
path = "%APPDATA%\\Claude\\claude_desktop_config.json";
38-
break;
39-
default:
40-
throw new Error("Unsupported platform: " + platform);
41-
}
42-
const user = await getUser(client);
43-
path = path.replace("$USER", user);
44-
try {
45-
const result = await client.docker.cli.exec("run", [
46-
"--rm",
47-
"--mount",
48-
`type=bind,source="${path}",target=/config.json`,
49-
BUSYBOX,
50-
"/bin/cat",
51-
"/config.json",
52-
]);
53-
return { content: result.stdout || undefined, path: path };
54-
} catch (e) {
55-
return { content: null, path: path };
56-
}
57-
};
5823
connect = async (client: v1.DockerDesktopClient) => {
5924
try {
6025
await client.extension.host?.cli.exec("host-binary", ["client", "connect", "--global", "claude-desktop"]);
@@ -69,10 +34,6 @@ class ClaudeDesktopClient implements MCPClient {
6934
client.desktopUI.toast.error("Unable to disconnect Claude Desktop");
7035
}
7136
};
72-
validateConfig = (content: string) => {
73-
const config = JSON.parse(content || "{}");
74-
return !!config.mcpServers?.MCP_DOCKER;
75-
};
7637
}
7738

7839
export default new ClaudeDesktopClient();
Lines changed: 5 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { v1 } from "@docker/extension-api-client-types";
2-
import { parse, stringify } from "yaml";
3-
import { BUSYBOX } from "../Constants";
4-
import { getUser, writeToMount } from "../utils/Files";
5-
import { mergeDeep } from "../MergeDeep";
2+
import { stringify } from "yaml";
63
import { MCPClient, SAMPLE_MCP_CONFIG } from "./MCPTypes";
74

85
class ContinueDotDev implements MCPClient {
@@ -19,112 +16,20 @@ class ContinueDotDev implements MCPClient {
1916
linux: "$HOME/.continue/config.yaml",
2017
win32: "$USERPROFILE\\.continue\\config.yaml",
2118
};
22-
readConfig = async (client: v1.DockerDesktopClient) => {
23-
const platform = client.host
24-
.platform as keyof typeof this.expectedConfigPath;
25-
const configPath = this.expectedConfigPath[platform].replace(
26-
"$USER",
27-
await getUser(client)
28-
);
29-
try {
30-
const result = await client.docker.cli.exec("run", [
31-
"--rm",
32-
"--mount",
33-
`type=bind,source=${configPath},target=/continue/config.yaml`,
34-
BUSYBOX,
35-
"/bin/cat",
36-
"/continue/config.yaml",
37-
]);
38-
return {
39-
content: result.stdout,
40-
path: configPath,
41-
};
42-
} catch (e) {
43-
return {
44-
content: null,
45-
path: configPath,
46-
};
47-
}
48-
};
4919
connect = async (client: v1.DockerDesktopClient) => {
50-
const config = await this.readConfig(client);
51-
let continueConfig = null;
52-
try {
53-
continueConfig = parse(config.content || "") as typeof SAMPLE_MCP_CONFIG;
54-
if (continueConfig.mcpServers?.MCP_DOCKER) {
55-
client.desktopUI.toast.warning(
56-
"Continue.dev MCP server already connected."
57-
);
58-
return;
59-
}
60-
} catch (e) {
61-
continueConfig = mergeDeep({}, SAMPLE_MCP_CONFIG);
62-
}
63-
const payload = mergeDeep(continueConfig, SAMPLE_MCP_CONFIG);
6420
try {
65-
await writeToMount(
66-
client,
67-
`type=bind,source=${config.path},target=/continue/config.yaml`,
68-
"/continue/config.yaml",
69-
stringify(payload)
70-
);
21+
await client.extension.host?.cli.exec("host-binary", ["client", "connect", "--global", "continue"]);
7122
} catch (e) {
72-
if ((e as any).stderr) {
73-
client.desktopUI.toast.error((e as any).stderr);
74-
} else {
75-
client.desktopUI.toast.error((e as Error).message);
76-
}
23+
client.desktopUI.toast.error("Unable to connect Continue.dev");
7724
}
7825
};
7926
disconnect = async (client: v1.DockerDesktopClient) => {
80-
const config = await this.readConfig(client);
81-
if (!config.content) {
82-
client.desktopUI.toast.error("No config found");
83-
return;
84-
}
85-
let continueConfig = null;
86-
try {
87-
continueConfig = parse(config.content) as typeof SAMPLE_MCP_CONFIG;
88-
if (!continueConfig.mcpServers?.MCP_DOCKER) {
89-
client.desktopUI.toast.error(
90-
"Docker MCP Server not connected to Continue.dev"
91-
);
92-
return;
93-
}
94-
} catch (e) {
95-
client.desktopUI.toast.error(
96-
"Failed to disconnect. Invalid Continue.dev config found at " +
97-
config.path
98-
);
99-
return;
100-
}
101-
const payload = {
102-
...continueConfig,
103-
mcpServers: Object.fromEntries(
104-
Object.entries(continueConfig.mcpServers).filter(
105-
([key]) => key !== "MCP_DOCKER"
106-
)
107-
),
108-
};
10927
try {
110-
await writeToMount(
111-
client,
112-
`type=bind,source=${config.path},target=/continue/config.yaml`,
113-
"/continue/config.yaml",
114-
stringify(payload)
115-
);
28+
await client.extension.host?.cli.exec("host-binary", ["client", "disconnect", "--global", "continue"]);
11629
} catch (e) {
117-
if ((e as any).stderr) {
118-
client.desktopUI.toast.error((e as any).stderr);
119-
} else {
120-
client.desktopUI.toast.error((e as Error).message);
121-
}
30+
client.desktopUI.toast.error("Unable to disconnect Continue.dev");
12231
}
12332
};
124-
validateConfig = (content: string) => {
125-
const config = JSON.parse(content || "{}") as typeof SAMPLE_MCP_CONFIG;
126-
return !!config.mcpServers?.MCP_DOCKER;
127-
};
12833
}
12934

13035
export default new ContinueDotDev();

0 commit comments

Comments
 (0)