@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.
@@ -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)),
@@ -1408,7 +1441,8 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1408
1441
  'create_api_endpoint',
1409
1442
  [
1410
1443
  'Business operation: create or update a custom REST endpoint with a handler in one safe operation.',
1411
- 'Use this when the user asks for a new route/endpoint/API path that computes or orchestrates behavior, such as GET /sum or POST /webhook.',
1444
+ 'Prefer api_endpoint_workflow when route access, role/user permissions, overwrite decisions, or multi-step planning matter.',
1445
+ 'Use this one-shot helper only when the endpoint contract is already clear and no authenticated route-permission step is needed in the same operation, such as a simple public webhook or private admin-only utility that will be granted separately.',
1412
1446
  'It creates the route without mainTableId, ensures the method is available, validates sourceCode, creates or overwrites the route handler, optionally makes the method public, reloads routes, and can smoke-test the endpoint.',
1413
1447
  'Use table/schema tools separately when the user needs persisted data. This tool is for custom behavior endpoints.',
1414
1448
  ].join(' '),
@@ -1423,9 +1457,11 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1423
1457
  overwrite: z.boolean().optional().default(false).describe('If a handler already exists for route+method, false fails; true updates its sourceCode.'),
1424
1458
  smokeTestQuery: z.string().optional().describe('Optional query JSON object for a smoke test after save, e.g. {"a":"1","b":"2"}.'),
1425
1459
  smokeTestBody: z.string().optional().describe('Optional body JSON object for a smoke test after save.'),
1460
+ globalRulesAckKey: globalRulesAckParam(z),
1426
1461
  knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
1427
1462
  },
