@enfyra/mcp-server 0.1.58 → 0.1.59

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
@@ -1,171 +1,48 @@
1
1
  # Enfyra MCP Server
2
2
 
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.
3
+ Connect Enfyra to MCP-compatible coding tools such as Codex, Claude Code, Cursor, VS Code / GitHub Copilot, Google Antigravity, and other STDIO MCP hosts.
4
4
 
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 TypeScript source in `src/lib/mcp-instructions.ts`, `src/lib/mcp-examples.ts`, and tool descriptions in `src/mcp-server-entry.ts`. Published packages run the compiled `dist/index.js` entry.
5
+ ## Install and Configure
6
6
 
7
- ## Quick Start
8
-
9
- From your project root:
7
+ Run this from the project that should use Enfyra:
10
8
 
11
9
  ```bash
12
10
  npx @enfyra/mcp-server@latest config
13
11
  ```
14
12
 
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
-
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
-
19
- Generated MCP host configs run `npx -y @enfyra/mcp-server@latest` so every host start resolves the current npm `latest` dist-tag published by Enfyra.
20
-
21
- ```bash
22
- # Non-interactive, all supported clients
23
- npx @enfyra/mcp-server@latest config --yes \
24
- --app-url http://localhost:3000 \
25
- -t efy_pat_your-token
26
-
27
- # One or more clients
28
- npx @enfyra/mcp-server@latest config --codex
29
- npx @enfyra/mcp-server@latest config --cursor --claude-code
30
- npx @enfyra/mcp-server@latest config --vscode
31
- npx @enfyra/mcp-server@latest config --antigravity
32
- ```
33
-
34
- Equivalent in this repo:
13
+ The setup asks for:
35
14
 
36
- ```bash
37
- yarn build
38
- yarn mcp:config
39
- ```
15
+ - the Enfyra app/admin URL, such as `http://localhost:3000` or `https://demo.enfyra.io`;
16
+ - an `ENFYRA_API_TOKEN` created from the Enfyra admin UI `/me` page;
17
+ - the MCP clients to configure.
40
18
 
41
- ## Development
42
-
43
- This repo uses Yarn 4 through Corepack and TypeScript source compiled to `dist`.
44
-
45
- ```bash
46
- yarn typecheck
47
- yarn build
48
- yarn test
49
- ```
50
-
51
- `yarn test` builds first and then runs Node tests against `dist` while static source assertions read `src/**/*.ts`.
52
-
53
- ## Choose A Client
19
+ It writes project-local configuration and replaces only the `enfyra` server entry.
54
20
 
55
21
  | Client | Command | Project config |
56
- |--------|---------|----------------|
22
+ |---|---|---|
57
23
  | Codex | `npx @enfyra/mcp-server@latest config --codex` | `.codex/config.toml` |
58
24
  | Claude Code | `npx @enfyra/mcp-server@latest config --claude-code` | `.mcp.json` |
59
25
  | Cursor | `npx @enfyra/mcp-server@latest config --cursor` | `.cursor/mcp.json` |
60
26
  | VS Code / GitHub Copilot | `npx @enfyra/mcp-server@latest config --vscode` | `.vscode/mcp.json` |
61
27
  | Google Antigravity | `npx @enfyra/mcp-server@latest config --antigravity` | `.agents/mcp_config.json` |
62
- | MCP Inspector / other project-scoped hosts | Paste the shared STDIO config below | Host-specific project config |
63
28
 
64
- <details>
65
- <summary><strong>Codex setup</strong></summary>
29
+ Non-interactive setup for all supported clients:
66
30
 
