multiagents 0.1.3 → 0.1.4
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/cli/install-mcp.ts +209 -63
- package/package.json +1 -1
package/cli/install-mcp.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* multiagents install-mcp — Configure MCP servers for
|
|
3
|
+
* multiagents install-mcp — Configure MCP servers globally for all detected agent CLIs.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 3. Prints instructions to restart Claude Code
|
|
5
|
+
* Claude Code: `claude mcp add -s user` → writes to ~/.claude.json
|
|
6
|
+
* Docs: https://docs.anthropic.com/en/docs/claude-code
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Codex CLI: `codex mcp add` → writes to ~/.codex/config.toml
|
|
9
|
+
* Docs: https://developers.openai.com/codex/mcp
|
|
10
|
+
*
|
|
11
|
+
* Gemini CLI: Direct write to ~/.gemini/settings.json (no CLI command for mcp add)
|
|
12
|
+
* Docs: https://geminicli.com/docs/tools/mcp-server/
|
|
13
|
+
*
|
|
14
|
+
* Each agent has its own config format and location. This script handles all three.
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
import * as fs from "node:fs";
|
|
@@ -15,9 +19,8 @@ import * as path from "node:path";
|
|
|
15
19
|
import * as os from "node:os";
|
|
16
20
|
|
|
17
21
|
const HOME = os.homedir();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const SETTINGS_JSON = path.join(CLAUDE_DIR, "settings.json");
|
|
22
|
+
|
|
23
|
+
// --- Binary resolution ---
|
|
21
24
|
|
|
22
25
|
function findBinary(name: string): string {
|
|
23
26
|
try {
|
|
@@ -28,6 +31,8 @@ function findBinary(name: string): string {
|
|
|
28
31
|
|
|
29
32
|
const candidates = [
|
|
30
33
|
path.join(HOME, ".bun", "bin", name),
|
|
34
|
+
path.join(HOME, ".local", "bin", name),
|
|
35
|
+
path.join(HOME, ".npm-global", "bin", name),
|
|
31
36
|
`/usr/local/bin/${name}`,
|
|
32
37
|
`/opt/homebrew/bin/${name}`,
|
|
33
38
|
];
|
|
@@ -37,89 +42,230 @@ function findBinary(name: string): string {
|
|
|
37
42
|
return name;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
function findAgentCli(name: string): string | null {
|
|
46
|
+
// Check `which` first
|
|
47
|
+
try {
|
|
48
|
+
const which = Bun.spawnSync(["which", name]);
|
|
49
|
+
if (which.exitCode === 0) return new TextDecoder().decode(which.stdout).trim();
|
|
50
|
+
} catch { /* ok */ }
|
|
51
|
+
|
|
52
|
+
// Known install locations
|
|
53
|
+
const knownPaths: Record<string, string[]> = {
|
|
54
|
+
claude: [
|
|
55
|
+
path.join(HOME, ".local", "bin", "claude"),
|
|
56
|
+
path.join(HOME, ".claude", "bin", "claude"),
|
|
57
|
+
"/usr/local/bin/claude",
|
|
58
|
+
"/opt/homebrew/bin/claude",
|
|
59
|
+
],
|
|
60
|
+
codex: [
|
|
61
|
+
path.join(HOME, ".local", "bin", "codex"),
|
|
62
|
+
path.join(HOME, ".npm-global", "bin", "codex"),
|
|
63
|
+
"/usr/local/bin/codex",
|
|
64
|
+
"/opt/homebrew/bin/codex",
|
|
65
|
+
],
|
|
66
|
+
gemini: [
|
|
67
|
+
path.join(HOME, ".local", "bin", "gemini"),
|
|
68
|
+
path.join(HOME, ".npm-global", "bin", "gemini"),
|
|
69
|
+
"/usr/local/bin/gemini",
|
|
70
|
+
"/opt/homebrew/bin/gemini",
|
|
71
|
+
],
|
|
72
|
+
};
|
|
45
73
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
74
|
+
for (const p of (knownPaths[name] ?? [])) {
|
|
75
|
+
if (fs.existsSync(p)) return p;
|
|
49
76
|
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --- Claude Code ---
|
|
81
|
+
// Uses: `claude mcp add <name> -s user -- <command>`
|
|
82
|
+
// Writes to: ~/.claude.json → mcpServers
|
|
83
|
+
// Docs: https://docs.anthropic.com/en/docs/claude-code
|
|
84
|
+
|
|
85
|
+
function configureClaude(serverBin: string, orchBin: string): string[] {
|
|
86
|
+
const logs: string[] = [];
|
|
87
|
+
const claudePath = findAgentCli("claude");
|
|
88
|
+
|
|
89
|
+
if (claudePath) {
|
|
90
|
+
// Use the official CLI (preferred)
|
|
91
|
+
// Remove first to avoid duplicates
|
|
92
|
+
Bun.spawnSync([claudePath, "mcp", "remove", "multiagents", "-s", "user"], { stderr: "ignore", stdout: "ignore" });
|
|
93
|
+
Bun.spawnSync([claudePath, "mcp", "remove", "multiagents-orch", "-s", "user"], { stderr: "ignore", stdout: "ignore" });
|
|
94
|
+
|
|
95
|
+
const r1 = Bun.spawnSync([claudePath, "mcp", "add", "multiagents", "-s", "user", "--", serverBin]);
|
|
96
|
+
const r2 = Bun.spawnSync([claudePath, "mcp", "add", "multiagents-orch", "-s", "user", "--", orchBin]);
|
|
50
97
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
mcpConfig = JSON.parse(fs.readFileSync(MCP_JSON, "utf-8"));
|
|
55
|
-
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
56
|
-
} catch {
|
|
57
|
-
fs.copyFileSync(MCP_JSON, MCP_JSON + ".bak");
|
|
58
|
-
mcpConfig = { mcpServers: {} };
|
|
59
|
-
logs.push(" \x1b[33m!\x1b[0m Existing .mcp.json was corrupted — backed up and recreated");
|
|
98
|
+
if (r1.exitCode === 0 && r2.exitCode === 0) {
|
|
99
|
+
logs.push(" \x1b[32m✔\x1b[0m Claude Code: MCP servers added (via claude mcp add -s user)");
|
|
100
|
+
return logs;
|
|
60
101
|
}
|
|
102
|
+
logs.push(" \x1b[33m!\x1b[0m Claude Code: CLI method failed, falling back to file config");
|
|
61
103
|
}
|
|
62
104
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
105
|
+
// Fallback: write directly to ~/.claude.json
|
|
106
|
+
const configPath = path.join(HOME, ".claude.json");
|
|
107
|
+
let config: Record<string, unknown> = {};
|
|
108
|
+
if (fs.existsSync(configPath)) {
|
|
109
|
+
try { config = JSON.parse(fs.readFileSync(configPath, "utf-8")); } catch { config = {}; }
|
|
110
|
+
}
|
|
67
111
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
112
|
+
const mcpServers = (config.mcpServers as Record<string, unknown>) ?? {};
|
|
113
|
+
mcpServers["multiagents"] = { type: "stdio", command: serverBin, args: [], env: {} };
|
|
114
|
+
mcpServers["multiagents-orch"] = { type: "stdio", command: orchBin, args: [], env: {} };
|
|
115
|
+
config.mcpServers = mcpServers;
|
|
116
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
117
|
+
logs.push(" \x1b[32m✔\x1b[0m Claude Code: MCP servers written to ~/.claude.json");
|
|
118
|
+
return logs;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- Codex CLI ---
|
|
122
|
+
// Uses: `codex mcp add <name> -- <command> <args...>`
|
|
123
|
+
// Writes to: ~/.codex/config.toml → [mcp_servers.<name>]
|
|
124
|
+
// Docs: https://developers.openai.com/codex/mcp
|
|
125
|
+
|
|
126
|
+
function configureCodex(serverBin: string): string[] {
|
|
127
|
+
const logs: string[] = [];
|
|
128
|
+
const codexPath = findAgentCli("codex");
|
|
129
|
+
|
|
130
|
+
if (codexPath) {
|
|
131
|
+
// Use the official CLI (preferred)
|
|
132
|
+
// Remove first to avoid duplicates
|
|
133
|
+
Bun.spawnSync([codexPath, "mcp", "remove", "multiagents"], { stderr: "ignore", stdout: "ignore" });
|
|
134
|
+
|
|
135
|
+
const r1 = Bun.spawnSync([codexPath, "mcp", "add", "multiagents", "--", serverBin, "--agent-type", "codex"]);
|
|
136
|
+
if (r1.exitCode === 0) {
|
|
137
|
+
logs.push(" \x1b[32m✔\x1b[0m Codex CLI: MCP server added (via codex mcp add)");
|
|
75
138
|
return logs;
|
|
76
139
|
}
|
|
140
|
+
logs.push(" \x1b[33m!\x1b[0m Codex CLI: CLI method failed, falling back to file config");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Fallback: write directly to ~/.codex/config.toml
|
|
144
|
+
const codexDir = path.join(HOME, ".codex");
|
|
145
|
+
const configPath = path.join(codexDir, "config.toml");
|
|
146
|
+
|
|
147
|
+
if (!fs.existsSync(codexDir)) {
|
|
148
|
+
fs.mkdirSync(codexDir, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let existing = "";
|
|
152
|
+
if (fs.existsSync(configPath)) {
|
|
153
|
+
existing = fs.readFileSync(configPath, "utf-8");
|
|
77
154
|
}
|
|
78
155
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
logs.push(" \x1b[32m✔\x1b[0m Enabled in ~/.claude/settings.json");
|
|
156
|
+
// Check if [mcp_servers.multiagents] already exists
|
|
157
|
+
if (existing.includes("[mcp_servers.multiagents]")) {
|
|
158
|
+
// Replace existing block
|
|
159
|
+
existing = existing.replace(
|
|
160
|
+
/\[mcp_servers\.multiagents\][^\[]*/s,
|
|
161
|
+
`[mcp_servers.multiagents]\ncommand = "${serverBin}"\nargs = ["--agent-type", "codex"]\n\n`
|
|
162
|
+
);
|
|
87
163
|
} else {
|
|
88
|
-
|
|
164
|
+
// Append new block
|
|
165
|
+
existing += `\n[mcp_servers.multiagents]\ncommand = "${serverBin}"\nargs = ["--agent-type", "codex"]\n`;
|
|
89
166
|
}
|
|
90
167
|
|
|
168
|
+
fs.writeFileSync(configPath, existing);
|
|
169
|
+
logs.push(" \x1b[32m✔\x1b[0m Codex CLI: MCP server written to ~/.codex/config.toml");
|
|
91
170
|
return logs;
|
|
92
171
|
}
|
|
93
172
|
|
|
173
|
+
// --- Gemini CLI ---
|
|
174
|
+
// No CLI command for adding MCP servers (as of March 2026).
|
|
175
|
+
// Direct write to: ~/.gemini/settings.json → mcpServers
|
|
176
|
+
// Docs: https://geminicli.com/docs/tools/mcp-server/
|
|
177
|
+
|
|
178
|
+
function configureGemini(serverBin: string): string[] {
|
|
179
|
+
const logs: string[] = [];
|
|
180
|
+
const geminiDir = path.join(HOME, ".gemini");
|
|
181
|
+
const configPath = path.join(geminiDir, "settings.json");
|
|
182
|
+
|
|
183
|
+
if (!fs.existsSync(geminiDir)) {
|
|
184
|
+
fs.mkdirSync(geminiDir, { recursive: true });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let settings: Record<string, unknown> = {};
|
|
188
|
+
if (fs.existsSync(configPath)) {
|
|
189
|
+
try { settings = JSON.parse(fs.readFileSync(configPath, "utf-8")); } catch { settings = {}; }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const mcpServers = (settings.mcpServers as Record<string, unknown>) ?? {};
|
|
193
|
+
mcpServers["multiagents"] = {
|
|
194
|
+
command: serverBin,
|
|
195
|
+
args: ["--agent-type", "gemini"],
|
|
196
|
+
timeout: 30000,
|
|
197
|
+
};
|
|
198
|
+
settings.mcpServers = mcpServers;
|
|
199
|
+
|
|
200
|
+
fs.writeFileSync(configPath, JSON.stringify(settings, null, 2) + "\n");
|
|
201
|
+
logs.push(" \x1b[32m✔\x1b[0m Gemini CLI: MCP server written to ~/.gemini/settings.json");
|
|
202
|
+
return logs;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// --- Public API ---
|
|
206
|
+
|
|
207
|
+
interface ConfigResult {
|
|
208
|
+
logs: string[];
|
|
209
|
+
configured: string[];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function configureMcp(): ConfigResult {
|
|
213
|
+
const serverBin = findBinary("multiagents-server");
|
|
214
|
+
const orchBin = findBinary("multiagents-orch");
|
|
215
|
+
const logs: string[] = [];
|
|
216
|
+
const configured: string[] = [];
|
|
217
|
+
|
|
218
|
+
// Claude Code (always — it's the primary orchestrator)
|
|
219
|
+
const claudeLogs = configureClaude(serverBin, orchBin);
|
|
220
|
+
logs.push(...claudeLogs);
|
|
221
|
+
configured.push("claude");
|
|
222
|
+
|
|
223
|
+
// Codex CLI (if installed)
|
|
224
|
+
if (findAgentCli("codex")) {
|
|
225
|
+
const codexLogs = configureCodex(serverBin);
|
|
226
|
+
logs.push(...codexLogs);
|
|
227
|
+
configured.push("codex");
|
|
228
|
+
} else {
|
|
229
|
+
logs.push(" \x1b[90m-\x1b[0m Codex CLI: not installed, skipping");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Gemini CLI (if installed)
|
|
233
|
+
if (findAgentCli("gemini")) {
|
|
234
|
+
const geminiLogs = configureGemini(serverBin);
|
|
235
|
+
logs.push(...geminiLogs);
|
|
236
|
+
configured.push("gemini");
|
|
237
|
+
} else {
|
|
238
|
+
logs.push(" \x1b[90m-\x1b[0m Gemini CLI: not installed, skipping");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { logs, configured };
|
|
242
|
+
}
|
|
243
|
+
|
|
94
244
|
/** Verbose version — standalone `multiagents install-mcp` command. */
|
|
95
245
|
export async function installMcp(): Promise<void> {
|
|
96
246
|
console.log("\n\x1b[1m\x1b[36m multiagents install-mcp\x1b[0m");
|
|
97
|
-
console.log("\x1b[90m Configure MCP servers for
|
|
247
|
+
console.log("\x1b[90m Configure MCP servers for all detected agent CLIs\x1b[0m\n");
|
|
98
248
|
|
|
99
|
-
const logs = configureMcp();
|
|
249
|
+
const { logs, configured } = configureMcp();
|
|
100
250
|
for (const line of logs) console.log(line);
|
|
101
251
|
|
|
102
252
|
console.log(`
|
|
103
|
-
\x1b[1m\x1b[32mDone!\x1b[0m MCP
|
|
104
|
-
|
|
105
|
-
\x1b[1mNext step:\x1b[0m Restart Claude Code to load the new tools.
|
|
106
|
-
Exit Claude Code and run: \x1b[90mclaude\x1b[0m
|
|
253
|
+
\x1b[1m\x1b[32mDone!\x1b[0m MCP configured for: ${configured.join(", ")}
|
|
107
254
|
|
|
108
|
-
\x1b[
|
|
109
|
-
\x1b[90m {
|
|
110
|
-
"mcpServers": {
|
|
111
|
-
"multiagents": { "command": "multiagents-server", "args": [] },
|
|
112
|
-
"multiagents-orch": { "command": "multiagents-orch", "args": [] }
|
|
113
|
-
}
|
|
114
|
-
}\x1b[0m
|
|
255
|
+
\x1b[1mNext step:\x1b[0m Restart your agent CLIs to load the new tools.
|
|
115
256
|
|
|
116
|
-
|
|
117
|
-
\x1b[
|
|
257
|
+
\x1b[1mVerify:\x1b[0m
|
|
258
|
+
${configured.includes("claude") ? " Claude: \x1b[90mclaude mcp list | grep multiagent\x1b[0m\n" : ""}${configured.includes("codex") ? " Codex: \x1b[90mcodex mcp list\x1b[0m\n" : ""}${configured.includes("gemini") ? " Gemini: \x1b[90mCheck ~/.gemini/settings.json\x1b[0m\n" : ""}
|
|
259
|
+
\x1b[1mManual setup:\x1b[0m
|
|
260
|
+
Claude: \x1b[90mclaude mcp add multiagents -s user -- multiagents-server\x1b[0m
|
|
261
|
+
Codex: \x1b[90mcodex mcp add multiagents -- multiagents-server --agent-type codex\x1b[0m
|
|
262
|
+
Gemini: Add to ~/.gemini/settings.json:
|
|
263
|
+
\x1b[90m{"mcpServers":{"multiagents":{"command":"multiagents-server","args":["--agent-type","gemini"]}}}\x1b[0m
|
|
118
264
|
`);
|
|
119
265
|
}
|
|
120
266
|
|
|
121
|
-
/** Quiet version — called from `setup` flow
|
|
267
|
+
/** Quiet version — called from `setup` flow. */
|
|
122
268
|
export async function installMcpSilent(): Promise<void> {
|
|
123
|
-
const logs = configureMcp();
|
|
269
|
+
const { logs } = configureMcp();
|
|
124
270
|
for (const line of logs) console.log(line);
|
|
125
271
|
}
|