@enfyra/mcp-server 0.1.5 → 0.1.7

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
@@ -244,14 +244,14 @@ Use `get_enfyra_examples` from the MCP tool list when asking an LLM to generate
244
244
  - files and storage
245
245
  - Enfyra admin extensions
246
246
 
247
- Use `get_enfyra_required_knowledge` before asking an LLM to save dynamic server code or Enfyra extension code. It returns short required contracts plus acknowledgement keys that code-writing tools verify before saving.
247
+ 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.
248
248
 
249
249
  ## Runtime Safety
250
250
 
251
251
  The MCP server includes safety guards for LLM callers:
252
252
 
253
253
  - Generic record mutations validate fields against live metadata.
254
- - Code-writing tools require `get_enfyra_required_knowledge` acknowledgement before saving dynamic scripts or extension code. Discovery, validation, and preview tools remain available without the acknowledgement so agents can read and plan first.
254
+ - 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.
255
255
  - Script-backed records validate `sourceCode` through `/admin/script/validate` before saving.
256
256
  - `validate_dynamic_script` checks handler, hook, flow, websocket, GraphQL, and bootstrap script source without saving.
257
257
  - `validate_extension_code` checks Enfyra admin extension code through `/enfyra_extension/preview` without saving.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfyra/mcp-server",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "MCP server for Enfyra - manage Enfyra instances from MCP-compatible coding tools",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -551,6 +551,7 @@ update_record({
551
551
  name: 'Create a chat conversation table',
