@enfyra/mcp-server 0.0.60 → 0.0.61

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfyra/mcp-server",
3
- "version": "0.0.60",
3
+ "version": "0.0.61",
4
4
  "description": "MCP server for Enfyra - manage your Enfyra instance via Claude Code",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -497,11 +497,11 @@ return @DATA\`
497
497
  tableName: "route_definition",
498
498
  id: "<route_id>",
499
499
  data: {
500
- publishedMethods: [{ id: 1 }]
500
+ publishedMethods: [{ id: "<GET_method_id_from_list_methods>" }]
501
501
  }
502
502
  })`,
503
503
  notes: [
504
- 'Method id 1 is GET. Use method_definition if you need to confirm method ids.',
504
+ 'Method ids are instance data. Use list_methods or inspect_route output to resolve the GET method id first.',
505
505
  'publishedMethods controls anonymous route access. Route permissions are not for public access.',
506
506
  'Route permissions apply when the method is not public.',
507
507
  ],
@@ -802,6 +802,7 @@ update_method({
802
802
  })`,
803
803
  notes: [
804
804
  'Use dedicated method tools instead of generic CRUD on method_definition.',
805
+ 'The backend stores the method label in method_definition.name; do not send or filter a method_definition.method field.',
805
806
  'buttonColor is the badge background and textColor is the badge text color.',
806
807
  'The eApp management UI is /settings/methods.',
807
808
  'delete_method is preview-first and should only be used for unused custom methods.',
@@ -114,9 +114,10 @@ export function buildMcpServerInstructions(apiBaseUrl) {
114
114
  '- **mainTable warning:** do not set `mainTable` on custom routes. It is reserved for canonical table routes only.',
115
115
  ' - **Many-to-one:** `"someRelation": {"id": 4}` (single object with id)',
116
116
  ' - **One-to-many / many-to-many:** `"publishedMethods": [{"id": 1}, {"id": 2}]` (array of objects with id)',
117
- '- **Method IDs** (for REST route publishedMethods, availableMethods, skipRoleGuardMethods): GET=1, POST=2, PATCH=3, DELETE=4. Query `method_definition` table if unsure.',
117
+ '- **Method IDs** are instance data, not a stable contract. Query `method_definition` or use method names through MCP route helpers before setting `publishedMethods`, `availableMethods`, `skipRoleGuardMethods`, hook methods, handler methods, or route permissions. Default CRUD records are `GET`, `POST`, `PATCH`, and `DELETE`; create extra records such as `PUT` through method tools when a route needs them.',
118
+ '- `method_definition.name` is the unique backend field for the HTTP method label. Do not filter, create, or update a `method_definition.method` field. MCP method tools accept an input named `method` for usability, but they write/read `name` on the server.',
118
119
  '- **Wrong:** `"publishedMethods": ["GET"]` or `"publishedMethods": [{"method": "GET"}]` — rejected or silently ignored.',
119
- '- **Right:** `"publishedMethods": [{"id": 1}]` (publishes GET). Multiple: `[{"id": 1}, {"id": 2}]` (publishes GET + POST).',
120
+ '- **Right:** first query method records, then pass their ids, for example `"publishedMethods": [{"id": <GET_METHOD_ID>}]`. Multiple methods use multiple id objects.',
120
121
  '- **To unset:** pass empty array `"publishedMethods": []`.',
121
122
  '',
122
123
  '### Dynamic script `$repos` mutation return shape',
@@ -298,6 +299,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
298
299
  '- **Operational list data loading:** do not use arbitrary fixed limits such as `limit=50` as the whole data strategy for admin pages. Use pagination, expose result count when the API supports `meta=filterCount`, and add search/filter controls for natural lookup keys such as id, name, slug, status, email, or external reference.',
299
300
  '- **ESV aggregate contract:** aggregate query must be an object keyed by a real field or relation, for example `aggregate: { id: { count: true }, status: { count: { _eq: "failed" } }, amount: { sum: true } }`. Results are returned in `response.meta.aggregate`. Time windows and cross-field conditions belong in top-level `filter`, not inside a field aggregate condition. Field aggregate conditions only support operators on that same field; relation aggregates use `countRecords`.',
300
301
  '- **Aggregate numeric rule:** `sum` and `avg` require a numeric field in ESV. Do not aggregate money stored as varchar/text. Use a numeric money field such as `amount_usd` with type `float`, `amount_cents`, or `amount` for revenue stats, or build a dedicated stats route that normalizes legacy values explicitly. If metadata says `float` but SQL aggregate still fails with `sum(character varying)`, the Enfyra Server physical schema is stale or missing the SQL float DDL mapping and must be redeployed/healed before relying on aggregate.',
302
+ '- **Snapshot migrations:** backend metadata/physical schema renames belong in `data/snapshot-migration.json` via table-driven `columnsToModify` entries. The server migration/self-heal path should read table name plus `oldName`/`newName` dynamically; do not hard-code one-off table repairs when the snapshot migration contract can express the change.',
301
303
  '- **Partial reload default:** ESV/ASV automatically triggers partial reloads for metadata, routes, menus, extensions, flows, handlers, and related caches after successful writes. Do not reflexively call `/admin/reload`, `/admin/reload/metadata`, or `/admin/reload/routes` after each change. Verify naturally first; use manual reload only when verification shows stale behavior, a reload event failed, or a concrete error indicates the partial reload did not apply.',
302
304
  '- **Menu/extension realtime reload contract:** `menu_definition` and `extension_definition` writes are runtime UI changes, not plain CRUD. The server cache orchestrator must emit `$system:reload` through the admin Socket.IO channel with identifiers that eApp handles; eApp must refetch menus/rebuild the menu registry for menu reloads and invalidate dynamic extension caches for extension reloads. Menu reloads can change route-to-extension mapping, so they should also invalidate extension cache. If an open admin tab does not reflect menu/extension changes, debug this two-sided reload contract before telling the user to refresh.',
303
305
  '- **Dashboard stats:** time range buttons must change the query filter and reload stats. Dashboards should summarize actionable errors and high-level activity; successful/no-error background runs usually do not need a standalone page unless there is a real workflow to manage.',
@@ -306,7 +308,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
306
308
  '- **Do not misuse PageHeader stats:** `PageHeader.stats` renders prominent stat cards inside the shell header. Do not put normal operational KPIs, capacity totals, billing totals, or detail metrics there by default; keep those as body cards/tables where the operator can scan them with the page content. Only use PageHeader stats for a deliberately compact overview page where the stats are truly header-level context.',
307
309
  '- **Page actions belong in registries:** Move page-level buttons into `useHeaderActionRegistry` or `useSubHeaderActionRegistry`; keep the extension body for operational content only. Sensitive registry actions must include a `permission` condition, for example `{ id: "create", label: "Create report", permission: { and: [{ route: "/report_definition", methods: ["POST"] }] }, onClick }`.',
308
310
  '- **Header action button variants:** choose the button variant by intent. Use `color: "primary", variant: "solid"` for the main page action. Use `color: "neutral", variant: "ghost"` for back/navigation actions and `color: "neutral", variant: "outline"` for visible secondary actions. `variant: "soft"` is only for low-emphasis secondary/chrome actions; do not use soft for critical or primary header actions just because it looks acceptable in dark mode.',
309
- '- **HTTP method management:** use the dedicated MCP tools `list_methods`, `create_method`, `update_method`, and preview-first `delete_method` for `method_definition`. The eApp UI for the same records is `/settings/methods`. Method color fields are `buttonColor` for badge background and `textColor` for badge text, both full hex colors. Do not use generic `create_record` on `method_definition` unless the dedicated tool is unavailable.',
311
+ '- **HTTP method management:** use the dedicated MCP tools `list_methods`, `create_method`, `update_method`, and preview-first `delete_method` for `method_definition`. The backend field is `method_definition.name`, unique per method; do not send `method_definition.method`. The eApp UI for the same records is `/settings/methods`. Method color fields are `buttonColor` for badge background and `textColor` for badge text, both full hex colors. Do not use generic `create_record` on `method_definition` unless the dedicated tool is unavailable.',
310
312
  '- **Extension navigation:** prefer `NuxtLink` or Nuxt UI components with `:to` for visible navigation links and drill-down cards/buttons. Use `navigateTo(...)` only for imperative navigation after submit, confirm, mutation, or another side effect.',
311
313
  '- **Extension runtime scope:** eApp exposes Vue APIs and injected Nuxt/Enfyra composables both to script global scope and Vue app `globalProperties`. Template expressions may call injected helpers directly, for example after a save handler can call `navigateTo("/data/report_definition")`, because Vue compiles template helpers to `_ctx.*`.',
312
314
  '- **Extension CSS affects shell utility ordering:** dynamic extension CSS is injected after the app shell CSS. Shell/page-header code must not put conflicting plain Tailwind utilities on the same element, such as `flex-col` plus `flex-row`, `items-start` plus `items-center`, or `text-left` plus `text-center`. Choose one mutually exclusive class per state; otherwise extension CSS can change which utility wins and shift shell layout.',
@@ -34,7 +34,7 @@ const CAPABILITY_AREAS = [
34
34
  {
35
35
  area: 'Dynamic REST API',
36
36
  tables: ['route_definition', 'route_handler_definition', 'pre_hook_definition', 'post_hook_definition', 'route_permission_definition', 'method_definition'],
37
- workflow: 'Create custom paths with create_route without mainTableId, then add handlers/hooks. mainTableId is only for canonical table routes like /table_name. REST methods are GET/POST/PATCH/DELETE.',
37
+ workflow: 'Create custom paths with create_route without mainTableId, then add handlers/hooks. mainTableId is only for canonical table routes like /table_name. Query method_definition before assigning route methods.',
38
38
  },
39
39
  {
40
40
  area: 'Auth, roles, sessions, OAuth',
@@ -197,8 +197,8 @@ function summarizeRoutes(routesResult) {
197
197
  id: route.id ?? route._id,
198
198
  path: route.path,
199
199
  mainTable: route.mainTable?.name || route.mainTableName || null,
200
- availableMethods: (route.availableMethods || []).map((method) => method.method).filter(Boolean),
201
- publishedMethods: (route.publishedMethods || []).map((method) => method.method).filter(Boolean),
200
+ availableMethods: (route.availableMethods || []).map((method) => method.name).filter(Boolean),
201
+ publishedMethods: (route.publishedMethods || []).map((method) => method.name).filter(Boolean),
202
202
  isEnabled: route.isEnabled,
203
203
  }));
204
204
  }
@@ -376,8 +376,8 @@ function normalizeHexColorInput(value, fieldName) {
376
376
  }
377
377
 
378
378
  async function findMethodRecordByName(method) {
379
- const filter = encodeURIComponent(JSON.stringify({ method: { _eq: method } }));
380
- const result = await fetchAPI(ENFYRA_API_URL, `/method_definition?filter=${filter}&limit=1&fields=id,_id,method,buttonColor,textColor,isSystem`);
379
+ const filter = encodeURIComponent(JSON.stringify({ name: { _eq: method } }));
380
+ const result = await fetchAPI(ENFYRA_API_URL, `/method_definition?filter=${filter}&limit=1&fields=id,_id,name,buttonColor,textColor,isSystem`);
381
381
  return unwrapData(result)[0] || null;
382
382
  }
383
383
 
@@ -476,7 +476,7 @@ server.tool(
476
476
  routes: routes.length,
477
477
  methods: methodsResult?.data?.length || 0,
478
478
  },
479
- methods: (methodsResult?.data || []).map((method) => ({ id: method.id || method._id, method: method.method })),
479
+ methods: (methodsResult?.data || []).map((method) => ({ id: method.id || method._id, name: method.name, method: method.name })),
480
480
  capabilityAreas: CAPABILITY_AREAS.map((item) => ({
481
481
  ...item,
482
482
  presentTables: item.tables.filter((table) => tableNames.includes(table)),
@@ -579,7 +579,7 @@ server.tool(
579
579
  storageConfigs: storageResult?.data?.length || 0,
580
580
  settings: settingsResult?.data?.length || 0,
581
581
  },
582
- methods: (methodsResult?.data || []).map((method) => ({ id: method.id || method._id, method: method.method })),
582
+ methods: (methodsResult?.data || []).map((method) => ({ id: method.id || method._id, name: method.name, method: method.name })),
583
583
  routeRuntime: {
584
584
  routePattern: 'GET/POST /<route-path>; PATCH/DELETE /<route-path>/:id; no dynamic GET /<route-path>/:id.',
585
585
  adminRoutes: adminRoutes.map((route) => route.path).sort(),
@@ -1050,10 +1050,11 @@ server.tool(
1050
1050
  'List method_definition records with their UI colors. Use this before creating route methods or method-colored UI.',
1051
1051
  {},
1052
1052
  async () => {
1053
- const result = await fetchAPI(ENFYRA_API_URL, '/method_definition?fields=id,_id,method,buttonColor,textColor,isSystem&sort=method&limit=0');
1053
+ const result = await fetchAPI(ENFYRA_API_URL, '/method_definition?fields=id,_id,name,buttonColor,textColor,isSystem&sort=name&limit=0');
1054
1054
  const methods = unwrapData(result).map((method) => ({
1055
1055
  id: getId(method),
1056
- method: method.method,
1056
+ name: method.name,
1057
+ method: method.name,
1057
1058
  buttonColor: method.buttonColor,
1058
1059
  textColor: method.textColor,
1059
1060
  isSystem: method.isSystem === true,
@@ -1082,7 +1083,7 @@ server.tool(
1082
1083
  throw new Error(`Method ${normalizedMethod} already exists with id ${getId(existing)}. Use update_method to change colors.`);
1083
1084
  }
1084
1085
  const body = {
1085
- method: normalizedMethod,
1086
+ name: normalizedMethod,
1086
1087
  buttonColor: normalizeHexColorInput(buttonColor, 'buttonColor'),
1087
1088
  textColor: normalizeHexColorInput(textColor, 'textColor'),
1088
1089
  isSystem: isSystem === true,
@@ -1094,6 +1095,7 @@ server.tool(
1094
1095
  _methodMap = null;
1095
1096
  return { content: [{ type: 'text', text: JSON.stringify({
1096
1097
  ...summarizeMutationResult(result, 'created', 'method_definition'),
1098
+ name: normalizedMethod,
1097
1099
  method: normalizedMethod,
1098
1100
  appUi: '/settings/methods',
1099
1101
  }, null, 2) }] };
@@ -1128,7 +1130,7 @@ server.tool(
1128
1130
  body.textColor = normalizeHexColorInput(textColor, 'textColor');
1129
1131
  }
1130
1132
  if (method !== undefined && id) {
1131
- body.method = normalizeMethodNameInput(method);
1133
+ body.name = normalizeMethodNameInput(method);
1132
1134
  }
1133
1135
  if (Object.keys(body).length === 0) {
1134
1136
  throw new Error('Provide buttonColor, textColor, or a new method name.');
@@ -1168,13 +1170,14 @@ server.tool(
1168
1170
  if (!target) {
1169
1171
  const primaryKey = await getPrimaryFieldName('method_definition');
1170
1172
  const filter = encodeURIComponent(JSON.stringify({ [primaryKey]: { _eq: targetId } }));
1171
- const result = await fetchAPI(ENFYRA_API_URL, `/method_definition?filter=${filter}&limit=1&fields=id,_id,method,buttonColor,textColor,isSystem`);
1173
+ const result = await fetchAPI(ENFYRA_API_URL, `/method_definition?filter=${filter}&limit=1&fields=id,_id,name,buttonColor,textColor,isSystem`);
1172
1174
  target = unwrapData(result)[0] || null;
1173
1175
  }
1174
1176
  return { content: [{ type: 'text', text: JSON.stringify({
1175
1177
  action: 'delete_method_preview',
1176
1178
  id: targetId,
1177
- method: target?.method,
1179
+ name: target?.name,
1180
+ method: target?.name,
1178
1181
  isSystem: target?.isSystem === true,
1179
1182
  destructive: true,
1180
1183
  warning: 'Only delete unused custom methods. Deleting a method can affect route method relations.',
@@ -1265,7 +1268,7 @@ async function getMethodMap() {
1265
1268
  const result = await fetchAPI(ENFYRA_API_URL, '/method_definition?limit=0');
1266
1269
  _methodMap = {};
1267
1270
  for (const m of result.data) {
1268
- _methodMap[m.method] = m.id || m._id;
1271
+ _methodMap[m.name] = m.id || m._id;
1269
1272
  }
1270
1273
  return _methodMap;
1271
1274
  }
@@ -1289,7 +1292,8 @@ function withMethodNames(records, methodIdNameMap, field = 'methods') {
1289
1292
  [field]: Array.isArray(record?.[field])
1290
1293
  ? record[field].map((item) => ({
1291
1294
  ...item,
1292
- method: item.method || methodIdNameMap[String(getId(item))] || null,
1295
+ name: item.name || methodIdNameMap[String(getId(item))] || null,
1296
+ method: item.name || methodIdNameMap[String(getId(item))] || null,
1293
1297
  }))
1294
1298
  : record?.[field],
1295
1299
  }));
@@ -1344,7 +1348,11 @@ function enrichRoute(route, state) {
1344
1348
  .filter((item) => sameId(refId(item.route), routeId))
1345
1349
  .map((item) => pickCodeSummary({
1346
1350
  ...item,
1347
- method: item.method ? { ...item.method, method: state.methodIdNameMap[String(getId(item.method))] || item.method.method || null } : item.method,
1351
+ method: item.method ? {
1352
+ ...item.method,
1353
+ name: state.methodIdNameMap[String(getId(item.method))] || item.method.name || null,
1354
+ method: state.methodIdNameMap[String(getId(item.method))] || item.method.name || null,
1355
+ } : item.method,
1348
1356
  }, 'sourceCode'));
1349
1357
  const routePreHooks = withMethodNames(
1350
1358
  state.preHooks.filter((item) => item.isGlobal || sameId(refId(item.route), routeId)),
@@ -1369,13 +1377,25 @@ function enrichRoute(route, state) {
1369
1377
  return {
1370
1378
  ...route,
1371
1379
  availableMethods: Array.isArray(route.availableMethods)
1372
- ? route.availableMethods.map((method) => ({ ...method, method: method.method || state.methodIdNameMap[String(getId(method))] || null }))
1380
+ ? route.availableMethods.map((method) => ({
1381
+ ...method,
1382
+ name: method.name || state.methodIdNameMap[String(getId(method))] || null,
1383
+ method: method.name || state.methodIdNameMap[String(getId(method))] || null,
1384
+ }))
1373
1385
  : route.availableMethods,
1374
1386
  publishedMethods: Array.isArray(route.publishedMethods)
1375
- ? route.publishedMethods.map((method) => ({ ...method, method: method.method || state.methodIdNameMap[String(getId(method))] || null }))
1387
+ ? route.publishedMethods.map((method) => ({
1388
+ ...method,
1389
+ name: method.name || state.methodIdNameMap[String(getId(method))] || null,
1390
+ method: method.name || state.methodIdNameMap[String(getId(method))] || null,
1391
+ }))
1376
1392
  : route.publishedMethods,
1377
1393
  skipRoleGuardMethods: Array.isArray(route.skipRoleGuardMethods)
1378
- ? route.skipRoleGuardMethods.map((method) => ({ ...method, method: method.method || state.methodIdNameMap[String(getId(method))] || null }))
1394
+ ? route.skipRoleGuardMethods.map((method) => ({
1395
+ ...method,
1396
+ name: method.name || state.methodIdNameMap[String(getId(method))] || null,
1397
+ method: method.name || state.methodIdNameMap[String(getId(method))] || null,
1398
+ }))
1379
1399
  : route.skipRoleGuardMethods,
1380
1400
  handlers: routeHandlers,
1381
1401
  preHooks: routePreHooks,
@@ -1536,7 +1556,7 @@ server.tool(
1536
1556
  'Use this after inspecting a route or changing handlers/hooks/guards. Pass paths like /table_definition?limit=1, not external URLs.',
1537
1557
  ].join(' '),
1538
1558
  {
1539
- method: z.enum(['GET', 'POST', 'PATCH', 'DELETE']).default('GET').describe('HTTP method'),
1559
+ method: z.string().optional().default('GET').describe('HTTP method name. Must exist in method_definition.name for Enfyra route-backed calls.'),
1540
1560
  path: z.string().describe('Enfyra API path, e.g. /route_definition?limit=1'),
1541
1561
  query: z.string().optional().describe('Optional query params JSON object, merged onto path query string'),
1542
1562
  body: z.string().optional().describe('Optional JSON request body string'),
@@ -1544,6 +1564,7 @@ server.tool(
1544
1564
  useAuth: z.boolean().optional().default(true).describe('Attach MCP admin Bearer token. Set false to test published/public access.'),
1545
1565
  },
1546
1566
  async ({ method, path, query, body, headers, useAuth }) => {
1567
+ const httpMethod = normalizeMethodNameInput(method || 'GET');
1547
1568
  const restPath = normalizeRestPath(path);
1548
1569
  const url = new URL(`${ENFYRA_API_URL.replace(/\/$/, '')}${restPath}`);
1549
1570
  const queryObj = parseJsonArg(query, {});
@@ -1561,9 +1582,9 @@ server.tool(
1561
1582
 
1562
1583
  const started = Date.now();
1563
1584
  const response = await fetch(url, {
1564
- method,
1585
+ method: httpMethod,
1565
1586
  headers: requestHeaders,
1566
- ...(body !== undefined && body !== null && method !== 'GET' ? { body } : {}),
1587
+ ...(body !== undefined && body !== null && httpMethod !== 'GET' ? { body } : {}),
1567
1588
  });
1568
1589
  const contentType = response.headers.get('content-type') || '';
1569
1590
  const responseText = await response.text();
@@ -1574,7 +1595,7 @@ server.tool(
1574
1595
 
1575
1596
  const payload = {
1576
1597
  request: {
1577
- method,
1598
+ method: httpMethod,
1578
1599
  url: url.toString(),
1579
1600
  authenticated: !!useAuth,
1580
1601
  },
@@ -1641,9 +1662,9 @@ server.tool(
1641
1662
  {
1642
1663
  path: z.string().describe('URL path, must start with / (e.g., "/my-endpoint")'),
1643
1664
  mainTableId: z.union([z.string(), z.number()]).optional().describe('Only set for the canonical table route `/<table_name>`. Omit for every custom route.'),
1644
- methods: z.array(z.enum(['GET', 'POST', 'PATCH', 'DELETE']))
1645
- .describe('HTTP methods this route supports (availableMethods). Common: ["GET","POST","PATCH","DELETE"]'),
1646
- publishedMethods: z.array(z.enum(['GET', 'POST', 'PATCH', 'DELETE'])).optional()
1665
+ methods: z.array(z.string())
1666
+ .describe('HTTP method names this route supports (availableMethods). Each value must exist in method_definition.name. Common: ["GET","POST","PATCH","DELETE"].'),
1667
+ publishedMethods: z.array(z.string()).optional()
1647
1668
  .describe('Methods accessible WITHOUT auth token. Omit = all methods require auth.'),
1648
1669
  isEnabled: z.boolean().optional().default(true).describe('Enable route immediately'),
1649
1670
  description: z.string().optional().describe('Route description'),
@@ -1687,7 +1708,7 @@ server.tool(
1687
1708
  publishedMethods: publishedMethods || [],
1688
1709
  },
1689
1710
  routesReloaded: true,
1690
- next: `Use create_handler({ routeId: ${JSON.stringify(getId(created))}, method: "GET"|"POST"|"PATCH"|"DELETE", sourceCode }) for custom code.`,
1711
+ next: `Use create_handler({ routeId: ${JSON.stringify(getId(created))}, method: "GET", sourceCode }) for custom code. Create extra method_definition.name rows first for custom methods such as PUT.`,
1691
1712
  }, null, 2) }] };
1692
1713
  },
1693
1714
  );
@@ -1704,9 +1725,9 @@ server.tool(
1704
1725
  ].join(' '),
1705
1726
  {
1706
1727
  routeId: z.union([z.string(), z.number()]).describe('Route definition ID'),
1707
- method: z.enum(['GET', 'POST', 'PATCH', 'DELETE']).optional()
1708
- .describe('Single method to create. Prefer this for one handler.'),
1709
- methods: z.array(z.enum(['GET', 'POST', 'PATCH', 'DELETE'])).optional()
1728
+ method: z.string().optional()
1729
+ .describe('Single method_definition.name to create. Prefer this for one handler.'),
1730
+ methods: z.array(z.string()).optional()
1710
1731
  .describe('Batch create multiple handlers. Use only when the same sourceCode applies to every method.'),
1711
1732
  sourceCode: z.string().describe('Handler JavaScript sourceCode. Do not use logic; backend CRUD rejects logic.'),
1712
1733
  scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language for compiler. Default javascript.'),
@@ -1768,8 +1789,8 @@ server.tool(
1768
1789
  name: z.string().describe('Hook name (unique per route)'),
1769
1790
  code: z.string().describe('Hook JavaScript sourceCode. MCP stores it as sourceCode and lets Enfyra compile compiledCode.'),
1770
1791
  scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language for compiler. Default javascript.'),
1771
- methods: z.array(z.enum(['GET', 'POST', 'PATCH', 'DELETE'])).optional()
1772
- .describe('Methods this hook applies to. Default: all REST methods.'),
1792
+ methods: z.array(z.string()).optional()
1793
+ .describe('Method names this hook applies to. Default: built-in REST methods GET, POST, PATCH, DELETE.'),
1773
1794
  priority: z.number().optional().default(0).describe('Execution order (lower = first)'),
1774
1795
  isEnabled: z.boolean().optional().default(true).describe('Enable hook immediately'),
1775
1796
  },
@@ -1822,8 +1843,8 @@ server.tool(
1822
1843
  name: z.string().describe('Hook name (unique per route)'),
1823
1844
  code: z.string().describe('Hook JavaScript sourceCode. MCP stores it as sourceCode and lets Enfyra compile compiledCode.'),
1824
1845
  scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language for compiler. Default javascript.'),
1825
- methods: z.array(z.enum(['GET', 'POST', 'PATCH', 'DELETE'])).optional()
1826
- .describe('Methods this hook applies to. Default: all REST methods.'),
1846
+ methods: z.array(z.string()).optional()
1847
+ .describe('Method names this hook applies to. Default: built-in REST methods GET, POST, PATCH, DELETE.'),
1827
1848
  priority: z.number().optional().default(0).describe('Execution order (lower = first)'),
1828
1849
  isEnabled: z.boolean().optional().default(true).describe('Enable hook immediately'),
1829
1850
  },
@@ -1954,7 +1975,7 @@ server.tool(
1954
1975
  {
1955
1976
  path: z.string().optional().describe('Route path, e.g. /user_definition'),
1956
1977
  routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
1957
- methods: z.array(z.enum(['GET', 'POST', 'PATCH', 'DELETE'])).describe('REST methods this permission allows'),
1978
+ methods: z.array(z.string()).describe('REST method names this permission allows. Each value must exist in method_definition.name.'),
1958
1979
  roleId: z.union([z.string(), z.number()]).optional().describe('Role id scope'),
1959
1980
  allowedUserIds: z.array(z.union([z.string(), z.number()])).optional().describe('Specific user ids scope'),
1960
1981
  description: z.string().optional().describe('Admin note'),
@@ -1999,7 +2020,7 @@ server.tool(
1999
2020
  position: z.enum(['pre_auth', 'post_auth']).default('pre_auth').describe('Execution position for root guard'),
2000
2021
  routeId: z.union([z.string(), z.number()]).optional().describe('Optional route id'),
2001
2022
  path: z.string().optional().describe('Optional route path'),
2002
- methods: z.array(z.enum(['GET', 'POST', 'PATCH', 'DELETE'])).optional().describe('Methods this guard applies to. Empty means all configured behavior for route/global.'),
2023
+ methods: z.array(z.string()).optional().describe('Method names this guard applies to. Empty means all configured behavior for route/global.'),
2003
2024
  combinator: z.enum(['and', 'or']).default('and').describe('How child guards/rules combine'),
2004
2025
  priority: z.number().optional().default(0).describe('Lower runs first'),
2005
2026
  isGlobal: z.boolean().optional().default(false).describe('Apply globally instead of one route'),