@enfyra/mcp-server 0.0.97 → 0.0.99

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
@@ -183,10 +183,13 @@ The MCP server includes safety guards for LLM callers:
183
183
 
184
184
  - Generic record mutations validate fields against live metadata.
185
185
  - Script-backed records validate `sourceCode` through `/admin/script/validate` before saving.
186
+ - `validate_dynamic_script` checks handler, hook, flow, websocket, GraphQL, and bootstrap script source without saving.
187
+ - `validate_extension_code` checks Enfyra admin extension code through `/enfyra_extension/preview` without saving.
186
188
  - `compiledCode` is generated from `sourceCode` and may differ textually because macros are expanded; the MCP server never accepts hand-written `compiledCode`.
187
189
  - Relation tools reject physical FK/junction names.
188
190
  - Generated code should use relation property names such as `conversation`, `sender`, and `member` instead of physical FK fields such as `conversationId`, `senderId`, or `memberId`.
189
191
  - Custom route tools reject `mainTableId` unless the route is the canonical table route.
192
+ - Platform operation tools such as `create_api_endpoint`, `publish_route_methods`, `add_route_methods`, `set_table_graphql`, `ensure_guard`, `ensure_field_permission`, `ensure_column_rule`, `ensure_websocket_event`, `ensure_script_flow_step`, `ensure_menu`, `ensure_page_extension`, `ensure_global_extension`, and `ensure_widget_extension` resolve metadata ids and validate code before saving.
190
193
  - Schema changes are serialized.
191
194
  - Destructive deletes return a preview before requiring `confirm=true`.
