@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 +3 -0
- package/package.json +1 -1
- package/src/lib/mcp-examples.js +19 -24
- package/src/lib/mcp-instructions.js +3 -1
- package/src/lib/platform-operation-tools.js +1221 -0
- package/src/mcp-server-entry.mjs +2 -63
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
package/src/lib/mcp-examples.js
CHANGED
|
@@ -996,22 +996,19 @@ ensure_route_access({
|
|
|
996
996
|
},
|
|
997
997
|
{
|
|
998
998
|
name: 'Publish read-only route',
|
|
999
|
-
code: `
|
|
1000
|
-
|
|
1001
|
-
|
|
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
|
-
'
|
|
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: `
|
|
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: `
|
|
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: `
|
|
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: `
|
|
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: `
|
|
1101
|
+
code: `ensure_field_permission({
|
|
1105
1102
|
tableName: "project",
|
|
1106
|
-
|
|
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: `
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(...)`.',
|