552
552
  code: `create_table({
553
553
  name: "chat_conversation",
554
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
554
555
  columns: JSON.stringify([
555
556
  { name: "kind", type: "varchar", isNullable: false, defaultValue: "dm" },
556
557
  { name: "title", type: "varchar", isNullable: true },
@@ -568,6 +569,7 @@ update_record({
568
569
  name: 'Create relations directly to enfyra_user',
569
570
  code: `create_table({
570
571
  name: "chat_message",
572
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
571
573
  columns: JSON.stringify([
572
574
  { name: "text", type: "text", isNullable: false },
573
575
  { name: "persistStatus", type: "varchar", defaultValue: "persisted" }
@@ -607,6 +609,7 @@ update_record({
607
609
  code: `create_relation({
608
610
  sourceTableId: "chat_message",
609
611
  targetTableId: "chat_conversation",
612
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
610
613
  propertyName: "conversation",
611
614
  inversePropertyName: "messages",
612
615
  type: "many-to-one",
@@ -625,6 +628,7 @@ update_record({
625
628
  name: 'Add chat_conversation.lastMessage after chat_message exists',
626
629
  code: `update_table({
627
630
  tableId: "<chat_conversation_id>",
631
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
628
632
  relations: JSON.stringify([
629
633
  {
630
634
  propertyName: "createdBy",
@@ -651,6 +655,7 @@ update_record({
651
655
  name: 'Unread/read table with unique and indexes',
652
656
  code: `create_table({
653
657
  name: "chat_message_read",
658
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
654
659
  columns: JSON.stringify([
655
660
  { name: "isRead", type: "boolean", defaultValue: false },
656
661
  { name: "readAt", type: "datetime", isNullable: true }
@@ -677,6 +682,7 @@ update_record({
677
682
  code: `create_column({
678
683
  tableId: "<enfyra_user_table_id>",
679
684
  name: "emailVerifiedAt",
685
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
680
686
  type: "datetime",
681
687
  isNullable: true,
682
688
  isPublished: true,
@@ -686,6 +692,7 @@ update_record({
686
692
  create_column({
687
693
  tableId: "<enfyra_user_table_id>",
688
694
  name: "emailVerificationStatus",
695
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
689
696
  type: "varchar",
690
697
  isNullable: false,
691
698
  defaultValue: "pending",
@@ -696,6 +703,7 @@ create_column({
696
703
  create_column({
697
704
  tableId: "<integration_secret_table_id>",
698
705
  name: "value",
706
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
699
707
  type: "text",
700
708
  isNullable: false,
701
709
  isPublished: false,
@@ -722,6 +730,7 @@ create_column({
722
730
  create_column({
723
731
  tableId: "<table_id>",
724
732
  name: "api_secret",
733
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
725
734
  type: "text",
726
735
  isPublished: false,
727
736
  isEncrypted: true
@@ -923,6 +932,7 @@ GET /enfyra/integrations?filter={"api_token":{"_eq":"plaintext-token"}}
923
932
  create_column({
924
933
  tableId: "<integrations_table_id>",
925
934
  name: "api_token_lookup_sha256",
935
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
926
936
  type: "varchar",
927
937
  isNullable: false,
928
938
  isPublished: false
@@ -958,6 +968,7 @@ const found = await #integrations.find({
958
968
  routeId: "<route_id>",
959
969
  method: "POST",
960
970
  scriptLanguage: "javascript",
971
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
961
972
  knowledgeAckKey: "<dynamicCodeAckKey from get_enfyra_required_knowledge>",
962
973
  sourceCode: \`const email = @BODY.email
963
974
  if (!email) @THROW400("Email is required")
@@ -966,7 +977,7 @@ return { ok: true, email }\`
966
977
  })`,
967
978
  notes: [
968
979
  'Use sourceCode, not logic. The server generates compiledCode.',
969
- 'Call get_enfyra_required_knowledge before saving dynamic code and pass dynamicCodeAckKey as knowledgeAckKey.',
980
+ 'Call get_enfyra_required_knowledge before saving dynamic code, pass globalRulesAckKey as globalRulesAckKey, and pass dynamicCodeAckKey as knowledgeAckKey.',
970
981
  'Use method for one handler, or methods only when the same sourceCode should be saved for multiple methods.',
971
982
  'Do not pass name to enfyra_route_handler; one handler is identified by route + method.',
972
983
  ],
@@ -1020,6 +1031,7 @@ const scope = {
1020
1031
  name: 'Encrypted field table definition',
1021
1032
  code: `create_table({
1022
1033
  name: "integrations",
1034
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
1023
1035
  columns: JSON.stringify([
1024
1036
  { name: "name", type: "varchar", isNullable: false },
1025
1037
  {
@@ -1047,6 +1059,7 @@ const scope = {
1047
1059
  name: "strip_email_verification_fields",
1048
1060
  methods: ["PATCH"],
1049
1061
  priority: -10,
1062
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
1050
1063
  knowledgeAckKey: "<dynamicCodeAckKey from get_enfyra_required_knowledge>",
1051
1064
  code: \`delete @BODY.emailVerifiedAt
1052
1065
  delete @BODY.emailVerificationStatus
@@ -1065,6 +1078,7 @@ delete @BODY.emailVerificationSentAt\`
1065
1078
  name: "shape_display_title",
1066
1079
  methods: ["GET"],
1067
1080
  priority: 0,
1081
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
1068
1082
  knowledgeAckKey: "<dynamicCodeAckKey from get_enfyra_required_knowledge>",
1069
1083
  code: \`if (@ERROR) {
1070
1084
  @LOGS("Request failed", @ERROR.message)
@@ -1558,6 +1572,7 @@ update_method({
1558
1572
  icon: "lucide:bar-chart-3",
1559
1573
  order: 20,
1560
1574
  isEnabled: true,
1575
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
1561
1576
  permission: JSON.stringify({
1562
1577
  or: [
1563
1578
  { route: "/reports", methods: ["GET"] },
@@ -1573,6 +1588,7 @@ ensure_page_extension({
1573
1588
  menuId: "<created-menu-id>",
1574
1589
  code: "<template><section class=\\"min-h-full w-full space-y-4\\"><div class=\\"grid gap-4 md:grid-cols-2 xl:grid-cols-3\\"><article class=\\"eapp-surface-card p-4\\"><div class=\\"flex items-start justify-between gap-3\\"><div><p class=\\"text-sm font-medium eapp-text-tertiary\\">Total</p><p class=\\"mt-2 text-2xl font-semibold eapp-text-primary\\">0</p></div><span class=\\"eapp-primary-soft eapp-icon-tile\\"><span class=\\"eapp-primary-text\\">◆</span></span></div><div class=\\"mt-3 h-1.5 overflow-hidden eapp-radius-pill eapp-surface-muted\\"><div class=\\"eapp-primary-solid h-full w-1/2\\"></div></div></article><article class=\\"eapp-primary-surface eapp-radius-panel border p-4\\"><p class=\\"text-sm font-semibold eapp-text-primary\\">Selected report</p><p class=\\"mt-1 text-sm eapp-text-tertiary\\">Only selected/current identity blocks use identity surface.</p></article></div></section></template><script setup>const { registerPageHeader } = usePageHeaderRegistry(); const { register: registerHeaderActions } = useHeaderActionRegistry(); registerPageHeader({ title: 'Reports', description: 'Operational report overview.', leadingIcon: 'lucide:bar-chart-3', gradient: 'none', variant: 'minimal' }); registerHeaderActions([{ id: 'refresh-reports', label: 'Refresh', icon: 'lucide:refresh-cw', color: 'neutral', variant: 'outline', onClick: () => {}, order: 80 }])</script>",
1575
1590
  isEnabled: true,
1591
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
1576
1592
  extensionKnowledgeAckKey: "<extensionAckKey from get_enfyra_required_knowledge>"
1577
1593
  })`,
1578
1594
  notes: [
@@ -1581,26 +1597,14 @@ ensure_page_extension({
1581
1597
  'Use enfyra_menu.label, not title.',
1582
1598
  'Sensitive admin menus should include a permission condition at creation time.',
1583
1599
  'For page extensions, create the menu first with ensure_menu and pass its id to ensure_page_extension.',
1584
- 'Call get_extension_theme_contract before writing or reviewing page/widget/global extension UI.',
1585
- 'Call get_enfyra_required_knowledge before saving extension code and pass extensionAckKey as extensionKnowledgeAckKey.',
1600
+ 'Call get_extension_theme_contract before writing or reviewing page/widget/global extension UI; that tool is the authority for theme, color, layout, modal, drawer, and shell registry details.',
1601
+ 'Call get_enfyra_required_knowledge before saving extension code, pass globalRulesAckKey as globalRulesAckKey, and pass extensionAckKey as extensionKnowledgeAckKey.',
1586
1602
  'Page extensions must register the app-shell PageHeader with usePageHeaderRegistry instead of rendering a custom top header.',
1587
- 'Use variant: "minimal" for operational pages unless a larger header is intentionally needed.',
1588
- 'Do not put ordinary KPI cards in PageHeader.stats; render metrics in the extension body.',
1589
1603
  'Put page-level actions in useHeaderActionRegistry or useSubHeaderActionRegistry, destructure register first, then call it with one action or an array.',
1590
- 'Page extensions should be full-bleed by default and responsive from the first version.',
1591
- 'The extension root is already inside Enfyra admin page main; do not add root-level page padding.',
1592
- 'Use eApp theme class tokens for panels, rows, badges, borders, controls, radius, and text. Generated extension templates should prefer eapp-surface-*, eapp-text-*, eapp-radius-*, eapp-divide-y, and eapp-primary-* over raw CSS variable utilities.',
1593
- 'Treat primary color as runtime-configurable by the app color picker. For Nuxt UI components, choose color="primary" by semantic intent. For custom extension UI, decide whether each element is neutral surface, runtime-primary identity, or status: regular panels/KPI cards/list rows use eapp-surface-card/eapp-surface-muted/eapp-surface-hover/eapp-divide-y and eapp-text-* classes, while selected/current identity blocks, primary tiles, progress fills, primary icons, and primary CTA fills use eapp-primary-surface, eapp-primary-soft, eapp-primary-subtle, eapp-primary-solid, or eapp-primary-text so the color picker controls them. eapp-primary-surface supplies selected identity color but does not replace card chrome; keep border/radius classes on selected blocks.',
1594
- 'Decision cases: normal decorative accents, feature icons, non-state tiles, active tabs, progress fills, selected segments, and primary actions use runtime primary/identity classes; true semantic states use their matching status colors such as error, warning, success, or info; large ordinary surfaces stay neutral and carry only small badges/icons for status; selected/current entity blocks may use eapp-primary-surface.',
1595
- 'Pattern examples: KPI/metric cards should be eapp-surface-card with a small icon tile using eapp-primary-soft eapp-icon-tile; selected/current entity cards may use eapp-primary-surface; progress bars use eapp-surface-muted tracks plus eapp-primary-solid fills; list rows use eapp-surface-card/eapp-divide-y/eapp-surface-hover and only small chips inside; primary scope actions use UButton color="primary" variant="solid"; secondary actions use neutral variants.',
1596
- 'Status colors belong only in UBadge/UAlert semantic colors or eapp-status-*-soft/text/border classes for badges, small icons, or short status text. Do not read --badge-* variables directly in extension templates. Do not color large panels, alert-like success blocks, KPI cards, list containers, or attention/reconciliation blocks green/yellow/red because of state; keep the block neutral and place the status badge/icon inside.',
1597
- 'Use PageHeader gradient: "none" for generated operational pages unless the user explicitly asks for a decorative page accent; do not hardcode cyan/violet/purple/blue/green gradients.',
1598
- 'For general card grids inside the shell, use md:grid-cols-2 xl:grid-cols-3 instead of lg:grid-cols-3 because the desktop sidebar leaves tablet-width content at 1024px.',
1599
- 'Do not use Nuxt UI neutral semantic classes such as bg-default, text-muted, text-dimmed, border-default, or divide-default inside extension code; use eApp class tokens instead. Do not write text-[var(...)], bg-[var(...)], or border-[var(...)] in generated extension templates unless no class token exists for that exact primitive.',
1600
- 'Do not pass ui.content: "eapp-surface-card" to UModal/CommonModal; modal content uses the app modal surface and caller content classes should only append z-index or width.',
1601
- 'CommonModal and CommonDrawer own action-only footers through cancelAction, primaryAction, dangerAction, leadingActions, and footerHint. Pass action intent through props instead of styling footer buttons manually; cancelAction defaults to neutral outline, dangerAction is for irreversible destructive work, and Keep editing should use tone: "primary" in discard dialogs.',
1602
- 'Use UTabs for page sections instead of custom tab bars so the app-level active/inactive indicators, spacing, focus rings, and theme contrast stay consistent.',
1603
- 'Do not inject global CSS, create theme guards, redefine the app palette, or solve one extension by overriding the whole app shell.',
1604
+ 'Page extensions should be full-bleed and responsive from the first version; the extension root is already inside the Enfyra admin page main.',
1605
+ 'Render ordinary metrics and lists in the body, not PageHeader.stats, unless the user explicitly wants a compact overview header.',
1606
+ 'Use app theme tokens and Nuxt UI semantic colors by intent; do not hard-code concrete palettes or redefine the app palette inside extension code.',
1607
+ 'Use app-owned primitives such as UTabs, CommonModal, CommonDrawer, Widget, useMenuNotificationRegistry, and useAccountPanelRegistry when the workflow matches them.',
1604
1608
  'Keep list selection local and fetch detail rows only; do not refetch the whole list after a row click unless the list data changed.',
1605
1609
  'Page extension paths are admin app UI routes. Do not verify them with test_rest_endpoint against ENFYRA_API_URL unless inspect_route shows an API route with the same path.',
1606
1610
  'After saving, open Enfyra admin tabs should update through the server/Enfyra admin UI realtime reload contract; do not tell the user to refresh unless that contract is proven broken.',
@@ -1648,6 +1652,7 @@ ensure_widget_extension({
1648
1652
  description: "Report status summary cards",
1649
1653
  code: reportStatusWidgetCode,
1650
1654
  isEnabled: true,
1655
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
1651
1656
  extensionKnowledgeAckKey: "<extensionAckKey from get_enfyra_required_knowledge>"
1652
1657
  })
1653
1658
 
@@ -1657,6 +1662,7 @@ ensure_page_extension({
1657
1662
  menuId: "<reports-menu-id>",
1658
1663
  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: 'none', 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>",
1659
1664
  isEnabled: true,
1665
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
1660
1666
  extensionKnowledgeAckKey: "<extensionAckKey from get_enfyra_required_knowledge>"
1661
1667
  })`,
1662
1668
  notes: [
@@ -1751,6 +1757,7 @@ ensure_global_extension({
1751
1757
  description: "Registers the app-wide notification bell in the account panel",
1752
1758
  code: notificationBellCode,
1753
1759
  isEnabled: true,
1760
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
1754
1761
  extensionKnowledgeAckKey: "<extensionAckKey from get_enfyra_required_knowledge>"
1755
1762
  })`,
1756
1763
  notes: [
@@ -1909,6 +1916,7 @@ ensure_global_extension({
1909
1916
  description: "Routes notification signals into account-panel and sidebar menu attention markers without polling destination lists",
1910
1917
  code: signalBridgeCode,
1911
1918
  isEnabled: true,
1919
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
1912
1920
  extensionKnowledgeAckKey: "<extensionAckKey from get_enfyra_required_knowledge>"
1913
1921
  })`,
1914
1922
  notes: [
@@ -2166,7 +2174,8 @@ console.log(ok, requiredTerms.has('terms'), loaded, label, date)
2166
2174
  name: 'Install and use an app package in an extension',
2167
2175
  code: `install_package({
2168
2176
  name: "dayjs",
2169
- type: "App"
2177
+ type: "App",
2178
+ globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>"
2170
2179
  })
2171
2180
 
2172
2181
  // Then in extension code:
@@ -29,7 +29,7 @@ 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. Use `get_enfyra_examples` by category. Before extension UI, call `get_extension_theme_contract`; call `get_theme_class_reference` for exact eapp/Nuxt UI theme classes.',
31
31
  '- For server scripts, call `discover_script_contexts` before writing or reviewing handler/hook/flow/websocket/GraphQL logic.',
32
- '- Before writing dynamic server or extension code, call `get_enfyra_required_knowledge` and pass the matching acknowledgement key into write tools.',
32
+ '- Before mutating metadata, schema, routes, permissions, menus, packages, cache state, dynamic code, or extension UI, call `get_enfyra_required_knowledge`, read the global rules, and pass `globalRulesAckKey` into write tools. Dynamic server code also requires `dynamicCodeAckKey`; extension code also requires `extensionAckKey`.',
33
33
  '- With non-root API tokens, call `get_permission_profile` before relying on admin helper tools or when debugging 403s. MCP admin helpers require ordinary route permissions for static admin routes such as `/admin/script/validate`, `/admin/test/run`, `/admin/flow/trigger/:id`, and `/admin/reload/*`.',
34
34
  '- Prefer the most specific business operation tool over raw metadata CRUD: `api_endpoint_workflow`, `create_api_endpoint`, `enable_route`, `add_route_methods`, `public_route_methods`, `set_table_graphql`, guard/permission/rule tools, websocket tools, flow tools, and `ensure_page_extension`/menu tools.',
35
35
  '- 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.',