@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 +18 -16
- package/package.json +1 -1
- package/src/lib/config-local.mjs +35 -19
- package/src/lib/mcp-instructions.js +7 -6
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` →
|
|
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
|
-
| **
|
|
38
|
-
| **
|
|
39
|
-
| **Typical install** | `npx @enfyra/mcp-server config --codex` | `
|
|
40
|
-
| **Precedence / merge** |
|
|
41
|
-
| **Gotcha** |
|
|
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
|
|
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.
|
|
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
|
|
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` —
|
|
192
|
+
### `ENFYRA_API_URL` — use the app proxy
|
|
192
193
|
|
|
193
|
-
|
|
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
|
-
|
|
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
package/src/lib/config-local.mjs
CHANGED
|
@@ -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
|
|
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
|
-
•
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
206
|
-
if (readCursor) paths.push(
|
|
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(
|
|
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 —
|
|
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 —
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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 (
|
|
52
|
-
'- **
|
|
53
|
-
'-
|
|
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
|
|
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
|
|
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**:
|
|
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.',
|