@enfyra/mcp-server 0.0.28 → 0.0.30

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
@@ -20,8 +20,9 @@ npx @enfyra/mcp-server config
20
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
 
@@ -184,18 +185,19 @@ Use this block in any host-specific `mcp.json` / `mcpServers` merge (adjust env
184
185
 
185
186
  | Variable | Description | Default |
186
187
  |----------|-------------|---------|
187
- | `ENFYRA_API_URL` | Base for REST + GraphQL + auth (see table below) | `http://localhost:3000/api` |
188
+ | `ENFYRA_API_URL` | Base for REST + GraphQL + auth through the Nuxt/app proxy | `http://localhost:3000/api` |
188
189
  | `ENFYRA_EMAIL` | Admin email | — |
189
190
  | `ENFYRA_PASSWORD` | Admin password | — |
190
191
 
191
- ### `ENFYRA_API_URL` — two valid setups
192
+ ### `ENFYRA_API_URL` — use the app proxy
192
193
 
193
- | Mode | Example | When to use |
194
- |------|---------|-------------|
195
- | **Via Nuxt admin (typical local dev)** | `http://localhost:3000/api` | Browser app on 3000; Nitro proxies `/api/*` to Nest (`API_URL`, often `http://localhost:1105`). GraphQL at `{ENFYRA_API_URL}/graphql` is proxied to the backend `/graphql`. |
196
- | **Direct to Nest backend** | `http://localhost:1105` | Call Enfyra **without** the Nuxt prefix. **Do not** append `/api` unless your reverse proxy serves routes under `/api`—`http://localhost:1105/api/...` will not match default Nest paths. |
194
+ For normal apps and demos, set `ENFYRA_API_URL` to the Nuxt/app proxy:
197
195
 
198
- Pick the base URL that matches how **your** HTTP client reaches the same server as the Enfyra REST API.
196
+ ```text
197
+ http://localhost:3000/api
198
+ ```
199
+
200
+ The Enfyra backend is private infrastructure. MCP, browser code, SSR routes, GraphQL calls, and generated app code should go through the app origin `/api/**`; do not connect them directly to the backend host/port. Direct backend URLs are only for Enfyra core/server debugging when you intentionally bypass the app proxy.
199
201
 
200
202
  ### SSR app auth pattern
201
203
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfyra/mcp-server",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "description": "MCP server for Enfyra - manage your Enfyra instance via Claude Code",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -13,32 +13,34 @@ function printHelp() {
13
13
  Usage:
14
14
  npx @enfyra/mcp-server config [options]
15
15
 
16
- Writes project config under the current working directory and Codex config under your home directory:
16
+ Writes project config under the current working directory:
17
17
  • ./.mcp.json — Claude Code project scope
18
18
  • ./.cursor/mcp.json — Cursor project scope
19
- ~/.codex/config.toml — Codex user scope
19
+ ./.codex/config.toml — Codex project scope
20
20
 
21
21
  Options:
22
22
  --api-url, -a <url> ENFYRA_API_URL
23
23
  --email, -e <email> ENFYRA_EMAIL
24
24
  --password, -p <secret> ENFYRA_PASSWORD
25
+ --global Write global/user config for selected hosts instead of project config
25
26
  --reconfig Always choose target again in interactive mode and replace the old enfyra config for that target
26
27
  --yes Non-interactive: no prompts (CI / scripts); use CLI, env, existing file, then defaults
27
28
  Target — non-interactive default is all; with TTY and no target flags, choose with ↑/↓:
28
29
  --claude-code, --claude, --claude-only Only ./.mcp.json (Claude Code project scope)
29
30
  --cursor, --cursor-only Only ./.cursor/mcp.json (Cursor)
30
- --codex, --codex-only Only ~/.codex/config.toml (Codex)
31
+ --codex, --codex-only Only ./.codex/config.toml (Codex project scope)
31
32
  Passing multiple target flags writes each selected target.
32
33
  -h, --help Show this help
33
34
 
34
35
  Interactive mode: lets you choose Claude Code / Cursor / Codex / all if you did not pass target flags; then asks for URL / email / password
35
- when missing. Existing ./.mcp.json, ./.cursor/mcp.json, and ~/.codex/config.toml are used as defaults. Re-run to update.
36
+ when missing. Existing project config is used as defaults. Re-run to update.
36
37
 
37
38
  Examples:
38
39
  npx @enfyra/mcp-server config
39
40
  npx @enfyra/mcp-server config --claude-code
40
41
  npx @enfyra/mcp-server config --cursor --yes
41
42
  npx @enfyra/mcp-server config --codex --yes
43
+ npx @enfyra/mcp-server config --global --codex
42
44
  npx @enfyra/mcp-server config --reconfig
43
45
  npx @enfyra/mcp-server config -a http://localhost:3000/api -e admin@x.com -p 'secret'
44
46
  npx @enfyra/mcp-server config --yes
@@ -57,6 +59,7 @@ function parseArgs(argv) {
57
59
  help: false,
58
60
  yes: false,
59
61
  reconfig: false,
62
+ global: false,
60
63
  };
61
64
  let pickClaude = false;
62
65
  let pickCursor = false;
@@ -73,6 +76,7 @@ function parseArgs(argv) {
73
76
  else if (a === 'help') out.help = true;
74
77
  else if (a === '--yes') out.yes = true;
75
78
  else if (a === '--reconfig') out.reconfig = true;
79
+ else if (a === '--global') out.global = true;
76
80
  else if (a === '--api-url' || a === '-a') out.apiUrl = next();
77
81
  else if (a === '--email' || a === '-e') out.email = next();
78
82
  else if (a === '--password' || a === '-p') out.password = next();
@@ -200,11 +204,23 @@ async function readCodexEnfyraEnv(absPath) {
200
204
  return null;
201
205
  }
202
206
 
203
- 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) {
204
220
  const paths = [];
205
- if (readClaude) paths.push(join(root, '.mcp.json'));
206
- if (readCursor) paths.push(join(root, '.cursor', 'mcp.json'));
207
- 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'));
208
224
  const seen = new Set();
209
225
  for (const p of paths) {
210
226
  if (seen.has(p)) continue;
@@ -225,7 +241,7 @@ async function loadExistingEnfyraEnv(root, readClaude, readCursor, readCodex) {
225
241
  }
226
242
  }
227
243
  if (readCodex) {
228
- const codex = await readCodexEnfyraEnv(join(homedir(), '.codex', 'config.toml'));
244
+ const codex = await readCodexEnfyraEnv(getCodexConfigPath(root, globalScope));
229
245
  if (codex) return codex;
230
246
  }
231
247
  return { apiUrl: '', email: '', password: '' };
@@ -234,15 +250,15 @@ async function loadExistingEnfyraEnv(root, readClaude, readCursor, readCodex) {
234
250
  async function promptTargetChoice() {
235
251
  const choices = [
236
252
  {
237
- label: 'Claude Code — ./.mcp.json',
253
+ label: 'Claude Code — project ./.mcp.json',
238
254
  value: { claude: true, cursor: false, codex: false },
239
255
  },
240
256
  {
241
- label: 'Cursor — ./.cursor/mcp.json',
257
+ label: 'Cursor — project ./.cursor/mcp.json',
242
258
  value: { claude: false, cursor: true, codex: false },
243
259
  },
244
260
  {
245
- label: 'Codex — ~/.codex/config.toml',
261
+ label: 'Codex — project ./.codex/config.toml',
246
262
  value: { claude: false, cursor: false, codex: true },
247
263
  },
248
264
  {
@@ -259,7 +275,7 @@ async function promptTargetChoice() {
259
275
  'Where should Enfyra MCP config be written?\n'
260
276
  + ' [1] Claude Code — ./.mcp.json\n'
261
277
  + ' [2] Cursor — ./.cursor/mcp.json\n'
262
- + ' [3] Codex — ~/.codex/config.toml\n'
278
+ + ' [3] Codex — ./.codex/config.toml\n'
263
279
  + ' [4] All [default]\n'
264
280
  + 'Choice [4]: ',
265
281
  )).trim().toLowerCase();
@@ -420,7 +436,7 @@ export async function runLocalConfig(argv) {
420
436
  writeCodex = t.codex;
421
437
  }
422
438
 
423
- const existing = await loadExistingEnfyraEnv(root, writeClaude, writeCursor, writeCodex);
439
+ const existing = await loadExistingEnfyraEnv(root, writeClaude, writeCursor, writeCodex, opts.global);
424
440
 
425
441
  let apiUrl;
426
442
  let email;
@@ -441,17 +457,17 @@ export async function runLocalConfig(argv) {
441
457
  const written = [];
442
458
 
443
459
  if (writeClaude) {
444
- const p = join(root, '.mcp.json');
460
+ const p = getClaudeConfigPath(root, opts.global);
445
461
  await mergeMcpFile(p, serverEntry);
446
462
  written.push(p);
447
463
  }
448
464
  if (writeCursor) {
449
- const p = join(root, '.cursor', 'mcp.json');
465
+ const p = getCursorConfigPath(root, opts.global);
450
466
  await mergeMcpFile(p, serverEntry);
451
467
  written.push(p);
452
468
  }
453
469
  if (writeCodex) {
454
- const p = join(homedir(), '.codex', 'config.toml');
470
+ const p = getCodexConfigPath(root, opts.global);
455
471
  await mergeCodexConfig(p, apiUrl, email, password);
456
472
  written.push(p);
457
473
  }
@@ -459,9 +475,9 @@ export async function runLocalConfig(argv) {
459
475
  console.log('Enfyra MCP — local config updated:\n');
460
476
  for (const p of written) console.log(` ${p}`);
461
477
  console.log('\nNext steps:');
462
- 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.');
463
479
  console.log(' • Claude Code: open this folder; approve project MCP if prompted (`claude mcp reset-project-choices` to reset).');
464
- 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.');
465
481
  console.log(' • Run `config` again anytime to change values (same files are merged/overwritten for `enfyra`).');
466
482
  if (!email || !password) {
467
483
  console.log('\nWarning: ENFYRA_EMAIL or ENFYRA_PASSWORD is empty — tools may not authenticate until set.');
@@ -48,9 +48,10 @@ export function buildMcpServerInstructions(apiBaseUrl) {
48
48
  '- **Extensions/packages/menus:** `extension_definition`, `menu_definition`, `package_definition`, `bootstrap_script_definition`; extensions are Vue SFC only, and packages should be installed with `install_package`.',
49
49
  '- **Platform config:** `setting_definition`, `cors_origin_definition`, reload endpoints, logs, and metadata endpoints.',
50
50
  '',
51
- '### ENFYRA_API_URL (two valid setups)',
52
- '- **Via Nuxt admin (typical):** `http://localhost:3000/api` Nuxt proxies `/api/*` to Nest (`API_URL`, e.g. `http://localhost:1105`). Use this when MCP talks to the app origin.',
53
- '- **Direct to Nest:** `http://localhost:1105` no `/api` suffix on default Nest. Wrong: `http://localhost:1105/api/table_definition` (404) unless a proxy adds `/api`.',
51
+ '### ENFYRA_API_URL (MCP must use the app proxy)',
52
+ '- **Required default:** point MCP at the Nuxt/app origin proxy, e.g. `http://localhost:3000/api`. Nuxt proxies `/api/*` to the hidden Enfyra backend (`API_URL`, e.g. `http://localhost:1105`).',
53
+ '- Treat the Enfyra backend as private infrastructure. Do not tell app-building agents to connect MCP, browser code, SSR code, or generated app code directly to the backend host/port.',
54
+ '- Direct-to-backend URLs such as `http://localhost:1105` are only for Enfyra core/server debugging when the user explicitly asks to bypass the app proxy. They are not valid guidance for normal apps or deployable demos.',
54
55
  '- GraphQL: `{base}/graphql` and `{base}/graphql-schema` always share this same base.',
55
56
  '',
56
57
  '### When the user asks how to connect a Nuxt/Next/SSR app to Enfyra',
@@ -60,7 +61,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
60
61
  '- After cookie-managed login, browser fetches use `/api/<resource>` with normal credentials/cookies; the SSR proxy/middleware attaches or refreshes the Bearer token server-side. Do not read JWTs in browser JavaScript for this mode.',
61
62
  '- For OAuth in SSR/cookie mode, enable cookie handling on the Enfyra OAuth config (`autoSetCookies` / set-cookies mode). Start OAuth at `{{ nuxtApp }}/api/auth/{provider}?redirect=<returnUrl>`. The proxy forwards to backend `/auth/{provider}` with the app origin, backend redirects to `{{ nuxtApp }}/api/auth/set-cookies`, the SSR route stores cookies, then redirects to `redirect`.',
62
63
  '- Token-query OAuth (`appCallbackUrl` receives `accessToken`, `refreshToken`, `expTime`) is only for non-SSR or manually managed token apps. Do not recommend it for Nuxt/Next SSR when Enfyra can set cookies.',
63
- '- If you are explaining MCP\'s own internal authentication, that is separate: this MCP server logs itself in with email/password against backend `/auth/login`. Do not copy that pattern into generated Nuxt/Next browser app code.',
64
+ '- If you are explaining MCP\'s own internal authentication, that is separate: this MCP server logs itself in with email/password against `{ENFYRA_API_URL}/auth/login`; for normal app work, `ENFYRA_API_URL` must still be the app proxy base such as `{{ nuxtApp }}/api`.',
64
65
  '',
65
66
  '### Routes vs tables (custom endpoints, handlers, hooks)',
66
67
  '- REST-first workflow for any feature: **`inspect_feature`** to locate candidates → **`inspect_table`** for table/field/relation/rule context → **`inspect_route`** for handlers/hooks/guards/permissions → **`test_rest_endpoint`** to verify the actual HTTP behavior.',
@@ -194,7 +195,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
194
195
  '- In custom dynamic code, use the same lightweight pattern: `const result = await @REPOS.main.find({ fields: "id", limit: 1, meta: filter ? "filterCount" : "totalCount", ...(filter ? { filter } : {}) }); const count = filter ? result.meta?.filterCount : result.meta?.totalCount;`.',
195
196
  '',
196
197
  '### GraphQL (same prefix as REST / ENFYRA_API_URL)',
197
- `- **POST** \`${graphqlHttpUrl}\` — GraphQL endpoint (body: GraphQL query). With Nuxt base: e.g. \`http://localhost:3000/api/graphql\`. With direct Nest: e.g. \`http://localhost:1105/graphql\`.`,
198
+ `- **POST** \`${graphqlHttpUrl}\` — GraphQL endpoint (body: GraphQL query). With the required app proxy base: e.g. \`http://localhost:3000/api/graphql\`.`,
198
199
  `- **GET** \`${graphqlSchemaUrl}\` — current schema SDL (text); same base pattern as above.`,
199
200
  '- A table appears in the schema when `gql_definition` has an enabled row for that table. The REST route `availableMethods` list does not enable GraphQL.',
200
201
  '- **Query** field = same string as `table_definition.name`. **Mutations** are literal concat: `create_`+tableName, `update_`+tableName, `delete_`+tableName (e.g. tableName `post` → `create_post`, input type `postInput`). See `generate-type-defs.ts`. If every column is skipped for input (only PK, or only `createdAt`/`updatedAt`, or all unpublished), the schema emits **no** `Query.<tableName>` and **no** create/update/delete mutations for that table (an output `type` may still exist for relation wiring).',
@@ -218,7 +219,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
218
219
  '- In **HTTP** handler/hook context: `reply`, `join`, `leave`, `disconnect` are not available (no socket). Use `emitToUser`, `emitToRoom`, `emitToGateway`, `broadcast`.',
219
220
  '- **Context**: Connection — `@BODY` = {id, ip, headers}, `@USER` if auth. Event — `@BODY` = payload, `@USER` if auth. Both have `@SOCKET`.',
220
221
  '- **ACK + results (recommended UX):** client can emit an event with Socket.IO ack callback. Server immediately acks `{ queued: true, requestId, eventName }` (or `{ queued: false, error }`). The handler result is returned asynchronously via `ws:result` or `ws:error` with the same `requestId`.',
221
- '- **Client**: `io("<HTTP_ORIGIN>/namespace", {auth: {token: JWT}})`. Use the **origin where Socket.IO is served** (usually the **Nest** HTTP origin, e.g. `http://localhost:1105/chat` in local server-only setups). If Socket.IO is exposed only through the Nuxt app, use that host and your deployment’s WS path—**do not** assume port 3000 without checking `API_URL` / proxy config. Gateway `path` in metadata = Socket.IO **namespace**.',
222
+ '- **Client**: Browser apps must connect through the app/Nuxt Socket.IO bridge, not the hidden Enfyra backend. For the Enfyra Nuxt bridge this is typically `io("/ws/<namespace>")`, e.g. `io("/ws/chat")`, while the backend gateway metadata path remains `/chat`.',
222
223
  '- **Workflow**: Create gateway → `create_record` on `websocket_definition`. Create event → `create_record` on `websocket_event_definition` with `gateway: {id}`. Changes auto-reload; test handlers before saving.',
223
224
  '- **Test WS handler (recommended):** `POST {base}/admin/test/run` with `{ kind:"websocket_event", gatewayPath, eventName, timeoutMs, payload, script }` to run a websocket event script without a real client. Returns `{ success, result, logs, emitted }`.',
224
225
  '- MCP wrapper: use **`run_admin_test`** with `kind:"websocket_event"` or `kind:"websocket_connection"` instead of hand-building the HTTP call.',