@enfyra/mcp-server 0.1.58 → 0.1.60
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 +46 -267
- package/dist/lib/dynamic-repository-builder.d.ts +18 -0
- package/dist/lib/dynamic-repository-builder.js +135 -0
- package/dist/lib/dynamic-repository-builder.js.map +1 -0
- package/dist/lib/mcp-examples.d.ts +9 -0
- package/dist/lib/mcp-examples.js +86 -19
- package/dist/lib/mcp-examples.js.map +1 -1
- package/dist/lib/mcp-instructions.js +1 -0
- package/dist/lib/mcp-instructions.js.map +1 -1
- package/dist/lib/mutation-guards.js +3 -10
- package/dist/lib/mutation-guards.js.map +1 -1
- package/dist/lib/platform-operation-tools.d.ts +7 -0
- package/dist/lib/platform-operation-tools.js +112 -13
- package/dist/lib/platform-operation-tools.js.map +1 -1
- package/dist/lib/required-knowledge.d.ts +1 -1
- package/dist/lib/required-knowledge.js +18 -13
- package/dist/lib/required-knowledge.js.map +1 -1
- package/dist/lib/runtime-zone-tools.js +23 -9
- package/dist/lib/runtime-zone-tools.js.map +1 -1
- package/dist/lib/tool-routing.d.ts +64 -4
- package/dist/lib/tool-routing.js +139 -17
- package/dist/lib/tool-routing.js.map +1 -1
- package/dist/lib/toolset-filter.js +9 -0
- package/dist/lib/toolset-filter.js.map +1 -1
- package/dist/mcp-server-entry.js +72 -18
- package/dist/mcp-server-entry.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,171 +1,48 @@
|
|
|
1
1
|
# Enfyra MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
## Install and Configure
|
|
6
6
|
|
|
7
|
-
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 --
|
|
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
|
-
|
|
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 --
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
Official reference: [Antigravity MCP](https://antigravity.google/docs/mcp).
|
|
62
|
+
Codex project configuration uses TOML:
|
|
188
63
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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` |
|
|
228
|
-
| `--reconfig` |
|
|
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
|
|
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` |
|
|
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
|
-
|
|
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
|
-
|
|
106
|
+
## Verify the Connection
|
|
303
107
|
|
|
304
|
-
|
|
108
|
+
Open a new session in the configured MCP client and ask it to:
|
|
305
109
|
|
|
306
|
-
|
|
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
|
-
|
|
114
|
+
Then try a read-only request:
|
|
322
115
|
|
|
323
116
|
```text
|
|
324
|
-
|
|
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
|
-
|
|
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
|
-
|
|
122
|
+
This repository uses Yarn 4 and TypeScript:
|
|
347
123
|
|
|
348
|
-
|
|
124
|
+
```bash
|
|
125
|
+
yarn typecheck
|
|
126
|
+
yarn test
|
|
127
|
+
```
|
|
349
128
|
|
|
350
|
-
|
|
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,135 @@
|
|
|
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 selectedFields = input.access === 'trusted_explicit'
|
|
27
|
+
? fields
|
|
28
|
+
: 'requestedFields?.length ? requestedFields : ' + fields;
|
|
29
|
+
const outputExpansion = input.access === 'trusted_explicit'
|
|
30
|
+
? {
|
|
31
|
+
deep: 'undefined',
|
|
32
|
+
meta: 'undefined',
|
|
33
|
+
aggregate: 'undefined',
|
|
34
|
+
debugMode: 'undefined',
|
|
35
|
+
}
|
|
36
|
+
: {
|
|
37
|
+
deep: '@QUERY.deep',
|
|
38
|
+
meta: '@QUERY.meta',
|
|
39
|
+
aggregate: '@QUERY.aggregate',
|
|
40
|
+
debugMode: '@QUERY.debugMode',
|
|
41
|
+
};
|
|
42
|
+
const idField = requireIdentifier(input.idField || 'id', 'idField');
|
|
43
|
+
const idExpression = input.idSource === 'body' ? '@BODY.id' : '@PARAMS.id';
|
|
44
|
+
let code;
|
|
45
|
+
if (input.operation === 'list') {
|
|
46
|
+
const fieldNormalization = input.access === 'trusted_explicit'
|
|
47
|
+
? ''
|
|
48
|
+
: `const requestedFields = (() => {
|
|
49
|
+
const rawFields = @QUERY.fields
|
|
50
|
+
if (Array.isArray(rawFields)) return rawFields
|
|
51
|
+
if (typeof rawFields !== 'string') return undefined
|
|
52
|
+
try {
|
|
53
|
+
const parsedFields = JSON.parse(rawFields)
|
|
54
|
+
if (Array.isArray(parsedFields)) return parsedFields
|
|
55
|
+
} catch {}
|
|
56
|
+
return rawFields.split(',').map((field) => field.trim()).filter(Boolean)
|
|
57
|
+
})()
|
|
58
|
+
|
|
59
|
+
`;
|
|
60
|
+
code = `${fieldNormalization}const result = await ${repository}.find({
|
|
61
|
+
fields: ${selectedFields},
|
|
62
|
+
filter: @QUERY.filter || {},
|
|
63
|
+
deep: ${outputExpansion.deep},
|
|
64
|
+
sort: @QUERY.sort,
|
|
65
|
+
page: @QUERY.page,
|
|
66
|
+
limit: Math.min(Number(@QUERY.limit) || 50, 100),
|
|
67
|
+
meta: ${outputExpansion.meta},
|
|
68
|
+
aggregate: ${outputExpansion.aggregate},
|
|
69
|
+
debugMode: ${outputExpansion.debugMode}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
return result`;
|
|
73
|
+
}
|
|
74
|
+
else if (input.operation === 'find_one') {
|
|
75
|
+
code = `const result = await ${repository}.find({
|
|
76
|
+
filter: { ${idField}: { _eq: ${idExpression} } },
|
|
77
|
+
fields: ${fields},
|
|
78
|
+
limit: 1
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const record = result.data?.[0] ?? null
|
|
82
|
+
if (!record) @THROW404("Record not found", { ${idField}: ${idExpression} })
|
|
83
|
+
return record`;
|
|
84
|
+
}
|
|
85
|
+
else if (input.operation === 'create') {
|
|
86
|
+
code = `const result = await ${repository}.create({
|
|
87
|
+
data: @BODY,
|
|
88
|
+
fields: ${fields}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const record = result.data?.[0] ?? null
|
|
92
|
+
return record`;
|
|
93
|
+
}
|
|
94
|
+
else if (input.operation === 'update') {
|
|
95
|
+
code = `const result = await ${repository}.update({
|
|
96
|
+
id: ${idExpression},
|
|
97
|
+
data: @BODY,
|
|
98
|
+
fields: ${fields}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const record = result.data?.[0] ?? null
|
|
102
|
+
return record`;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
code = `await ${repository}.delete({ id: ${idExpression} })
|
|
106
|
+
return { ok: true, id: ${idExpression} }`;
|
|
107
|
+
}
|
|
108
|
+
const fieldPermissionsEnforced = input.access !== 'trusted_explicit';
|
|
109
|
+
return {
|
|
110
|
+
access: input.access,
|
|
111
|
+
operation: input.operation,
|
|
112
|
+
tableName,
|
|
113
|
+
repository,
|
|
114
|
+
fieldPermissionsEnforced,
|
|
115
|
+
securityBoundary: fieldPermissionsEnforced
|
|
116
|
+
? 'Field permissions are enforced by the selected secure repository. Owner, tenant, membership, and route authorization remain separate checks.'
|
|
117
|
+
: '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.',
|
|
118
|
+
code,
|
|
119
|
+
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.',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function registerDynamicRepositoryBuilder(server) {
|
|
123
|
+
server.tool('build_dynamic_repository_usage', [
|
|
124
|
+
'Generate validated Enfyra dynamic repository code for list, find-one, create, update, or delete.',
|
|
125
|
+
'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.',
|
|
126
|
+
].join(' '), {
|
|
127
|
+
access: z.enum(['secure_main', 'secure_explicit', 'trusted_explicit']).describe('Repository security class. Prefer secure_main or secure_explicit for user-facing code.'),
|
|
128
|
+
operation: z.enum(['list', 'find_one', 'create', 'update', 'delete']).describe('Repository operation pattern to generate.'),
|
|
129
|
+
tableName: z.string().optional().describe('Required for explicit access. Omit for secure_main when the route main table is already known.'),
|
|
130
|
+
fields: z.array(z.string()).optional().describe('Exact metadata-backed fields to select or return. Defaults to id.'),
|
|
131
|
+
idField: z.string().optional().default('id').describe('Primary key field used by find_one. Defaults to id; use _id for Mongo metadata when applicable.'),
|
|
132
|
+
idSource: z.enum(['params', 'body']).optional().default('params').describe('Read record id from @PARAMS.id or @BODY.id.'),
|
|
133
|
+
}, async (input) => jsonContent(buildDynamicRepositoryUsage(input)));
|
|
134
|
+
}
|
|
135
|
+
//# 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":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,MAAM,kBAAkB,GAAG,0BAA0B,CAAC;AAEtD,SAAS,iBAAiB,CAAC,KAAyB,EAAE,KAAa;IACjE,IAAI,CAAC,KAAK,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,4EAA4E,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,SAAS,CAAC,MAAiB;IAClC,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAChD,KAAK,MAAM,KAAK,IAAI,MAAM;QAAE,iBAAiB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IACnE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,KAO3C;IACC,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,KAAK,aAAa;QAC9C,CAAC,CAAC,KAAK,CAAC,SAAS,IAAI,IAAI;QACzB,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,KAAK,aAAa;QAC/C,CAAC,CAAC,aAAa;QACf,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,iBAAiB;YAClC,CAAC,CAAC,WAAW,SAAS,EAAE;YACxB,CAAC,CAAC,IAAI,SAAS,EAAE,CAAC;IACtB,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,KAAK,kBAAkB;QACxD,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,8CAA8C,GAAG,MAAM,CAAC;IAC5D,MAAM,eAAe,GAAG,KAAK,CAAC,MAAM,KAAK,kBAAkB;QACzD,CAAC,CAAC;YACA,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,WAAW;YACjB,SAAS,EAAE,WAAW;YACtB,SAAS,EAAE,WAAW;SACvB;QACD,CAAC,CAAC;YACA,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,aAAa;YACnB,SAAS,EAAE,kBAAkB;YAC7B,SAAS,EAAE,kBAAkB;SAC9B,CAAC;IACJ,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,IAAI,IAAI,EAAE,SAAS,CAAC,CAAC;IACpE,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC;IAC3E,IAAI,IAAY,CAAC;IAEjB,IAAI,KAAK,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;QAC/B,MAAM,kBAAkB,GAAG,KAAK,CAAC,MAAM,KAAK,kBAAkB;YAC5D,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;;;;;;;;;;;CAWP,CAAC;QACE,IAAI,GAAG,GAAG,kBAAkB,wBAAwB,UAAU;YACtD,cAAc;;UAEhB,eAAe,CAAC,IAAI;;;;UAIpB,eAAe,CAAC,IAAI;eACf,eAAe,CAAC,SAAS;eACzB,eAAe,CAAC,SAAS;;;cAG1B,CAAC;IACb,CAAC;SAAM,IAAI,KAAK,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QAC1C,IAAI,GAAG,wBAAwB,UAAU;cAC/B,OAAO,YAAY,YAAY;YACjC,MAAM;;;;;+CAK6B,OAAO,KAAK,YAAY;cACzD,CAAC;IACb,CAAC;SAAM,IAAI,KAAK,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QACxC,IAAI,GAAG,wBAAwB,UAAU;;YAEjC,MAAM;;;;cAIJ,CAAC;IACb,CAAC;SAAM,IAAI,KAAK,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QACxC,IAAI,GAAG,wBAAwB,UAAU;QACrC,YAAY;;YAER,MAAM;;;;cAIJ,CAAC;IACb,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,SAAS,UAAU,iBAAiB,YAAY;yBAClC,YAAY,IAAI,CAAC;IACxC,CAAC;IAED,MAAM,wBAAwB,GAAG,KAAK,CAAC,MAAM,KAAK,kBAAkB,CAAC;IACrE,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS;QACT,UAAU;QACV,wBAAwB;QACxB,gBAAgB,EAAE,wBAAwB;YACxC,CAAC,CAAC,8IAA8I;YAChJ,CAAC,CAAC,2LAA2L;QAC/L,IAAI;QACJ,IAAI,EAAE,gKAAgK;KACvK,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gCAAgC,CAAC,MAAW;IAC1D,MAAM,CAAC,IAAI,CACT,gCAAgC,EAChC;QACE,kGAAkG;QAClG,sLAAsL;KACvL,CAAC,IAAI,CAAC,GAAG,CAAC,EACX;QACE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,CAAC,CAAC,QAAQ,CAAC,wFAAwF,CAAC;QACzK,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,2CAA2C,CAAC;QAC3H,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gGAAgG,CAAC;QAC3I,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mEAAmE,CAAC;QACpH,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,iGAAiG,CAAC;QACxJ,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,6CAA6C,CAAC;KAC1H,EACD,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,2BAA2B,CAAC,KAAK,CAAC,CAAC,CACjE,CAAC;AACJ,CAAC"}
|
|
@@ -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;
|