@enfyra/mcp-server 0.0.27 → 0.0.29

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 CHANGED
@@ -17,11 +17,12 @@ From your **Enfyra project root**:
17
17
  npx @enfyra/mcp-server config
18
18
  ```
19
19
 
20
- - **Interactive (default in a terminal):** first asks **where** to write config — `[1]` Claude Code, `[2]` Cursor, `[3]` Codex, `[4]` all (default) — unless you already passed target flags. Then prompts for `ENFYRA_API_URL`, `ENFYRA_EMAIL`, and `ENFYRA_PASSWORD` when missing. Press **Enter** to accept bracketed defaults from env or existing `enfyra` config. Password **Enter** keeps the current saved password when updating.
20
+ - **Interactive (default in a terminal):** first asks **where** to write config with an arrow-key selector — Claude Code, Cursor, Codex, or all — unless you already passed target flags. Then prompts for `ENFYRA_API_URL`, `ENFYRA_EMAIL`, and `ENFYRA_PASSWORD` when missing. Press **Enter** to accept bracketed defaults from env or existing `enfyra` config. Password **Enter** keeps the current saved password when updating.
21
21
  - **Re-run anytime** to update the same files; other entries under `mcpServers` are preserved.
22
22
  - **Non-interactive** (CI / scripts): `npx @enfyra/mcp-server config --yes` plus optional `-a` / `-e` / `-p` and/or env vars.
23
- - **One host only:** `--claude-code` / `--claude` / `--claude-only` → `./.mcp.json`. `--cursor` / `--cursor-only` → `./.cursor/mcp.json`. `--codex` / `--codex-only` → `~/.codex/config.toml`. Pass multiple target flags to write each selected host.
24
- - **Reconfigure:** `npx @enfyra/mcp-server config --reconfig` prompts for the target host again, uses existing values as defaults, and replaces the old `enfyra` entry for that host.
23
+ - **One host only:** `--claude-code` / `--claude` / `--claude-only` → `./.mcp.json`. `--cursor` / `--cursor-only` → `./.cursor/mcp.json`. `--codex` / `--codex-only` → `./.codex/config.toml`. Pass multiple target flags to write each selected host.
24
+ - **Reconfigure:** `npx @enfyra/mcp-server config --reconfig` prompts for the target host again, uses existing project values as defaults, and replaces the old project `enfyra` entry for that host.
25
+ - **Global/user config:** add `--global` only when you intentionally want the selected host config under your home directory instead of this project.
25
26
  - **Help:** `npx @enfyra/mcp-server -h` or `npx @enfyra/mcp-server config --help`
26
27
 
27
28
  Equivalent in this repo: `yarn mcp:config` (Yarn v1 reserves `yarn config` for registry settings). Same as `node src/index.mjs config` / `npm run mcp:config`.
@@ -34,18 +35,18 @@ Use this table to see **where** each host stores config. The **`mcpServers.enfyr
34
35
 
35
36
  | | **Codex** | **Claude Code** | **Cursor** |
36
37
  |---|-----------|-----------------|------------|
37
- | **Global (all projects)** | `~/.codex/config.toml` | `~/.claude.json` scopes **user** or **local** | `~/.cursor/mcp.json` |
38
- | **Project (repo)** | Use global config | **`.mcp.json`** at repository root (`--scope project`) | **`.cursor/mcp.json`** in the project |
39
- | **Typical install** | `npx @enfyra/mcp-server config --codex` | `claude mcp add --transport stdio …` | Edit `mcp.json` or **Settings → MCP** |
40
- | **Precedence / merge** | `config.toml` section is replaced for `enfyra`; other servers are preserved | local → project `.mcp.json` user | Project `.cursor/mcp.json` overrides global `~/.cursor/mcp.json` |
41
- | **Gotcha** | Restart Codex or start a new session after editing config | Do not put MCP server definitions in `.claude/settings.json` | Root **`.mcp.json`** is for Claude Code project scope, not Cursor — use **`.cursor/mcp.json`** for Cursor |
38
+ | **Project (repo, default)** | **`.codex/config.toml`** in the project | **`.mcp.json`** at repository root | **`.cursor/mcp.json`** in the project |
39
+ | **Global (explicit `--global`)** | `~/.codex/config.toml` | `~/.mcp.json` from this helper, or Claude's `~/.claude.json` via `claude mcp add --scope user` | `~/.cursor/mcp.json` |
40
+ | **Typical install** | `npx @enfyra/mcp-server config --codex` | `npx @enfyra/mcp-server config --claude-code` | `npx @enfyra/mcp-server config --cursor` |
41
+ | **Precedence / merge** | Project config is merged/replaced for `enfyra` | Project `.mcp.json` is merged/replaced for `enfyra` | Project `.cursor/mcp.json` is merged/replaced for `enfyra` |
42
+ | **Gotcha** | Open this folder in a new Codex session after editing config | Do not put MCP server definitions in `.claude/settings.json` | Root **`.mcp.json`** is for Claude Code project scope, not Cursor — use **`.cursor/mcp.json`** for Cursor |
42
43
 
