@enfyra/mcp-server 0.0.123 → 0.1.0
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 +71 -5
- package/package.json +1 -1
- package/src/index.mjs +5 -3
- package/src/lib/config-local.mjs +158 -63
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Enfyra MCP Server
|
|
2
2
|
|
|
3
|
-
Manage Enfyra instances from MCP-compatible coding tools such as **Codex**, **Claude Code**, **Cursor**, MCP Inspector, and other STDIO MCP hosts.
|
|
3
|
+
Manage Enfyra instances from MCP-compatible coding tools such as **Codex**, **Claude Code**, **Cursor**, **VS Code / GitHub Copilot**, **Google Antigravity**, MCP Inspector, and other STDIO MCP hosts.
|
|
4
4
|
|
|
5
5
|
This package is the MCP bridge only. Assistant rules, schema behavior, dynamic script guidance, and examples are served through the MCP server itself from `src/lib/mcp-instructions.js`, `src/lib/mcp-examples.js`, and tool descriptions in `src/mcp-server-entry.mjs`.
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ From your project root:
|
|
|
12
12
|
npx @enfyra/mcp-server config
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The config command writes project config for Codex, Claude Code, and
|
|
15
|
+
The config command writes project config for Codex, Claude Code, Cursor, VS Code / GitHub Copilot, and Google Antigravity. It preserves other MCP servers and replaces only the `enfyra` entry.
|
|
16
16
|
|
|
17
17
|
Interactive setup asks for your Enfyra app/admin URL, then guides you to the token page when needed and asks for `ENFYRA_API_TOKEN`.
|
|
18
18
|
|
|
@@ -25,6 +25,8 @@ npx @enfyra/mcp-server config --yes \
|
|
|
25
25
|
# One or more clients
|
|
26
26
|
npx @enfyra/mcp-server config --codex
|
|
27
27
|
npx @enfyra/mcp-server config --cursor --claude-code
|
|
28
|
+
npx @enfyra/mcp-server config --vscode
|
|
29
|
+
npx @enfyra/mcp-server config --antigravity
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
Equivalent in this repo:
|
|
@@ -40,7 +42,9 @@ yarn mcp:config
|
|
|
40
42
|
| Codex | `npx @enfyra/mcp-server config --codex` | `.codex/config.toml` |
|
|
41
43
|
| Claude Code | `npx @enfyra/mcp-server config --claude-code` | `.mcp.json` |
|
|
42
44
|
| Cursor | `npx @enfyra/mcp-server config --cursor` | `.cursor/mcp.json` |
|
|
43
|
-
|
|
|
45
|
+
| VS Code / GitHub Copilot | `npx @enfyra/mcp-server config --vscode` | `.vscode/mcp.json` |
|
|
46
|
+
| Google Antigravity | `npx @enfyra/mcp-server config --antigravity` | `.agents/mcp_config.json` |
|
|
47
|
+
| MCP Inspector / other project-scoped hosts | Paste the shared STDIO config below | Host-specific project config |
|
|
44
48
|
|
|
45
49
|
<details>
|
|
46
50
|
<summary><strong>Codex setup</strong></summary>
|
|
@@ -108,10 +112,71 @@ Official reference: [Cursor MCP](https://cursor.com/docs/context/mcp).
|
|
|
108
112
|
|
|
109
113
|
</details>
|
|
110
114
|
|
|
115
|
+
<details>
|
|
116
|
+
<summary><strong>VS Code / GitHub Copilot setup</strong></summary>
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx @enfyra/mcp-server config --vscode
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
VS Code workspace config is written to `.vscode/mcp.json`:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"servers": {
|
|
127
|
+
"enfyra": {
|
|
128
|
+
"type": "stdio",
|
|
129
|
+
"command": "npx",
|
|
130
|
+
"args": ["-y", "@enfyra/mcp-server"],
|
|
131
|
+
"env": {
|
|
132
|
+
"ENFYRA_API_URL": "http://localhost:3000/api",
|
|
133
|
+
"ENFYRA_API_TOKEN": "efy_pat_your-token"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Use the VS Code command `MCP: List Servers` to inspect or start the server after setup. This is a workspace config, so it stays tied to the current project.
|
|
141
|
+
|
|
142
|
+
Official references: [VS Code MCP servers](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) and [VS Code MCP configuration](https://code.visualstudio.com/docs/agents/reference/mcp-configuration).
|
|
143
|
+
|
|
144
|
+
</details>
|
|
145
|
+
|
|
146
|
+
<details>
|
|
147
|
+
<summary><strong>Google Antigravity setup</strong></summary>
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npx @enfyra/mcp-server config --antigravity
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Antigravity project config is written to `.agents/mcp_config.json`:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"mcpServers": {
|
|
158
|
+
"enfyra": {
|
|
159
|
+
"command": "npx",
|
|
160
|
+
"args": ["-y", "@enfyra/mcp-server"],
|
|
161
|
+
"env": {
|
|
162
|
+
"ENFYRA_API_URL": "http://localhost:3000/api",
|
|
163
|
+
"ENFYRA_API_TOKEN": "efy_pat_your-token"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Antigravity also documents a shared user config at `~/.gemini/config/mcp_config.json`; this helper intentionally writes the project-local `.agents/mcp_config.json` file so Enfyra URL and token stay scoped to the current workspace.
|
|
171
|
+
|
|
172
|
+
Official reference: [Antigravity MCP](https://antigravity.google/docs/mcp).
|
|
173
|
+
|
|
174
|
+
</details>
|
|
175
|
+
|
|
111
176
|
<details>
|
|
112
177
|
<summary><strong>Other MCP hosts and MCP Inspector</strong></summary>
|
|
113
178
|
|
|
114
|
-
Use the shared STDIO config with any host that accepts an `mcpServers` JSON block:
|
|
179
|
+
Use the shared STDIO config with any project-scoped host that accepts an `mcpServers` JSON block:
|
|
115
180
|
|
|
116
181
|
```json
|
|
117
182
|
{
|
|
@@ -145,11 +210,12 @@ npx @enfyra/mcp-server config [options]
|
|
|
145
210
|
| `--app-url` | Set the Enfyra app/admin URL |
|
|
146
211
|
| `--api-token`, `-t` | Set `ENFYRA_API_TOKEN` |
|
|
147
212
|
| `--yes` | Non-interactive mode for CI/scripts |
|
|
148
|
-
| `--global` | Write global/user config instead of project config |
|
|
149
213
|
| `--reconfig` | Prompt for target clients again and replace the existing `enfyra` entry |
|
|
150
214
|
| `--codex` | Write Codex config |
|
|
151
215
|
| `--claude-code`, `--claude` | Write Claude Code config |
|
|
152
216
|
| `--cursor` | Write Cursor config |
|
|
217
|
+
| `--vscode`, `--copilot` | Write VS Code / GitHub Copilot config |
|
|
218
|
+
| `--antigravity` | Write Google Antigravity config |
|
|
153
219
|
| `-h`, `--help` | Show CLI help |
|
|
154
220
|
|
|
155
221
|
Without a target flag, interactive mode asks which client to configure. Non-interactive mode defaults to all supported clients.
|
package/package.json
CHANGED
package/src/index.mjs
CHANGED
|
@@ -11,15 +11,17 @@ if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
|
|
|
11
11
|
|
|
12
12
|
Usage:
|
|
13
13
|
npx @enfyra/mcp-server Start the MCP stdio server
|
|
14
|
-
npx @enfyra/mcp-server config [flags] Write local MCP host config
|
|
14
|
+
npx @enfyra/mcp-server config [flags] Write project-local MCP host config
|
|
15
15
|
|
|
16
16
|
Common config flags:
|
|
17
|
-
--codex Write
|
|
17
|
+
--codex Write ./.codex/config.toml
|
|
18
18
|
--claude-code Write ./.mcp.json
|
|
19
19
|
--cursor Write ./.cursor/mcp.json
|
|
20
|
+
--vscode Write ./.vscode/mcp.json
|
|
21
|
+
--antigravity Write ./.agents/mcp_config.json
|
|
20
22
|
--reconfig Prompt for host and credentials again, replacing the enfyra entry
|
|
21
23
|
--yes Non-interactive
|
|
22
|
-
|
|
24
|
+
--app-url ENFYRA_APP_URL
|
|
23
25
|
-t, --api-token ENFYRA_API_TOKEN
|
|
24
26
|
-h, --help Show config help
|
|
25
27
|
|
package/src/lib/config-local.mjs
CHANGED
|
@@ -3,7 +3,6 @@ import { createInterface } from 'node:readline/promises';
|
|
|
3
3
|
import { emitKeypressEvents } from 'node:readline';
|
|
4
4
|
import { stdin as input, stdout as output, cwd } from 'node:process';
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
|
-
import { homedir } from 'node:os';
|
|
7
6
|
|
|
8
7
|
const SERVER_KEY = 'enfyra';
|
|
9
8
|
const forceColor = process.env.FORCE_COLOR != null && process.env.FORCE_COLOR !== '0';
|
|
@@ -36,6 +35,16 @@ const clients = {
|
|
|
36
35
|
path: './.cursor/mcp.json',
|
|
37
36
|
color: style.cyan,
|
|
38
37
|
},
|
|
38
|
+
vscode: {
|
|
39
|
+
label: 'VS Code / Copilot',
|
|
40
|
+
path: './.vscode/mcp.json',
|
|
41
|
+
color: style.blue,
|
|
42
|
+
},
|
|
43
|
+
antigravity: {
|
|
44
|
+
label: 'Antigravity',
|
|
45
|
+
path: './.agents/mcp_config.json',
|
|
46
|
+
color: style.yellow,
|
|
47
|
+
},
|
|
39
48
|
};
|
|
40
49
|
|
|
41
50
|
function statusIcon(kind) {
|
|
@@ -55,7 +64,7 @@ function exitCancelled() {
|
|
|
55
64
|
|
|
56
65
|
function printHelp() {
|
|
57
66
|
console.log(`${style.bold('Enfyra MCP config')}
|
|
58
|
-
${style.dim('Write local MCP client config for Enfyra.')}
|
|
67
|
+
${style.dim('Write project-local MCP client config for Enfyra.')}
|
|
59
68
|
|
|
60
69
|
${style.bold('Usage')}
|
|
61
70
|
npx @enfyra/mcp-server config [options]
|
|
@@ -64,12 +73,12 @@ ${style.bold('Supported clients')}
|
|
|
64
73
|
Codex ./.codex/config.toml
|
|
65
74
|
Claude Code ./.mcp.json
|
|
66
75
|
Cursor ./.cursor/mcp.json
|
|
67
|
-
|
|
76
|
+
VS Code ./.vscode/mcp.json
|
|
77
|
+
Antigravity ./.agents/mcp_config.json
|
|
68
78
|
|
|
69
79
|
${style.bold('Options')}
|
|
70
80
|
--app-url <url> Enfyra app/admin URL, for example https://demo.enfyra.io
|
|
71
81
|
--api-token, -t <secret> ENFYRA_API_TOKEN
|
|
72
|
-
--global Write global/user config for selected clients instead of project config
|
|
73
82
|
--reconfig Always choose target again in interactive mode and replace the old enfyra config for that target
|
|
74
83
|
--yes Non-interactive: no prompts (CI / scripts); use CLI, env, existing file, then defaults
|
|
75
84
|
|
|
@@ -79,20 +88,23 @@ ${style.bold('Client selection')}
|
|
|
79
88
|
--claude-code, --claude, --claude-only Only ./.mcp.json (Claude Code project scope)
|
|
80
89
|
--cursor, --cursor-only Only ./.cursor/mcp.json (Cursor)
|
|
81
90
|
--codex, --codex-only Only ./.codex/config.toml (Codex project scope)
|
|
91
|
+
--vscode, --copilot, --vscode-only Only ./.vscode/mcp.json (VS Code / Copilot)
|
|
92
|
+
--antigravity, --antigravity-only Only ./.agents/mcp_config.json (Antigravity)
|
|
82
93
|
Passing multiple target flags writes each selected target.
|
|
83
94
|
|
|
84
95
|
-h, --help Show this help
|
|
85
96
|
|
|
86
97
|
${style.bold('Interactive mode')}
|
|
87
|
-
Choose Codex, Claude Code, Cursor, or all clients; then enter ENFYRA_APP_URL and ENFYRA_API_TOKEN.
|
|
98
|
+
Choose Codex, Claude Code, Cursor, VS Code, Antigravity, or all clients; then enter ENFYRA_APP_URL and ENFYRA_API_TOKEN.
|
|
88
99
|
Existing Enfyra config and environment variables are used as defaults. Re-run anytime to update.
|
|
89
100
|
|
|
90
101
|
${style.bold('Examples')}
|
|
91
102
|
npx @enfyra/mcp-server config
|
|
92
103
|
npx @enfyra/mcp-server config --yes
|
|
93
104
|
npx @enfyra/mcp-server config --codex --cursor
|
|
105
|
+
npx @enfyra/mcp-server config --vscode
|
|
106
|
+
npx @enfyra/mcp-server config --antigravity
|
|
94
107
|
npx @enfyra/mcp-server config --claude-code
|
|
95
|
-
npx @enfyra/mcp-server config --global --codex
|
|
96
108
|
npx @enfyra/mcp-server config --reconfig
|
|
97
109
|
npx @enfyra/mcp-server config --app-url http://localhost:3000 -t 'efy_pat_...'
|
|
98
110
|
ENFYRA_APP_URL=https://demo.enfyra.io ENFYRA_API_TOKEN=efy_pat_... npx @enfyra/mcp-server config --yes
|
|
@@ -109,11 +121,14 @@ function parseArgs(argv) {
|
|
|
109
121
|
help: false,
|
|
110
122
|
yes: false,
|
|
111
123
|
reconfig: false,
|
|
112
|
-
|
|
124
|
+
vscode: true,
|
|
125
|
+
antigravity: true,
|
|
113
126
|
};
|
|
114
127
|
let pickClaude = false;
|
|
115
128
|
let pickCursor = false;
|
|
116
129
|
let pickCodex = false;
|
|
130
|
+
let pickVscode = false;
|
|
131
|
+
let pickAntigravity = false;
|
|
117
132
|
for (let i = 0; i < argv.length; i += 1) {
|
|
118
133
|
const a = argv[i];
|
|
119
134
|
const next = () => {
|
|
@@ -126,7 +141,6 @@ function parseArgs(argv) {
|
|
|
126
141
|
else if (a === 'help') out.help = true;
|
|
127
142
|
else if (a === '--yes') out.yes = true;
|
|
128
143
|
else if (a === '--reconfig') out.reconfig = true;
|
|
129
|
-
else if (a === '--global') out.global = true;
|
|
130
144
|
else if (a === '--app-url') out.appUrl = next();
|
|
131
145
|
else if (a === '--api-url' || a === '-a') {
|
|
132
146
|
throw new Error(`${a} is no longer supported for setup; use --app-url instead`);
|
|
@@ -138,13 +152,17 @@ function parseArgs(argv) {
|
|
|
138
152
|
else if (a === '--claude-only' || a === '--claude-code' || a === '--claude') pickClaude = true;
|
|
139
153
|
else if (a === '--cursor-only' || a === '--cursor') pickCursor = true;
|
|
140
154
|
else if (a === '--codex-only' || a === '--codex') pickCodex = true;
|
|
155
|
+
else if (a === '--vscode-only' || a === '--vscode' || a === '--copilot') pickVscode = true;
|
|
156
|
+
else if (a === '--antigravity-only' || a === '--antigravity') pickAntigravity = true;
|
|
141
157
|
else throw new Error(`Unknown argument: ${a}`);
|
|
142
158
|
}
|
|
143
|
-
out.targetExplicit = pickClaude || pickCursor || pickCodex;
|
|
159
|
+
out.targetExplicit = pickClaude || pickCursor || pickCodex || pickVscode || pickAntigravity;
|
|
144
160
|
if (out.targetExplicit) {
|
|
145
161
|
out.claude = pickClaude;
|
|
146
162
|
out.cursor = pickCursor;
|
|
147
163
|
out.codex = pickCodex;
|
|
164
|
+
out.vscode = pickVscode;
|
|
165
|
+
out.antigravity = pickAntigravity;
|
|
148
166
|
}
|
|
149
167
|
return out;
|
|
150
168
|
}
|
|
@@ -193,9 +211,7 @@ async function mergeMcpFile(absPath, serverEntry) {
|
|
|
193
211
|
try {
|
|
194
212
|
const raw = await readFile(absPath, 'utf8');
|
|
195
213
|
const parsed = JSON.parse(raw);
|
|
196
|
-
if (parsed && typeof parsed === 'object' &&
|
|
197
|
-
data.mcpServers = { ...parsed.mcpServers };
|
|
198
|
-
} else if (parsed && typeof parsed === 'object') {
|
|
214
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
199
215
|
data = { ...parsed, mcpServers: parsed.mcpServers && typeof parsed.mcpServers === 'object' ? { ...parsed.mcpServers } : {} };
|
|
200
216
|
}
|
|
201
217
|
} catch (e) {
|
|
@@ -207,6 +223,31 @@ async function mergeMcpFile(absPath, serverEntry) {
|
|
|
207
223
|
await writeFile(absPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
208
224
|
}
|
|
209
225
|
|
|
226
|
+
async function mergeVscodeMcpFile(absPath, serverEntry) {
|
|
227
|
+
let data = { servers: {} };
|
|
228
|
+
try {
|
|
229
|
+
const raw = await readFile(absPath, 'utf8');
|
|
230
|
+
const parsed = JSON.parse(raw);
|
|
231
|
+
if (parsed && typeof parsed === 'object') {
|
|
232
|
+
data = {
|
|
233
|
+
...parsed,
|
|
234
|
+
servers: parsed.servers && typeof parsed.servers === 'object' ? { ...parsed.servers } : {},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
if (e.code !== 'ENOENT') throw e;
|
|
239
|
+
}
|
|
240
|
+
data.servers = {
|
|
241
|
+
...data.servers,
|
|
242
|
+
[SERVER_KEY]: {
|
|
243
|
+
type: 'stdio',
|
|
244
|
+
...serverEntry,
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
await mkdir(dirname(absPath), { recursive: true });
|
|
248
|
+
await writeFile(absPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
249
|
+
}
|
|
250
|
+
|
|
210
251
|
function tomlString(value) {
|
|
211
252
|
return JSON.stringify(String(value ?? ''));
|
|
212
253
|
}
|
|
@@ -285,50 +326,69 @@ async function readCodexEnfyraEnv(absPath) {
|
|
|
285
326
|
return null;
|
|
286
327
|
}
|
|
287
328
|
|
|
288
|
-
function getCodexConfigPath(root
|
|
289
|
-
return
|
|
329
|
+
function getCodexConfigPath(root) {
|
|
330
|
+
return join(root, '.codex', 'config.toml');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function getClaudeConfigPath(root) {
|
|
334
|
+
return join(root, '.mcp.json');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function getCursorConfigPath(root) {
|
|
338
|
+
return join(root, '.cursor', 'mcp.json');
|
|
290
339
|
}
|
|
291
340
|
|
|
292
|
-
function
|
|
293
|
-
return
|
|
341
|
+
function getVscodeConfigPath(root) {
|
|
342
|
+
return join(root, '.vscode', 'mcp.json');
|
|
294
343
|
}
|
|
295
344
|
|
|
296
|
-
function
|
|
297
|
-
return
|
|
345
|
+
function getAntigravityConfigPath(root) {
|
|
346
|
+
return join(root, '.agents', 'mcp_config.json');
|
|
298
347
|
}
|
|
299
348
|
|
|
300
|
-
function getClientPath(client, root
|
|
301
|
-
if (client === 'claude') return getClaudeConfigPath(root
|
|
302
|
-
if (client === 'cursor') return getCursorConfigPath(root
|
|
303
|
-
if (client === 'codex') return getCodexConfigPath(root
|
|
349
|
+
function getClientPath(client, root) {
|
|
350
|
+
if (client === 'claude') return getClaudeConfigPath(root);
|
|
351
|
+
if (client === 'cursor') return getCursorConfigPath(root);
|
|
352
|
+
if (client === 'codex') return getCodexConfigPath(root);
|
|
353
|
+
if (client === 'vscode') return getVscodeConfigPath(root);
|
|
354
|
+
if (client === 'antigravity') return getAntigravityConfigPath(root);
|
|
304
355
|
throw new Error(`Unknown MCP client: ${client}`);
|
|
305
356
|
}
|
|
306
357
|
|
|
307
|
-
async function
|
|
358
|
+
async function readMcpServerEnv(absPath, serverRootKey) {
|
|
359
|
+
try {
|
|
360
|
+
const raw = await readFile(absPath, 'utf8');
|
|
361
|
+
const j = JSON.parse(raw);
|
|
362
|
+
const e = j?.[serverRootKey]?.[SERVER_KEY]?.env;
|
|
363
|
+
if (e && typeof e === 'object' && (e.ENFYRA_API_URL || e.ENFYRA_API_TOKEN)) {
|
|
364
|
+
return {
|
|
365
|
+
apiUrl: typeof e.ENFYRA_API_URL === 'string' ? e.ENFYRA_API_URL : '',
|
|
366
|
+
apiToken: typeof e.ENFYRA_API_TOKEN === 'string' ? e.ENFYRA_API_TOKEN : '',
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
/* */
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function loadExistingEnfyraEnv(root, readClaude, readCursor, readCodex, readVscode, readAntigravity) {
|
|
308
376
|
const paths = [];
|
|
309
|
-
if (readClaude) paths.push(getClaudeConfigPath(root,
|
|
310
|
-
if (readCursor) paths.push(getCursorConfigPath(root,
|
|
311
|
-
if (!
|
|
377
|
+
if (readClaude) paths.push({ path: getClaudeConfigPath(root), rootKey: 'mcpServers' });
|
|
378
|
+
if (readCursor) paths.push({ path: getCursorConfigPath(root), rootKey: 'mcpServers' });
|
|
379
|
+
if (!readClaude && readCursor) paths.push({ path: getClaudeConfigPath(root), rootKey: 'mcpServers' });
|
|
380
|
+
if (readVscode) paths.push({ path: getVscodeConfigPath(root), rootKey: 'servers' });
|
|
381
|
+
if (readAntigravity) paths.push({ path: getAntigravityConfigPath(root), rootKey: 'mcpServers' });
|
|
312
382
|
const seen = new Set();
|
|
313
|
-
for (const
|
|
314
|
-
|
|
315
|
-
seen.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const e = j?.mcpServers?.[SERVER_KEY]?.env;
|
|
320
|
-
if (e && typeof e === 'object' && (e.ENFYRA_API_URL || e.ENFYRA_API_TOKEN)) {
|
|
321
|
-
return {
|
|
322
|
-
apiUrl: typeof e.ENFYRA_API_URL === 'string' ? e.ENFYRA_API_URL : '',
|
|
323
|
-
apiToken: typeof e.ENFYRA_API_TOKEN === 'string' ? e.ENFYRA_API_TOKEN : '',
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
} catch {
|
|
327
|
-
/* */
|
|
328
|
-
}
|
|
383
|
+
for (const entry of paths) {
|
|
384
|
+
const key = `${entry.rootKey}:${entry.path}`;
|
|
385
|
+
if (seen.has(key)) continue;
|
|
386
|
+
seen.add(key);
|
|
387
|
+
const env = await readMcpServerEnv(entry.path, entry.rootKey);
|
|
388
|
+
if (env) return env;
|
|
329
389
|
}
|
|
330
390
|
if (readCodex) {
|
|
331
|
-
const codex = await readCodexEnfyraEnv(getCodexConfigPath(root
|
|
391
|
+
const codex = await readCodexEnfyraEnv(getCodexConfigPath(root));
|
|
332
392
|
if (codex) return codex;
|
|
333
393
|
}
|
|
334
394
|
return { apiUrl: '', apiToken: '' };
|
|
@@ -338,23 +398,31 @@ async function promptTargetChoice() {
|
|
|
338
398
|
const choices = [
|
|
339
399
|
{
|
|
340
400
|
client: 'codex',
|
|
341
|
-
value: { claude: false, cursor: false, codex: true },
|
|
401
|
+
value: { claude: false, cursor: false, codex: true, vscode: false, antigravity: false },
|
|
342
402
|
},
|
|
343
403
|
{
|
|
344
404
|
client: 'claude',
|
|
345
|
-
value: { claude: true, cursor: false, codex: false },
|
|
405
|
+
value: { claude: true, cursor: false, codex: false, vscode: false, antigravity: false },
|
|
346
406
|
},
|
|
347
407
|
{
|
|
348
408
|
client: 'cursor',
|
|
349
|
-
value: { claude: false, cursor: true, codex: false },
|
|
409
|
+
value: { claude: false, cursor: true, codex: false, vscode: false, antigravity: false },
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
client: 'vscode',
|
|
413
|
+
value: { claude: false, cursor: false, codex: false, vscode: true, antigravity: false },
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
client: 'antigravity',
|
|
417
|
+
value: { claude: false, cursor: false, codex: false, vscode: false, antigravity: true },
|
|
350
418
|
},
|
|
351
419
|
{
|
|
352
420
|
client: 'all',
|
|
353
|
-
value: { claude: true, cursor: true, codex: true },
|
|
421
|
+
value: { claude: true, cursor: true, codex: true, vscode: true, antigravity: true },
|
|
354
422
|
},
|
|
355
423
|
];
|
|
356
424
|
if (input.setRawMode && output.isTTY) {
|
|
357
|
-
return promptTargetSelect(choices,
|
|
425
|
+
return promptTargetSelect(choices, 5);
|
|
358
426
|
}
|
|
359
427
|
|
|
360
428
|
const rl = createInterface({ input, output });
|
|
@@ -363,23 +431,31 @@ async function promptTargetChoice() {
|
|
|
363
431
|
+ ' [1] Codex ./.codex/config.toml\n'
|
|
364
432
|
+ ' [2] Claude Code ./.mcp.json\n'
|
|
365
433
|
+ ' [3] Cursor ./.cursor/mcp.json\n'
|
|
366
|
-
+ ' [4]
|
|
367
|
-
+ '
|
|
434
|
+
+ ' [4] VS Code ./.vscode/mcp.json\n'
|
|
435
|
+
+ ' [5] Antigravity ./.agents/mcp_config.json\n'
|
|
436
|
+
+ ' [6] All [default]\n'
|
|
437
|
+
+ 'Choice [6]: ',
|
|
368
438
|
)).trim().toLowerCase();
|
|
369
439
|
await rl.close();
|
|
370
|
-
if (line === '' || line === '
|
|
371
|
-
return { claude: true, cursor: true, codex: true };
|
|
440
|
+
if (line === '' || line === '6' || line === 'all' || line === 'a') {
|
|
441
|
+
return { claude: true, cursor: true, codex: true, vscode: true, antigravity: true };
|
|
372
442
|
}
|
|
373
443
|
if (line === '1' || line === 'codex' || line === 'x') {
|
|
374
|
-
return { claude: false, cursor: false, codex: true };
|
|
444
|
+
return { claude: false, cursor: false, codex: true, vscode: false, antigravity: false };
|
|
375
445
|
}
|
|
376
446
|
if (line === '2' || line === 'claude' || line === 'claude-code') {
|
|
377
|
-
return { claude: true, cursor: false, codex: false };
|
|
447
|
+
return { claude: true, cursor: false, codex: false, vscode: false, antigravity: false };
|
|
378
448
|
}
|
|
379
449
|
if (line === '3' || line === 'cursor' || line === 'u') {
|
|
380
|
-
return { claude: false, cursor: true, codex: false };
|
|
450
|
+
return { claude: false, cursor: true, codex: false, vscode: false, antigravity: false };
|
|
451
|
+
}
|
|
452
|
+
if (line === '4' || line === 'vscode' || line === 'vs-code' || line === 'copilot') {
|
|
453
|
+
return { claude: false, cursor: false, codex: false, vscode: true, antigravity: false };
|
|
454
|
+
}
|
|
455
|
+
if (line === '5' || line === 'antigravity') {
|
|
456
|
+
return { claude: false, cursor: false, codex: false, vscode: false, antigravity: true };
|
|
381
457
|
}
|
|
382
|
-
return { claude: true, cursor: true, codex: true };
|
|
458
|
+
return { claude: true, cursor: true, codex: true, vscode: true, antigravity: true };
|
|
383
459
|
}
|
|
384
460
|
|
|
385
461
|
async function promptTargetSelect(choices, initialIndex = 0) {
|
|
@@ -392,7 +468,7 @@ async function promptTargetSelect(choices, initialIndex = 0) {
|
|
|
392
468
|
if (choice.client === 'all') {
|
|
393
469
|
const label = active ? style.bold(style.underline('All supported clients')) : 'All supported clients';
|
|
394
470
|
const paddedLabel = label + ' '.repeat(22 - 'All supported clients'.length);
|
|
395
|
-
const hint = active ? style.cyan('Codex + Claude Code + Cursor') : style.dim('Codex + Claude Code + Cursor');
|
|
471
|
+
const hint = active ? style.cyan('Codex + Claude Code + Cursor + VS Code + Antigravity') : style.dim('Codex + Claude Code + Cursor + VS Code + Antigravity');
|
|
396
472
|
return `${accent} ${indicator} ${paddedLabel} ${hint}`;
|
|
397
473
|
}
|
|
398
474
|
|
|
@@ -545,14 +621,18 @@ export async function runLocalConfig(argv) {
|
|
|
545
621
|
let writeClaude = opts.claude;
|
|
546
622
|
let writeCursor = opts.cursor;
|
|
547
623
|
let writeCodex = opts.codex;
|
|
624
|
+
let writeVscode = opts.vscode;
|
|
625
|
+
let writeAntigravity = opts.antigravity;
|
|
548
626
|
if (usePrompt && (!opts.targetExplicit || opts.reconfig)) {
|
|
549
627
|
const t = await promptTargetChoice();
|
|
550
628
|
writeClaude = t.claude;
|
|
551
629
|
writeCursor = t.cursor;
|
|
552
630
|
writeCodex = t.codex;
|
|
631
|
+
writeVscode = t.vscode;
|
|
632
|
+
writeAntigravity = t.antigravity;
|
|
553
633
|
}
|
|
554
634
|
|
|
555
|
-
const existing = await loadExistingEnfyraEnv(root, writeClaude, writeCursor, writeCodex,
|
|
635
|
+
const existing = await loadExistingEnfyraEnv(root, writeClaude, writeCursor, writeCodex, writeVscode, writeAntigravity);
|
|
556
636
|
|
|
557
637
|
let apiUrl;
|
|
558
638
|
let apiToken;
|
|
@@ -578,23 +658,32 @@ export async function runLocalConfig(argv) {
|
|
|
578
658
|
const written = [];
|
|
579
659
|
|
|
580
660
|
if (writeCodex) {
|
|
581
|
-
const p = getClientPath('codex', root
|
|
661
|
+
const p = getClientPath('codex', root);
|
|
582
662
|
await mergeCodexConfig(p, apiUrl, apiToken);
|
|
583
663
|
written.push({ client: 'codex', path: p });
|
|
584
664
|
}
|
|
585
665
|
if (writeClaude) {
|
|
586
|
-
const p = getClientPath('claude', root
|
|
666
|
+
const p = getClientPath('claude', root);
|
|
587
667
|
await mergeMcpFile(p, serverEntry);
|
|
588
668
|
written.push({ client: 'claude', path: p });
|
|
589
669
|
}
|
|
590
670
|
if (writeCursor) {
|
|
591
|
-
const p = getClientPath('cursor', root
|
|
671
|
+
const p = getClientPath('cursor', root);
|
|
592
672
|
await mergeMcpFile(p, serverEntry);
|
|
593
673
|
written.push({ client: 'cursor', path: p });
|
|
594
674
|
}
|
|
675
|
+
if (writeVscode) {
|
|
676
|
+
const p = getClientPath('vscode', root);
|
|
677
|
+
await mergeVscodeMcpFile(p, serverEntry);
|
|
678
|
+
written.push({ client: 'vscode', path: p });
|
|
679
|
+
}
|
|
680
|
+
if (writeAntigravity) {
|
|
681
|
+
const p = getClientPath('antigravity', root);
|
|
682
|
+
await mergeMcpFile(p, serverEntry);
|
|
683
|
+
written.push({ client: 'antigravity', path: p });
|
|
684
|
+
}
|
|
595
685
|
|
|
596
|
-
|
|
597
|
-
console.log(`${statusIcon('success')} ${style.bold(style.green('Enfyra MCP config updated'))} ${style.dim(`(${scopeLabel})`)}\n`);
|
|
686
|
+
console.log(`${statusIcon('success')} ${style.bold(style.green('Enfyra MCP config updated'))} ${style.dim('(project)')}\n`);
|
|
598
687
|
for (const entry of written) {
|
|
599
688
|
const meta = clients[entry.client];
|
|
600
689
|
console.log(` ${style.cyan('•')} ${style.bold(meta.color(meta.label))}`);
|
|
@@ -612,6 +701,12 @@ export async function runLocalConfig(argv) {
|
|
|
612
701
|
if (selectedClients.has('cursor')) {
|
|
613
702
|
console.log(' - Cursor: restart Cursor or reload MCP, then confirm the server under Settings -> MCP.');
|
|
614
703
|
}
|
|
704
|
+
if (selectedClients.has('vscode')) {
|
|
705
|
+
console.log(' - VS Code / Copilot: run MCP: List Servers or reload the workspace, then start the Enfyra server if prompted.');
|
|
706
|
+
}
|
|
707
|
+
if (selectedClients.has('antigravity')) {
|
|
708
|
+
console.log(' - Antigravity: reopen the workspace or reload MCP servers so ./.agents/mcp_config.json is picked up.');
|
|
709
|
+
}
|
|
615
710
|
console.log(' - Re-run this command anytime to update the same Enfyra entries.');
|
|
616
711
|
if (!apiToken) {
|
|
617
712
|
console.log(`\n${statusIcon('warn')} ${style.yellow('ENFYRA_API_TOKEN is empty; tools will not authenticate until it is set.')}`);
|