@enfyra/mcp-server 0.0.17 → 0.0.19
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
CHANGED
|
@@ -48,7 +48,16 @@ export function buildMcpServerInstructions(apiBaseUrl) {
|
|
|
48
48
|
` - **DELETE** \`${delOne}\` — delete one row.`,
|
|
49
49
|
`- **No** **GET** \`${base}/<table_name>/<id>\`. For one row by id use **GET** \`${getOneById}\` or MCP \`query_table\` / \`find_one_record\`.`,
|
|
50
50
|
'',
|
|
51
|
-
'###
|
|
51
|
+
'### Relation field format (create_record / update_record)',
|
|
52
|
+
'- Relation fields (mainTable, publishedMethods, availableMethods, handlers, preHooks, postHooks, etc.) use **object references with `id`**:',
|
|
53
|
+
' - **Many-to-one:** `"mainTable": {"id": 4}` (single object with id)',
|
|
54
|
+
' - **One-to-many / many-to-many:** `"publishedMethods": [{"id": 1}, {"id": 2}]` (array of objects with id)',
|
|
55
|
+
'- **Method IDs** (for publishedMethods, availableMethods, skipRoleGuardMethods): GET=1, POST=2, PATCH=3, DELETE=4, GQL_QUERY=5, GQL_MUTATION=6. Query `method_definition` table if unsure.',
|
|
56
|
+
'- **Wrong:** `"publishedMethods": ["GET"]` or `"publishedMethods": [{"method": "GET"}]` — rejected or silently ignored.',
|
|
57
|
+
'- **Right:** `"publishedMethods": [{"id": 1}]` (publishes GET). Multiple: `[{"id": 1}, {"id": 2}]` (publishes GET + POST).',
|
|
58
|
+
'- **To unset:** pass empty array `"publishedMethods": []`.',
|
|
59
|
+
'',
|
|
60
|
+
'### Auth and publishedMethods (Enfyra server)',
|
|
52
61
|
'- Each route has **publishedMethods** (which HTTP verbs are “public”) and **routePermissions** (roles/users for protected access).',
|
|
53
62
|
'- If the **current request method** is listed in **publishedMethods** for that route, the server allows the call **without** a Bearer token (`RoleGuard`).',
|
|
54
63
|
'- Otherwise the client must send an **Authorization** header with **Bearer** JWT from login. Then the user must satisfy **routePermissions** (unless root admin).',
|
|
@@ -87,6 +96,16 @@ export function buildMcpServerInstructions(apiBaseUrl) {
|
|
|
87
96
|
'- **Tables confirmed to have REST routes (system):** `table_definition`, `route_definition`, `user_definition`, `setting_definition`, `ai_config_definition`, `role_definition`, `menu_definition`, `extension_definition`, `folder_definition`, `file_definition`, `file_permission_definition`, `package_definition`, `bootstrap_script_definition`, `storage_config_definition`, `ai_conversation_definition`, `ai_message_definition`, `websocket_definition`, `websocket_event_definition`, `oauth_config_definition`, `oauth_account_definition`, `method_definition`, `pre_hook_definition`, `post_hook_definition`, `route_handler_definition`, `route_permission_definition`, `flow_definition`, `flow_step_definition`, `flow_execution_definition`.',
|
|
88
97
|
'- **Tables without REST routes (internal/system only):** `column_definition`, `relation_definition` — these are managed indirectly via cascade on `table_definition` (PATCH /table_definition/{id} with columns/relations array). The `create_column` MCP tool handles this automatically.',
|
|
89
98
|
'',
|
|
99
|
+
'### Body validation & column rules',
|
|
100
|
+
'- Each `table_definition` has a **`validateBody`** flag (default `true` for new tables). When on, every `POST /<table>` and `PATCH /<table>/<id>` is validated server-side against the column types + any **column rules** attached to columns of that table.',
|
|
101
|
+
'- Failure returns **HTTP 400** with `{ statusCode: 400, message: string[], error: "Bad Request" }`. `message` is an **array of strings** (one per violation, prefixed with the field name like `"email: Invalid email"`).',
|
|
102
|
+
'- **`column_rule_definition`** stores per-column rules: fields are `column` (FK to `column_definition`), `ruleType` (one of `min`, `max`, `minLength`, `maxLength`, `pattern`, `format`, `minItems`, `maxItems`, `custom`), `value` (JSON payload e.g. `{v:10}` or `{v:"email"}` or `{v:"^[a-z]+$", flags:"i"}`), `message` (optional override), `isEnabled`. Has a default REST route at `/column_rule_definition` so MCP `create_record` / `update_record` / `delete_record` work — but the **canonical workflow is the admin UI** (Collections → table → column row → ruler icon → Manage Rules), which knows the per-`column.type` allowlist (e.g. `format` only for strings, `minItems` only for `array-select`) and prevents duplicate rule types per column.',
|
|
103
|
+
'- **Rules are additive only** — they never replace the column\'s built-in type/nullable/length checks. There is no `required` rule type; required-ness comes from `column.isNullable = false`.',
|
|
104
|
+
'- If the user wants to add a validation constraint to a field, the right answer is: open the column rules modal in the admin UI. **Do not** suggest writing pre-hooks for standard constraints (only for truly custom logic).',
|
|
105
|
+
'- For `pattern` rules: the `value.v` is a JavaScript RegExp body (no surrounding `/.../`). Anchors matter — `^[a-z]+$` requires a full match, plain `[a-z]+` matches any substring. Flags go in `value.flags` (e.g. `"i"`).',
|
|
106
|
+
'- Validation cache is invalidated **automatically** when a column rule is created/updated/deleted via MCP or UI — no manual `reload_*` call needed afterward. Same for flipping `validateBody` on `table_definition`.',
|
|
107
|
+
'- To turn validation off for an entire table (e.g. legacy or test tables), either toggle **Validate Body** in the table form UI, or `update_record` on `table_definition` with `{ validateBody: false }`.',
|
|
108
|
+
'',
|
|
90
109
|
'### Schema / table migration (sequential only)',
|
|
91
110
|
'- When creating, updating, or deleting tables (or columns), run operations **one at a time**. The migration process locks the DB per operation.',
|
|
92
111
|
'- Do NOT batch multiple schema changes (e.g. create 3 tables in parallel, or create table + add columns simultaneously). Execute each `create_table`, `create_column`, sync, or drop sequentially; wait for completion before the next.',
|
|
@@ -131,15 +150,15 @@ export function buildMcpServerInstructions(apiBaseUrl) {
|
|
|
131
150
|
'- **Flow** (`flow_definition`): `name`, `triggerType` (`schedule`, `manual`), `triggerConfig` (JSON), `timeout`, `maxExecutions` (default 100, auto-cleanup old history), `isEnabled`.',
|
|
132
151
|
'- **Step** (`flow_step_definition`): `flow` → flow id, `key` (unique identifier for data chain), `stepOrder`, `type` (`script`, `condition`, `query`, `create`, `update`, `delete`, `http`, `trigger_flow`, `sleep`, `log`), `config` (JSON), `timeout`, `onError` (`stop`, `skip`, `retry`), `retryAttempts`, `parent` → self-ref to condition step (null = root), `branch` (`true`/`false` — which branch of parent condition).',
|
|
133
152
|
'- **Execution history** (`flow_execution_definition`): `flow` → flow id, `status`, `payload`, `context` (full data chain), `completedSteps`, `currentStep`, `error`, `startedAt`, `completedAt`, `duration`. Query separately — NOT nested under flow_definition.',
|
|
134
|
-
'- **triggerConfig examples**: schedule: `{"cron":"0 2 * * *","timezone":"UTC"}`, manual: `{}`. For event/webhook use cases, create a handler/hook with `@
|
|
135
|
-
'- **Step config examples**: script: `{"code":"return #user_definition.find({limit:10})"}`, condition: `{"code":"return @
|
|
136
|
-
'- **Data chain**: Steps access previous results via `@FLOW.<stepKey>` and `@
|
|
137
|
-
'- **Template syntax (flows)**: `@
|
|
153
|
+
'- **triggerConfig examples**: schedule: `{"cron":"0 2 * * *","timezone":"UTC"}`, manual: `{}`. For event/webhook use cases, create a handler/hook with `@TRIGGER("flow-name", payload)` instead.',
|
|
154
|
+
'- **Step config examples**: script: `{"code":"return #user_definition.find({limit:10})"}`, condition: `{"code":"return @FLOW_LAST?.data?.length > 0"}` (uses JS truthy/falsy: `return user` = truthy if exists, `return null` = falsy), query: `{"table":"user_definition","filter":{"status":{"_eq":"active"}},"limit":10}`, http: `{"url":"https://api.example.com","method":"POST","body":{}}` (auto Content-Type: application/json; **http `url` must be public-safe**—see Safety), sleep: `{"ms":5000}`, trigger_flow: `{"flowId":2}`.',
|
|
155
|
+
'- **Data chain**: Steps access previous results via `@FLOW.<stepKey>` and `@FLOW_LAST`. Input payload via `@FLOW_PAYLOAD`. Repos via `#table_name`.',
|
|
156
|
+
'- **Template syntax (flows)**: `@FLOW_PAYLOAD` → `$ctx.$flow.$payload` (input data), `@FLOW_LAST` → `$ctx.$flow.$last`, `@FLOW` → `$ctx.$flow`, `@FLOW_META` → `$ctx.$flow.$meta`, `#table_name` → `$ctx.$repos.table_name`, `@HELPERS` → `$ctx.$helpers`, `@THROW400`–`@THROW503` / `@THROW` → `$ctx.$throw[...]`. Trigger other flows in handlers via `@TRIGGER(name, payload)` or `$ctx.$trigger(name, payload)`.',
|
|
138
157
|
'- **Condition branching**: Condition step uses JavaScript truthy/falsy evaluation (e.g. `return user` → truthy if exists, falsy if null/0/undefined). Children with matching `parent: {id: conditionStepId}` and `branch: "true"/"false"` execute. Root steps (no parent) always execute sequentially.',
|
|
139
|
-
'- **Safety**: Max nesting depth 10 (flow triggering flow). Circular flow detection prevents A→B→A loops. HTTP steps: **SSRF hardening** — only `http`/`https`; blocks `localhost`, private IPs, and hostnames resolving to private IPs (use internet-facing URLs like `https://api.example.com`, not internal services, unless server policy changes). Default HTTP timeout 30s (AbortController). `$
|
|
158
|
+
'- **Safety**: Max nesting depth 10 (flow triggering flow). Circular flow detection prevents A→B→A loops. HTTP steps: **SSRF hardening** — only `http`/`https`; blocks `localhost`, private IPs, and hostnames resolving to private IPs (use internet-facing URLs like `https://api.example.com`, not internal services, unless server policy changes). Default HTTP timeout 30s (AbortController). `$trigger()` available inside flow steps.',
|
|
140
159
|
'- **Workflow**: Create flow → `create_record` on `flow_definition`. Add steps → `create_record` on `flow_step_definition` with `flow: {id}`. For branch steps, set `parent: {id: conditionStepId}` and `branch: "true"` or `"false"`. Trigger manually via `POST /admin/flow/trigger/{flowId}`.',
|
|
141
160
|
'- **Test step**: `POST /admin/flow/test-step` with body `{type, config, timeout}` — runs a single step without saving, returns `{success, result, error, duration}`.',
|
|
142
|
-
'- **In handlers/hooks**: Trigger flows via `$ctx.$
|
|
161
|
+
'- **In handlers/hooks**: Trigger flows via `$ctx.$trigger("flow-name", {payload})` or `$ctx.$trigger(flowId, {payload})`.',
|
|
143
162
|
'',
|
|
144
163
|
'### Extension (Vue SFC only — NOT React)',
|
|
145
164
|
'- **CRITICAL:** MUST call `create_record` or `update_record` on `extension_definition` — outputting Vue code in chat does NOT save it. User will NOT see it.',
|
package/src/mcp-server-entry.mjs
CHANGED
|
@@ -374,7 +374,7 @@ registerTableTools(server, ENFYRA_API_URL);
|
|
|
374
374
|
// CACHE & SYSTEM TOOLS
|
|
375
375
|
// ============================================================================
|
|
376
376
|
|
|
377
|
-
server.tool('reload_all', 'Reload all caches (metadata, routes,
|
|
377
|
+
server.tool('reload_all', 'Reload all caches (metadata, routes, GraphQL)', {}, async () => {
|
|
378
378
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload', { method: 'POST' });
|
|
379
379
|
return { content: [{ type: 'text', text: `System reloaded:\n${JSON.stringify(result, null, 2)}` }] };
|
|
380
380
|
});
|
|
@@ -389,11 +389,6 @@ server.tool('reload_routes', 'Reload routes cache only', {}, async () => {
|
|
|
389
389
|
return { content: [{ type: 'text', text: `Routes reloaded:\n${JSON.stringify(result, null, 2)}` }] };
|
|
390
390
|
});
|
|
391
391
|
|
|
392
|
-
server.tool('reload_swagger', 'Reload Swagger/OpenAPI spec', {}, async () => {
|
|
393
|
-
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload/swagger', { method: 'POST' });
|
|
394
|
-
return { content: [{ type: 'text', text: `Swagger reloaded:\n${JSON.stringify(result, null, 2)}` }] };
|
|
395
|
-
});
|
|
396
|
-
|
|
397
392
|
server.tool('reload_graphql', 'Reload GraphQL schema', {}, async () => {
|
|
398
393
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload/graphql', { method: 'POST' });
|
|
399
394
|
return { content: [{ type: 'text', text: `GraphQL reloaded:\n${JSON.stringify(result, null, 2)}` }] };
|