43
44
  Expand **one** block below for step-by-step setup.
44
45
 
45
46
  <details open>
46
47
  <summary><strong>Codex</strong> — setup</summary>
47
48
 
48
- The config command can write/update `~/.codex/config.toml` directly:
49
+ The config command writes project Codex config to `./.codex/config.toml` by default:
49
50
 
50
51
  ```bash
51
52
  npx @enfyra/mcp-server config --codex
@@ -73,7 +74,7 @@ ENFYRA_EMAIL = "your-email@example.com"
73
74
  ENFYRA_PASSWORD = "your-password"
74
75
  ```
75
76
 
76
- The config writer replaces only `[mcp_servers.enfyra]` and `[mcp_servers.enfyra.env]`; other Codex config and other MCP servers are preserved. Restart Codex or start a new session after updating `~/.codex/config.toml`.
77
+ The config writer replaces only `[mcp_servers.enfyra]` and `[mcp_servers.enfyra.env]`; other Codex config and other MCP servers are preserved. Open this folder in a new Codex session after updating `./.codex/config.toml`. Use `--global --codex` only when you intentionally want `~/.codex/config.toml`.
77
78
 
78
79
  </details>
79
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfyra/mcp-server",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "MCP server for Enfyra - manage your Enfyra instance via Claude Code",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,5 +1,6 @@
1
1
  import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
2
  import { createInterface } from 'node:readline/promises';
3
+ import { emitKeypressEvents } from 'node:readline';
3
4
  import { stdin as input, stdout as output, cwd } from 'node:process';
4
5
  import { dirname, join } from 'node:path';
5
6
  import { homedir } from 'node:os';