1428
- async ({ path, method, sourceCode, scriptLanguage, public: makePublic, description, timeout, overwrite, smokeTestQuery, smokeTestBody, knowledgeAckKey }) => {
1463
+ async ({ path, method, sourceCode, scriptLanguage, public: makePublic, description, timeout, overwrite, smokeTestQuery, smokeTestBody, globalRulesAckKey, knowledgeAckKey }) => {
1464
+ assertGlobalRulesAck(globalRulesAckKey);
1429
1465
  assertDynamicCodeKnowledgeAck(knowledgeAckKey);
1430
1466
  const normalizedPath = normalizeRestPath(path);
1431
1467
  const methodName = normalizeMethodName(method);
@@ -1556,8 +1592,10 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1556
1592
  message: z.string().optional().describe('Custom validation error message.'),
1557
1593
  description: z.string().optional().describe('Admin note.'),
1558
1594
  isEnabled: z.boolean().optional().default(true).describe('Enable the rule.'),
1595
+ globalRulesAckKey: globalRulesAckParam(z),
1559
1596
  },
1560
- async ({ tableName, columnName, ruleType, value, message, description, isEnabled }) => {
1597
+ async ({ tableName, columnName, ruleType, value, message, description, isEnabled, globalRulesAckKey }) => {
1598
+ assertGlobalRulesAck(globalRulesAckKey);
1561
1599
  const table = resolveTable(await getMetadataTables(ENFYRA_API_URL), tableName);
1562
1600
  const column = resolveColumn(table, columnName);
1563
1601
  const existing = await findRecord(ENFYRA_API_URL, 'enfyra_column_rule', {
@@ -1597,8 +1635,10 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1597
1635
  condition: z.string().optional().describe('Condition JSON object using field permission DSL.'),
1598
1636
  description: z.string().optional().describe('Admin note.'),
1599
1637
  isEnabled: z.boolean().optional().default(true).describe('Enable the permission.'),
1638
+ globalRulesAckKey: globalRulesAckParam(z),
1600
1639
  },
1601
- async ({ tableName, columnName, relationName, action, effect, roleId, roleName, allowedUserIds, condition, description, isEnabled }) => {
1640
+ async ({ tableName, columnName, relationName, action, effect, roleId, roleName, allowedUserIds, condition, description, isEnabled, globalRulesAckKey }) => {
1641
+ assertGlobalRulesAck(globalRulesAckKey);
1602
1642
  if (!!columnName === !!relationName) throw new Error('Provide exactly one of columnName or relationName.');
1603
1643
  assertOneScope({ roleId, roleName, allowedUserIds });
1604
1644
  const [tables, role] = await Promise.all([
@@ -1656,8 +1696,10 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1656
1696
  description: z.string().optional().describe('Admin note.'),
1657
1697
  rules: z.string().optional().describe('Rules JSON array: [{type, config, priority, isEnabled, description, userIds}].'),
1658
1698
  rulesMode: z.enum(['append', 'replace', 'none']).optional().default('append').describe('append creates rules, replace disables existing rules first, none leaves rules unchanged.'),
1699
+ globalRulesAckKey: globalRulesAckParam(z),
1659
1700
  },
1660
- async ({ name, guardId, position, routeId, path, methods, combinator, priority, isGlobal, isEnabled, description, rules, rulesMode }) => {
1701
+ async ({ name, guardId, position, routeId, path, methods, combinator, priority, isGlobal, isEnabled, description, rules, rulesMode, globalRulesAckKey }) => {
1702
+ assertGlobalRulesAck(globalRulesAckKey);
1661
1703
  if (path && routeId) throw new Error('Provide path or routeId, not both.');
1662
1704
  const ruleInputs = parseJsonArrayArg('rules', rules, []);
1663
1705
  if (position === 'pre_auth') {
@@ -1733,9 +1775,11 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1733
1775
  scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language for connection handler.'),
1734
1776
  isEnabled: z.boolean().optional().default(true).describe('Enable gateway.'),
1735
1777
  description: z.string().optional().describe('Admin note.'),
1778
+ globalRulesAckKey: globalRulesAckParam(z),
1736
1779
  knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required when sourceCode is provided. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
1737
1780
  },
1738
- async ({ path, sourceCode, scriptLanguage, isEnabled, description, knowledgeAckKey }) => {
1781
+ async ({ path, sourceCode, scriptLanguage, isEnabled, description, globalRulesAckKey, knowledgeAckKey }) => {
1782
+ assertGlobalRulesAck(globalRulesAckKey);
1739
1783
  assertDynamicCodeKnowledgeAckIf(sourceCode !== undefined, knowledgeAckKey);
1740
1784
  const normalizedPath = normalizeRestPath(path);
1741
1785
  const validation = sourceCode === undefined
@@ -1765,9 +1809,11 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1765
1809
  scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language.'),
1766
1810
  isEnabled: z.boolean().optional().default(true).describe('Enable event.'),
1767
1811
  description: z.string().optional().describe('Admin note.'),
1812
+ globalRulesAckKey: globalRulesAckParam(z),
1768
1813
  knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
1769
1814
  },
1770
- async ({ gatewayPath, gatewayId, eventName, sourceCode, scriptLanguage, isEnabled, description, knowledgeAckKey }) => {
1815
+ async ({ gatewayPath, gatewayId, eventName, sourceCode, scriptLanguage, isEnabled, description, globalRulesAckKey, knowledgeAckKey }) => {
1816
+ assertGlobalRulesAck(globalRulesAckKey);
1771
1817
  assertDynamicCodeKnowledgeAck(knowledgeAckKey);
1772
1818
  if (!gatewayPath && !gatewayId) throw new Error('Provide gatewayPath or gatewayId.');
1773
1819
  if (gatewayPath && gatewayId) throw new Error('Provide gatewayPath or gatewayId, not both.');
@@ -1802,14 +1848,16 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1802
1848
  maxExecutions: z.number().int().positive().optional().default(100).describe('Execution history cap.'),
1803
1849
  isEnabled: z.boolean().optional().default(true).describe('Enable flow.'),
1804
1850
  description: z.string().optional().describe('Admin note.'),
1851
+ globalRulesAckKey: globalRulesAckParam(z),
1805
1852
  },
1806
- async ({ name, timeout, maxExecutions, isEnabled, description }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
1853
+ async ({ name, timeout, maxExecutions, isEnabled, description, globalRulesAckKey }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
1807
1854
  name,
1808
1855
  triggerType: 'manual',
1809
1856
  timeout,
1810
1857
  maxExecutions,
1811
1858
  isEnabled,
1812
1859
  description,
1860
+ globalRulesAckKey,
1813
1861
  })),
1814
1862
  );
1815
1863
 
@@ -1823,8 +1871,9 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1823
1871
  maxExecutions: z.number().int().positive().optional().default(100).describe('Execution history cap.'),
1824
1872
  isEnabled: z.boolean().optional().default(true).describe('Enable flow.'),
1825
1873
  description: z.string().optional().describe('Admin note.'),
1874
+ globalRulesAckKey: globalRulesAckParam(z),
1826
1875
  },
1827
- async ({ name, triggerConfig, timeout, maxExecutions, isEnabled, description }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
1876
+ async ({ name, triggerConfig, timeout, maxExecutions, isEnabled, description, globalRulesAckKey }) => jsonText(await ensureFlow(ENFYRA_API_URL, {
1828
1877
  name,
1829
1878
  triggerType: 'schedule',
1830
1879
  triggerConfig,
@@ -1832,6 +1881,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1832
1881
  maxExecutions,
1833
1882
  isEnabled,
1834
1883
  description,
1884
+ globalRulesAckKey,
1835
1885
  })),
1836
1886
  );
1837
1887
 
@@ -1870,6 +1920,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1870
1920
  scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language.'),
1871
1921
  timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
1872
1922
  isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
1923
+ globalRulesAckKey: globalRulesAckParam(z),
1873
1924
  knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
1874
1925
  },
1875
1926
  async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
@@ -1891,6 +1942,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1891
1942
  scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language.'),
1892
1943
  timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
1893
1944
  isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
1945
+ globalRulesAckKey: globalRulesAckParam(z),
1894
1946
  knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
1895
1947
  },
1896
1948
  async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
@@ -1910,6 +1962,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1910
1962
  order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
1911
1963
  timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
1912
1964
  isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
1965
+ globalRulesAckKey: globalRulesAckParam(z),
1913
1966
  },
1914
1967
  async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
1915
1968
  ...input,
@@ -1928,6 +1981,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1928
1981
  order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
1929
1982
  timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
1930
1983
  isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
1984
+ globalRulesAckKey: globalRulesAckParam(z),
1931
1985
  },
1932
1986
  async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
1933
1987
  ...input,
@@ -1946,6 +2000,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1946
2000
  order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
1947
2001
  timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
1948
2002
  isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
2003
+ globalRulesAckKey: globalRulesAckParam(z),
1949
2004
  },
1950
2005
  async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
1951
2006
  ...input,
@@ -1964,6 +2019,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1964
2019
  order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
1965
2020
  timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
1966
2021
  isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
2022
+ globalRulesAckKey: globalRulesAckParam(z),
1967
2023
  },
1968
2024
  async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
1969
2025
  ...input,
@@ -1982,6 +2038,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
1982
2038
  order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
1983
2039
  timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
1984
2040
  isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
2041
+ globalRulesAckKey: globalRulesAckParam(z),
1985
2042
  },
1986
2043
  async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
1987
2044
  ...input,
@@ -2000,6 +2057,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
2000
2057
  order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
2001
2058
  timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
2002
2059
  isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
2060
+ globalRulesAckKey: globalRulesAckParam(z),
2003
2061
  },