192
195
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfyra/mcp-server",
3
- "version": "0.0.97",
3
+ "version": "0.0.99",
4
4
  "description": "MCP server for Enfyra - manage Enfyra instances from MCP-compatible coding tools",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -996,22 +996,19 @@ ensure_route_access({
996
996
  },
997
997
  {
998
998
  name: 'Publish read-only route',
999
- code: `update_record({
1000
- tableName: "enfyra_route",
1001
- id: "<route_id>",
1002
- data: {
1003
- publicMethods: [{ id: "<GET_method_id_from_list_methods>" }]
1004
- }
999
+ code: `publish_route_methods({
1000
+ path: "/articles",
1001
+ methods: ["GET"]
1005
1002
  })`,
1006
1003
  notes: [
1007
- 'Method ids are instance data. Use list_methods or inspect_route output to resolve the GET method id first.',
1004
+ 'Use publish_route_methods instead of raw enfyra_route updates; the tool resolves method ids and validates that GET is available.',
1008
1005
  'publicMethods controls anonymous route access. Route permissions are not for public access.',
1009
1006
  'Route permissions apply when the method is not public.',
1010
1007
  ],
1011
1008
  },
1012
1009
  {
1013
1010
  name: 'Rate limit anonymous requests by IP',
1014
- code: `create_guard({
1011
+ code: `ensure_guard({
1015
1012
  name: "Public signup IP rate limit",
1016
1013
  path: "/newsletter_signup",
1017
1014
  methods: ["POST"],
@@ -1042,7 +1039,7 @@ test_rest_endpoint({
1042
1039
  },
1043
1040
  {
1044
1041
  name: 'Rate limit authenticated users',
1045
- code: `create_guard({
1042
+ code: `ensure_guard({
1046
1043
  name: "Project create per-user limit",
1047
1044
  path: "/projects",
1048
1045
  methods: ["POST"],
@@ -1064,7 +1061,7 @@ test_rest_endpoint({
1064
1061
  },
1065
1062
  {
1066
1063
  name: 'Restrict an admin-only route to office IPs',
1067
- code: `create_guard({
1064
+ code: `ensure_guard({
1068
1065
  name: "Admin reports office allowlist",
1069
1066
  path: "/admin/reports",
1070
1067
  methods: ["GET", "POST"],
@@ -1086,7 +1083,7 @@ test_rest_endpoint({
1086
1083
  },
1087
1084
  {
1088
1085
  name: 'Column rule for email format',
1089
- code: `create_column_rule({
1086
+ code: `ensure_column_rule({
1090
1087
  tableName: "enfyra_user",
1091
1088
  columnName: "email",
1092
1089
  ruleType: "format",
@@ -1101,10 +1098,12 @@ test_rest_endpoint({
1101
1098
  },
1102
1099
  {
1103
1100
  name: 'Field permission condition',
1104
- code: `create_field_permission({
1101
+ code: `ensure_field_permission({
1105
1102
  tableName: "project",
1106
- fieldName: "internal_notes",
1103
+ columnName: "internal_notes",
1107
1104
  action: "read",
1105
+ effect: "allow",
1106
+ roleName: "user",
1108
1107
  condition: JSON.stringify({
1109
1108
  owner: { id: { _eq: "@USER.id" } }
1110
1109
  })
@@ -1417,7 +1416,7 @@ update_method({
1417
1416
  },
1418
1417
  {
1419
1418
  name: 'Create menu then extension',
1420
- code: `create_menu({
1419
+ code: `ensure_menu({
1421
1420
  label: "Reports",
1422
1421
  type: "Menu",
1423
1422
  path: "/reports",
@@ -1433,8 +1432,7 @@ update_method({
1433
1432
  })
1434
1433
 
1435
1434
  // Read the created menu id from the tool response, then:
1436
- create_extension({
1437
- type: "page",
1435
+ ensure_page_extension({
1438
1436
  name: "ReportsPage",
1439
1437
  description: "Reports dashboard",
1440
1438
  menuId: "<created-menu-id>",
@@ -1445,7 +1443,7 @@ create_extension({
1445
1443
  'Menu provides navigation; extension provides content.',
1446
1444
  'Use enfyra_menu.label, not title.',
1447
1445
  'Sensitive admin menus should include a permission condition at creation time.',
1448
- 'For page extensions, create the menu first and pass menuId to create_extension.',
1446
+ 'For page extensions, create the menu first with ensure_menu and pass its id to ensure_page_extension.',
1449
1447
  'Page extensions must register the app-shell PageHeader with usePageHeaderRegistry instead of rendering a custom top header.',
1450
1448
  'Use variant: "minimal" for operational pages unless a larger header is intentionally needed.',
1451
1449
  'Do not put ordinary KPI cards in PageHeader.stats; render metrics in the extension body.',
@@ -1488,8 +1486,7 @@ function openLatest() {
1488
1486
  </script>
1489
1487
  \`
1490
1488
 
1491
- create_extension({
1492
- type: "widget",
1489
+ ensure_widget_extension({
1493
1490
  name: "ReportStatusWidget",
1494
1491
  description: "Report status summary cards",
1495
1492
  code: reportStatusWidgetCode,
@@ -1497,8 +1494,7 @@ create_extension({
1497
1494
  })
1498
1495
 
1499
1496
  // Read the created widget record id, then embed it from the page extension.
1500
- create_extension({
1501
- type: "page",
1497
+ ensure_page_extension({
1502
1498
  name: "ReportsPage",
1503
1499
  menuId: "<reports-menu-id>",
1504
1500
  code: "<template><section class=\\"min-h-full w-full space-y-4\\"><Widget :id=\\"<report-status-widget-id>\\" :total=\\"totalReports\\" :rows=\\"reportRows\\" :open-details=\\"openReportDetails\\" @refresh=\\"refresh\\" /><Widget :id=\\"<report-table-widget-id>\\" :rows=\\"reportRows\\" @refresh=\\"refresh\\" /></section></template><script setup>const { registerPageHeader } = usePageHeaderRegistry(); registerPageHeader({ title: 'Reports', description: 'Operational report overview.', leadingIcon: 'lucide:bar-chart-3', gradient: 'cyan', variant: 'minimal' }); const totalReports = ref(0); const reportRows = ref([]); function refresh() {} function openReportDetails(row) { navigateTo('/data/report?filter=' + encodeURIComponent(JSON.stringify({ id: { _eq: row.id } }))) }</script>",
@@ -1574,8 +1570,7 @@ onUnmounted(() => {
1574
1570
  </script>
1575
1571
  \`
1576
1572
 
1577
- create_extension({
1578
- type: "global",
1573
+ ensure_global_extension({
1579
1574
  name: "NotificationBellGlobal",
1580
1575
  description: "Registers the app-wide notification bell in the account panel",
1581
1576
  code: notificationBellCode,
@@ -1719,7 +1714,7 @@ registerHeaderActions([
1719
1714
  {
1720
1715
  name: 'Plan an admin dashboard as multiple pages',
1721
1716
  code: `// Recommended menu shape for an operations surface:
1722
- create_menu({
1717
+ ensure_menu({
1723
1718
  type: "Dropdown Menu",
1724
1719
  label: "Operations",
1725
1720
  path: "/operations",
@@ -29,6 +29,8 @@ export function buildMcpServerInstructions(apiBaseUrl) {
29
29
  '- Inspect narrowly. Use `inspect_table`, `inspect_route`, and `inspect_feature` for the table/route/feature being changed instead of loading broad metadata.',
30
30
  '- Load examples only when needed. Before generating schemas, app connection code, OAuth, Socket.IO, handlers/hooks, flows, files, guards, permissions, or extensions, call `get_enfyra_examples` with the matching category.',
31
31
  '- For server scripts, call `discover_script_contexts` before writing or reviewing handler/hook/flow/websocket/GraphQL logic.',
32
+ '- Prefer the most specific business operation tool over raw metadata CRUD: `create_api_endpoint`; route tools such as `add_route_methods`, `publish_route_methods`, and `unpublish_route_methods`; `set_table_graphql`; `ensure_guard`; permission/rule tools; websocket tools; flow tools such as `ensure_manual_flow`, `ensure_scheduled_flow`, and fixed-type flow step tools; and extension tools such as `ensure_menu`, `ensure_page_extension`, `ensure_global_extension`, and `ensure_widget_extension`.',
33
+ '- Before saving standalone dynamic script or extension code, call `validate_dynamic_script` or `validate_extension_code` unless the chosen ensure/update tool already validates the code.',
32
34
  '- For existing script-backed records, use `trace_metadata_usage` then `get_script_source`; edit with `patch_script_source` or `update_script_source` so source is hash-checked and validated.',
33
35
  '- Validate behavior with `test_rest_endpoint`, `run_admin_test`, `test_flow_step`, or the route-specific tool before claiming a dynamic feature works.',
34
36
  '',
@@ -40,7 +42,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
40
42
  '- Dynamic repository reads use `filter`, not `where`: `@REPOS.table.find({ filter: {...} })`, `#table.find({ filter: {...} })`, and `exists(filter)`.',
41
43
  '- Use `enfyra_user` as the user table. Model record links as real relations using relation `propertyName` values, not physical FK fields like `userId`, `conversationId`, `senderId`, or `memberId` in generated DB code.',
42
44
  '- Do not call internal/no-route system tables such as `enfyra_column` or `enfyra_session` through generic CRUD. Use table/column/relation tools and route-backed tables discovered from metadata.',
43
- '- Custom API paths use `create_route` without `mainTableId`; `create_table` is only for new persisted data.',
45
+ '- Custom API paths use `create_api_endpoint` when a handler is needed. Use lower-level `create_route` without `mainTableId` only when intentionally creating a route shell; `create_table` is only for new persisted data.',
44
46
  '- For canonical table reads and RLS, preserve client-controlled query shape: do not override `@QUERY.fields`, `@QUERY.deep`, `@QUERY.sort`, `@QUERY.limit`, `@QUERY.page`, `@QUERY.meta`, `@QUERY.aggregate`, or `debugMode`. Merge only security filters into `@QUERY.filter`.',
45
47
  '- Script source is `sourceCode`; `compiledCode` is generated and may differ textually because macros expand. Do not warn about source/compiled mismatch unless validation or runtime behavior proves the compiled artifact is stale.',
46
48
  '- For intentional user/domain errors in scripts use `@THROW400`-style helpers or `$ctx.$throw[...]`, not `throw new Error(...)`.',