@@ -12,32 +13,34 @@ function printHelp() {
12
13
  Usage:
13
14
  npx @enfyra/mcp-server config [options]
14
15
 
15
- Writes project config under the current working directory and Codex config under your home directory:
16
+ Writes project config under the current working directory:
16
17
  • ./.mcp.json — Claude Code project scope
17
18
  • ./.cursor/mcp.json — Cursor project scope
18
- ~/.codex/config.toml — Codex user scope
19
+ ./.codex/config.toml — Codex project scope
19
20
 
20
21
  Options:
21
22
  --api-url, -a <url> ENFYRA_API_URL
22
23
  --email, -e <email> ENFYRA_EMAIL
23
24
  --password, -p <secret> ENFYRA_PASSWORD
25
+ --global Write global/user config for selected hosts instead of project config
24
26
  --reconfig Always choose target again in interactive mode and replace the old enfyra config for that target
25
27
  --yes Non-interactive: no prompts (CI / scripts); use CLI, env, existing file, then defaults
26
- Target — non-interactive default is all; with TTY and no target flags, you are prompted [1]/[2]/[3]/[4]:
28
+ Target — non-interactive default is all; with TTY and no target flags, choose with ↑/↓:
27
29
  --claude-code, --claude, --claude-only Only ./.mcp.json (Claude Code project scope)
28
30
  --cursor, --cursor-only Only ./.cursor/mcp.json (Cursor)
29
- --codex, --codex-only Only ~/.codex/config.toml (Codex)
31
+ --codex, --codex-only Only ./.codex/config.toml (Codex project scope)
30
32
  Passing multiple target flags writes each selected target.
31
33
  -h, --help Show this help
32
34
 
33
- Interactive mode: asks Claude Code vs Cursor vs both if you did not pass target flags; then asks for URL / email / password
34
- when missing. Existing ./.mcp.json, ./.cursor/mcp.json, and ~/.codex/config.toml are used as defaults. Re-run to update.
35
+ Interactive mode: lets you choose Claude Code / Cursor / Codex / all if you did not pass target flags; then asks for URL / email / password
36
+ when missing. Existing project config is used as defaults. Re-run to update.
35
37
 
36
38
  Examples:
37
39
  npx @enfyra/mcp-server config
38
40
  npx @enfyra/mcp-server config --claude-code
39
41
  npx @enfyra/mcp-server config --cursor --yes
40
42
  npx @enfyra/mcp-server config --codex --yes
43
+ npx @enfyra/mcp-server config --global --codex
41
44
  npx @enfyra/mcp-server config --reconfig
42
45
  npx @enfyra/mcp-server config -a http://localhost:3000/api -e admin@x.com -p 'secret'
43
46
  npx @enfyra/mcp-server config --yes
@@ -56,6 +59,7 @@ function parseArgs(argv) {
56
59
  help: false,
57
60
  yes: false,
58
61
  reconfig: false,
62
+ global: false,
59
63
  };
60
64
  let pickClaude = false;
61
65
  let pickCursor = false;
@@ -72,6 +76,7 @@ function parseArgs(argv) {
72
76
  else if (a === 'help') out.help = true;
73
77
  else if (a === '--yes') out.yes = true;
74
78
  else if (a === '--reconfig') out.reconfig = true;
79
+ else if (a === '--global') out.global = true;
75
80
  else if (a === '--api-url' || a === '-a') out.apiUrl = next();
76
81
  else if (a === '--email' || a === '-e') out.email = next();
77
82
  else if (a === '--password' || a === '-p') out.password = next();
@@ -199,11 +204,23 @@ async function readCodexEnfyraEnv(absPath) {
199
204
  return null;
200
205
  }
201
206
 
202
- async function loadExistingEnfyraEnv(root, readClaude, readCursor, readCodex) {
207
+ function getCodexConfigPath(root, globalScope) {
208
+ return globalScope ? join(homedir(), '.codex', 'config.toml') : join(root, '.codex', 'config.toml');
209
+ }
210
+
211
+ function getClaudeConfigPath(root, globalScope) {
212
+ return globalScope ? join(homedir(), '.mcp.json') : join(root, '.mcp.json');
213
+ }
214
+
215
+ function getCursorConfigPath(root, globalScope) {
216
+ return globalScope ? join(homedir(), '.cursor', 'mcp.json') : join(root, '.cursor', 'mcp.json');
217
+ }
218
+
219
+ async function loadExistingEnfyraEnv(root, readClaude, readCursor, readCodex, globalScope) {
203
220
  const paths = [];
204
- if (readClaude) paths.push(join(root, '.mcp.json'));
205
- if (readCursor) paths.push(join(root, '.cursor', 'mcp.json'));
206
- if (!readClaude && readCursor) paths.push(join(root, '.mcp.json'));
221
+ if (readClaude) paths.push(getClaudeConfigPath(root, globalScope));
222
+ if (readCursor) paths.push(getCursorConfigPath(root, globalScope));
223
+ if (!globalScope && !readClaude && readCursor) paths.push(join(root, '.mcp.json'));
207
224
  const seen = new Set();
208
225
  for (const p of paths) {
209
226
  if (seen.has(p)) continue;
@@ -224,19 +241,41 @@ async function loadExistingEnfyraEnv(root, readClaude, readCursor, readCodex) {
224
241
  }
225
242
  }
226
243
  if (readCodex) {
227
- const codex = await readCodexEnfyraEnv(join(homedir(), '.codex', 'config.toml'));
244
+ const codex = await readCodexEnfyraEnv(getCodexConfigPath(root, globalScope));
228
245
  if (codex) return codex;
229
246
  }
230
247
  return { apiUrl: '', email: '', password: '' };
231
248
  }
232
249
 
233
250
  async function promptTargetChoice() {
251
+ const choices = [
252
+ {
253
+ label: 'Claude Code — project ./.mcp.json',
254
+ value: { claude: true, cursor: false, codex: false },
255
+ },
256
+ {
257
+ label: 'Cursor — project ./.cursor/mcp.json',
258
+ value: { claude: false, cursor: true, codex: false },
259
+ },
260
+ {
261
+ label: 'Codex — project ./.codex/config.toml',
262
+ value: { claude: false, cursor: false, codex: true },
263
+ },
264
+ {
265
+ label: 'All',
266
+ value: { claude: true, cursor: true, codex: true },
267
+ },
268
+ ];
269
+ if (input.setRawMode && output.isTTY) {
270
+ return promptTargetSelect(choices, 3);
271
+ }
272
+
234
273
  const rl = createInterface({ input, output });
235
274
  const line = (await rl.question(
236
275
  'Where should Enfyra MCP config be written?\n'
237
276
  + ' [1] Claude Code — ./.mcp.json\n'
238
277
  + ' [2] Cursor — ./.cursor/mcp.json\n'
239
- + ' [3] Codex — ~/.codex/config.toml\n'
278
+ + ' [3] Codex — ./.codex/config.toml\n'
240
279
  + ' [4] All [default]\n'
241
280
  + 'Choice [4]: ',
242
281
  )).trim().toLowerCase();
@@ -256,6 +295,66 @@ async function promptTargetChoice() {
256
295
  return { claude: true, cursor: true, codex: true };
257
296
  }
258
297
 
298
+ async function promptTargetSelect(choices, initialIndex = 0) {
299
+ let selected = Math.max(0, Math.min(initialIndex, choices.length - 1));
300
+ let renderedLines = 0;
301
+
302
+ const render = () => {
303
+ if (renderedLines > 0) {
304
+ output.write(`\x1B[${renderedLines}A\x1B[0J`);
305
+ }
306
+ const lines = [
307
+ 'Where should Enfyra MCP config be written?',
308
+ ...choices.map((choice, index) => `${index === selected ? '›' : ' '} ${choice.label}`),
309
+ '',
310
+ 'Use ↑/↓ to move, Enter to select.',
311
+ ];
312
+ renderedLines = lines.length;
313
+ output.write(`${lines.join('\n')}\n`);
314
+ };
315
+
316
+ return new Promise((resolve, reject) => {
317
+ const wasRaw = input.isRaw;
318
+ const cleanup = () => {
319
+ input.off('keypress', onKeypress);
320
+ if (!wasRaw) input.setRawMode(false);
321
+ output.write('\x1B[?25h');
322
+ };
323
+ const finish = () => {
324
+ cleanup();
325
+ resolve(choices[selected].value);
326
+ };
327
+ const onKeypress = (_str, key = {}) => {
328
+ if (key.ctrl && key.name === 'c') {
329
+ cleanup();
330
+ output.write('\n');
331
+ reject(new Error('Cancelled'));
332
+ return;
333
+ }
334
+ if (key.name === 'up') {
335
+ selected = selected <= 0 ? choices.length - 1 : selected - 1;
336
+ render();
337
+ return;
338
+ }
339
+ if (key.name === 'down') {
340
+ selected = selected >= choices.length - 1 ? 0 : selected + 1;
341
+ render();
342
+ return;
343
+ }
344
+ if (key.name === 'return' || key.name === 'enter') {
345
+ finish();
346
+ }
347
+ };
348
+
349
+ emitKeypressEvents(input);
350
+ input.setRawMode(true);
351
+ input.resume();
352
+ output.write('\x1B[?25l');
353
+ input.on('keypress', onKeypress);
354
+ render();
355
+ });
356
+ }
357
+
259
358
  async function promptConfig(opts, existing) {
260
359
  let apiUrl = opts.apiUrl;
261
360
  let email = opts.email;
@@ -337,7 +436,7 @@ export async function runLocalConfig(argv) {
337
436
  writeCodex = t.codex;
338
437
  }
339
438
 
340
- const existing = await loadExistingEnfyraEnv(root, writeClaude, writeCursor, writeCodex);
439
+ const existing = await loadExistingEnfyraEnv(root, writeClaude, writeCursor, writeCodex, opts.global);
341
440
 
342
441
  let apiUrl;
343
442
  let email;
@@ -358,17 +457,17 @@ export async function runLocalConfig(argv) {
358
457
  const written = [];
359
458
 
360
459
  if (writeClaude) {
361
- const p = join(root, '.mcp.json');
460
+ const p = getClaudeConfigPath(root, opts.global);
362
461
  await mergeMcpFile(p, serverEntry);
363
462
  written.push(p);
364
463
  }
365
464
  if (writeCursor) {
366
- const p = join(root, '.cursor', 'mcp.json');
465
+ const p = getCursorConfigPath(root, opts.global);
367
466
  await mergeMcpFile(p, serverEntry);
368
467
  written.push(p);
369
468
  }
370
469
  if (writeCodex) {
371
- const p = join(homedir(), '.codex', 'config.toml');
470
+ const p = getCodexConfigPath(root, opts.global);
372
471
  await mergeCodexConfig(p, apiUrl, email, password);
373
472
  written.push(p);
374
473
  }
@@ -376,9 +475,9 @@ export async function runLocalConfig(argv) {
376
475
  console.log('Enfyra MCP — local config updated:\n');
377
476
  for (const p of written) console.log(` ${p}`);
378
477
  console.log('\nNext steps:');
379
- console.log(' • Codex: restart Codex or start a new session so ~/.codex/config.toml is reloaded.');
478
+ console.log(' • Codex: open this folder in a new Codex session so project ./.codex/config.toml is loaded.');
380
479
  console.log(' • Claude Code: open this folder; approve project MCP if prompted (`claude mcp reset-project-choices` to reset).');
381
- console.log(' • Cursor: restart Cursor or reload MCP; confirm server under Settings → MCP.');
480
+ console.log(' • Cursor: open this folder, restart Cursor or reload MCP, then confirm server under Settings → MCP.');
382
481
  console.log(' • Run `config` again anytime to change values (same files are merged/overwritten for `enfyra`).');
383
482
  if (!email || !password) {
384
483
  console.log('\nWarning: ENFYRA_EMAIL or ENFYRA_PASSWORD is empty — tools may not authenticate until set.');