2004
2062
  async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
2005
2063
  ...input,
@@ -2018,6 +2076,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
2018
2076
  order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
2019
2077
  timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
2020
2078
  isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
2079
+ globalRulesAckKey: globalRulesAckParam(z),
2021
2080
  },
2022
2081
  async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
2023
2082
  ...input,
@@ -2036,6 +2095,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
2036
2095
  order: z.number().optional().default(0).describe('Step order. Saved as enfyra_flow_step.stepOrder.'),
2037
2096
  timeout: z.number().int().positive().optional().describe('Step timeout in ms.'),
2038
2097
  isEnabled: z.boolean().optional().default(true).describe('Enable step.'),
2098
+ globalRulesAckKey: globalRulesAckParam(z),
2039
2099
  },
2040
2100
  async (input) => jsonText(await ensureFlowStep(ENFYRA_API_URL, {
2041
2101
  ...input,
@@ -2055,6 +2115,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
2055
2115
  permission: z.string().optional().describe('Menu permission JSON object.'),
2056
2116
  description: z.string().optional().describe('Admin note.'),
2057
2117
  isEnabled: z.boolean().optional().default(true).describe('Enable menu.'),
2118
+ globalRulesAckKey: globalRulesAckParam(z),
2058
2119
  },
2059
2120
  async (input) => jsonText({
2060
2121
  action: 'menu_ensured',
@@ -2072,6 +2133,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
2072
2133
  description: z.string().optional().describe('Extension description.'),
2073
2134
  isEnabled: z.boolean().optional().default(true).describe('Enable extension.'),
2074
2135
  version: z.string().optional().default('1.0.0').describe('Extension version.'),
2136
+ globalRulesAckKey: globalRulesAckParam(z),
2075
2137
  extensionKnowledgeAckKey: extensionKnowledgeAckParam(z),
2076
2138
  },
2077
2139
  async (input) => jsonText({
@@ -2089,6 +2151,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
2089
2151
  description: z.string().optional().describe('Extension description.'),
2090
2152
  isEnabled: z.boolean().optional().default(true).describe('Enable extension.'),
2091
2153
  version: z.string().optional().default('1.0.0').describe('Extension version.'),
2154
+ globalRulesAckKey: globalRulesAckParam(z),
2092
2155
  extensionKnowledgeAckKey: extensionKnowledgeAckParam(z),
2093
2156
  },
2094
2157
  async (input) => jsonText({
@@ -2106,6 +2169,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
2106
2169
  description: z.string().optional().describe('Extension description.'),
2107
2170
  isEnabled: z.boolean().optional().default(true).describe('Enable extension.'),
2108
2171
  version: z.string().optional().default('1.0.0').describe('Extension version.'),
2172
+ globalRulesAckKey: globalRulesAckParam(z),
2109
2173
  extensionKnowledgeAckKey: extensionKnowledgeAckParam(z),
2110
2174
  },
2111
2175
  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-06-30.secure-repo-v1';
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 generating or saving Enfyra dynamic server code through MCP.',
52
+ purpose: 'Read this before mutating Enfyra metadata, schema, routes, permissions, menus, packages, cache state, 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',