ac-framework 1.9.1 → 1.9.2
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/package.json +1 -1
- package/src/services/mcp-installer.js +226 -76
package/package.json
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* mcp-installer.js — MCP config installer for AC Framework
|
|
3
3
|
*
|
|
4
4
|
* Detects installed AI assistants and injects the ac-framework-memory
|
|
5
|
-
* MCP server into their config files.
|
|
5
|
+
* MCP server into their config files using the CORRECT format for each.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Verified formats (Mar 2026):
|
|
8
|
+
* opencode → ~/.config/opencode/opencode.json key:"mcp" type:"local" command:array
|
|
9
|
+
* claude → ~/.claude.json key:"mcpServers" type:"stdio"
|
|
10
|
+
* cursor → ~/.cursor/mcp.json key:"mcpServers"
|
|
11
|
+
* windsurf → ~/.codeium/windsurf/mcp_config.json key:"mcpServers"
|
|
12
|
+
* gemini → ~/.gemini/settings.json key:"mcpServers"
|
|
13
|
+
* codex → ~/.codex/config.toml TOML format
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
@@ -17,61 +20,149 @@ import { fileURLToPath } from 'node:url';
|
|
|
17
20
|
|
|
18
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
/** Absolute path to the MCP server entry point */
|
|
21
24
|
export function getMCPServerPath() {
|
|
22
|
-
|
|
23
|
-
return srcPath;
|
|
25
|
+
return join(__dirname, '../mcp/server.js');
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
// ── Supported assistants ──────────────────────────────────────────
|
|
27
|
-
|
|
28
28
|
const home = homedir();
|
|
29
|
-
const IS_WIN = platform() === 'win32';
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
// ── Assistant definitions ─────────────────────────────────────────
|
|
31
|
+
//
|
|
32
|
+
// Each entry may have a custom `install` / `uninstall` function if its
|
|
33
|
+
// format differs from the standard JSON mcpServers pattern.
|
|
34
|
+
|
|
35
|
+
export const ASSISTANTS = [
|
|
36
|
+
// ── OpenCode ───────────────────────────────────────────────────
|
|
37
|
+
// Config: ~/.config/opencode/opencode.json
|
|
38
|
+
// Schema: { "mcp": { "<name>": { "type": "local", "command": ["node", "path"] } } }
|
|
38
39
|
{
|
|
39
40
|
name: 'opencode',
|
|
40
|
-
configPath: join(home, '.opencode', '
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
configPath: join(home, '.config', 'opencode', 'opencode.json'),
|
|
42
|
+
detectDir: join(home, '.config', 'opencode'),
|
|
43
|
+
install(serverPath) {
|
|
44
|
+
const configDir = dirname(this.configPath);
|
|
45
|
+
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
46
|
+
|
|
47
|
+
let config = {};
|
|
48
|
+
if (existsSync(this.configPath)) {
|
|
49
|
+
try { config = JSON.parse(readFileSync(this.configPath, 'utf8')); } catch { config = {}; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!config.mcp) config.mcp = {};
|
|
53
|
+
config.mcp['ac-framework-memory'] = {
|
|
54
|
+
type: 'local',
|
|
55
|
+
command: ['node', serverPath],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
59
|
+
return true;
|
|
60
|
+
},
|
|
61
|
+
uninstall() {
|
|
62
|
+
if (!existsSync(this.configPath)) return true;
|
|
63
|
+
let config = {};
|
|
64
|
+
try { config = JSON.parse(readFileSync(this.configPath, 'utf8')); } catch { return true; }
|
|
65
|
+
if (config.mcp?.['ac-framework-memory']) {
|
|
66
|
+
delete config.mcp['ac-framework-memory'];
|
|
67
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
},
|
|
43
71
|
},
|
|
72
|
+
|
|
73
|
+
// ── Claude Code CLI ────────────────────────────────────────────
|
|
74
|
+
// Config: ~/.claude.json (top-level mcpServers key, merges with existing data)
|
|
75
|
+
// Schema: { "mcpServers": { "<name>": { "type": "stdio", "command": "node", "args": ["path"] } } }
|
|
44
76
|
{
|
|
45
77
|
name: 'claude',
|
|
46
|
-
// Claude Code CLI uses ~/.claude.json (mcpServers key)
|
|
47
78
|
configPath: join(home, '.claude.json'),
|
|
48
|
-
configKey: 'mcpServers',
|
|
49
|
-
detectDir: home,
|
|
50
79
|
detectFile: join(home, '.claude.json'),
|
|
80
|
+
install(serverPath) {
|
|
81
|
+
let config = {};
|
|
82
|
+
if (existsSync(this.configPath)) {
|
|
83
|
+
try { config = JSON.parse(readFileSync(this.configPath, 'utf8')); } catch { config = {}; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
87
|
+
config.mcpServers['ac-framework-memory'] = {
|
|
88
|
+
type: 'stdio',
|
|
89
|
+
command: 'node',
|
|
90
|
+
args: [serverPath],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
94
|
+
return true;
|
|
95
|
+
},
|
|
96
|
+
uninstall() {
|
|
97
|
+
if (!existsSync(this.configPath)) return true;
|
|
98
|
+
let config = {};
|
|
99
|
+
try { config = JSON.parse(readFileSync(this.configPath, 'utf8')); } catch { return true; }
|
|
100
|
+
if (config.mcpServers?.['ac-framework-memory']) {
|
|
101
|
+
delete config.mcpServers['ac-framework-memory'];
|
|
102
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
},
|
|
51
106
|
},
|
|
107
|
+
|
|
108
|
+
// ── Cursor IDE ─────────────────────────────────────────────────
|
|
109
|
+
// Config: ~/.cursor/mcp.json
|
|
110
|
+
// Schema: { "mcpServers": { "<name>": { "command": "node", "args": ["path"] } } }
|
|
52
111
|
{
|
|
53
112
|
name: 'cursor',
|
|
54
113
|
configPath: join(home, '.cursor', 'mcp.json'),
|
|
55
|
-
configKey: 'mcpServers',
|
|
56
114
|
detectDir: join(home, '.cursor'),
|
|
115
|
+
install(serverPath) {
|
|
116
|
+
return installJsonMcpServers(this.configPath, serverPath);
|
|
117
|
+
},
|
|
118
|
+
uninstall() {
|
|
119
|
+
return uninstallJsonMcpServers(this.configPath);
|
|
120
|
+
},
|
|
57
121
|
},
|
|
122
|
+
|
|
123
|
+
// ── Windsurf IDE ───────────────────────────────────────────────
|
|
124
|
+
// Config: ~/.codeium/windsurf/mcp_config.json (NOT ~/.windsurf/mcp.json)
|
|
125
|
+
// Schema: { "mcpServers": { "<name>": { "command": "node", "args": ["path"] } } }
|
|
58
126
|
{
|
|
59
127
|
name: 'windsurf',
|
|
60
|
-
configPath: join(home, '.windsurf', '
|
|
61
|
-
|
|
62
|
-
|
|
128
|
+
configPath: join(home, '.codeium', 'windsurf', 'mcp_config.json'),
|
|
129
|
+
detectDir: join(home, '.codeium', 'windsurf'),
|
|
130
|
+
install(serverPath) {
|
|
131
|
+
return installJsonMcpServers(this.configPath, serverPath);
|
|
132
|
+
},
|
|
133
|
+
uninstall() {
|
|
134
|
+
return uninstallJsonMcpServers(this.configPath);
|
|
135
|
+
},
|
|
63
136
|
},
|
|
137
|
+
|
|
138
|
+
// ── Google Gemini CLI ──────────────────────────────────────────
|
|
139
|
+
// Config: ~/.gemini/settings.json (NOT ~/.gemini/mcp.json)
|
|
140
|
+
// Schema: { "mcpServers": { "<name>": { "command": "node", "args": ["path"] } } }
|
|
64
141
|
{
|
|
65
142
|
name: 'gemini',
|
|
66
|
-
configPath: join(home, '.gemini', '
|
|
67
|
-
configKey: 'mcpServers',
|
|
143
|
+
configPath: join(home, '.gemini', 'settings.json'),
|
|
68
144
|
detectDir: join(home, '.gemini'),
|
|
145
|
+
install(serverPath) {
|
|
146
|
+
return installJsonMcpServers(this.configPath, serverPath);
|
|
147
|
+
},
|
|
148
|
+
uninstall() {
|
|
149
|
+
return uninstallJsonMcpServers(this.configPath);
|
|
150
|
+
},
|
|
69
151
|
},
|
|
152
|
+
|
|
153
|
+
// ── OpenAI Codex CLI ───────────────────────────────────────────
|
|
154
|
+
// Config: ~/.codex/config.toml (TOML format, NOT JSON)
|
|
155
|
+
// Schema: [mcp_servers.ac-framework-memory]\ncommand = "node"\nargs = ["/path"]
|
|
70
156
|
{
|
|
71
157
|
name: 'codex',
|
|
72
|
-
configPath: join(home, '.codex', '
|
|
73
|
-
configKey: 'mcpServers',
|
|
158
|
+
configPath: join(home, '.codex', 'config.toml'),
|
|
74
159
|
detectDir: join(home, '.codex'),
|
|
160
|
+
install(serverPath) {
|
|
161
|
+
return installTomlMcpServer(this.configPath, serverPath);
|
|
162
|
+
},
|
|
163
|
+
uninstall() {
|
|
164
|
+
return uninstallTomlMcpServer(this.configPath);
|
|
165
|
+
},
|
|
75
166
|
},
|
|
76
167
|
];
|
|
77
168
|
|
|
@@ -79,7 +170,6 @@ const ASSISTANTS = [
|
|
|
79
170
|
|
|
80
171
|
export function isAssistantInstalled(assistant) {
|
|
81
172
|
try {
|
|
82
|
-
// If a specific file is the detection signal, use that
|
|
83
173
|
if (assistant.detectFile) return existsSync(assistant.detectFile);
|
|
84
174
|
return existsSync(assistant.detectDir);
|
|
85
175
|
} catch {
|
|
@@ -87,39 +177,115 @@ export function isAssistantInstalled(assistant) {
|
|
|
87
177
|
}
|
|
88
178
|
}
|
|
89
179
|
|
|
90
|
-
// ──
|
|
180
|
+
// ── Generic JSON mcpServers helpers ───────────────────────────────
|
|
91
181
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
182
|
+
function installJsonMcpServers(configPath, serverPath) {
|
|
183
|
+
const configDir = dirname(configPath);
|
|
184
|
+
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
95
185
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
186
|
+
let config = {};
|
|
187
|
+
if (existsSync(configPath)) {
|
|
188
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch { config = {}; }
|
|
189
|
+
}
|
|
99
190
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
191
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
192
|
+
config.mcpServers['ac-framework-memory'] = {
|
|
193
|
+
command: 'node',
|
|
194
|
+
args: [serverPath],
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
109
200
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
201
|
+
function uninstallJsonMcpServers(configPath) {
|
|
202
|
+
if (!existsSync(configPath)) return true;
|
|
203
|
+
let config = {};
|
|
204
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch { return true; }
|
|
205
|
+
if (config.mcpServers?.['ac-framework-memory']) {
|
|
206
|
+
delete config.mcpServers['ac-framework-memory'];
|
|
207
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── TOML helpers (Codex) ──────────────────────────────────────────
|
|
213
|
+
//
|
|
214
|
+
// We write TOML manually (no external dep). The section to add/replace:
|
|
215
|
+
//
|
|
216
|
+
// [mcp_servers.ac-framework-memory]
|
|
217
|
+
// command = "node"
|
|
218
|
+
// args = ["/abs/path/to/server.js"]
|
|
219
|
+
|
|
220
|
+
const TOML_SECTION = 'mcp_servers.ac-framework-memory';
|
|
221
|
+
|
|
222
|
+
function installTomlMcpServer(configPath, serverPath) {
|
|
223
|
+
const configDir = dirname(configPath);
|
|
224
|
+
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
225
|
+
|
|
226
|
+
// Escape backslashes for Windows paths
|
|
227
|
+
const escapedPath = serverPath.replace(/\\/g, '\\\\');
|
|
228
|
+
const newBlock = [
|
|
229
|
+
`[${TOML_SECTION}]`,
|
|
230
|
+
`command = "node"`,
|
|
231
|
+
`args = ["${escapedPath}"]`,
|
|
232
|
+
].join('\n');
|
|
233
|
+
|
|
234
|
+
let existing = '';
|
|
235
|
+
if (existsSync(configPath)) {
|
|
236
|
+
try { existing = readFileSync(configPath, 'utf8'); } catch { existing = ''; }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (existing.includes(`[${TOML_SECTION}]`)) {
|
|
240
|
+
// Replace existing block — remove old section, append new one
|
|
241
|
+
existing = removeTomlSection(existing, TOML_SECTION);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const separator = existing.trim() ? '\n\n' : '';
|
|
245
|
+
writeFileSync(configPath, existing.trimEnd() + separator + newBlock + '\n');
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function uninstallTomlMcpServer(configPath) {
|
|
250
|
+
if (!existsSync(configPath)) return true;
|
|
251
|
+
let content = '';
|
|
252
|
+
try { content = readFileSync(configPath, 'utf8'); } catch { return true; }
|
|
253
|
+
if (content.includes(`[${TOML_SECTION}]`)) {
|
|
254
|
+
content = removeTomlSection(content, TOML_SECTION);
|
|
255
|
+
writeFileSync(configPath, content);
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Removes a TOML section (and its key-value lines) from a TOML string.
|
|
262
|
+
* Stops at the next [section] header or end of file.
|
|
263
|
+
*/
|
|
264
|
+
function removeTomlSection(toml, sectionKey) {
|
|
265
|
+
const lines = toml.split('\n');
|
|
266
|
+
const result = [];
|
|
267
|
+
let inSection = false;
|
|
268
|
+
|
|
269
|
+
for (const line of lines) {
|
|
270
|
+
const trimmed = line.trim();
|
|
271
|
+
if (trimmed === `[${sectionKey}]`) {
|
|
272
|
+
inSection = true;
|
|
273
|
+
continue;
|
|
113
274
|
}
|
|
275
|
+
if (inSection && trimmed.startsWith('[')) {
|
|
276
|
+
inSection = false;
|
|
277
|
+
}
|
|
278
|
+
if (!inSection) result.push(line);
|
|
279
|
+
}
|
|
114
280
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
};
|
|
281
|
+
return result.join('\n');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── Install / Uninstall per assistant ────────────────────────────
|
|
120
285
|
|
|
121
|
-
|
|
122
|
-
|
|
286
|
+
export function installMCPForAssistant(assistant) {
|
|
287
|
+
try {
|
|
288
|
+
return assistant.install(getMCPServerPath());
|
|
123
289
|
} catch (error) {
|
|
124
290
|
console.error(` Failed to install MCP for ${assistant.name}: ${error.message}`);
|
|
125
291
|
return false;
|
|
@@ -128,21 +294,7 @@ export function installMCPForAssistant(assistant) {
|
|
|
128
294
|
|
|
129
295
|
export function uninstallMCPForAssistant(assistant) {
|
|
130
296
|
try {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
let config = {};
|
|
134
|
-
try {
|
|
135
|
-
config = JSON.parse(readFileSync(assistant.configPath, 'utf8'));
|
|
136
|
-
} catch {
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (config[assistant.configKey]?.['ac-framework-memory']) {
|
|
141
|
-
delete config[assistant.configKey]['ac-framework-memory'];
|
|
142
|
-
writeFileSync(assistant.configPath, JSON.stringify(config, null, 2));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return true;
|
|
297
|
+
return assistant.uninstall();
|
|
146
298
|
} catch (error) {
|
|
147
299
|
console.error(` Failed to uninstall MCP for ${assistant.name}: ${error.message}`);
|
|
148
300
|
return false;
|
|
@@ -190,5 +342,3 @@ export function uninstallAllMCPs() {
|
|
|
190
342
|
}
|
|
191
343
|
return { success };
|
|
192
344
|
}
|
|
193
|
-
|
|
194
|
-
export { ASSISTANTS };
|