@darkiceinteractive/mcp-conductor 3.0.0-beta.2 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -286
- package/dist/bin/cli.d.ts +2 -2
- package/dist/bin/cli.js +37 -14
- package/dist/bin/cli.js.map +1 -1
- package/dist/bridge/http-server.d.ts.map +1 -1
- package/dist/bridge/http-server.js +11 -5
- package/dist/bridge/http-server.js.map +1 -1
- package/dist/cache/disk.d.ts.map +1 -1
- package/dist/cache/disk.js +36 -3
- package/dist/cache/disk.js.map +1 -1
- package/dist/cli/clients/adapter.d.ts +103 -0
- package/dist/cli/clients/adapter.d.ts.map +1 -0
- package/dist/cli/clients/adapter.js +24 -0
- package/dist/cli/clients/adapter.js.map +1 -0
- package/dist/cli/clients/claude-code.d.ts +15 -0
- package/dist/cli/clients/claude-code.d.ts.map +1 -0
- package/dist/cli/clients/claude-code.js +110 -0
- package/dist/cli/clients/claude-code.js.map +1 -0
- package/dist/cli/clients/claude-desktop.d.ts +26 -0
- package/dist/cli/clients/claude-desktop.d.ts.map +1 -0
- package/dist/cli/clients/claude-desktop.js +118 -0
- package/dist/cli/clients/claude-desktop.js.map +1 -0
- package/dist/cli/clients/cline.d.ts +40 -0
- package/dist/cli/clients/cline.d.ts.map +1 -0
- package/dist/cli/clients/cline.js +134 -0
- package/dist/cli/clients/cline.js.map +1 -0
- package/dist/cli/clients/codex.d.ts +30 -0
- package/dist/cli/clients/codex.d.ts.map +1 -0
- package/dist/cli/clients/codex.js +176 -0
- package/dist/cli/clients/codex.js.map +1 -0
- package/dist/cli/clients/continue.d.ts +41 -0
- package/dist/cli/clients/continue.d.ts.map +1 -0
- package/dist/cli/clients/continue.js +150 -0
- package/dist/cli/clients/continue.js.map +1 -0
- package/dist/cli/clients/cursor.d.ts +20 -0
- package/dist/cli/clients/cursor.d.ts.map +1 -0
- package/dist/cli/clients/cursor.js +110 -0
- package/dist/cli/clients/cursor.js.map +1 -0
- package/dist/cli/clients/gemini-cli.d.ts +42 -0
- package/dist/cli/clients/gemini-cli.d.ts.map +1 -0
- package/dist/cli/clients/gemini-cli.js +169 -0
- package/dist/cli/clients/gemini-cli.js.map +1 -0
- package/dist/cli/clients/index.d.ts +28 -0
- package/dist/cli/clients/index.d.ts.map +1 -0
- package/dist/cli/clients/index.js +44 -0
- package/dist/cli/clients/index.js.map +1 -0
- package/dist/cli/clients/kimi-code.d.ts +33 -0
- package/dist/cli/clients/kimi-code.d.ts.map +1 -0
- package/dist/cli/clients/kimi-code.js +177 -0
- package/dist/cli/clients/kimi-code.js.map +1 -0
- package/dist/cli/clients/opencode.d.ts +21 -0
- package/dist/cli/clients/opencode.d.ts.map +1 -0
- package/dist/cli/clients/opencode.js +150 -0
- package/dist/cli/clients/opencode.js.map +1 -0
- package/dist/cli/clients/registry.d.ts +51 -0
- package/dist/cli/clients/registry.d.ts.map +1 -0
- package/dist/cli/clients/registry.js +169 -0
- package/dist/cli/clients/registry.js.map +1 -0
- package/dist/cli/clients/zed.d.ts +41 -0
- package/dist/cli/clients/zed.d.ts.map +1 -0
- package/dist/cli/clients/zed.js +171 -0
- package/dist/cli/clients/zed.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +27 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +71 -0
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/export-servers.d.ts +60 -0
- package/dist/cli/commands/export-servers.d.ts.map +1 -1
- package/dist/cli/commands/export-servers.js +85 -1
- package/dist/cli/commands/export-servers.js.map +1 -1
- package/dist/cli/commands/import-servers.d.ts +13 -2
- package/dist/cli/commands/import-servers.d.ts.map +1 -1
- package/dist/cli/commands/import-servers.js +42 -5
- package/dist/cli/commands/import-servers.js.map +1 -1
- package/dist/cli/wizard/setup.d.ts +29 -3
- package/dist/cli/wizard/setup.d.ts.map +1 -1
- package/dist/cli/wizard/setup.js +204 -7
- package/dist/cli/wizard/setup.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +1 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/loader.d.ts +10 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +14 -24
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +7 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/daemon/client.d.ts.map +1 -1
- package/dist/daemon/client.js +14 -6
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/server.d.ts +9 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +80 -9
- package/dist/daemon/server.js.map +1 -1
- package/dist/metrics/index.d.ts +1 -1
- package/dist/metrics/index.d.ts.map +1 -1
- package/dist/metrics/index.js +1 -1
- package/dist/metrics/index.js.map +1 -1
- package/dist/metrics/metrics-collector.d.ts +120 -0
- package/dist/metrics/metrics-collector.d.ts.map +1 -1
- package/dist/metrics/metrics-collector.js +136 -0
- package/dist/metrics/metrics-collector.js.map +1 -1
- package/dist/runtime/pool/recycle.d.ts +8 -2
- package/dist/runtime/pool/recycle.d.ts.map +1 -1
- package/dist/runtime/pool/recycle.js.map +1 -1
- package/dist/runtime/pool/worker-pool.d.ts.map +1 -1
- package/dist/runtime/pool/worker-pool.js +17 -10
- package/dist/runtime/pool/worker-pool.js.map +1 -1
- package/dist/runtime/pool/worker.d.ts +7 -1
- package/dist/runtime/pool/worker.d.ts.map +1 -1
- package/dist/runtime/pool/worker.js +9 -1
- package/dist/runtime/pool/worker.js.map +1 -1
- package/dist/server/mcp-server.d.ts +15 -3
- package/dist/server/mcp-server.d.ts.map +1 -1
- package/dist/server/mcp-server.js +81 -7
- package/dist/server/mcp-server.js.map +1 -1
- package/dist/utils/backup.d.ts +24 -0
- package/dist/utils/backup.d.ts.map +1 -0
- package/dist/utils/backup.js +40 -0
- package/dist/utils/backup.js.map +1 -0
- package/dist/utils/tokenize.d.ts +8 -0
- package/dist/utils/tokenize.d.ts.map +1 -1
- package/dist/utils/tokenize.js +8 -0
- package/dist/utils/tokenize.js.map +1 -1
- package/dist/version.d.ts +3 -3
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +3 -3
- package/dist/version.js.map +1 -1
- package/package.json +23 -2
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPClientAdapter implementation for Claude Code.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code stores MCP servers under the canonical Anthropic `mcpServers` key
|
|
5
|
+
* in a JSON settings file, so parse() reads the key directly without any field
|
|
6
|
+
* translation, and serialize() writes it back in the same shape.
|
|
7
|
+
*
|
|
8
|
+
* The adapter registers itself in `ADAPTERS` at module load time; callers only
|
|
9
|
+
* need to `import './claude-code.js'` (done in `index.ts`) to enable it.
|
|
10
|
+
*
|
|
11
|
+
* @module cli/clients/claude-code
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
|
|
14
|
+
import { ADAPTERS } from './adapter.js';
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Internal helpers
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* Generate a `.bak.YYYYMMDDHHMMSS` path alongside `filePath` and copy the
|
|
20
|
+
* original file to it.
|
|
21
|
+
*
|
|
22
|
+
* Matches the same strategy as `writeBackup` in
|
|
23
|
+
* `src/cli/commands/import-servers.ts`:
|
|
24
|
+
* - Derive a 14-digit UTC timestamp from ISO string.
|
|
25
|
+
* - Guard against sub-second collisions with a 4-char hex salt.
|
|
26
|
+
*
|
|
27
|
+
* @returns The path of the backup file written.
|
|
28
|
+
*/
|
|
29
|
+
function writeBackup(filePath) {
|
|
30
|
+
// toISOString() → "2026-05-05T11:23:45.678Z"; strip non-digits, take first 14.
|
|
31
|
+
const ts = new Date().toISOString().replace(/\D/g, '').slice(0, 14);
|
|
32
|
+
let backupPath = `${filePath}.bak.${ts}`;
|
|
33
|
+
// Sub-second collision guard.
|
|
34
|
+
if (existsSync(backupPath)) {
|
|
35
|
+
const salt = Math.floor(Math.random() * 0xffff).toString(16).padStart(4, '0');
|
|
36
|
+
backupPath = `${backupPath}.${salt}`;
|
|
37
|
+
}
|
|
38
|
+
copyFileSync(filePath, backupPath);
|
|
39
|
+
return backupPath;
|
|
40
|
+
}
|
|
41
|
+
export const CLAUDE_CODE_ADAPTER = {
|
|
42
|
+
client: 'claude-code',
|
|
43
|
+
/**
|
|
44
|
+
* Parse the Claude Code settings file at `path`.
|
|
45
|
+
*
|
|
46
|
+
* Returns `null` when the file does not exist, is not valid JSON, or contains
|
|
47
|
+
* no `mcpServers` entries — allowing callers to skip gracefully.
|
|
48
|
+
*/
|
|
49
|
+
parse(path) {
|
|
50
|
+
if (!existsSync(path)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
let raw;
|
|
54
|
+
try {
|
|
55
|
+
raw = JSON.parse(readFileSync(path, 'utf-8'));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const mcpServers = raw.mcpServers;
|
|
61
|
+
if (!mcpServers ||
|
|
62
|
+
typeof mcpServers !== 'object' ||
|
|
63
|
+
Object.keys(mcpServers).length === 0) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// Claude Code uses the canonical Anthropic shape — no field translation needed.
|
|
67
|
+
const servers = {};
|
|
68
|
+
for (const [name, entry] of Object.entries(mcpServers)) {
|
|
69
|
+
if (entry && typeof entry.command === 'string') {
|
|
70
|
+
servers[name] = {
|
|
71
|
+
command: entry.command,
|
|
72
|
+
...(Array.isArray(entry.args) ? { args: entry.args } : {}),
|
|
73
|
+
...(entry.env && typeof entry.env === 'object' ? { env: entry.env } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { servers, raw };
|
|
78
|
+
},
|
|
79
|
+
/**
|
|
80
|
+
* Write the (potentially modified) config back to disk at `path`.
|
|
81
|
+
*
|
|
82
|
+
* Steps:
|
|
83
|
+
* 1. Write a `.bak.YYYYMMDDHHMMSS` backup if the file already exists.
|
|
84
|
+
* 2. Start from `config.raw` so non-MCP keys (e.g. `apiKeyHelper`) are kept.
|
|
85
|
+
* 3. If `keepOnlyConductor` is set, replace `mcpServers` with just the
|
|
86
|
+
* conductor entry; otherwise write the full `config.servers` map and
|
|
87
|
+
* ensure `"mcp-conductor"` is present and up-to-date.
|
|
88
|
+
*/
|
|
89
|
+
serialize(path, config, options) {
|
|
90
|
+
if (existsSync(path)) {
|
|
91
|
+
writeBackup(path);
|
|
92
|
+
}
|
|
93
|
+
// Clone raw to avoid mutating the caller's in-memory config.
|
|
94
|
+
const output = { ...config.raw };
|
|
95
|
+
let mcpServers;
|
|
96
|
+
if (options.keepOnlyConductor) {
|
|
97
|
+
mcpServers = { 'mcp-conductor': options.conductorEntry };
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// Spread existing servers then overlay the conductor entry.
|
|
101
|
+
mcpServers = { ...config.servers };
|
|
102
|
+
mcpServers['mcp-conductor'] = options.conductorEntry;
|
|
103
|
+
}
|
|
104
|
+
output['mcpServers'] = mcpServers;
|
|
105
|
+
writeFileSync(path, JSON.stringify(output, null, 2) + '\n', 'utf-8');
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
// Side-effect registration: runs when this module is first imported.
|
|
109
|
+
ADAPTERS.set('claude-code', CLAUDE_CODE_ADAPTER);
|
|
110
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../../src/cli/clients/claude-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPClientAdapter implementation for Claude Desktop.
|
|
3
|
+
*
|
|
4
|
+
* Claude Desktop stores its MCP server list in a JSON file under:
|
|
5
|
+
* - macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
6
|
+
* - Linux: ~/.config/claude/claude_desktop_config.json
|
|
7
|
+
* - Windows: %APPDATA%\Claude\claude_desktop_config.json
|
|
8
|
+
*
|
|
9
|
+
* The schema is identical to the canonical Anthropic MCP format:
|
|
10
|
+
* { mcpServers: { "<name>": { command, args?, env? } } }
|
|
11
|
+
*
|
|
12
|
+
* This adapter auto-registers in the ADAPTERS singleton at module load time
|
|
13
|
+
* so consumers only need to import this file as a side-effect.
|
|
14
|
+
*
|
|
15
|
+
* @module cli/clients/claude-desktop
|
|
16
|
+
*/
|
|
17
|
+
import type { MCPClientAdapter } from './adapter.js';
|
|
18
|
+
/**
|
|
19
|
+
* MCPClientAdapter for Claude Desktop.
|
|
20
|
+
*
|
|
21
|
+
* Reads and writes `claude_desktop_config.json` in the canonical Anthropic
|
|
22
|
+
* `{ command, args, env }` format under the `mcpServers` key. All non-MCP
|
|
23
|
+
* top-level keys in the config file are preserved verbatim on every write.
|
|
24
|
+
*/
|
|
25
|
+
export declare const CLAUDE_DESKTOP_ADAPTER: MCPClientAdapter;
|
|
26
|
+
//# sourceMappingURL=claude-desktop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-desktop.d.ts","sourceRoot":"","sources":["../../../src/cli/clients/claude-desktop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,OAAO,KAAK,EACV,gBAAgB,EAIjB,MAAM,cAAc,CAAC;AAqBtB;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,gBA2FpC,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPClientAdapter implementation for Claude Desktop.
|
|
3
|
+
*
|
|
4
|
+
* Claude Desktop stores its MCP server list in a JSON file under:
|
|
5
|
+
* - macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
6
|
+
* - Linux: ~/.config/claude/claude_desktop_config.json
|
|
7
|
+
* - Windows: %APPDATA%\Claude\claude_desktop_config.json
|
|
8
|
+
*
|
|
9
|
+
* The schema is identical to the canonical Anthropic MCP format:
|
|
10
|
+
* { mcpServers: { "<name>": { command, args?, env? } } }
|
|
11
|
+
*
|
|
12
|
+
* This adapter auto-registers in the ADAPTERS singleton at module load time
|
|
13
|
+
* so consumers only need to import this file as a side-effect.
|
|
14
|
+
*
|
|
15
|
+
* @module cli/clients/claude-desktop
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { dirname } from 'node:path';
|
|
19
|
+
import { safeJsonParse } from '../../utils/index.js';
|
|
20
|
+
import { writeBackup } from '../commands/import-servers.js';
|
|
21
|
+
import { ADAPTERS } from './adapter.js';
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Adapter implementation
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* MCPClientAdapter for Claude Desktop.
|
|
27
|
+
*
|
|
28
|
+
* Reads and writes `claude_desktop_config.json` in the canonical Anthropic
|
|
29
|
+
* `{ command, args, env }` format under the `mcpServers` key. All non-MCP
|
|
30
|
+
* top-level keys in the config file are preserved verbatim on every write.
|
|
31
|
+
*/
|
|
32
|
+
export const CLAUDE_DESKTOP_ADAPTER = {
|
|
33
|
+
client: 'claude-desktop',
|
|
34
|
+
/**
|
|
35
|
+
* Parse `claude_desktop_config.json` at `path` into normalised form.
|
|
36
|
+
*
|
|
37
|
+
* Returns `null` when:
|
|
38
|
+
* - the file does not exist
|
|
39
|
+
* - the file is not valid JSON
|
|
40
|
+
* - the file has no `mcpServers` key (or the key is empty)
|
|
41
|
+
*
|
|
42
|
+
* The original parsed object is stored in `raw` for round-trip writes.
|
|
43
|
+
*/
|
|
44
|
+
parse(path) {
|
|
45
|
+
if (!existsSync(path)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
let content;
|
|
49
|
+
try {
|
|
50
|
+
content = readFileSync(path, 'utf-8');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const raw = safeJsonParse(content, {});
|
|
56
|
+
const mcpServers = raw.mcpServers;
|
|
57
|
+
if (!mcpServers || typeof mcpServers !== 'object') {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const servers = {};
|
|
61
|
+
for (const [name, entry] of Object.entries(mcpServers)) {
|
|
62
|
+
if (typeof entry.command !== 'string') {
|
|
63
|
+
// Skip malformed entries rather than crashing.
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
servers[name] = {
|
|
67
|
+
command: entry.command,
|
|
68
|
+
...(Array.isArray(entry.args) && { args: entry.args }),
|
|
69
|
+
...(entry.env && typeof entry.env === 'object' && { env: entry.env }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { servers, raw };
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* Write a (possibly modified) config back to `path`.
|
|
76
|
+
*
|
|
77
|
+
* Steps:
|
|
78
|
+
* 1. If the file exists, write a `.bak.YYYYMMDDHHMMSS` backup first.
|
|
79
|
+
* 2. Determine the server map to persist:
|
|
80
|
+
* - `keepOnlyConductor: true` → only `{ "mcp-conductor": conductorEntry }`
|
|
81
|
+
* - otherwise → merge `config.servers` with `conductorEntry` under
|
|
82
|
+
* `"mcp-conductor"` (conductorEntry always wins for that key).
|
|
83
|
+
* 3. Overlay the resulting `mcpServers` onto `config.raw`, preserving every
|
|
84
|
+
* other top-level key unchanged.
|
|
85
|
+
* 4. Write the resulting JSON with 2-space indentation and a trailing newline.
|
|
86
|
+
*
|
|
87
|
+
* Creates parent directories if they do not yet exist (first-time install).
|
|
88
|
+
*/
|
|
89
|
+
serialize(path, config, options) {
|
|
90
|
+
const { keepOnlyConductor, conductorEntry } = options;
|
|
91
|
+
// Write a backup before touching the original (only when it already exists).
|
|
92
|
+
if (existsSync(path)) {
|
|
93
|
+
writeBackup(path);
|
|
94
|
+
}
|
|
95
|
+
// Build the mcpServers map to persist.
|
|
96
|
+
const mcpServers = keepOnlyConductor
|
|
97
|
+
? { 'mcp-conductor': conductorEntry }
|
|
98
|
+
: { ...config.servers, 'mcp-conductor': conductorEntry };
|
|
99
|
+
// Merge into the raw object so non-MCP keys are preserved.
|
|
100
|
+
const raw = typeof config.raw === 'object' && config.raw !== null
|
|
101
|
+
? config.raw
|
|
102
|
+
: {};
|
|
103
|
+
const output = { ...raw, mcpServers };
|
|
104
|
+
// Ensure parent directories exist (first-time write on a fresh install).
|
|
105
|
+
const dir = dirname(path);
|
|
106
|
+
if (!existsSync(dir)) {
|
|
107
|
+
mkdirSync(dir, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
writeFileSync(path, JSON.stringify(output, null, 2) + '\n', 'utf-8');
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Auto-registration
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Register at module load time so any side-effect importer of this file gets
|
|
116
|
+
// the adapter into the ADAPTERS map without needing an explicit call.
|
|
117
|
+
ADAPTERS.set('claude-desktop', CLAUDE_DESKTOP_ADAPTER);
|
|
118
|
+
//# sourceMappingURL=claude-desktop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-desktop.js","sourceRoot":"","sources":["../../../src/cli/clients/claude-desktop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPClientAdapter for Cline (VS Code extension by Saoud Rizwan).
|
|
3
|
+
*
|
|
4
|
+
* Cline stores MCP server definitions in VS Code's per-extension globalStorage
|
|
5
|
+
* directory. The path is stable as long as the extension ID remains
|
|
6
|
+
* `saoudrizwan.claude-dev`. If Cline ever changes its extension ID the path
|
|
7
|
+
* returned by the registry becomes stale and the adapter will return `null`
|
|
8
|
+
* from `parse()` (file not found) rather than throwing.
|
|
9
|
+
*
|
|
10
|
+
* Platform paths (registered in `src/cli/clients/registry.ts`):
|
|
11
|
+
* macOS ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json
|
|
12
|
+
* Linux ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json
|
|
13
|
+
* Windows %APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json
|
|
14
|
+
*
|
|
15
|
+
* Config shape:
|
|
16
|
+
* ```json
|
|
17
|
+
* {
|
|
18
|
+
* "mcpServers": {
|
|
19
|
+
* "<server-name>": {
|
|
20
|
+
* "command": "node",
|
|
21
|
+
* "args": ["path/to/server.js"],
|
|
22
|
+
* "env": { "KEY": "value" }
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* The format is Anthropic-compatible (same shape as Claude Desktop / Claude
|
|
29
|
+
* Code), so normalisation is straightforward.
|
|
30
|
+
*
|
|
31
|
+
* @module cli/clients/cline
|
|
32
|
+
*/
|
|
33
|
+
import type { MCPClientAdapter } from './adapter.js';
|
|
34
|
+
/**
|
|
35
|
+
* Singleton adapter instance for the Cline VS Code extension.
|
|
36
|
+
*
|
|
37
|
+
* Registered in `ADAPTERS` by `src/cli/clients/index.ts` at module load time.
|
|
38
|
+
*/
|
|
39
|
+
export declare const CLINE_ADAPTER: MCPClientAdapter;
|
|
40
|
+
//# sourceMappingURL=cline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cline.d.ts","sourceRoot":"","sources":["../../../src/cli/clients/cline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAA4C,MAAM,cAAc,CAAC;AAsI/F;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,gBAA+B,CAAC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPClientAdapter for Cline (VS Code extension by Saoud Rizwan).
|
|
3
|
+
*
|
|
4
|
+
* Cline stores MCP server definitions in VS Code's per-extension globalStorage
|
|
5
|
+
* directory. The path is stable as long as the extension ID remains
|
|
6
|
+
* `saoudrizwan.claude-dev`. If Cline ever changes its extension ID the path
|
|
7
|
+
* returned by the registry becomes stale and the adapter will return `null`
|
|
8
|
+
* from `parse()` (file not found) rather than throwing.
|
|
9
|
+
*
|
|
10
|
+
* Platform paths (registered in `src/cli/clients/registry.ts`):
|
|
11
|
+
* macOS ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json
|
|
12
|
+
* Linux ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json
|
|
13
|
+
* Windows %APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json
|
|
14
|
+
*
|
|
15
|
+
* Config shape:
|
|
16
|
+
* ```json
|
|
17
|
+
* {
|
|
18
|
+
* "mcpServers": {
|
|
19
|
+
* "<server-name>": {
|
|
20
|
+
* "command": "node",
|
|
21
|
+
* "args": ["path/to/server.js"],
|
|
22
|
+
* "env": { "KEY": "value" }
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* The format is Anthropic-compatible (same shape as Claude Desktop / Claude
|
|
29
|
+
* Code), so normalisation is straightforward.
|
|
30
|
+
*
|
|
31
|
+
* @module cli/clients/cline
|
|
32
|
+
*/
|
|
33
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
34
|
+
import { dirname } from 'node:path';
|
|
35
|
+
import { writeBackup } from '../../utils/backup.js';
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Adapter implementation
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
const clineAdapter = {
|
|
40
|
+
client: 'cline',
|
|
41
|
+
/**
|
|
42
|
+
* Parse `cline_mcp_settings.json` at `path` into the normalised shape.
|
|
43
|
+
*
|
|
44
|
+
* Returns `null` when:
|
|
45
|
+
* - The file does not exist (Cline not installed or path stale after an
|
|
46
|
+
* extension-ID change).
|
|
47
|
+
* - The file cannot be parsed as JSON.
|
|
48
|
+
* - `mcpServers` is absent or empty.
|
|
49
|
+
*/
|
|
50
|
+
parse(path) {
|
|
51
|
+
if (!existsSync(path)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
let raw;
|
|
55
|
+
try {
|
|
56
|
+
raw = JSON.parse(readFileSync(path, 'utf-8'));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const mcpServers = raw.mcpServers ?? {};
|
|
62
|
+
if (Object.keys(mcpServers).length === 0) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const servers = {};
|
|
66
|
+
for (const [name, entry] of Object.entries(mcpServers)) {
|
|
67
|
+
servers[name] = {
|
|
68
|
+
command: entry.command,
|
|
69
|
+
...(entry.args !== undefined ? { args: entry.args } : {}),
|
|
70
|
+
...(entry.env !== undefined ? { env: entry.env } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { servers, raw };
|
|
74
|
+
},
|
|
75
|
+
/**
|
|
76
|
+
* Write a (potentially modified) config back to `path`.
|
|
77
|
+
*
|
|
78
|
+
* Steps:
|
|
79
|
+
* 1. If the file exists, create a `.bak.YYYYMMDDHHMMSS` backup first.
|
|
80
|
+
* 2. Build the server map: either only the conductor entry
|
|
81
|
+
* (`keepOnlyConductor: true`) or all servers from `config.servers` with
|
|
82
|
+
* the conductor entry merged in.
|
|
83
|
+
* 3. Merge the new `mcpServers` map into `config.raw`, preserving any other
|
|
84
|
+
* top-level keys Cline may have written.
|
|
85
|
+
* 4. Write pretty-printed JSON (2-space indent) to `path`, creating parent
|
|
86
|
+
* directories if they do not yet exist.
|
|
87
|
+
*/
|
|
88
|
+
serialize(path, config, options) {
|
|
89
|
+
// Backup before any destructive operation.
|
|
90
|
+
if (existsSync(path)) {
|
|
91
|
+
writeBackup(path);
|
|
92
|
+
}
|
|
93
|
+
// Build the server map to write.
|
|
94
|
+
const serversToWrite = {};
|
|
95
|
+
if (!options.keepOnlyConductor) {
|
|
96
|
+
// Carry over every server from the normalised config.
|
|
97
|
+
for (const [name, entry] of Object.entries(config.servers)) {
|
|
98
|
+
serversToWrite[name] = {
|
|
99
|
+
command: entry.command,
|
|
100
|
+
...(entry.args !== undefined ? { args: entry.args } : {}),
|
|
101
|
+
...(entry.env !== undefined ? { env: entry.env } : {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Always ensure the conductor entry is present.
|
|
106
|
+
serversToWrite['mcp-conductor'] = {
|
|
107
|
+
command: options.conductorEntry.command,
|
|
108
|
+
...(options.conductorEntry.args !== undefined ? { args: options.conductorEntry.args } : {}),
|
|
109
|
+
...(options.conductorEntry.env !== undefined ? { env: options.conductorEntry.env } : {}),
|
|
110
|
+
};
|
|
111
|
+
// Merge into the original raw object so non-MCP keys are preserved.
|
|
112
|
+
const output = {
|
|
113
|
+
...config.raw,
|
|
114
|
+
mcpServers: serversToWrite,
|
|
115
|
+
};
|
|
116
|
+
// Ensure parent directory exists (first-time setup for users who have
|
|
117
|
+
// never launched Cline on this machine).
|
|
118
|
+
const dir = dirname(path);
|
|
119
|
+
if (!existsSync(dir)) {
|
|
120
|
+
mkdirSync(dir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
writeFileSync(path, JSON.stringify(output, null, 2), 'utf-8');
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Exports
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
/**
|
|
129
|
+
* Singleton adapter instance for the Cline VS Code extension.
|
|
130
|
+
*
|
|
131
|
+
* Registered in `ADAPTERS` by `src/cli/clients/index.ts` at module load time.
|
|
132
|
+
*/
|
|
133
|
+
export const CLINE_ADAPTER = clineAdapter;
|
|
134
|
+
//# sourceMappingURL=cline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cline.js","sourceRoot":"","sources":["../../../src/cli/clients/cline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPClientAdapter for the OpenAI Codex CLI.
|
|
3
|
+
*
|
|
4
|
+
* Codex stores MCP server definitions in TOML format at:
|
|
5
|
+
* Global: ~/.codex/config.toml
|
|
6
|
+
* Project: .codex/config.toml
|
|
7
|
+
*
|
|
8
|
+
* Schema:
|
|
9
|
+
* [mcp_servers.<name>]
|
|
10
|
+
* command = "node"
|
|
11
|
+
* args = ["server.js"]
|
|
12
|
+
*
|
|
13
|
+
* [mcp_servers.<name>.env_vars]
|
|
14
|
+
* KEY = "plain-string-value" # plain env var → normalised env.KEY
|
|
15
|
+
*
|
|
16
|
+
* [mcp_servers.<name>.env_vars.KEY] # remote-source form → dropped with warning
|
|
17
|
+
* name = "KEY"
|
|
18
|
+
* source = "remote"
|
|
19
|
+
*
|
|
20
|
+
* Key divergence from the normalised shape:
|
|
21
|
+
* - Codex uses `env_vars`, not `env`.
|
|
22
|
+
* - `env_vars` values can be plain strings OR `{name, source}` objects.
|
|
23
|
+
* Plain strings → normalised `env` map.
|
|
24
|
+
* source="remote" → dropped; warning logged (we don't proxy remote exec).
|
|
25
|
+
*
|
|
26
|
+
* @module cli/clients/codex
|
|
27
|
+
*/
|
|
28
|
+
import type { MCPClientAdapter } from './adapter.js';
|
|
29
|
+
export declare const CODEX_ADAPTER: MCPClientAdapter;
|
|
30
|
+
//# sourceMappingURL=codex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../../src/cli/clients/codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH,OAAO,KAAK,EAAE,gBAAgB,EAA4C,MAAM,cAAc,CAAC;AAuE/F,eAAO,MAAM,aAAa,EAAE,gBA4H3B,CAAC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPClientAdapter for the OpenAI Codex CLI.
|
|
3
|
+
*
|
|
4
|
+
* Codex stores MCP server definitions in TOML format at:
|
|
5
|
+
* Global: ~/.codex/config.toml
|
|
6
|
+
* Project: .codex/config.toml
|
|
7
|
+
*
|
|
8
|
+
* Schema:
|
|
9
|
+
* [mcp_servers.<name>]
|
|
10
|
+
* command = "node"
|
|
11
|
+
* args = ["server.js"]
|
|
12
|
+
*
|
|
13
|
+
* [mcp_servers.<name>.env_vars]
|
|
14
|
+
* KEY = "plain-string-value" # plain env var → normalised env.KEY
|
|
15
|
+
*
|
|
16
|
+
* [mcp_servers.<name>.env_vars.KEY] # remote-source form → dropped with warning
|
|
17
|
+
* name = "KEY"
|
|
18
|
+
* source = "remote"
|
|
19
|
+
*
|
|
20
|
+
* Key divergence from the normalised shape:
|
|
21
|
+
* - Codex uses `env_vars`, not `env`.
|
|
22
|
+
* - `env_vars` values can be plain strings OR `{name, source}` objects.
|
|
23
|
+
* Plain strings → normalised `env` map.
|
|
24
|
+
* source="remote" → dropped; warning logged (we don't proxy remote exec).
|
|
25
|
+
*
|
|
26
|
+
* @module cli/clients/codex
|
|
27
|
+
*/
|
|
28
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
29
|
+
import { dirname } from 'node:path';
|
|
30
|
+
import TOML from '@iarna/toml';
|
|
31
|
+
import { writeBackup } from '../../utils/backup.js';
|
|
32
|
+
import { ADAPTERS } from './adapter.js';
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Helpers
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* Translate a Codex `env_vars` map into the normalised `env` map.
|
|
38
|
+
*
|
|
39
|
+
* - Plain string values → `{ KEY: value }`
|
|
40
|
+
* - `{ source: "remote" }` objects → dropped with a console warning.
|
|
41
|
+
* - Any other unexpected shape → dropped silently.
|
|
42
|
+
*/
|
|
43
|
+
function normaliseEnvVars(envVars, serverName) {
|
|
44
|
+
const env = {};
|
|
45
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
46
|
+
if (typeof value === 'string') {
|
|
47
|
+
env[key] = value;
|
|
48
|
+
}
|
|
49
|
+
else if (typeof value === 'object' &&
|
|
50
|
+
value !== null &&
|
|
51
|
+
'source' in value &&
|
|
52
|
+
value['source'] === 'remote') {
|
|
53
|
+
// Remote-source env vars require Codex's own remote execution pipeline.
|
|
54
|
+
// mcp-conductor does not proxy remote exec — drop and warn.
|
|
55
|
+
console.warn(`[codex-adapter] server "${serverName}": env_vars.${key} has source="remote" ` +
|
|
56
|
+
`— skipped (remote-exec env vars are not supported by mcp-conductor).`);
|
|
57
|
+
}
|
|
58
|
+
// Other unexpected shapes are silently dropped.
|
|
59
|
+
}
|
|
60
|
+
return env;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Translate the normalised `env` map back to Codex `env_vars` plain-string form.
|
|
64
|
+
*/
|
|
65
|
+
function denormaliseEnv(env) {
|
|
66
|
+
return env ?? {};
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// CODEX_ADAPTER singleton
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
export const CODEX_ADAPTER = {
|
|
72
|
+
client: 'codex',
|
|
73
|
+
/**
|
|
74
|
+
* Parse `~/.codex/config.toml` (or a project-local equivalent) into the
|
|
75
|
+
* normalised shape.
|
|
76
|
+
*
|
|
77
|
+
* Returns `null` when:
|
|
78
|
+
* - The file does not exist.
|
|
79
|
+
* - The file cannot be parsed as valid TOML.
|
|
80
|
+
* - The file contains no `[mcp_servers]` table (or it is empty).
|
|
81
|
+
*/
|
|
82
|
+
parse(path) {
|
|
83
|
+
if (!existsSync(path))
|
|
84
|
+
return null;
|
|
85
|
+
let raw;
|
|
86
|
+
try {
|
|
87
|
+
const content = readFileSync(path, 'utf-8');
|
|
88
|
+
raw = TOML.parse(content);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const mcpServers = raw.mcp_servers;
|
|
94
|
+
if (!mcpServers ||
|
|
95
|
+
typeof mcpServers !== 'object' ||
|
|
96
|
+
Object.keys(mcpServers).length === 0) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const servers = {};
|
|
100
|
+
for (const [name, server] of Object.entries(mcpServers)) {
|
|
101
|
+
if (!server || typeof server !== 'object')
|
|
102
|
+
continue;
|
|
103
|
+
const command = server.command;
|
|
104
|
+
if (typeof command !== 'string' || command.trim() === '')
|
|
105
|
+
continue;
|
|
106
|
+
const args = Array.isArray(server.args)
|
|
107
|
+
? server.args.filter((a) => typeof a === 'string')
|
|
108
|
+
: undefined;
|
|
109
|
+
const rawEnvVars = server.env_vars;
|
|
110
|
+
const env = rawEnvVars && typeof rawEnvVars === 'object'
|
|
111
|
+
? normaliseEnvVars(rawEnvVars, name)
|
|
112
|
+
: undefined;
|
|
113
|
+
servers[name] = {
|
|
114
|
+
command,
|
|
115
|
+
...(args !== undefined && { args }),
|
|
116
|
+
...(env !== undefined && Object.keys(env).length > 0 && { env }),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { servers, raw };
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* Write the normalised config back to `path` as TOML.
|
|
123
|
+
*
|
|
124
|
+
* 1. Determines which servers to include (all vs. conductor-only).
|
|
125
|
+
* 2. Ensures `conductorEntry` is present under `"mcp-conductor"`.
|
|
126
|
+
* 3. Preserves all non-`mcp_servers` top-level keys from `config.raw`.
|
|
127
|
+
* 4. Creates a `.bak.YYYYMMDDHHMMSS` backup if the file already exists.
|
|
128
|
+
* 5. Serialises to TOML and writes via `writeFileSync`.
|
|
129
|
+
*/
|
|
130
|
+
serialize(path, config, options) {
|
|
131
|
+
const { keepOnlyConductor, conductorEntry } = options;
|
|
132
|
+
// Preserve non-mcp_servers top-level keys from the original raw config.
|
|
133
|
+
const rawRoot = (config.raw && typeof config.raw === 'object' ? config.raw : {});
|
|
134
|
+
// Build the mcp_servers table.
|
|
135
|
+
const mcpServers = {};
|
|
136
|
+
if (!keepOnlyConductor) {
|
|
137
|
+
for (const [name, entry] of Object.entries(config.servers)) {
|
|
138
|
+
mcpServers[name] = {
|
|
139
|
+
command: entry.command,
|
|
140
|
+
...(entry.args !== undefined && { args: entry.args }),
|
|
141
|
+
...(entry.env && Object.keys(entry.env).length > 0
|
|
142
|
+
? { env_vars: denormaliseEnv(entry.env) }
|
|
143
|
+
: {}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Always ensure mcp-conductor is present (add or overwrite).
|
|
148
|
+
mcpServers['mcp-conductor'] = {
|
|
149
|
+
command: conductorEntry.command,
|
|
150
|
+
...(conductorEntry.args !== undefined && { args: conductorEntry.args }),
|
|
151
|
+
...(conductorEntry.env && Object.keys(conductorEntry.env).length > 0
|
|
152
|
+
? { env_vars: denormaliseEnv(conductorEntry.env) }
|
|
153
|
+
: {}),
|
|
154
|
+
};
|
|
155
|
+
// Compose final TOML object: preserve other top-level keys, then mcp_servers.
|
|
156
|
+
const tomlObj = {};
|
|
157
|
+
for (const [k, v] of Object.entries(rawRoot)) {
|
|
158
|
+
if (k !== 'mcp_servers') {
|
|
159
|
+
tomlObj[k] = v;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
tomlObj['mcp_servers'] = mcpServers;
|
|
163
|
+
// Backup existing file before any write.
|
|
164
|
+
if (existsSync(path)) {
|
|
165
|
+
writeBackup(path);
|
|
166
|
+
}
|
|
167
|
+
// Ensure the parent directory exists (project-local .codex/ may not exist yet).
|
|
168
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
169
|
+
writeFileSync(path, TOML.stringify(tomlObj), 'utf-8');
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Auto-register in the ADAPTERS map at module load time.
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
ADAPTERS.set('codex', CODEX_ADAPTER);
|
|
176
|
+
//# sourceMappingURL=codex.js.map
|