67
31
  ```bash
68
- npx @enfyra/mcp-server@latest config --codex
69
- ```
70
-
71
- Generated project config:
72
-
73
- ```toml
74
- [mcp_servers.enfyra]
75
- command = "npx"
76
- args = ["-y", "@enfyra/mcp-server@latest"]
77
-
78
- [mcp_servers.enfyra.env]
79
- ENFYRA_API_URL = "http://localhost:3000/api"
80
- ENFYRA_API_TOKEN = "efy_pat_your-token"
81
- ```
82
-
83
- The writer replaces only `[mcp_servers.enfyra]` and `[mcp_servers.enfyra.env]`. Other Codex config and other MCP servers are preserved.
84
-
85
- Open this folder in a new Codex session and approve the project MCP config if prompted. The setup command only writes `.codex/config.toml`; it does not ship or create `.codex/skills`.
86
-
87
- Official reference: [Codex config](https://developers.openai.com/codex/config-reference).
88
-
89
- </details>
90
-
91
- <details>
92
- <summary><strong>Claude Code setup</strong></summary>
93
-
94
- ```bash
95
- npx @enfyra/mcp-server@latest config --claude-code
96
- ```
97
-
98
- Project config is written to `.mcp.json`. MCP server definitions do not belong in `.claude/settings.json`.
99
-
100
- Claude Code also supports its own CLI:
101
-
102
- ```bash
103
- claude mcp add --transport stdio --scope project \
104
- --env ENFYRA_API_URL=http://localhost:3000/api \
105
- --env ENFYRA_API_TOKEN=efy_pat_your-token \
106
- enfyra -- npx -y @enfyra/mcp-server@latest
107
- ```
108
-
109
- Scope precedence when the same server name exists in multiple places is local, then project, then user. Project-scoped `.mcp.json` may require approval in Claude Code.
110
-
111
- Official references: [Claude Code MCP](https://docs.anthropic.com/en/docs/claude-code/mcp) and [Claude Code settings](https://docs.anthropic.com/en/docs/claude-code/settings).
112
-
113
- </details>
114
-
115
- <details>
116
- <summary><strong>Cursor setup</strong></summary>
117
-
118
- ```bash
119
- npx @enfyra/mcp-server@latest config --cursor
32
+ npx @enfyra/mcp-server@latest config --yes \
33
+ --app-url http://localhost:3000 \
34
+ --api-token efy_pat_your-token
120
35
  ```
121
36
 
122
- Cursor project config is written to `.cursor/mcp.json`. Global config is `~/.cursor/mcp.json` on macOS/Linux or `%USERPROFILE%\.cursor\mcp.json` on Windows.
123
-
124
- After edits, restart Cursor or reload MCP, then confirm the server under Cursor MCP settings. Use MCP logs if the server fails to start.
125
-
126
- Official reference: [Cursor MCP](https://cursor.com/docs/context/mcp).
127
-
128
- </details>
129
-
130
- <details>
131
- <summary><strong>VS Code / GitHub Copilot setup</strong></summary>
37
+ Configure more than one selected client:
132
38
 
133
39
  ```bash
134
- npx @enfyra/mcp-server@latest config --vscode
135
- ```
136
-
137
- VS Code workspace config is written to `.vscode/mcp.json`:
138
-
139
- ```json
140
- {
141
- "servers": {
142
- "enfyra": {
143
- "type": "stdio",
144
- "command": "npx",
145
- "args": ["-y", "@enfyra/mcp-server@latest"],
146
- "env": {
147
- "ENFYRA_API_URL": "http://localhost:3000/api",
148
- "ENFYRA_API_TOKEN": "efy_pat_your-token"
149
- }
150
- }
151
- }
152
- }
40
+ npx @enfyra/mcp-server@latest config --cursor --claude-code
153
41
  ```
154
42
 
155
- 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.
156
-
157
- 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).
43
+ ## Manual Configuration
158
44
 
159
- </details>
160
-
161
- <details>
162
- <summary><strong>Google Antigravity setup</strong></summary>
163
-
164
- ```bash
165
- npx @enfyra/mcp-server@latest config --antigravity
166
- ```
167
-
168
- Antigravity project config is written to `.agents/mcp_config.json`:
45
+ For hosts that accept an `mcpServers` JSON block:
169
46
 
170
47
  ```json
171
48
  {
@@ -182,37 +59,19 @@ Antigravity project config is written to `.agents/mcp_config.json`:
182
59
  }
183
60
  ```
184
61
 
185
- 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.
186
-
187
- Official reference: [Antigravity MCP](https://antigravity.google/docs/mcp).
62
+ Codex project configuration uses TOML:
188
63
 
189
- </details>
190
-
191
- <details>
192
- <summary><strong>Other MCP hosts and MCP Inspector</strong></summary>
193
-
194
- Use the shared STDIO config with any project-scoped host that accepts an `mcpServers` JSON block:
64
+ ```toml
65
+ [mcp_servers.enfyra]
66
+ command = "npx"
67
+ args = ["-y", "@enfyra/mcp-server@latest"]
195
68
 
196
- ```json
197
- {
198
- "mcpServers": {
199
- "enfyra": {
200
- "command": "npx",
201
- "args": ["-y", "@enfyra/mcp-server@latest"],
202
- "env": {
203
- "ENFYRA_API_URL": "http://localhost:3000/api",
204
- "ENFYRA_API_TOKEN": "efy_pat_your-token"
205
- }
206
- }
207
- }
208
- }
69
+ [mcp_servers.enfyra.env]
70
+ ENFYRA_API_URL = "http://localhost:3000/api"
71
+ ENFYRA_API_TOKEN = "efy_pat_your-token"
209
72
  ```
210
73
 
211
- `ENFYRA_API_TOKEN` is a programmatic token from the Enfyra admin UI `/me`. It is not a JWT; the MCP server exchanges it through `POST {ENFYRA_API_URL}/auth/token/exchange` before calling Enfyra REST APIs.
212
-
213
- Official reference: [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector).
214
-
215
- </details>
74
+ Restart or reload the MCP client after writing configuration. Keep the token out of committed files.
216
75
 
217
76
  ## Config Command
218
77
 
@@ -221,11 +80,11 @@ npx @enfyra/mcp-server@latest config [options]
221
80
  ```
222
81
 
223
82
  | Option | Use |
224
- |--------|-----|
83
+ |---|---|
225
84
  | `--app-url` | Set the Enfyra app/admin URL |
226
85
  | `--api-token`, `-t` | Set `ENFYRA_API_TOKEN` |
227
- | `--yes` | Non-interactive mode for CI/scripts |
228
- | `--reconfig` | Prompt for target clients again and replace the existing `enfyra` entry |
86
+ | `--yes` | Run non-interactively |
87
+ | `--reconfig` | Select clients again and replace the existing `enfyra` entry |
229
88
  | `--codex` | Write Codex config |
230
89
  | `--claude-code`, `--claude` | Write Claude Code config |
231
90
  | `--cursor` | Write Cursor config |
@@ -233,118 +92,38 @@ npx @enfyra/mcp-server@latest config [options]
233
92
  | `--antigravity` | Write Google Antigravity config |
234
93
  | `-h`, `--help` | Show CLI help |
235
94
 
236
- Without a target flag, interactive mode asks which client to configure. Non-interactive mode defaults to all supported clients.
237
-
238
95
  ## Environment
239
96
 
240
97
  | Variable | Description | Default |
241
- |----------|-------------|---------|
98
+ |---|---|---|
242
99
  | `ENFYRA_APP_URL` | App/admin URL used by setup | `http://localhost:3000` |
243
- | `ENFYRA_API_URL` | Runtime API base written into MCP client config | Generated by setup |
100
+ | `ENFYRA_API_URL` | Runtime API base written into MCP config | Derived from the app URL |
244
101
  | `ENFYRA_API_TOKEN` | Programmatic token from the Enfyra admin UI `/me` | Required |
245
- | `ENFYRA_MCP_TOOLSET` | Tool visibility mode: `guided` for curated default tools, or `full` for every low-level escape hatch | `guided` |
246
-
247
- For normal apps and demos, enter the app/admin URL such as `http://localhost:3000` or `https://demo.enfyra.io`. Treat the direct Enfyra backend host as private infrastructure unless you are debugging Enfyra core/server internals.
248
-
249
- ## Metadata Contract
250
-
251
- The MCP server uses Enfyra metadata lazily:
252
-
253
- - `GET /metadata` returns only `dbType` and `enfyraVersion`.
254
- - `GET /metadata/{tableName}` returns one permission-projected table schema.
255
- - `get_all_tables` and normal discovery use a lightweight `enfyra_table` catalog.
256
- - `get_table_metadata`, `inspect_table`, mutation validation, and schema operations fetch only the table schemas they need.
257
- - Explicit broad schema searches fan out to per-table metadata with bounded concurrency and reuse the MCP runtime cache.
258
-
259
- Use `get_all_tables` for table names, then `get_table_metadata` or `inspect_table` for columns and relations. `get_all_metadata(includeFull=true)` is an explicit broad operation and should not be used as startup discovery.
260
-
261
- ## Common Examples
262
-
263
- Use `get_enfyra_examples` from the MCP tool list when asking an LLM to generate implementation patterns. It returns focused examples for:
264
-
265
- - SSR app auth and proxy setup
266
- - OAuth provider setup
267
- - schema, columns, relations, indexes, and validation
268
- - query filters, sorting, fields, deep relations, and aggregates
269
- - handlers, hooks, permissions, and RLS
270
- - websocket gateways and events
271
- - flows
272
- - files and storage
273
- - Enfyra admin extensions
274
-
275
- Use `discover_enfyra_workflows` when an LLM knows the goal but may not know the right Enfyra tool path. It returns progressive-disclosure workflow matches with first tools, required acknowledgements, verification tools, relevant example categories, and `avoidTools` boundaries that prevent near-correct but unsafe tool choices.
276
-
277
- Use `get_enfyra_required_knowledge` before asking an LLM to mutate metadata, schema, routes, permissions, menus, packages, cache state, dynamic server code, or Enfyra extension code. It returns global rules plus acknowledgement keys that write tools verify before saving. Dynamic server code also requires the dynamic-code acknowledgement key, and extension code also requires the extension acknowledgement key.
278
-
279
- ## Runtime Safety
280
-
281
- The MCP server includes safety guards for LLM callers:
282
-
283
- - Generic record mutations validate fields against live metadata.
284
- - Write tools require `get_enfyra_required_knowledge` acknowledgement before mutating Enfyra state. Discovery, validation, and preview tools remain available without the acknowledgement so agents can read and plan first. If the acknowledgement is missing, the tool error tells the caller to read `get_enfyra_required_knowledge` and pass the required key.
285
- - Script-backed records validate `sourceCode` through `/admin/script/validate` before saving.
286
- - `validate_dynamic_script` checks handler, hook, flow, websocket, GraphQL, and bootstrap script source without saving.
287
- - `validate_extension_code` locally rejects common extension component-resolution mistakes, such as `resolveComponent()` or lowercase auto-injected component tags like `<ubutton>`, then checks Enfyra admin extension code through `/enfyra_extension/preview` without saving.
288
- - Dynamic script guidance distinguishes secure repositories (`@REPOS.main`, `@REPOS.secure.<table>`) from trusted internal repositories (`@REPOS.<table>`), and tells agents not to return raw trusted records to users.
289
- - `compiledCode` is generated from `sourceCode` and may differ textually because macros are expanded; the MCP server never accepts hand-written `compiledCode`.
290
- - Long source/code values in read responses are written to `/tmp/enfyra-mcp-sources` and returned as length/hash/preview/tmpFile metadata so LLM callers can inspect full source from the file path without truncating tool output.
291
- - JSON responses include `compressionStats` with estimated token savings. Arrays of objects are converted to columnar form only when the compact shape is smaller than raw JSON.
292
- - Relation tools reject physical FK/junction names and resolve table ids from exact table names or aliases before schema mutation.
293
- - Generated code should use relation property names such as `conversation`, `sender`, and `member` instead of physical FK fields such as `conversationId`, `senderId`, or `memberId`.
294
- - Custom route tools reject `mainTableId` unless the route is the canonical table route.
295
- - `discover_enfyra_workflows` maps task intent to workflow surfaces before the agent loads detailed examples or guesses between similar tools.
296
- - Platform operation tools such as `api_endpoint_workflow`, `extension_workflow`, `flow_workflow`, `search_admin_extensions`, `debug_field_exposure`, `enable_route`, `disable_route`, `delete_route`, `public_route_methods`, `set_table_graphql`, `ensure_route_rate_limit`, `ensure_guard`, `ensure_field_permission`, `ensure_column_rule`, `ensure_websocket_event`, `ensure_menu`, `reorder_menus`, `ensure_page_extension`, `ensure_global_extension`, and `ensure_widget_extension` resolve metadata ids and validate code before saving.
297
- - Schema changes are serialized.
298
- - Destructive deletes return a preview before requiring `confirm=true`.
102
+ | `ENFYRA_MCP_TOOLSET` | `guided` for the normal curated toolset or `full` for low-level debugging tools | `guided` |
299
103
 
300
- ## Query Notes
104
+ The API token is exchanged for a short-lived access token at runtime. It is not sent directly as a Bearer token.
301
105
 
302
- Use explicit `fields` in read tools. Include mode is the default, such as `fields=id,email`. Any excluded field switches that scope to exclude mode: `fields=-compiledCode` returns all readable fields except `compiledCode`, and `fields=id,-compiledCode` still means all except `compiledCode`. Dotted exclusions such as `fields=-owner.avatar` work for relation fields when the relation exists in metadata. Every broad list/query call must pass either `limit` for a bounded page or `all: true` for a complete list. Locator searches on `get_all_routes` and `get_all_tables` may omit `limit` when `search` is provided; they return a small bounded lookup window. When a caller needs every matching row, pass `all: true` to `query_table`, `get_all_routes`, or `get_all_tables`; the tool should not choose an arbitrary page size like 30 or 50.
106
+ ## Verify the Connection
303
107
 
304
- ## Enfyra URL Pattern
108
+ Open a new session in the configured MCP client and ask it to:
305
109
 
306
- Generated apps should use a same-origin proxy:
307
-
308
- ```js
309
- export default defineNuxtConfig({
310
- routeRules: {
311
- "/enfyra/**": {
312
- proxy: {
313
- to: `${process.env.ENFYRA_API_URL}/**`,
314
- fetchOptions: { redirect: "manual" }
315
- }
316
- }
317
- }
318
- })
110
+ ```text
111
+ Use Enfyra MCP to show the connected API context.
319
112
  ```
320
113
 
321
- Browser code then calls:
114
+ Then try a read-only request:
322
115
 
323
116
  ```text
324
- POST /enfyra/login
325
- GET /enfyra/me
326
- POST /enfyra/logout
327
- GET /enfyra/<table>
117
+ Use Enfyra MCP to list the available tables without changing anything.
328
118
  ```
329
119
 
330
- Do not create custom login/logout/me routes that manually set Enfyra token cookies when the proxy is enough.
331
-
332
- ## Tool Summary
333
-
334
- By default, the MCP server starts with `ENFYRA_MCP_TOOLSET=guided`, a curated tool surface optimized for weaker LLMs and one-shot success. It exposes workflow routing, focused discovery, runtime zone search, schema tools, query/CRUD envelopes, row-scope pre-hooks, focused extension patching, operation-level route/permission/extension/flow/websocket tools, validation, and narrow verification tools. Set `ENFYRA_MCP_TOOLSET=full` only for expert debugging or compatibility work that needs low-level escape hatches such as raw route construction, cache reloads, method metadata, broad metadata reads, or raw log file reads.
335
-
336
- The guided approach has two explicit operating criteria: a fully specified, non-destructive task should complete in one task turn for GLM-5-Turbo or stronger, using the fewest necessary MCP calls; and task-specific knowledge is lazy-loaded, so startup context only routes work and does not preload unrelated schemas, examples, or UI/script contracts. Preview-first destructive changes, explicit user approval, and necessary security checks are deliberate exceptions. When a contract is clear, prefer an operation tool's atomic or `applyAll` path; use plan-only calls only when a decision is genuinely unresolved.
337
-
338
- Routes have two separate controls. `isEnabled` controls runtime registration: disabled routes return `404`. Use `enable_route` and `disable_route` for this lifecycle. `publicMethods` controls anonymous access for enabled routes; use `public_route_methods` and `private_route_methods` for that access boundary.
339
-
340
- Use `reorder_menus` for menu order or parent changes. It calls the Enfyra 2.2.6 `/admin/menu/reorder` operation route so hierarchy validation and menu cache invalidation are handled by the server instead of PATCHing individual `enfyra_menu` records.
341
-
342
- Admin app page paths and API paths are different surfaces. A page extension path such as `/cloud/projects/:id` is a UI route unless an enabled Enfyra API route with that exact path exists. Use `test_rest_endpoint` only for actual API routes under `ENFYRA_API_URL`; verify page extensions through the app URL/browser or extension/menu metadata.
343
-
344
- For authenticated route access, use `audit_route_access` before changing permissions and `ensure_route_access` to grant access by route path plus role/user. For production script edits, use `trace_metadata_usage`, `get_script_source`, and `patch_script_source` so changes are targeted, hash-checked, and validated.
120
+ ## Development
345
121
 
346
- ## Security
122
+ This repository uses Yarn 4 and TypeScript:
347
123
 
348
- Treat permission and security as the first step for every change: decide public/private methods, authenticated route access, owner/tenant scope, and field exposure before creating handlers, flows, extensions, or UI.
124
+ ```bash
125
+ yarn typecheck
126
+ yarn test
127
+ ```
349
128
 
350
- API calls use exchanged JWTs and Enfyra permissions are still enforced server-side. Keep `ENFYRA_API_TOKEN` out of committed config unless the project intentionally uses environment interpolation or another secret-management path.
129
+ Use [AGENTS.md](./AGENTS.md) for maintainer architecture, tool contracts, verification rules, and backend sync points. LLM runtime guidance is served by the MCP instructions, tool descriptions, workflow discovery, required-knowledge tools, builders, and examples—not by this README.
@@ -0,0 +1,18 @@
1
+ export declare function buildDynamicRepositoryUsage(input: {
2
+ access: 'secure_main' | 'secure_explicit' | 'trusted_explicit';
3
+ operation: 'list' | 'find_one' | 'create' | 'update' | 'delete';
4
+ tableName?: string;
5
+ fields?: string[];
6
+ idField?: string;
7
+ idSource?: 'params' | 'body';
8
+ }): {
9
+ access: "secure_main" | "secure_explicit" | "trusted_explicit";
10
+ operation: "delete" | "create" | "update" | "list" | "find_one";
11
+ tableName: string;
12
+ repository: string;
13
+ fieldPermissionsEnforced: boolean;
14
+ securityBoundary: string;
15
+ code: string;
16
+ next: string;
17
+ };
18
+ export declare function registerDynamicRepositoryBuilder(server: any): void;
@@ -0,0 +1,105 @@
1
+ import { z } from 'zod';
2
+ import { jsonContent } from './response-format.js';
3
+ const IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
4
+ function requireIdentifier(value, label) {
5
+ if (!value || !IDENTIFIER_PATTERN.test(value)) {
6
+ throw new Error(`${label} must be a valid Enfyra identifier using letters, digits, and underscores.`);
7
+ }
8
+ return value;
9
+ }
10
+ function fieldList(fields) {
11
+ const values = fields?.length ? fields : ['id'];
12
+ for (const field of values)
13
+ requireIdentifier(field, 'Each field');
14
+ return `[${values.map((field) => JSON.stringify(field)).join(', ')}]`;
15
+ }
16
+ export function buildDynamicRepositoryUsage(input) {
17
+ const tableName = input.access === 'secure_main'
18
+ ? input.tableName || null
19
+ : requireIdentifier(input.tableName, 'tableName');
20
+ const repository = input.access === 'secure_main'
21
+ ? '@REPOS.main'
22
+ : input.access === 'secure_explicit'
23
+ ? `#secure.${tableName}`
24
+ : `#${tableName}`;
25
+ const fields = fieldList(input.fields);
26
+ const idField = requireIdentifier(input.idField || 'id', 'idField');
27
+ const idExpression = input.idSource === 'body' ? '@BODY.id' : '@PARAMS.id';
28
+ let code;
29
+ if (input.operation === 'list') {
30
+ code = `const result = await ${repository}.find({
31
+ fields: @QUERY.fields?.length ? @QUERY.fields : ${fields},
32
+ filter: @QUERY.filter || {},
33
+ deep: @QUERY.deep,
34
+ sort: @QUERY.sort,
35
+ page: @QUERY.page,
36
+ limit: Math.min(Number(@QUERY.limit) || 50, 100),
37
+ meta: @QUERY.meta,
38
+ aggregate: @QUERY.aggregate,
39
+ debugMode: @QUERY.debugMode
40
+ })
41
+
42
+ return result`;
43
+ }
44
+ else if (input.operation === 'find_one') {
45
+ code = `const result = await ${repository}.find({
46
+ filter: { ${idField}: { _eq: ${idExpression} } },
47
+ fields: ${fields},
48
+ limit: 1
49
+ })
50
+
51
+ const record = result.data?.[0] ?? null
52
+ if (!record) @THROW404("Record not found", { ${idField}: ${idExpression} })
53
+ return record`;
54
+ }
55
+ else if (input.operation === 'create') {
56
+ code = `const result = await ${repository}.create({
57
+ data: @BODY,
58
+ fields: ${fields}
59
+ })
60
+
61
+ const record = result.data?.[0] ?? null
62
+ return record`;
63
+ }
64
+ else if (input.operation === 'update') {
65
+ code = `const result = await ${repository}.update({
66
+ id: ${idExpression},
67
+ data: @BODY,
68
+ fields: ${fields}
69
+ })
70
+
71
+ const record = result.data?.[0] ?? null
72
+ return record`;
73
+ }
74
+ else {
75
+ code = `await ${repository}.delete({ id: ${idExpression} })
76
+ return { ok: true, id: ${idExpression} }`;
77
+ }
78
+ const fieldPermissionsEnforced = input.access !== 'trusted_explicit';
79
+ return {
80
+ access: input.access,
81
+ operation: input.operation,
82
+ tableName,
83
+ repository,
84
+ fieldPermissionsEnforced,
85
+ securityBoundary: fieldPermissionsEnforced
86
+ ? 'Field permissions are enforced by the selected secure repository. Owner, tenant, membership, and route authorization remain separate checks.'
87
+ : 'This trusted repository bypasses field permissions. Use it only for intentional internal work, request exact fields, enforce authorization explicitly, and never return raw trusted rows.',
88
+ code,
89
+ next: 'Adapt only live field, filter, owner/tenant, and domain error details; keep the repository access class, await, result.data shape, and bounded query contract.',
90
+ };
91
+ }
92
+ export function registerDynamicRepositoryBuilder(server) {
93
+ server.tool('build_dynamic_repository_usage', [
94
+ 'Generate validated Enfyra dynamic repository code for list, find-one, create, update, or delete.',
95
+ 'Use secure_main for a canonical route main table, secure_explicit for user-facing explicit-table access, and trusted_explicit only for intentional internal field-permission bypass.',
96
+ ].join(' '), {
97
+ access: z.enum(['secure_main', 'secure_explicit', 'trusted_explicit']).describe('Repository security class. Prefer secure_main or secure_explicit for user-facing code.'),
98
+ operation: z.enum(['list', 'find_one', 'create', 'update', 'delete']).describe('Repository operation pattern to generate.'),
99
+ tableName: z.string().optional().describe('Required for explicit access. Omit for secure_main when the route main table is already known.'),
100
+ fields: z.array(z.string()).optional().describe('Exact metadata-backed fields to select or return. Defaults to id.'),
101
+ idField: z.string().optional().default('id').describe('Primary key field used by find_one. Defaults to id; use _id for Mongo metadata when applicable.'),
102
+ idSource: z.enum(['params', 'body']).optional().default('params').describe('Read record id from @PARAMS.id or @BODY.id.'),
103
+ }, async (input) => jsonContent(buildDynamicRepositoryUsage(input)));
104
+ }
105
+ //# sourceMappingURL=dynamic-repository-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-repository-builder.js","sourceRoot":"","sources":["../../src/lib/dynamic-repository-builder.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,15 @@ export declare const EXAMPLE_CATEGORIES: {
36
36
  notes: string[];
37
37
  }[];
38
38
  };
39
+ graphql: {
40
+ title: string;
41
+ useWhen: string;
42
+ examples: {
43
+ name: string;
44
+ code: string;
45
+ notes: string[];
46
+ }[];
47
+ };
39
48
  'handlers-hooks': {
40
49
  title: string;
41
50
  useWhen: string;