@enfyra/mcp-server 0.1.5 → 0.1.6
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 +2 -2
- package/package.json +1 -1
- package/src/lib/mcp-examples.js +24 -3
- package/src/lib/mcp-instructions.js +1 -1
- package/src/lib/platform-operation-tools.js +74 -17
- package/src/lib/required-knowledge.js +63 -2
- package/src/lib/table-tools.js +23 -6
- package/src/mcp-server-entry.mjs +60 -18
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
|
|
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
|
-
-
|
|
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
package/src/lib/mcp-examples.js
CHANGED
|
@@ -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: [
|
|
@@ -1582,7 +1598,7 @@ ensure_page_extension({
|
|
|
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
1600
|
'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.',
|
|
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
1603
|
'Use variant: "minimal" for operational pages unless a larger header is intentionally needed.',
|
|
1588
1604
|
'Do not put ordinary KPI cards in PageHeader.stats; render metrics in the extension body.',
|
|
@@ -1648,6 +1664,7 @@ ensure_widget_extension({
|
|
|
1648
1664
|
description: "Report status summary cards",
|
|
1649
1665
|
code: reportStatusWidgetCode,
|
|
1650
1666
|
isEnabled: true,
|
|
1667
|
+
globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
|
|
1651
1668
|
extensionKnowledgeAckKey: "<extensionAckKey from get_enfyra_required_knowledge>"
|
|
1652
1669
|
})
|
|
1653
1670
|
|
|
@@ -1657,6 +1674,7 @@ ensure_page_extension({
|
|
|
1657
1674
|
menuId: "<reports-menu-id>",
|
|
1658
1675
|
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
1676
|
isEnabled: true,
|
|
1677
|
+
globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
|
|
1660
1678
|
extensionKnowledgeAckKey: "<extensionAckKey from get_enfyra_required_knowledge>"
|
|
1661
1679
|
})`,
|
|
1662
1680
|
notes: [
|
|
@@ -1751,6 +1769,7 @@ ensure_global_extension({
|
|
|
1751
1769
|
description: "Registers the app-wide notification bell in the account panel",
|
|
1752
1770
|
code: notificationBellCode,
|
|
1753
1771
|
isEnabled: true,
|
|
1772
|
+
globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
|
|
1754
1773
|
extensionKnowledgeAckKey: "<extensionAckKey from get_enfyra_required_knowledge>"
|
|
1755
1774
|
})`,
|
|
1756
1775
|
notes: [
|
|
@@ -1909,6 +1928,7 @@ ensure_global_extension({
|
|
|
1909
1928
|
description: "Routes notification signals into account-panel and sidebar menu attention markers without polling destination lists",
|
|
1910
1929
|
code: signalBridgeCode,
|
|
1911
1930
|
isEnabled: true,
|
|
1931
|
+
globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>",
|
|
1912
1932
|
extensionKnowledgeAckKey: "<extensionAckKey from get_enfyra_required_knowledge>"
|
|
1913
1933
|
})`,
|
|
1914
1934
|
notes: [
|
|
@@ -2166,7 +2186,8 @@ console.log(ok, requiredTerms.has('terms'), loaded, label, date)
|
|
|
2166
2186
|
name: 'Install and use an app package in an extension',
|
|
2167
2187
|
code: `install_package({
|
|
2168
2188
|
name: "dayjs",
|
|
2169
|
-
type: "App"
|
|
2189
|
+
type: "App",
|
|
2190
|
+
globalRulesAckKey: "<globalRulesAckKey from get_enfyra_required_knowledge>"
|
|
2170
2191
|
})
|
|
2171
2192
|
|
|
2172
2193
|
// 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
|
|
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.',
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
assertDynamicCodeKnowledgeAck,
|
|
7
7
|
assertDynamicCodeKnowledgeAckIf,
|
|
8
8
|
assertExtensionKnowledgeAck,
|
|
9
|
+
assertGlobalRulesAck,
|
|
9
10
|
dynamicCodeKnowledgeAckParam,
|
|
10
11
|
extensionKnowledgeAckParam,
|
|
12
|
+
globalRulesAckParam,
|
|
11
13
|
} from './required-knowledge.js';
|
|
12
14
|
|
|
13
15
|
function unwrapData(result) {
|
|
@@ -111,7 +113,8 @@ async function resolveRoute(apiUrl, { path, routeId }) {
|
|
|
111
113
|
return { route, routes, path: route.path };
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
async function updateRouteMethods(apiUrl, { path, routeId, methods, mode, isEnabled }) {
|
|
116
|
+
async function updateRouteMethods(apiUrl, { path, routeId, methods, mode, isEnabled, globalRulesAckKey }) {
|
|
117
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
115
118
|
const [{ route }, { methodMap, methodIdNameMap }] = await Promise.all([
|
|
116
119
|
resolveRoute(apiUrl, { path, routeId }),
|
|
117
120
|
getMethodContext(apiUrl),
|
|
@@ -141,7 +144,8 @@ async function updateRouteMethods(apiUrl, { path, routeId, methods, mode, isEnab
|
|
|
141
144
|
};
|
|
142
145
|
}
|
|
143
146
|
|
|
144
|
-
async function updateRoutePublicMethods(apiUrl, { path, routeId, methods, mode }) {
|
|
147
|
+
async function updateRoutePublicMethods(apiUrl, { path, routeId, methods, mode, globalRulesAckKey }) {
|
|
148
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
145
149
|
const [{ route }, { methodMap, methodIdNameMap }] = await Promise.all([
|
|
146
150
|
resolveRoute(apiUrl, { path, routeId }),
|
|
147
151
|
getMethodContext(apiUrl),
|
|
@@ -171,7 +175,8 @@ async function updateRoutePublicMethods(apiUrl, { path, routeId, methods, mode }
|
|
|
171
175
|
};
|
|
172
176
|
}
|
|
173
177
|
|
|
174
|
-
async function setRouteEnabled(apiUrl, { path, routeId, isEnabled }) {
|
|
178
|
+
async function setRouteEnabled(apiUrl, { path, routeId, isEnabled, globalRulesAckKey }) {
|
|
179
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
175
180
|
const { route } = await resolveRoute(apiUrl, { path, routeId });
|
|
176
181
|
const before = route?.isEnabled !== false;
|
|
177
182
|
if (before === isEnabled) {
|
|
@@ -235,7 +240,7 @@ async function deleteRows(apiUrl, tableName, rows) {
|
|
|
235
240
|
return deleted;
|
|
236
241
|
}
|
|
237
242
|
|
|
238
|
-
async function deleteRoute(apiUrl, { path, routeId, expectedPath, confirm }) {
|
|
243
|
+
async function deleteRoute(apiUrl, { path, routeId, expectedPath, confirm, globalRulesAckKey }) {
|
|
239
244
|
const { route } = await resolveRoute(apiUrl, { path, routeId });
|
|
240
245
|
if (expectedPath && route.path !== normalizeRestPath(expectedPath)) {
|
|
241
246
|
throw new Error(`Route path mismatch: resolved ${route.path}, expected ${normalizeRestPath(expectedPath)}.`);
|
|
@@ -255,6 +260,7 @@ async function deleteRoute(apiUrl, { path, routeId, expectedPath, confirm }) {
|
|
|
255
260
|
next: 'Call delete_route again with confirm=true and expectedPath set to this route path to delete the route and related handlers/hooks/permissions/guards.',
|
|
256
261
|
};
|
|
257
262
|
}
|
|
263
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
258
264
|
|
|
259
265
|
await deleteRows(apiUrl, 'enfyra_route_handler', dependencies.handlers);
|
|
260
266
|
await deleteRows(apiUrl, 'enfyra_pre_hook', dependencies.preHooks);
|
|
@@ -641,7 +647,9 @@ async function ensureMenu(apiUrl, {
|
|
|
641
647
|
permission,
|
|
642
648
|
description,
|
|
643
649
|
isEnabled = true,
|
|
650
|
+
globalRulesAckKey,
|
|
644
651
|
}) {
|
|
652
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
645
653
|
const normalizedPath = path ? normalizeRestPath(path) : undefined;
|
|
646
654
|
const existing = normalizedPath
|
|
647
655
|
? await findRecord(apiUrl, 'enfyra_menu', { path: { _eq: normalizedPath } }, 'id,_id,path,label')
|
|
@@ -673,8 +681,10 @@ async function ensureExtension(apiUrl, {
|
|
|
673
681
|
description,
|
|
674
682
|
isEnabled = true,
|
|
675
683
|
version = '1.0.0',
|
|
684
|
+
globalRulesAckKey,
|
|
676
685
|
extensionKnowledgeAckKey,
|
|
677
686
|
}) {
|
|
687
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
678
688
|
assertExtensionKnowledgeAck(extensionKnowledgeAckKey);
|
|
679
689
|
if (type === 'page' && !menuId) {
|
|
680
690
|
throw new Error('menuId is required for page extensions. Use ensure_menu first, then ensure_page_extension.');
|
|
@@ -711,7 +721,9 @@ async function ensureFlow(apiUrl, {
|
|
|
711
721
|
maxExecutions = 100,
|
|
712
722
|
isEnabled = true,
|
|
713
723
|
description,
|
|
724
|
+
globalRulesAckKey,
|
|
714
725
|
}) {
|
|
726
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
715
727
|
const existing = await findRecord(apiUrl, 'enfyra_flow', { name: { _eq: name } }, 'id,_id,name');
|
|
716
728
|
const operation = await createOrPatch(apiUrl, 'enfyra_flow', existing, {
|
|
717
729
|
name,
|
|
@@ -737,8 +749,10 @@ async function ensureFlowStep(apiUrl, {
|
|
|
737
749
|
scriptLanguage,
|
|
738
750
|
timeout,
|
|
739
751
|
isEnabled,
|
|
752
|
+
globalRulesAckKey,
|
|
740
753
|
knowledgeAckKey,
|
|
741
754
|
}) {
|
|
755
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
742
756
|
if (!flowName && !flowId) throw new Error('Provide flowName or flowId.');
|
|
743
757
|
if (flowName && flowId) throw new Error('Provide flowName or flowId, not both.');
|
|
744
758
|
const flow = flowId
|
|
@@ -1124,6 +1138,7 @@ async function runApiEndpointWorkflow(apiUrl, opts) {
|
|
|
1124
1138
|
const operations = [];
|
|
1125
1139
|
let completedEphemeralStepId = null;
|
|
1126
1140
|
if (opts.apply || opts.applyAll) {
|
|
1141
|
+
assertGlobalRulesAck(opts.globalRulesAckKey);
|
|
1127
1142
|
if (opts.applyAll && state.steps.some((item) => item.id === 'save_handler' && ['pending', 'waiting'].includes(item.status))) {
|
|
1128
1143
|
assertDynamicCodeKnowledgeAck(opts.knowledgeAckKey);
|
|
1129
1144
|
}
|
|
@@ -1227,8 +1242,10 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1227
1242
|
{
|
|
1228
1243
|
tableName: z.string().describe('Table name, alias, or id.'),
|
|
1229
1244
|
isEnabled: z.boolean().describe('Desired GraphQL enabled state for the table.'),
|
|
1245
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1230
1246
|
},
|
|
1231
|
-
async ({ tableName, isEnabled }) => {
|
|
1247
|
+
async ({ tableName, isEnabled, globalRulesAckKey }) => {
|
|
1248
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1232
1249
|
const table = resolveTable(await getMetadataTables(ENFYRA_API_URL), tableName);
|
|
1233
1250
|
const existing = await findRecord(ENFYRA_API_URL, 'enfyra_graphql', { table: { id: { _eq: getId(table) } } }, 'id,_id,table.id,isEnabled');
|
|
1234
1251
|
const operation = await createOrPatch(ENFYRA_API_URL, 'enfyra_graphql', existing, {
|
|
@@ -1254,13 +1271,15 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1254
1271
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1255
1272
|
methods: z.array(z.string()).min(1).describe('HTTP method names to add.'),
|
|
1256
1273
|
isEnabled: z.boolean().optional().describe('Optionally enable/disable the route in the same safe patch.'),
|
|
1274
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1257
1275
|
},
|
|
1258
|
-
async ({ path, routeId, methods, isEnabled }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1276
|
+
async ({ path, routeId, methods, isEnabled, globalRulesAckKey }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1259
1277
|
path,
|
|
1260
1278
|
routeId,
|
|
1261
1279
|
methods,
|
|
1262
1280
|
mode: 'merge',
|
|
1263
1281
|
isEnabled,
|
|
1282
|
+
globalRulesAckKey,
|
|
1264
1283
|
})),
|
|
1265
1284
|
);
|
|
1266
1285
|
|
|
@@ -1272,13 +1291,15 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1272
1291
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1273
1292
|
methods: z.array(z.string()).min(1).describe('Exact HTTP method names for availableMethods.'),
|
|
1274
1293
|
isEnabled: z.boolean().optional().describe('Optionally enable/disable the route in the same safe patch.'),
|
|
1294
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1275
1295
|
},
|
|
1276
|
-
async ({ path, routeId, methods, isEnabled }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1296
|
+
async ({ path, routeId, methods, isEnabled, globalRulesAckKey }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1277
1297
|
path,
|
|
1278
1298
|
routeId,
|
|
1279
1299
|
methods,
|
|
1280
1300
|
mode: 'replace',
|
|
1281
1301
|
isEnabled,
|
|
1302
|
+
globalRulesAckKey,
|
|
1282
1303
|
})),
|
|
1283
1304
|
);
|
|
1284
1305
|
|
|
@@ -1290,13 +1311,15 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1290
1311
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1291
1312
|
methods: z.array(z.string()).min(1).describe('HTTP method names to remove.'),
|
|
1292
1313
|
isEnabled: z.boolean().optional().describe('Optionally enable/disable the route in the same safe patch.'),
|
|
1314
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1293
1315
|
},
|
|
1294
|
-
async ({ path, routeId, methods, isEnabled }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1316
|
+
async ({ path, routeId, methods, isEnabled, globalRulesAckKey }) => jsonText(await updateRouteMethods(ENFYRA_API_URL, {
|
|
1295
1317
|
path,
|
|
1296
1318
|
routeId,
|
|
1297
1319
|
methods,
|
|
1298
1320
|
mode: 'remove',
|
|
1299
1321
|
isEnabled,
|
|
1322
|
+
globalRulesAckKey,
|
|
1300
1323
|
})),
|
|
1301
1324
|
);
|
|
1302
1325
|
|
|
@@ -1306,11 +1329,13 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1306
1329
|
{
|
|
1307
1330
|
path: z.string().optional().describe('Route path, e.g. /sum. Use either path or routeId.'),
|
|
1308
1331
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1332
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1309
1333
|
},
|
|
1310
|
-
async ({ path, routeId }) => jsonText(await setRouteEnabled(ENFYRA_API_URL, {
|
|
1334
|
+
async ({ path, routeId, globalRulesAckKey }) => jsonText(await setRouteEnabled(ENFYRA_API_URL, {
|
|
1311
1335
|
path,
|
|
1312
1336
|
routeId,
|
|
1313
1337
|
isEnabled: true,
|
|
1338
|
+
globalRulesAckKey,
|
|
1314
1339
|
})),
|
|
1315
1340
|
);
|
|
1316
1341
|
|
|
@@ -1320,11 +1345,13 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1320
1345
|
{
|
|
1321
1346
|
path: z.string().optional().describe('Route path, e.g. /sum. Use either path or routeId.'),
|
|
1322
1347
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1348
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1323
1349
|
},
|
|
1324
|
-
async ({ path, routeId }) => jsonText(await setRouteEnabled(ENFYRA_API_URL, {
|
|
1350
|
+
async ({ path, routeId, globalRulesAckKey }) => jsonText(await setRouteEnabled(ENFYRA_API_URL, {
|
|
1325
1351
|
path,
|
|
1326
1352
|
routeId,
|
|
1327
1353
|
isEnabled: false,
|
|
1354
|
+
globalRulesAckKey,
|
|
1328
1355
|
})),
|
|
1329
1356
|
);
|
|
1330
1357
|
|
|
@@ -1336,6 +1363,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1336
1363
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1337
1364
|
expectedPath: z.string().optional().describe('Optional safety check. When confirm=true, pass the path returned by the preview.'),
|
|
1338
1365
|
confirm: z.boolean().optional().default(false).describe('false returns a dependency preview only; true deletes the route and related route-owned records.'),
|
|
1366
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when confirm=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
1339
1367
|
},
|
|
1340
1368
|
async (input) => jsonText(await deleteRoute(ENFYRA_API_URL, input)),
|
|
1341
1369
|
);
|
|
@@ -1347,12 +1375,14 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1347
1375
|
path: z.string().optional().describe('Route path, e.g. /sum. Use either path or routeId.'),
|
|
1348
1376
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1349
1377
|
methods: z.array(z.string()).min(1).describe('HTTP method names to make public. They must already be available on the route.'),
|
|
1378
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1350
1379
|
},
|
|
1351
|
-
async ({ path, routeId, methods }) => jsonText(await updateRoutePublicMethods(ENFYRA_API_URL, {
|
|
1380
|
+
async ({ path, routeId, methods, globalRulesAckKey }) => jsonText(await updateRoutePublicMethods(ENFYRA_API_URL, {
|
|
1352
1381
|
path,
|
|
1353
1382
|
routeId,
|
|
1354
1383
|
methods,
|
|
1355
1384
|
mode: 'merge',
|
|
1385
|
+
globalRulesAckKey,
|
|
1356
1386
|
})),
|
|
1357
1387
|
);
|
|
1358
1388
|
|
|
@@ -1363,12 +1393,14 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1363
1393
|
path: z.string().optional().describe('Route path, e.g. /sum. Use either path or routeId.'),
|
|
1364
1394
|
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1365
1395
|
methods: z.array(z.string()).min(1).describe('HTTP method names to remove from publicMethods.'),
|
|
1396
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1366
1397
|
},
|
|
1367
|
-
async ({ path, routeId, methods }) => jsonText(await updateRoutePublicMethods(ENFYRA_API_URL, {
|
|
1398
|
+
async ({ path, routeId, methods, globalRulesAckKey }) => jsonText(await updateRoutePublicMethods(ENFYRA_API_URL, {
|
|
1368
1399
|
path,
|
|
1369
1400
|
routeId,
|
|
1370
1401
|
methods,
|
|
1371
1402
|
mode: 'remove',
|
|
1403
|
+
globalRulesAckKey,
|
|
1372
1404
|
})),
|
|
1373
1405
|
);
|
|
1374
1406
|
|
|
@@ -1399,6 +1431,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1399
1431
|
apply: z.boolean().optional().default(false).describe('false returns plan only; true applies exactly the next pending step.'),
|
|
1400
1432
|
applyAll: z.boolean().optional().default(false).describe('true applies all safe pending steps in order. Prefer apply=true for production changes.'),
|
|
1401
1433
|
stepId: z.string().optional().describe('Optional pending step id to apply. Omit to apply the next pending step.'),
|
|
1434
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when apply/applyAll mutates metadata. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
1402
1435
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required when apply/applyAll reaches the save_handler step. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
1403
1436
|
},
|
|
1404
1437
|
async (input) => jsonText(await runApiEndpointWorkflow(ENFYRA_API_URL, input)),
|
|
@@ -1423,9 +1456,11 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1423
1456
|
overwrite: z.boolean().optional().default(false).describe('If a handler already exists for route+method, false fails; true updates its sourceCode.'),
|
|
1424
1457
|
smokeTestQuery: z.string().optional().describe('Optional query JSON object for a smoke test after save, e.g. {"a":"1","b":"2"}.'),
|
|
1425
1458
|
smokeTestBody: z.string().optional().describe('Optional body JSON object for a smoke test after save.'),
|
|
1459
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1426
1460
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
1427
1461
|
},
|
|
1428
|
-
async ({ path, method, sourceCode, scriptLanguage, public: makePublic, description, timeout, overwrite, smokeTestQuery, smokeTestBody, knowledgeAckKey }) => {
|
|
1462
|
+
async ({ path, method, sourceCode, scriptLanguage, public: makePublic, description, timeout, overwrite, smokeTestQuery, smokeTestBody, globalRulesAckKey, knowledgeAckKey }) => {
|
|
1463
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1429
1464
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
1430
1465
|
const normalizedPath = normalizeRestPath(path);
|
|
1431
1466
|
const methodName = normalizeMethodName(method);
|
|
@@ -1733,9 +1768,11 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1733
1768
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language for connection handler.'),
|
|
1734
1769
|
isEnabled: z.boolean().optional().default(true).describe('Enable gateway.'),
|
|
1735
1770
|
description: z.string().optional().describe('Admin note.'),
|
|
1771
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1736
1772
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required when sourceCode is provided. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
1737
1773
|
},
|
|
1738
|
-
async ({ path, sourceCode, scriptLanguage, isEnabled, description, knowledgeAckKey }) => {
|
|
1774
|
+
async ({ path, sourceCode, scriptLanguage, isEnabled, description, globalRulesAckKey, knowledgeAckKey }) => {
|
|
1775
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1739
1776
|
assertDynamicCodeKnowledgeAckIf(sourceCode !== undefined, knowledgeAckKey);
|
|
1740
1777
|
const normalizedPath = normalizeRestPath(path);
|
|
1741
1778
|
const validation = sourceCode === undefined
|
|
@@ -1765,9 +1802,11 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1765
1802
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language.'),
|
|
1766
1803
|
isEnabled: z.boolean().optional().default(true).describe('Enable event.'),
|
|
1767
1804
|
description: z.string().optional().describe('Admin note.'),
|
|
1805
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1768
1806
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
1769
1807
|
},
|
|
1770
|
-
async ({ gatewayPath, gatewayId, eventName, sourceCode, scriptLanguage, isEnabled, description, knowledgeAckKey }) => {
|
|
1808
|
+
async ({ gatewayPath, gatewayId, eventName, sourceCode, scriptLanguage, isEnabled, description, globalRulesAckKey, knowledgeAckKey }) => {
|
|
1809
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1771
1810
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
1772
1811
|
if (!gatewayPath && !gatewayId) throw new Error('Provide gatewayPath or gatewayId.');
|
|
1773
1812
|
if (gatewayPath && gatewayId) throw new Error('Provide gatewayPath or gatewayId, not both.');
|
|
@@ -1802,14 +1841,16 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1802
1841
|
maxExecutions: z.number().int().positive().optional().default(100).describe('Execution history cap.'),
|
|
1803
1842
|
isEnabled: z.boolean().optional().default(true).describe('Enable flow.'),
|
|
1804
1843
|
description: z.string().optional().describe('Admin note.'),
|
|
1844
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1805
1845
|
},
|
|
1806
|
-
async ({ name, timeout, maxExecutions, isEnabled, description }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
|
|
1846
|
+
async ({ name, timeout, maxExecutions, isEnabled, description, globalRulesAckKey }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
|
|
1807
1847
|
name,
|
|
1808
1848
|
triggerType: 'manual',
|
|
1809
1849
|
timeout,
|
|
1810
1850
|
maxExecutions,
|
|
1811
1851
|
isEnabled,
|
|
1812
1852
|
description,
|
|
1853
|
+
globalRulesAckKey,
|
|
1813
1854
|
})),
|
|
1814
1855
|
);
|
|
1815
1856
|
|
|
@@ -1823,8 +1864,9 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1823
1864
|
maxExecutions: z.number().int().positive().optional().default(100).describe('Execution history cap.'),
|
|
1824
1865
|
isEnabled: z.boolean().optional().default(true).describe('Enable flow.'),
|
|
1825
1866
|
description: z.string().optional().describe('Admin note.'),
|
|
1867
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1826
1868
|
},
|
|
1827
|
-
async ({ name, triggerConfig, timeout, maxExecutions, isEnabled, description }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
|
|
1869
|
+
async ({ name, triggerConfig, timeout, maxExecutions, isEnabled, description, globalRulesAckKey }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
|
|
1828
1870
|
name,
|
|
1829
1871
|
triggerType: 'schedule',
|
|
1830
1872
|
triggerConfig,
|
|
@@ -1832,6 +1874,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1832
1874
|
maxExecutions,
|
|
1833
1875
|
isEnabled,
|
|
1834
1876
|
description,
|
|
1877
|
+
globalRulesAckKey,
|
|
1835
1878
|
})),
|
|
1836
1879
|
);
|
|
1837
1880
|
|
|
@@ -1870,6 +1913,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1870
1913
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language.'),
|
|
1871
1914
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1872
1915
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
1916
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1873
1917
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
1874
1918
|
},
|
|
1875
1919
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
@@ -1891,6 +1935,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1891
1935
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language.'),
|
|
1892
1936
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1893
1937
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
1938
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1894
1939
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
1895
1940
|
},
|
|
1896
1941
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
@@ -1910,6 +1955,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1910
1955
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
1911
1956
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1912
1957
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
1958
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1913
1959
|
},
|
|
1914
1960
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
1915
1961
|
...input,
|
|
@@ -1928,6 +1974,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1928
1974
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
1929
1975
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1930
1976
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
1977
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1931
1978
|
},
|
|
1932
1979
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
1933
1980
|
...input,
|
|
@@ -1946,6 +1993,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1946
1993
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
1947
1994
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1948
1995
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
1996
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1949
1997
|
},
|
|
1950
1998
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
1951
1999
|
...input,
|
|
@@ -1964,6 +2012,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1964
2012
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
1965
2013
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1966
2014
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
2015
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1967
2016
|
},
|
|
1968
2017
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
1969
2018
|
...input,
|
|
@@ -1982,6 +2031,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1982
2031
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
1983
2032
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
1984
2033
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
2034
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1985
2035
|
},
|
|
1986
2036
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
1987
2037
|
...input,
|
|
@@ -2000,6 +2050,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2000
2050
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
2001
2051
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
2002
2052
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
2053
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2003
2054
|
},
|
|
2004
2055
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
2005
2056
|
...input,
|
|
@@ -2018,6 +2069,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2018
2069
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
2019
2070
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
2020
2071
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
2072
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2021
2073
|
},
|
|
2022
2074
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
2023
2075
|
...input,
|
|
@@ -2036,6 +2088,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2036
2088
|
order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
|
|
2037
2089
|
timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
|
|
2038
2090
|
isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
|
|
2091
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2039
2092
|
},
|
|
2040
2093
|
async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
|
|
2041
2094
|
...input,
|
|
@@ -2055,6 +2108,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2055
2108
|
permission: z.string().optional().describe('Menu permission JSON object.'),
|
|
2056
2109
|
description: z.string().optional().describe('Admin note.'),
|
|
2057
2110
|
isEnabled: z.boolean().optional().default(true).describe('Enable menu.'),
|
|
2111
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2058
2112
|
},
|
|
2059
2113
|
async (input) => jsonText({
|
|
2060
2114
|
action: 'menu_ensured',
|
|
@@ -2072,6 +2126,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2072
2126
|
description: z.string().optional().describe('Extension description.'),
|
|
2073
2127
|
isEnabled: z.boolean().optional().default(true).describe('Enable extension.'),
|
|
2074
2128
|
version: z.string().optional().default('1.0.0').describe('Extension version.'),
|
|
2129
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2075
2130
|
extensionKnowledgeAckKey: extensionKnowledgeAckParam(z),
|
|
2076
2131
|
},
|
|
2077
2132
|
async (input) => jsonText({
|
|
@@ -2089,6 +2144,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2089
2144
|
description: z.string().optional().describe('Extension description.'),
|
|
2090
2145
|
isEnabled: z.boolean().optional().default(true).describe('Enable extension.'),
|
|
2091
2146
|
version: z.string().optional().default('1.0.0').describe('Extension version.'),
|
|
2147
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2092
2148
|
extensionKnowledgeAckKey: extensionKnowledgeAckParam(z),
|
|
2093
2149
|
},
|
|
2094
2150
|
async (input) => jsonText({
|
|
@@ -2106,6 +2162,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2106
2162
|
description: z.string().optional().describe('Extension description.'),
|
|
2107
2163
|
isEnabled: z.boolean().optional().default(true).describe('Enable extension.'),
|
|
2108
2164
|
version: z.string().optional().default('1.0.0').describe('Extension version.'),
|
|
2165
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2109
2166
|
extensionKnowledgeAckKey: extensionKnowledgeAckParam(z),
|
|
2110
2167
|
},
|
|
2111
2168
|
async (input) => jsonText({
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
export const GLOBAL_RULES_ACK_KEY = 'EFYRA::GLOBAL-RULES::READ-LIVE-CONTEXT-FIRST::2bW-20260701';
|
|
1
2
|
export const DYNAMIC_CODE_KNOWLEDGE_ACK_KEY = 'EFYRA::SECURE-REPO-CONTRACT::R9x-kelp-42Q::NO-RAW-TRUSTED';
|
|
2
3
|
export const EXTENSION_KNOWLEDGE_ACK_KEY = 'EFYRA::EXTENSION-THEME-CONTRACT::VIOLET-IS-NOT-A-PLAN::7mQ';
|
|
3
4
|
|
|
4
|
-
const REQUIRED_KNOWLEDGE_VERSION = '2026-
|
|
5
|
+
const REQUIRED_KNOWLEDGE_VERSION = '2026-07-01.global-rules-v1';
|
|
6
|
+
|
|
7
|
+
export function globalRulesAckParam(z) {
|
|
8
|
+
return z.string().describe('Required global-rules acknowledgement key from get_enfyra_required_knowledge. Call that tool, read the global Enfyra MCP rules, then pass globalRulesAckKey exactly.');
|
|
9
|
+
}
|
|
5
10
|
|
|
6
11
|
export function dynamicCodeKnowledgeAckParam(z) {
|
|
7
12
|
return z.string().describe('Required dynamic-code acknowledgement key from get_enfyra_required_knowledge. Call that tool, read the dynamic server code knowledge, then pass dynamicCodeAckKey exactly.');
|
|
@@ -11,6 +16,16 @@ export function extensionKnowledgeAckParam(z) {
|
|
|
11
16
|
return z.string().describe('Required extension acknowledgement key from get_enfyra_required_knowledge. Call that tool, read the extension/theme knowledge, then pass extensionAckKey exactly.');
|
|
12
17
|
}
|
|
13
18
|
|
|
19
|
+
export function assertGlobalRulesAck(key) {
|
|
20
|
+
if (key !== GLOBAL_RULES_ACK_KEY) {
|
|
21
|
+
throw new Error('Missing or invalid global-rules acknowledgement. Call get_enfyra_required_knowledge, read the global Enfyra MCP rules, then pass globalRulesAckKey as globalRulesAckKey.');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function assertGlobalRulesAckIf(condition, key) {
|
|
26
|
+
if (condition) assertGlobalRulesAck(key);
|
|
27
|
+
}
|
|
28
|
+
|
|
14
29
|
export function assertDynamicCodeKnowledgeAck(key) {
|
|
15
30
|
if (key !== DYNAMIC_CODE_KNOWLEDGE_ACK_KEY) {
|
|
16
31
|
throw new Error('Missing or invalid dynamic-code knowledge acknowledgement. Call get_enfyra_required_knowledge, read the dynamic server code contracts, then pass dynamicCodeAckKey as knowledgeAckKey.');
|
|
@@ -34,13 +49,59 @@ export function assertExtensionKnowledgeAckIf(condition, key) {
|
|
|
34
49
|
export function buildRequiredKnowledgePayload() {
|
|
35
50
|
return {
|
|
36
51
|
version: REQUIRED_KNOWLEDGE_VERSION,
|
|
37
|
-
purpose: 'Read this before
|
|
52
|
+
purpose: 'Read this before mutating Enfyra metadata, schema, routes, permissions, dynamic server code, or extension UI through MCP.',
|
|
53
|
+
globalRulesAckKey: GLOBAL_RULES_ACK_KEY,
|
|
38
54
|
dynamicCodeAckKey: DYNAMIC_CODE_KNOWLEDGE_ACK_KEY,
|
|
39
55
|
extensionAckKey: EXTENSION_KNOWLEDGE_ACK_KEY,
|
|
40
56
|
usage: [
|
|
57
|
+
'Pass globalRulesAckKey exactly as globalRulesAckKey when calling MCP tools that mutate Enfyra metadata, schema, routes, permissions, menus, packages, cache state, dynamic code, or extension UI.',
|
|
41
58
|
'Pass dynamicCodeAckKey exactly as knowledgeAckKey when calling MCP tools that create or update dynamic server code.',
|
|
42
59
|
'Pass extensionAckKey exactly as extensionKnowledgeAckKey when calling MCP tools that create or update Enfyra extension code.',
|
|
43
60
|
],
|
|
61
|
+
globalRules: [
|
|
62
|
+
{
|
|
63
|
+
id: 'examples-are-reasoning-anchors',
|
|
64
|
+
rules: [
|
|
65
|
+
'Examples explain transferable decisions, not copy-paste mandates.',
|
|
66
|
+
'Preserve platform contracts and safety boundaries, then adapt names, routes, fields, menus, labels, and lifecycle to live metadata and the user goal.',
|
|
67
|
+
'When examples use chat, order, report, cloud, email, or support domains, treat those as analogies unless the current task is exactly that domain.',
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'discover-before-changing',
|
|
72
|
+
rules: [
|
|
73
|
+
'Inspect live metadata/routes/features before schema, route, permission, extension, flow, or handler changes.',
|
|
74
|
+
'Use narrow inspection tools for the table, route, feature, or script being changed instead of broad discovery after the target is known.',
|
|
75
|
+
'Read sourceCode, not compiledCode, for editable dynamic scripts.',
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'mutations-are-intentional',
|
|
80
|
+
rules: [
|
|
81
|
+
'Prefer business operation tools over generic CRUD when a specific tool exists.',
|
|
82
|
+
'Destructive operations are preview-first; pass confirm=true only after explicit user approval.',
|
|
83
|
+
'Do not manually reload caches unless natural partial reload is proven stale or a concrete reload error requires it.',
|
|
84
|
+
'Never fabricate ids, field names, relation names, paths, package names, or permission scopes.',
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'security-first',
|
|
89
|
+
rules: [
|
|
90
|
+
'Treat permission and owner/tenant scope as the first design step for any route, handler, hook, flow, extension, websocket, or data surface.',
|
|
91
|
+
'Route permission only lets authenticated users reach a route after RoleGuard; handlers, hooks, RLS, and scripts still enforce record ownership and tenant/project scope.',
|
|
92
|
+
'Do not expose unpublished fields, private relation facts, secret values, token hashes, stack traces, SQL, provider payloads, or generated passwords to user-facing clients.',
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'shell-signals',
|
|
97
|
+
rules: [
|
|
98
|
+
'For app shell menu/account-panel notifications, decide the signal source before choosing count or dot.',
|
|
99
|
+
'Use a count only when the shell receives an exact or bounded count from a notification/summary source.',
|
|
100
|
+
'Use a dot when realtime only proves new attention exists.',
|
|
101
|
+
'Do not fetch destination domain lists such as messages, tickets, orders, or jobs solely to decorate the menu; the destination page owns domain fetching.',
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
],
|
|
44
105
|
dynamicServerCode: [
|
|
45
106
|
{
|
|
46
107
|
id: 'secure-vs-trusted-repositories',
|
package/src/lib/table-tools.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { fetchAPI } from './fetch.js';
|
|
6
6
|
import { jsonContent } from './response-format.js';
|
|
7
|
+
import { assertGlobalRulesAck, globalRulesAckParam } from './required-knowledge.js';
|
|
7
8
|
|
|
8
9
|
let schemaQueue = Promise.resolve();
|
|
9
10
|
|
|
@@ -308,6 +309,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
308
309
|
const apiBase = ENFYRA_API_URL.replace(/\/$/, '');
|
|
309
310
|
|
|
310
311
|
async function appendColumnToTable(args) {
|
|
312
|
+
assertGlobalRulesAck(args.globalRulesAckKey);
|
|
311
313
|
return withSchemaQueue(async () => {
|
|
312
314
|
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, args.tableId);
|
|
313
315
|
if (!tableData) {
|
|
@@ -330,6 +332,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
330
332
|
}
|
|
331
333
|
|
|
332
334
|
async function appendRelationToTable(args) {
|
|
335
|
+
assertGlobalRulesAck(args.globalRulesAckKey);
|
|
333
336
|
return withSchemaQueue(async () => {
|
|
334
337
|
assertNoForbiddenRelationKeys(args);
|
|
335
338
|
const { sourceTableId, targetTableId, type, propertyName, inversePropertyName, mappedBy, isNullable, onDelete, description } = args;
|
|
@@ -359,7 +362,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
359
362
|
});
|
|
360
363
|
}
|
|
361
364
|
|
|
362
|
-
async function removeColumnFromTable({ tableId, columnId, confirm }) {
|
|
365
|
+
async function removeColumnFromTable({ tableId, columnId, confirm, globalRulesAckKey }) {
|
|
363
366
|
return withSchemaQueue(async () => {
|
|
364
367
|
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, tableId);
|
|
365
368
|
if (!tableData) {
|
|
@@ -385,6 +388,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
385
388
|
}, null, 2) }],
|
|
386
389
|
};
|
|
387
390
|
}
|
|
391
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
388
392
|
|
|
389
393
|
const columns = existingColumns
|
|
390
394
|
.filter(col => String(getId(col)) !== String(columnId))
|
|
@@ -401,7 +405,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
401
405
|
});
|
|
402
406
|
}
|
|
403
407
|
|
|
404
|
-
async function removeRelationFromTable({ tableId, relationId, confirm }) {
|
|
408
|
+
async function removeRelationFromTable({ tableId, relationId, confirm, globalRulesAckKey }) {
|
|
405
409
|
return withSchemaQueue(async () => {
|
|
406
410
|
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, tableId);
|
|
407
411
|
if (!tableData) {
|
|
@@ -427,6 +431,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
427
431
|
}, null, 2) }],
|
|
428
432
|
};
|
|
429
433
|
}
|
|
434
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
430
435
|
|
|
431
436
|
const relations = existingRelations
|
|
432
437
|
.filter(rel => String(getId(rel)) !== String(relationId))
|
|
@@ -458,6 +463,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
458
463
|
defaultValue: z.string().optional().describe('Default value as JSON string or backend-supported literal.'),
|
|
459
464
|
description: z.string().optional().describe('Column description.'),
|
|
460
465
|
options: z.string().optional().describe('Column options as JSON string (e.g., enum values).'),
|
|
466
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
461
467
|
};
|
|
462
468
|
|
|
463
469
|
const relationCreateSchema = {
|
|
@@ -477,18 +483,21 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
477
483
|
targetColumn: z.never().optional().describe('Forbidden. Use propertyName only; Enfyra derives FK columns.'),
|
|
478
484
|
junctionSourceColumn: z.never().optional().describe('Forbidden. Use relation property names only; Enfyra derives junction columns.'),
|
|
479
485
|
junctionTargetColumn: z.never().optional().describe('Forbidden. Use relation property names only; Enfyra derives junction columns.'),
|
|
486
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
480
487
|
};
|
|
481
488
|
|
|
482
489
|
const columnDeleteSchema = {
|
|
483
490
|
tableId: z.string().describe('Table definition ID.'),
|
|
484
491
|
columnId: z.string().describe('Column definition ID to delete.'),
|
|
485
492
|
confirm: z.boolean().optional().default(false).describe('Required true to apply the destructive delete. Omit/false returns a preview only.'),
|
|
493
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when confirm=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
486
494
|
};
|
|
487
495
|
|
|
488
496
|
const relationDeleteSchema = {
|
|
489
497
|
tableId: z.string().describe('Table definition ID (source table of the relation).'),
|
|
490
498
|
relationId: z.string().describe('Relation definition ID to delete.'),
|
|
491
499
|
confirm: z.boolean().optional().default(false).describe('Required true to apply the destructive delete. Omit/false returns a preview only.'),
|
|
500
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when confirm=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
492
501
|
};
|
|
493
502
|
|
|
494
503
|
// ─── READ ───
|
|
@@ -565,8 +574,10 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
565
574
|
relations: z.string().optional().describe('JSON array of relation definitions to create with the table in the same cascade call. Each relation: { targetTable, type, propertyName, inversePropertyName?, mappedBy?, isNullable?, onDelete?, description? }. targetTable can be an id, {"id": <id>}, or an exact table name that MCP resolves to an id before mutation. Do not include physical FK/junction columns such as fkCol, foreignKeyColumn, sourceColumn, targetColumn, junctionSourceColumn, or junctionTargetColumn; Enfyra derives them and hides FK columns from app schema. Omit inversePropertyName unless a concrete response, UI, deep query, aggregate sort/count, or parent-to-child traversal needs the reverse field. Example only when parent posts are queried: [{"targetTable":2,"type":"many-to-one","propertyName":"author","inversePropertyName":"posts","isNullable":false,"onDelete":"CASCADE"}]'),
|
|
566
575
|
indexes: z.string().optional().describe('JSON array of logical index field groups. Each group can be ["fieldA","fieldB"] or {"value":["fieldA","fieldB"]}. Relation property names are allowed. Example: [["member","isRead","conversation"],["conversation","member","isRead"]]'),
|
|
567
576
|
uniques: z.string().optional().describe('JSON array of logical unique field groups. Each group can be ["fieldA","fieldB"] or {"value":["fieldA","fieldB"]}. Example: [["message","member"]]'),
|
|
577
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
568
578
|
},
|
|
569
|
-
async ({ name, description, isSingleRecord, columns: columnsJson, relations: relationsJson, indexes: indexesJson, uniques: uniquesJson }) => withSchemaQueue(async () => {
|
|
579
|
+
async ({ name, description, isSingleRecord, columns: columnsJson, relations: relationsJson, indexes: indexesJson, uniques: uniquesJson, globalRulesAckKey }) => withSchemaQueue(async () => {
|
|
580
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
570
581
|
const idColumn = { name: 'id', type: 'int', isPrimary: true, isGenerated: true, isNullable: false };
|
|
571
582
|
const userColumns = parseJsonArrayParam('columns', columnsJson);
|
|
572
583
|
const parsedRelations = parseJsonArrayParam('relations', relationsJson).map(normalizeRelationForTablePatch);
|
|
@@ -627,8 +638,10 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
627
638
|
graphqlEnabled: z.boolean().optional().describe('Enable or disable GraphQL for this table by syncing enfyra_graphql.isEnabled. GraphQL table data still requires Bearer auth; anonymous root or schema probes may return 200.'),
|
|
628
639
|
indexes: z.string().optional().describe('Complete JSON array of logical index field groups to store on enfyra_table.indexes. Each group can be ["fieldA","fieldB"] or {"value":["fieldA","fieldB"]}. Omit to preserve current indexes; pass [] to clear.'),
|
|
629
640
|
uniques: z.string().optional().describe('Complete JSON array of logical unique field groups to store on enfyra_table.uniques. Each group can be ["fieldA","fieldB"] or {"value":["fieldA","fieldB"]}. Omit to preserve current uniques; pass [] to clear.'),
|
|
641
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
630
642
|
},
|
|
631
|
-
async ({ tableId, name, alias, description, isSingleRecord, graphqlEnabled, indexes: indexesJson, uniques: uniquesJson }) => withSchemaQueue(async () => {
|
|
643
|
+
async ({ tableId, name, alias, description, isSingleRecord, graphqlEnabled, indexes: indexesJson, uniques: uniquesJson, globalRulesAckKey }) => withSchemaQueue(async () => {
|
|
644
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
632
645
|
const body = {};
|
|
633
646
|
if (name !== undefined) body.name = name;
|
|
634
647
|
if (alias !== undefined) body.alias = alias;
|
|
@@ -657,8 +670,9 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
657
670
|
{
|
|
658
671
|
tableId: z.string().describe('Table definition ID to delete.'),
|
|
659
672
|
confirm: z.boolean().optional().default(false).describe('Required true to apply the destructive delete. Omit/false returns a preview only.'),
|
|
673
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when confirm=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
660
674
|
},
|
|
661
|
-
async ({ tableId, confirm }) => withSchemaQueue(async () => {
|
|
675
|
+
async ({ tableId, confirm, globalRulesAckKey }) => withSchemaQueue(async () => {
|
|
662
676
|
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, tableId);
|
|
663
677
|
if (!confirm) {
|
|
664
678
|
return {
|
|
@@ -673,6 +687,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
673
687
|
}, null, 2) }],
|
|
674
688
|
};
|
|
675
689
|
}
|
|
690
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
676
691
|
const result = await fetchAPI(ENFYRA_API_URL, `/enfyra_table/${tableId}`, {
|
|
677
692
|
method: 'DELETE',
|
|
678
693
|
});
|
|
@@ -720,8 +735,10 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
720
735
|
defaultValue: z.string().optional().describe('New default value as JSON string.'),
|
|
721
736
|
description: z.string().optional().describe('New description.'),
|
|
722
737
|
options: z.string().optional().describe('New options as JSON string.'),
|
|
738
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
723
739
|
},
|
|
724
|
-
async ({ tableId, columnId, name, type, isNullable, isPublished, isUpdatable, defaultValue, description, options }) => withSchemaQueue(async () => {
|
|
740
|
+
async ({ tableId, columnId, name, type, isNullable, isPublished, isUpdatable, defaultValue, description, options, globalRulesAckKey }) => withSchemaQueue(async () => {
|
|
741
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
725
742
|
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, tableId);
|
|
726
743
|
if (!tableData) {
|
|
727
744
|
return { content: [{ type: 'text', text: `Error: Table with ID ${tableId} not found.` }] };
|
package/src/mcp-server-entry.mjs
CHANGED
|
@@ -27,9 +27,11 @@ import {
|
|
|
27
27
|
assertDynamicCodeKnowledgeAck,
|
|
28
28
|
assertDynamicCodeKnowledgeAckIf,
|
|
29
29
|
assertExtensionKnowledgeAckIf,
|
|
30
|
+
assertGlobalRulesAck,
|
|
30
31
|
buildRequiredKnowledgePayload,
|
|
31
32
|
dynamicCodeKnowledgeAckParam,
|
|
32
33
|
extensionKnowledgeAckParam,
|
|
34
|
+
globalRulesAckParam,
|
|
33
35
|
} from './lib/required-knowledge.js';
|
|
34
36
|
import { validateMainTableRoutePath } from './lib/route-guards.js';
|
|
35
37
|
import { installColumnarToolFormatter, jsonContent } from './lib/response-format.js';
|
|
@@ -1471,9 +1473,11 @@ server.tool('create_record', 'Create a new record in any route-backed table. The
|
|
|
1471
1473
|
tableName: z.string().describe('Table name to insert into'),
|
|
1472
1474
|
data: z.string().describe('Record data as JSON string'),
|
|
1473
1475
|
queryParams: z.string().optional().describe('Optional query params as JSON object string, e.g. {"expired_at":"2026-09-20"}. Use for route contracts that intentionally keep workflow fields out of the validated body.'),
|
|
1476
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1474
1477
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required only when data contains sourceCode. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
1475
1478
|
extensionKnowledgeAckKey: extensionKnowledgeAckParam(z).optional().describe('Required only when tableName is enfyra_extension and data contains code. Use extensionAckKey from get_enfyra_required_knowledge.'),
|
|
1476
|
-
}, async ({ tableName, data, queryParams, knowledgeAckKey, extensionKnowledgeAckKey }) => {
|
|
1479
|
+
}, async ({ tableName, data, queryParams, globalRulesAckKey, knowledgeAckKey, extensionKnowledgeAckKey }) => {
|
|
1480
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1477
1481
|
validateTableName(tableName);
|
|
1478
1482
|
assertKnowledgeForGenericMutation(tableName, data, { knowledgeAckKey, extensionKnowledgeAckKey });
|
|
1479
1483
|
const prepared = await prepareGenericMutation(tableName, data);
|
|
@@ -1490,9 +1494,11 @@ server.tool('update_record', 'Update an existing record by ID using PATCH. The t
|
|
|
1490
1494
|
id: z.string().describe('Record ID to update'),
|
|
1491
1495
|
data: z.string().describe('Fields to update as JSON string'),
|
|
1492
1496
|
queryParams: z.string().optional().describe('Optional query params as JSON object string for route contracts that intentionally keep workflow fields out of the validated body.'),
|
|
1497
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1493
1498
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required only when data contains sourceCode. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
1494
1499
|
extensionKnowledgeAckKey: extensionKnowledgeAckParam(z).optional().describe('Required only when tableName is enfyra_extension and data contains code. Use extensionAckKey from get_enfyra_required_knowledge.'),
|
|
1495
|
-
}, async ({ tableName, id, data, queryParams, knowledgeAckKey, extensionKnowledgeAckKey }) => {
|
|
1500
|
+
}, async ({ tableName, id, data, queryParams, globalRulesAckKey, knowledgeAckKey, extensionKnowledgeAckKey }) => {
|
|
1501
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1496
1502
|
validateTableName(tableName);
|
|
1497
1503
|
assertKnowledgeForGenericMutation(tableName, data, { knowledgeAckKey, extensionKnowledgeAckKey });
|
|
1498
1504
|
const prepared = await prepareGenericMutation(tableName, data);
|
|
@@ -1548,9 +1554,10 @@ server.tool(
|
|
|
1548
1554
|
expectedSourceSha256: z.string().optional().describe('Optional SHA-256 from get_script_source; fails if source changed.'),
|
|
1549
1555
|
scriptLanguage: z.string().optional().describe('Script language to save. Defaults to existing scriptLanguage or javascript.'),
|
|
1550
1556
|
apply: z.boolean().optional().default(false).describe('false returns preview only; true validates and saves.'),
|
|
1557
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when apply=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
1551
1558
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required when apply=true. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
1552
1559
|
},
|
|
1553
|
-
async ({ tableName, id, oldText, newText, occurrence, expectedSourceSha256, scriptLanguage, apply, knowledgeAckKey }) => {
|
|
1560
|
+
async ({ tableName, id, oldText, newText, occurrence, expectedSourceSha256, scriptLanguage, apply, globalRulesAckKey, knowledgeAckKey }) => {
|
|
1554
1561
|
const { record, sourceField, sourceCode } = await fetchScriptRecord(tableName, id);
|
|
1555
1562
|
if (sourceField !== 'sourceCode') {
|
|
1556
1563
|
throw new Error(`patch_script_source only saves sourceCode records. Record uses "${sourceField}"; use update_record intentionally for this legacy field.`);
|
|
@@ -1581,6 +1588,7 @@ server.tool(
|
|
|
1581
1588
|
if (!apply) {
|
|
1582
1589
|
return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
|
|
1583
1590
|
}
|
|
1591
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1584
1592
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
1585
1593
|
const language = scriptLanguage || record.scriptLanguage || 'javascript';
|
|
1586
1594
|
const prepared = await prepareGenericMutation(
|
|
@@ -1623,9 +1631,11 @@ server.tool(
|
|
|
1623
1631
|
id: z.string().describe('Record ID to update'),
|
|
1624
1632
|
sourceCode: z.string().describe('Editable script sourceCode. Pass the raw code string; do not JSON-escape it yourself.'),
|
|
1625
1633
|
scriptLanguage: z.string().optional().default('javascript').describe('Script language, usually javascript or typescript'),
|
|
1634
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1626
1635
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
1627
1636
|
},
|
|
1628
|
-
async ({ tableName, id, sourceCode, scriptLanguage, knowledgeAckKey }) => {
|
|
1637
|
+
async ({ tableName, id, sourceCode, scriptLanguage, globalRulesAckKey, knowledgeAckKey }) => {
|
|
1638
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1629
1639
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
1630
1640
|
validateTableName(tableName);
|
|
1631
1641
|
const prepared = await prepareGenericMutation(
|
|
@@ -1652,7 +1662,8 @@ server.tool('delete_record', 'Delete a record by ID', {
|
|
|
1652
1662
|
id: z.string().describe('Record ID to delete'),
|
|
1653
1663
|
queryParams: z.string().optional().describe('Optional query params as JSON object string for route-specific confirmation contracts.'),
|
|
1654
1664
|
confirm: z.boolean().optional().default(false).describe('Required true to apply the destructive delete. Omit/false returns a preview only.'),
|
|
1655
|
-
|
|
1665
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when confirm=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
1666
|
+
}, async ({ tableName, id, queryParams, confirm, globalRulesAckKey }) => {
|
|
1656
1667
|
validateTableName(tableName);
|
|
1657
1668
|
const primaryKey = await getPrimaryFieldName(tableName);
|
|
1658
1669
|
if (!confirm) {
|
|
@@ -1673,6 +1684,7 @@ server.tool('delete_record', 'Delete a record by ID', {
|
|
|
1673
1684
|
next: 'Call delete_record again with confirm=true to delete this route-backed record.',
|
|
1674
1685
|
}, null, 2) }] };
|
|
1675
1686
|
}
|
|
1687
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1676
1688
|
const query = parseQueryParamsArg(queryParams);
|
|
1677
1689
|
const result = await fetchAPI(ENFYRA_API_URL, appendQuery(`/${tableName}/${id}`, query), { method: 'DELETE' });
|
|
1678
1690
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
@@ -1713,8 +1725,10 @@ server.tool(
|
|
|
1713
1725
|
buttonColor: z.string().describe('Badge background color as full hex, e.g. #dbeafe.'),
|
|
1714
1726
|
textColor: z.string().describe('Badge text color as full hex, e.g. #1d4ed8.'),
|
|
1715
1727
|
isSystem: z.boolean().optional().default(false).describe('Set true only for built-in/runtime-owned methods. Normal app methods should leave this false.'),
|
|
1728
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1716
1729
|
},
|
|
1717
|
-
async ({ method, buttonColor, textColor, isSystem }) => {
|
|
1730
|
+
async ({ method, buttonColor, textColor, isSystem, globalRulesAckKey }) => {
|
|
1731
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1718
1732
|
const normalizedMethod = normalizeMethodNameInput(method);
|
|
1719
1733
|
const existing = await findMethodRecordByName(normalizedMethod);
|
|
1720
1734
|
if (existing) {
|
|
@@ -1747,8 +1761,10 @@ server.tool(
|
|
|
1747
1761
|
method: z.string().optional().describe('Existing method name to find, or new name when id is provided.'),
|
|
1748
1762
|
buttonColor: z.string().optional().describe('Badge background color as full hex, e.g. #dbeafe.'),
|
|
1749
1763
|
textColor: z.string().optional().describe('Badge text color as full hex, e.g. #1d4ed8.'),
|
|
1764
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1750
1765
|
},
|
|
1751
|
-
async ({ id, method, buttonColor, textColor }) => {
|
|
1766
|
+
async ({ id, method, buttonColor, textColor, globalRulesAckKey }) => {
|
|
1767
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1752
1768
|
let targetId = id;
|
|
1753
1769
|
let existing = null;
|
|
1754
1770
|
if (!targetId) {
|
|
@@ -1793,8 +1809,9 @@ server.tool(
|
|
|
1793
1809
|
id: z.string().optional().describe('Method record id. If omitted, method is used to find the record.'),
|
|
1794
1810
|
method: z.string().optional().describe('Method name to find when id is omitted.'),
|
|
1795
1811
|
confirm: z.boolean().optional().default(false).describe('Required true to apply the destructive delete. Omit/false returns a preview only.'),
|
|
1812
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when confirm=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
1796
1813
|
},
|
|
1797
|
-
async ({ id, method, confirm }) => {
|
|
1814
|
+
async ({ id, method, confirm, globalRulesAckKey }) => {
|
|
1798
1815
|
let targetId = id;
|
|
1799
1816
|
let target = null;
|
|
1800
1817
|
if (!targetId) {
|
|
@@ -1820,6 +1837,7 @@ server.tool(
|
|
|
1820
1837
|
next: 'Call delete_method again with confirm=true to delete.',
|
|
1821
1838
|
}, null, 2) }] };
|
|
1822
1839
|
}
|
|
1840
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1823
1841
|
const result = await fetchAPI(ENFYRA_API_URL, `/enfyra_method/${encodeURIComponent(String(targetId))}`, { method: 'DELETE' });
|
|
1824
1842
|
_methodMap = null;
|
|
1825
1843
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
@@ -2449,8 +2467,10 @@ server.tool(
|
|
|
2449
2467
|
.describe('Methods accessible WITHOUT auth token. Omit = all methods require auth.'),
|
|
2450
2468
|
isEnabled: z.boolean().optional().default(true).describe('Enable route immediately'),
|
|
2451
2469
|
description: z.string().optional().describe('Route description'),
|
|
2470
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2452
2471
|
},
|
|
2453
|
-
async ({ path: routePath, mainTableId, methods, publicMethods, isEnabled, description }) => {
|
|
2472
|
+
async ({ path: routePath, mainTableId, methods, publicMethods, isEnabled, description, globalRulesAckKey }) => {
|
|
2473
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2454
2474
|
const methodMap = await getMethodMap();
|
|
2455
2475
|
const normalizedPath = normalizeRestPath(routePath);
|
|
2456
2476
|
|
|
@@ -2513,9 +2533,11 @@ server.tool(
|
|
|
2513
2533
|
sourceCode: z.string().describe('Handler JavaScript sourceCode. Do not use logic; backend CRUD rejects logic.'),
|
|
2514
2534
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language for compiler. Default javascript.'),
|
|
2515
2535
|
timeout: z.number().optional().describe('Timeout in ms (default: system DEFAULT_HANDLER_TIMEOUT, usually 30000)'),
|
|
2536
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2516
2537
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
2517
2538
|
},
|
|
2518
|
-
async ({ routeId, method, methods, sourceCode, scriptLanguage, timeout, knowledgeAckKey }) => {
|
|
2539
|
+
async ({ routeId, method, methods, sourceCode, scriptLanguage, timeout, globalRulesAckKey, knowledgeAckKey }) => {
|
|
2540
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2519
2541
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
2520
2542
|
const methodNames = methods && methods.length > 0 ? methods : method ? [method] : [];
|
|
2521
2543
|
if (methodNames.length === 0) throw new Error('Provide method or methods');
|
|
@@ -2576,9 +2598,11 @@ server.tool(
|
|
|
2576
2598
|
.describe('Method names this hook applies to. Default: built-in REST methods GET, POST, PATCH, DELETE.'),
|
|
2577
2599
|
priority: z.number().optional().default(0).describe('Execution order (lower = first)'),
|
|
2578
2600
|
isEnabled: z.boolean().optional().default(true).describe('Enable hook immediately'),
|
|
2601
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2579
2602
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
2580
2603
|
},
|
|
2581
|
-
async ({ routeId, name, code, scriptLanguage, methods, priority, isEnabled, knowledgeAckKey }) => {
|
|
2604
|
+
async ({ routeId, name, code, scriptLanguage, methods, priority, isEnabled, globalRulesAckKey, knowledgeAckKey }) => {
|
|
2605
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2582
2606
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
2583
2607
|
const methodMap = await getMethodMap();
|
|
2584
2608
|
const methodNames = methods || ['GET', 'POST', 'PATCH', 'DELETE'];
|
|
@@ -2632,9 +2656,11 @@ server.tool(
|
|
|
2632
2656
|
.describe('Method names this hook applies to. Default: built-in REST methods GET, POST, PATCH, DELETE.'),
|
|
2633
2657
|
priority: z.number().optional().default(0).describe('Execution order (lower = first)'),
|
|
2634
2658
|
isEnabled: z.boolean().optional().default(true).describe('Enable hook immediately'),
|
|
2659
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2635
2660
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
2636
2661
|
},
|
|
2637
|
-
async ({ routeId, name, code, scriptLanguage, methods, priority, isEnabled, knowledgeAckKey }) => {
|
|
2662
|
+
async ({ routeId, name, code, scriptLanguage, methods, priority, isEnabled, globalRulesAckKey, knowledgeAckKey }) => {
|
|
2663
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2638
2664
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
2639
2665
|
const methodMap = await getMethodMap();
|
|
2640
2666
|
const methodNames = methods || ['GET', 'POST', 'PATCH', 'DELETE'];
|
|
@@ -2751,8 +2777,10 @@ server.tool(
|
|
|
2751
2777
|
mode: z.enum(['merge', 'replace']).optional().default('merge').describe('merge adds methods to an existing permission; replace overwrites methods on the matched permission.'),
|
|
2752
2778
|
description: z.string().optional().describe('Admin note'),
|
|
2753
2779
|
isEnabled: z.boolean().optional().default(true).describe('Enable the permission'),
|
|
2780
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2754
2781
|
},
|
|
2755
|
-
async ({ path, routeId, methods, roleId, roleName, allowedUserIds, mode, description, isEnabled }) => {
|
|
2782
|
+
async ({ path, routeId, methods, roleId, roleName, allowedUserIds, mode, description, isEnabled, globalRulesAckKey }) => {
|
|
2783
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2756
2784
|
if (!path && !routeId) throw new Error('Provide path or routeId.');
|
|
2757
2785
|
if (path && routeId) throw new Error('Provide path or routeId, not both.');
|
|
2758
2786
|
if (roleId && roleName) throw new Error('Provide roleId or roleName, not both.');
|
|
@@ -2852,22 +2880,34 @@ registerPlatformOperationTools(server, ENFYRA_API_URL);
|
|
|
2852
2880
|
// CACHE & SYSTEM TOOLS
|
|
2853
2881
|
// ============================================================================
|
|
2854
2882
|
|
|
2855
|
-
server.tool('reload_all', 'Reload all caches (metadata, routes, GraphQL)', {
|
|
2883
|
+
server.tool('reload_all', 'Reload all caches (metadata, routes, GraphQL)', {
|
|
2884
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2885
|
+
}, async ({ globalRulesAckKey }) => {
|
|
2886
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2856
2887
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload', { method: 'POST' });
|
|
2857
2888
|
return { content: [{ type: 'text', text: `System reloaded:\n${JSON.stringify(result, null, 2)}` }] };
|
|
2858
2889
|
});
|
|
2859
2890
|
|
|
2860
|
-
server.tool('reload_metadata', 'Reload metadata cache only', {
|
|
2891
|
+
server.tool('reload_metadata', 'Reload metadata cache only', {
|
|
2892
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2893
|
+
}, async ({ globalRulesAckKey }) => {
|
|
2894
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2861
2895
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload/metadata', { method: 'POST' });
|
|
2862
2896
|
return { content: [{ type: 'text', text: `Metadata reloaded:\n${JSON.stringify(result, null, 2)}` }] };
|
|
2863
2897
|
});
|
|
2864
2898
|
|
|
2865
|
-
server.tool('reload_routes', 'Reload routes cache only', {
|
|
2899
|
+
server.tool('reload_routes', 'Reload routes cache only', {
|
|
2900
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2901
|
+
}, async ({ globalRulesAckKey }) => {
|
|
2902
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2866
2903
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload/routes', { method: 'POST' });
|
|
2867
2904
|
return { content: [{ type: 'text', text: `Routes reloaded:\n${JSON.stringify(result, null, 2)}` }] };
|
|
2868
2905
|
});
|
|
2869
2906
|
|
|
2870
|
-
server.tool('reload_graphql', 'Reload GraphQL schema', {
|
|
2907
|
+
server.tool('reload_graphql', 'Reload GraphQL schema', {
|
|
2908
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2909
|
+
}, async ({ globalRulesAckKey }) => {
|
|
2910
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2871
2911
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload/graphql', { method: 'POST' });
|
|
2872
2912
|
return { content: [{ type: 'text', text: `GraphQL reloaded:\n${JSON.stringify(result, null, 2)}` }] };
|
|
2873
2913
|
});
|
|
@@ -3015,8 +3055,10 @@ server.tool(
|
|
|
3015
3055
|
name: z.string().describe('Exact NPM package name (e.g., "node-ssh", "axios")'),
|
|
3016
3056
|
type: z.enum(['Server', 'App']).default('Server').describe('Where to install: Server (handlers/hooks) or App (extensions)'),
|
|
3017
3057
|
version: z.string().optional().describe('Specific version. If omitted, fetches latest from NPM.'),
|
|
3058
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
3018
3059
|
},
|
|
3019
|
-
async ({ name, type, version }) => {
|
|
3060
|
+
async ({ name, type, version, globalRulesAckKey }) => {
|
|
3061
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
3020
3062
|
// Step 1: Get package info from NPM if version not specified
|
|
3021
3063
|
let pkgVersion = version;
|
|
3022
3064
|
let pkgDescription = '';
|