Skip to content

Commit 7b1ec64

Browse files
authored
Merge pull request #146 from divanshu-go/mcp0
feat: add more app configs to mcp
2 parents 36e66e4 + a3d79d7 commit 7b1ec64

File tree

3 files changed

+331
-228
lines changed

3 files changed

+331
-228
lines changed

terminator-mcp-agent/README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,22 @@ You can also use the CLI to configure your app automatically:
4343
npx -y terminator-mcp-agent --add-to-app [app]
4444
```
4545

46-
Replace `[app]` with one of: `cursor`, `claude`, `vscode`, `insiders`, `windsurf`.
46+
Replace `[app]` with one of:
47+
48+
- cursor
49+
- claude
50+
- vscode
51+
- insiders
52+
- windsurf
53+
- cline
54+
- roocode
55+
- witsy
56+
- enconvo
57+
- boltai
58+
- amazon-bedrock
59+
- amazonq
60+
61+
If you omit `[app]`, the CLI will prompt you to select from all available options.
4762

4863
---
4964

terminator-mcp-agent/config.js

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
const fs = require("fs");
2+
const os = require("os");
3+
const path = require("path");
4+
const { execFileSync } = require("child_process");
5+
6+
/**
7+
* @typedef {Object} ClientConfig
8+
* @property {Object.<string, any>} mcpServers
9+
*/
10+
11+
/**
12+
* @typedef {Object} ClientFileTarget
13+
* @property {"file"} type
14+
* @property {string} path
15+
*/
16+
17+
/**
18+
* @typedef {Object} ClientCommandTarget
19+
* @property {"command"} type
20+
* @property {string} command
21+
*/
22+
23+
// Initialize platform-specific paths
24+
const homeDir = os.homedir();
25+
26+
const platformPaths = {
27+
win32: {
28+
baseDir: process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"),
29+
vscodePath: path.join("Code", "User", "globalStorage"),
30+
},
31+
darwin: {
32+
baseDir: path.join(homeDir, "Library", "Application Support"),
33+
vscodePath: path.join("Code", "User", "globalStorage"),
34+
},
35+
linux: {
36+
baseDir: process.env.XDG_CONFIG_HOME || path.join(homeDir, ".config"),
37+
vscodePath: path.join("Code/User/globalStorage"),
38+
},
39+
};
40+
41+
const platform = process.platform;
42+
const { baseDir, vscodePath } = platformPaths[platform] || platformPaths.linux;
43+
const defaultClaudePath = path.join(
44+
baseDir,
45+
"Claude",
46+
"claude_desktop_config.json",
47+
);
48+
49+
// Define client paths using the platform-specific base directories
50+
const clientPaths = {
51+
claude: { type: "file", path: defaultClaudePath },
52+
cline: {
53+
type: "file",
54+
path: path.join(
55+
baseDir,
56+
vscodePath,
57+
"saoudrizwan.claude-dev",
58+
"settings",
59+
"cline_mcp_settings.json",
60+
),
61+
},
62+
roocode: {
63+
type: "file",
64+
path: path.join(
65+
baseDir,
66+
vscodePath,
67+
"rooveterinaryinc.roo-cline",
68+
"settings",
69+
"mcp_settings.json",
70+
),
71+
},
72+
windsurf: {
73+
type: "file",
74+
path: path.join(homeDir, ".codeium", "windsurf", "mcp_config.json"),
75+
},
76+
witsy: { type: "file", path: path.join(baseDir, "Witsy", "settings.json") },
77+
enconvo: {
78+
type: "file",
79+
path: path.join(homeDir, ".config", "enconvo", "mcp_config.json"),
80+
},
81+
cursor: { type: "file", path: path.join(homeDir, ".cursor", "mcp.json") },
82+
vscode: {
83+
type: "command",
84+
command: process.platform === "win32" ? "code.cmd" : "code",
85+
},
86+
"vscode-insiders": {
87+
type: "command",
88+
command:
89+
process.platform === "win32" ? "code-insiders.cmd" : "code-insiders",
90+
},
91+
boltai: { type: "file", path: path.join(homeDir, ".boltai", "mcp.json") },
92+
"amazon-bedrock": {
93+
type: "file",
94+
path: path.join(homeDir, "Amazon Bedrock Client", "mcp_config.json"),
95+
},
96+
amazonq: {
97+
type: "file",
98+
path: path.join(homeDir, ".aws", "amazonq", "mcp.json"),
99+
},
100+
};
101+
102+
/**
103+
* @param {string} [client]
104+
* @returns {ClientFileTarget|ClientCommandTarget}
105+
*/
106+
function getConfigPath(client) {
107+
const normalizedClient = client ? client.toLowerCase() : "claude";
108+
verbose(`Getting config path for client: ${normalizedClient}`);
109+
110+
const configTarget = clientPaths[normalizedClient] || {
111+
type: "file",
112+
path: path.join(
113+
path.dirname(defaultClaudePath),
114+
"..",
115+
client || "claude",
116+
`${normalizedClient}_config.json`,
117+
),
118+
};
119+
120+
verbose(`Config path resolved to: ${JSON.stringify(configTarget)}`);
121+
return configTarget;
122+
}
123+
124+
/**
125+
* @param {string} client
126+
* @returns {ClientConfig}
127+
*/
128+
function readConfig(client) {
129+
verbose(`Reading config for client: ${client}`);
130+
try {
131+
const configPath = getConfigPath(client);
132+
133+
// Command-based installers (i.e. VS Code) do not currently support listing servers
134+
if (configPath.type === "command") {
135+
return { mcpServers: {} };
136+
}
137+
138+
verbose(`Checking if config file exists at: ${configPath.path}`);
139+
if (!fs.existsSync(configPath.path)) {
140+
verbose(`Config file not found, returning default empty config`);
141+
return { mcpServers: {} };
142+
}
143+
144+
verbose(`Reading config file content`);
145+
const rawConfig = JSON.parse(fs.readFileSync(configPath.path, "utf8"));
146+
verbose(
147+
`Config loaded successfully: ${JSON.stringify(rawConfig, null, 2)}`,
148+
);
149+
150+
return {
151+
...rawConfig,
152+
mcpServers: rawConfig.mcpServers || {},
153+
};
154+
} catch (error) {
155+
verbose(
156+
`Error reading config: ${error instanceof Error ? error.stack : JSON.stringify(error)}`,
157+
);
158+
return { mcpServers: {} };
159+
}
160+
}
161+
162+
/**
163+
* @param {ClientConfig} config
164+
* @param {string} [client]
165+
*/
166+
function writeConfig(configObj, client) {
167+
verbose(`Writing config for client: ${client || "default"}`);
168+
verbose(`Config data: ${JSON.stringify(configObj, null, 2)}`);
169+
170+
if (!configObj.mcpServers || typeof configObj.mcpServers !== "object") {
171+
verbose(`Invalid mcpServers structure in config`);
172+
throw new Error("Invalid mcpServers structure");
173+
}
174+
175+
const configPath = getConfigPath(client);
176+
if (configPath.type === "command") {
177+
writeConfigCommand(configObj, configPath);
178+
} else {
179+
writeConfigFile(configObj, configPath);
180+
}
181+
}
182+
183+
/**
184+
* @param {ClientConfig} config
185+
* @param {ClientCommandTarget} target
186+
*/
187+
function writeConfigCommand(config, target) {
188+
const args = [];
189+
for (const [name, server] of Object.entries(config.mcpServers)) {
190+
args.push("--add-mcp", JSON.stringify({ ...server, name }));
191+
}
192+
193+
verbose(`Running command: ${JSON.stringify([target.command, ...args])}`);
194+
195+
try {
196+
const output = execFileSync(target.command, args);
197+
verbose(`Executed command successfully: ${output.toString()}`);
198+
} catch (error) {
199+
verbose(
200+
`Error executing command: ${error instanceof Error ? error.message : String(error)}`,
201+
);
202+
203+
if (error && error.code === "ENOENT") {
204+
throw new Error(
205+
`Command '${target.command}' not found. Make sure ${target.command} is installed and on your PATH`,
206+
);
207+
}
208+
209+
throw error;
210+
}
211+
}
212+
213+
/**
214+
* @param {ClientConfig} config
215+
* @param {ClientFileTarget} target
216+
*/
217+
function writeConfigFile(config, target) {
218+
const configDir = path.dirname(target.path);
219+
220+
verbose(`Ensuring config directory exists: ${configDir}`);
221+
if (!fs.existsSync(configDir)) {
222+
verbose(`Creating directory: ${configDir}`);
223+
fs.mkdirSync(configDir, { recursive: true });
224+
}
225+
226+
let existingConfig = { mcpServers: {} };
227+
try {
228+
if (fs.existsSync(target.path)) {
229+
verbose(`Reading existing config file for merging`);
230+
existingConfig = JSON.parse(fs.readFileSync(target.path, "utf8"));
231+
verbose(
232+
`Existing config loaded: ${JSON.stringify(existingConfig, null, 2)}`,
233+
);
234+
}
235+
} catch (error) {
236+
verbose(
237+
`Error reading existing config for merge: ${error instanceof Error ? error.message : String(error)}`,
238+
);
239+
// If reading fails, continue with empty existing config
240+
}
241+
242+
verbose(`Merging configs`);
243+
const mergedConfig = {
244+
...existingConfig,
245+
...config,
246+
};
247+
verbose(`Merged config: ${JSON.stringify(mergedConfig, null, 2)}`);
248+
249+
verbose(`Writing config to file: ${target.path}`);
250+
fs.writeFileSync(target.path, JSON.stringify(mergedConfig, null, 2));
251+
verbose(`Config successfully written`);
252+
}
253+
254+
function verbose(msg) {
255+
if (process.env.MCP_VERBOSE) {
256+
console.log(`[config] ${msg}`);
257+
}
258+
}
259+
260+
const supportedClients = [
261+
{ key: "cursor", label: "Cursor" },
262+
{ key: "claude", label: "Claude" },
263+
{ key: "vscode", label: "VS Code" },
264+
{ key: "insiders", label: "VS Code Insiders" },
265+
{ key: "windsurf", label: "Windsurf" },
266+
{ key: "cline", label: "Cline" },
267+
{ key: "roocode", label: "RooCode" },
268+
{ key: "witsy", label: "Witsy" },
269+
{ key: "enconvo", label: "Enconvo" },
270+
{ key: "boltai", label: "BoltAI" },
271+
{ key: "amazon-bedrock", label: "Amazon Bedrock" },
272+
{ key: "amazonq", label: "Amazon Q" },
273+
];
274+
275+
module.exports = {
276+
getConfigPath,
277+
readConfig,
278+
writeConfig,
279+
supportedClients,
280+
};

0 commit comments

Comments
 (0)