@enfyra/mcp-server 0.0.107 → 0.0.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -231,7 +231,7 @@ Do not create custom login/logout/me routes that manually set Enfyra token cooki
|
|
|
231
231
|
|
|
232
232
|
The MCP server exposes tools for metadata discovery, examples, query/CRUD, method management, route lifecycle, route access audit/grant, routes, handlers, hooks, tables, columns, relations, cache reloads, logs, users, roles, packages, menus, extensions, scripts, flows, websocket, files, and `get_enfyra_api_context`.
|
|
233
233
|
|
|
234
|
-
Routes have two separate controls. `isEnabled` controls runtime registration: disabled routes return `404`. Use `enable_route` and `disable_route` for this lifecycle. `publicMethods` controls anonymous access for enabled routes; use `public_route_methods
|
|
234
|
+
Routes have two separate controls. `isEnabled` controls runtime registration: disabled routes return `404`. Use `enable_route` and `disable_route` for this lifecycle. `publicMethods` controls anonymous access for enabled routes; use `public_route_methods` and `private_route_methods` for that access boundary.
|
|
235
235
|
|
|
236
236
|
For authenticated route access, use `audit_route_access` before changing permissions and `ensure_route_access` to grant access by route path plus role/user. For production script edits, use `trace_metadata_usage`, `get_script_source`, and `patch_script_source` so changes are targeted, hash-checked, and validated.
|
|
237
237
|
|
package/package.json
CHANGED
|
@@ -36,7 +36,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
|
|
|
36
36
|
'',
|
|
37
37
|
'### Core Contracts',
|
|
38
38
|
'- Tool JSON responses use `responseFormat: "json+columnar-v1"`. Large arrays of objects may be encoded as `{ format: "columnar-v1", columns: [...], rows: [[...]], rowCount }` only when that is smaller than raw JSON; read each row value by matching `columns[index]` to `rows[n][index]`. Do not guess object keys inside `rows`. `compressionStats` estimates token savings and includes whether compression was applied; use it only when the user asks about savings.',
|
|
39
|
-
'- `query_table` and `
|
|
39
|
+
'- `query_table`, `get_all_routes`, and `get_all_tables` require explicit intent: pass `limit` for bounded reads or `all: true` for a complete list. Do not invent arbitrary limits such as 30 or 50.',
|
|
40
40
|
'- Read tools are minimal by default. Pass explicit `fields`; use metadata inspection before guessing field/relation names. Field exclusion mode exists: `fields=-compiledCode`, and `fields=id,-compiledCode` still means all readable fields except `compiledCode`.',
|
|
41
41
|
'- Mutations return ids/status by default. Re-read with `find_one_record` or `query_table` and explicit `fields` when the saved row matters.',
|
|
42
42
|
'- Dynamic repository reads use `filter`, not `where`: `@REPOS.table.find({ filter: {...} })`, `#table.find({ filter: {...} })`, and `exists(filter)`.',
|
|
@@ -72,7 +72,7 @@ export function rejectUnsafeRelationDefinitionPayload(tableName, payload) {
|
|
|
72
72
|
if (forbidden.length > 0) {
|
|
73
73
|
throw new Error(
|
|
74
74
|
`Do not send physical FK/junction fields to enfyra_relation: ${forbidden.join(', ')}. ` +
|
|
75
|
-
'Use create_relation
|
|
75
|
+
'Use create_relation with targetTable/type/propertyName; Enfyra derives physical columns.'
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -1047,22 +1047,6 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1047
1047
|
})),
|
|
1048
1048
|
);
|
|
1049
1049
|
|
|
1050
|
-
server.tool(
|
|
1051
|
-
'set_public_route_methods',
|
|
1052
|
-
'Business operation: replace a route publicMethods list exactly.',
|
|
1053
|
-
{
|
|
1054
|
-
path: z.string().optional().describe('Route path, e.g. /sum. Use either path or routeId.'),
|
|
1055
|
-
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
1056
|
-
methods: z.array(z.string()).describe('Exact HTTP method names that should be public. Use an empty array to make all methods private.'),
|
|
1057
|
-
},
|
|
1058
|
-
async ({ path, routeId, methods }) => jsonText(await updateRoutePublicMethods(ENFYRA_API_URL, {
|
|
1059
|
-
path,
|
|
1060
|
-
routeId,
|
|
1061
|
-
methods,
|
|
1062
|
-
mode: 'replace',
|
|
1063
|
-
})),
|
|
1064
|
-
);
|
|
1065
|
-
|
|
1066
1050
|
server.tool(
|
|
1067
1051
|
'private_route_methods',
|
|
1068
1052
|
'Business operation: make specific public route methods private again.',
|
package/src/lib/table-tools.js
CHANGED
|
@@ -381,7 +381,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
381
381
|
targetColumn: target,
|
|
382
382
|
preservedColumnIds: beforeIds.filter((id) => id !== String(columnId)),
|
|
383
383
|
destructive: true,
|
|
384
|
-
next: 'Call delete_column
|
|
384
|
+
next: 'Call delete_column again with confirm=true to drop the physical column and metadata.',
|
|
385
385
|
}, null, 2) }],
|
|
386
386
|
};
|
|
387
387
|
}
|
|
@@ -423,7 +423,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
423
423
|
targetRelation: target,
|
|
424
424
|
preservedRelationIds: beforeIds.filter((id) => id !== String(relationId)),
|
|
425
425
|
destructive: true,
|
|
426
|
-
next: 'Call delete_relation
|
|
426
|
+
next: 'Call delete_relation again with confirm=true to drop relation metadata and any derived FK/junction structures.',
|
|
427
427
|
}, null, 2) }],
|
|
428
428
|
};
|
|
429
429
|
}
|
|
@@ -699,18 +699,6 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
699
699
|
appendColumnToTable
|
|
700
700
|
);
|
|
701
701
|
|
|
702
|
-
server.tool(
|
|
703
|
-
'add_column',
|
|
704
|
-
[
|
|
705
|
-
'Alias for create_column. Add a column to an existing table through the canonical enfyra_table cascade.',
|
|
706
|
-
'Use this for schema additions, including hidden secret fields with isPublished=false.',
|
|
707
|
-
'Reads full table metadata and skips non-persisted generated/derived column metadata without id/_id when rebuilding the table columns payload.',
|
|
708
|
-
'Run schema changes sequentially — migration locks DB per operation.',
|
|
709
|
-
].join(' '),
|
|
710
|
-
columnCreateSchema,
|
|
711
|
-
appendColumnToTable
|
|
712
|
-
);
|
|
713
|
-
|
|
714
702
|
// ─── UPDATE COLUMN ───
|
|
715
703
|
|
|
716
704
|
server.tool(
|
|
@@ -788,18 +776,6 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
788
776
|
removeColumnFromTable
|
|
789
777
|
);
|
|
790
778
|
|
|
791
|
-
server.tool(
|
|
792
|
-
'remove_column',
|
|
793
|
-
[
|
|
794
|
-
'Alias for delete_column. Remove a column through the canonical enfyra_table cascade.',
|
|
795
|
-
'This drops the physical column. Confirm destructive schema changes before calling.',
|
|
796
|
-
'Reads full table metadata and skips non-persisted generated/derived column metadata without id/_id when rebuilding the table columns payload.',
|
|
797
|
-
'Run schema changes sequentially — migration locks DB per operation.',
|
|
798
|
-
].join(' '),
|
|
799
|
-
columnDeleteSchema,
|
|
800
|
-
removeColumnFromTable
|
|
801
|
-
);
|
|
802
|
-
|
|
803
779
|
// ─── CREATE RELATION ───
|
|
804
780
|
|
|
805
781
|
server.tool(
|
|
@@ -817,18 +793,6 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
817
793
|
appendRelationToTable
|
|
818
794
|
);
|
|
819
795
|
|
|
820
|
-
server.tool(
|
|
821
|
-
'add_relation',
|
|
822
|
-
[
|
|
823
|
-
'Alias for create_relation. Add a relation through the canonical enfyra_table cascade.',
|
|
824
|
-
'sourceTableId and targetTableId may be table ids, exact table names, or aliases.',
|
|
825
|
-
'Use relation propertyName only; never provide physical FK or junction column names.',
|
|
826
|
-
'Run schema changes sequentially — migration locks DB per operation.',
|
|
827
|
-
].join(' '),
|
|
828
|
-
relationCreateSchema,
|
|
829
|
-
appendRelationToTable
|
|
830
|
-
);
|
|
831
|
-
|
|
832
796
|
// ─── DELETE RELATION ───
|
|
833
797
|
|
|
834
798
|
server.tool(
|
|
@@ -844,13 +808,4 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
844
808
|
removeRelationFromTable
|
|
845
809
|
);
|
|
846
810
|
|
|
847
|
-
server.tool(
|
|
848
|
-
'remove_relation',
|
|
849
|
-
[
|
|
850
|
-
'Alias for delete_relation. Remove a relation through the canonical enfyra_table cascade.',
|
|
851
|
-
'This can drop FK columns or junction tables. Confirm destructive schema changes before calling.',
|
|
852
|
-
].join(' '),
|
|
853
|
-
relationDeleteSchema,
|
|
854
|
-
removeRelationFromTable
|
|
855
|
-
);
|
|
856
811
|
}
|
package/src/mcp-server-entry.mjs
CHANGED
|
@@ -2468,137 +2468,6 @@ server.tool(
|
|
|
2468
2468
|
},
|
|
2469
2469
|
);
|
|
2470
2470
|
|
|
2471
|
-
server.tool(
|
|
2472
|
-
'create_column_rule',
|
|
2473
|
-
[
|
|
2474
|
-
'Create a REST body validation rule for a table column.',
|
|
2475
|
-
'Use inspect_table first to confirm validateBody, column type, and existing rules. Rule value is JSON; common shape is {"v": ...}.',
|
|
2476
|
-
].join(' '),
|
|
2477
|
-
{
|
|
2478
|
-
tableName: z.string().describe('Table name or alias'),
|
|
2479
|
-
columnName: z.string().describe('Column name'),
|
|
2480
|
-
ruleType: z.enum(['min', 'max', 'minLength', 'maxLength', 'pattern', 'format', 'minItems', 'maxItems', 'custom']).describe('Validation rule type'),
|
|
2481
|
-
value: z.string().optional().describe('Rule payload JSON, e.g. {"v":10} or {"v":"email"}'),
|
|
2482
|
-
message: z.string().optional().describe('Custom validation error message'),
|
|
2483
|
-
description: z.string().optional().describe('Admin note'),
|
|
2484
|
-
isEnabled: z.boolean().optional().default(true).describe('Enable the rule immediately'),
|
|
2485
|
-
},
|
|
2486
|
-
async ({ tableName, columnName, ruleType, value, message, description, isEnabled }) => {
|
|
2487
|
-
const { tables } = await getMetadataTables();
|
|
2488
|
-
const table = resolveTableOrThrow(tables, tableName);
|
|
2489
|
-
const column = resolveFieldOrThrow(table, columnName, 'column');
|
|
2490
|
-
const body = {
|
|
2491
|
-
ruleType,
|
|
2492
|
-
value: parseJsonArg(value, null),
|
|
2493
|
-
message,
|
|
2494
|
-
description,
|
|
2495
|
-
isEnabled,
|
|
2496
|
-
column: { id: getId(column) },
|
|
2497
|
-
};
|
|
2498
|
-
const result = await fetchAPI(ENFYRA_API_URL, '/enfyra_column_rule', {
|
|
2499
|
-
method: 'POST',
|
|
2500
|
-
body: JSON.stringify(body),
|
|
2501
|
-
});
|
|
2502
|
-
return { content: [{ type: 'text', text: `Column rule created for ${table.name}.${column.name}.\n${JSON.stringify(result, null, 2)}` }] };
|
|
2503
|
-
},
|
|
2504
|
-
);
|
|
2505
|
-
|
|
2506
|
-
server.tool(
|
|
2507
|
-
'create_field_permission',
|
|
2508
|
-
[
|
|
2509
|
-
'Create a field permission for one column or relation.',
|
|
2510
|
-
'Exactly one of columnName or relationName is required. Scope requires roleId or allowedUserIds. Conditions use the field permission condition DSL, not the full query DSL.',
|
|
2511
|
-
].join(' '),
|
|
2512
|
-
{
|
|
2513
|
-
tableName: z.string().describe('Table name or alias'),
|
|
2514
|
-
columnName: z.string().optional().describe('Column name to protect'),
|
|
2515
|
-
relationName: z.string().optional().describe('Relation propertyName to protect'),
|
|
2516
|
-
action: z.enum(['read', 'create', 'update']).default('read').describe('Action this permission applies to'),
|
|
2517
|
-
effect: z.enum(['allow', 'deny']).default('allow').describe('Allow or deny this field action'),
|
|
2518
|
-
roleId: z.union([z.string(), z.number()]).optional().describe('Role id scope'),
|
|
2519
|
-
allowedUserIds: z.array(z.union([z.string(), z.number()])).optional().describe('Specific user ids scope'),
|
|
2520
|
-
condition: z.string().optional().describe('Optional condition JSON using field permission condition DSL'),
|
|
2521
|
-
description: z.string().optional().describe('Admin note'),
|
|
2522
|
-
isEnabled: z.boolean().optional().default(true).describe('Enable immediately'),
|
|
2523
|
-
},
|
|
2524
|
-
async ({ tableName, columnName, relationName, action, effect, roleId, allowedUserIds, condition, description, isEnabled }) => {
|
|
2525
|
-
if (!!columnName === !!relationName) throw new Error('Provide exactly one of columnName or relationName');
|
|
2526
|
-
if (!roleId && (!allowedUserIds || allowedUserIds.length === 0)) {
|
|
2527
|
-
throw new Error('Provide roleId or allowedUserIds');
|
|
2528
|
-
}
|
|
2529
|
-
const { tables } = await getMetadataTables();
|
|
2530
|
-
const table = resolveTableOrThrow(tables, tableName);
|
|
2531
|
-
const body = {
|
|
2532
|
-
isEnabled,
|
|
2533
|
-
description,
|
|
2534
|
-
action,
|
|
2535
|
-
effect,
|
|
2536
|
-
condition: parseJsonArg(condition, null),
|
|
2537
|
-
...(roleId ? { role: { id: roleId } } : {}),
|
|
2538
|
-
...(allowedUserIds?.length ? { allowedUsers: allowedUserIds.map((id) => ({ id })) } : {}),
|
|
2539
|
-
};
|
|
2540
|
-
if (columnName) {
|
|
2541
|
-
body.column = { id: getId(resolveFieldOrThrow(table, columnName, 'column')) };
|
|
2542
|
-
} else {
|
|
2543
|
-
body.relation = { id: getId(resolveFieldOrThrow(table, relationName, 'relation')) };
|
|
2544
|
-
}
|
|
2545
|
-
const result = await fetchAPI(ENFYRA_API_URL, '/enfyra_field_permission', {
|
|
2546
|
-
method: 'POST',
|
|
2547
|
-
body: JSON.stringify(body),
|
|
2548
|
-
});
|
|
2549
|
-
return { content: [{ type: 'text', text: `Field permission created on ${table.name}.${columnName || relationName}.\n${JSON.stringify(result, null, 2)}` }] };
|
|
2550
|
-
},
|
|
2551
|
-
);
|
|
2552
|
-
|
|
2553
|
-
server.tool(
|
|
2554
|
-
'create_route_permission',
|
|
2555
|
-
[
|
|
2556
|
-
'Create route access permission for a route and REST methods.',
|
|
2557
|
-
'Use this when a non-root role/user should access an authenticated route. publicMethods are for public access; route permissions are for authenticated role/user access.',
|
|
2558
|
-
].join(' '),
|
|
2559
|
-
{
|
|
2560
|
-
path: z.string().optional().describe('Route path, e.g. /enfyra_user'),
|
|
2561
|
-
routeId: z.union([z.string(), z.number()]).optional().describe('Route id. Use either path or routeId.'),
|
|
2562
|
-
methods: z.array(z.string()).describe('REST method names this permission allows. Each value must exist in enfyra_method.name.'),
|
|
2563
|
-
roleId: z.union([z.string(), z.number()]).optional().describe('Role id scope'),
|
|
2564
|
-
allowedUserIds: z.array(z.union([z.string(), z.number()])).optional().describe('Specific user ids scope'),
|
|
2565
|
-
description: z.string().optional().describe('Admin note'),
|
|
2566
|
-
isEnabled: z.boolean().optional().default(true).describe('Enable immediately'),
|
|
2567
|
-
},
|
|
2568
|
-
async ({ path, routeId, methods, roleId, allowedUserIds, description, isEnabled }) => {
|
|
2569
|
-
if (!path && !routeId) throw new Error('Provide path or routeId');
|
|
2570
|
-
if (!roleId && (!allowedUserIds || allowedUserIds.length === 0)) {
|
|
2571
|
-
throw new Error('Provide roleId or allowedUserIds');
|
|
2572
|
-
}
|
|
2573
|
-
const routes = await fetchAll('/enfyra_route?limit=1000');
|
|
2574
|
-
const route = routes.find((item) => (
|
|
2575
|
-
routeId ? sameId(getId(item), routeId) : item.path === normalizeRestPath(path)
|
|
2576
|
-
));
|
|
2577
|
-
if (!route) throw new Error(`Route not found: ${routeId || path}`);
|
|
2578
|
-
const methodMap = await getMethodMap();
|
|
2579
|
-
const body = {
|
|
2580
|
-
isEnabled,
|
|
2581
|
-
description,
|
|
2582
|
-
route: { id: getId(route) },
|
|
2583
|
-
methods: resolveMethodIds(methodMap, methods),
|
|
2584
|
-
...(roleId ? { role: { id: roleId } } : {}),
|
|
2585
|
-
...(allowedUserIds?.length ? { allowedUsers: allowedUserIds.map((id) => ({ id })) } : {}),
|
|
2586
|
-
};
|
|
2587
|
-
const result = await fetchAPI(ENFYRA_API_URL, '/enfyra_route_permission', {
|
|
2588
|
-
method: 'POST',
|
|
2589
|
-
body: JSON.stringify(body),
|
|
2590
|
-
});
|
|
2591
|
-
const routeReload = await reloadRoutesResult();
|
|
2592
|
-
return { content: [{ type: 'text', text: JSON.stringify({
|
|
2593
|
-
action: 'created',
|
|
2594
|
-
kind: 'route_permission',
|
|
2595
|
-
route: route.path,
|
|
2596
|
-
routeReload,
|
|
2597
|
-
result,
|
|
2598
|
-
}, null, 2) }] };
|
|
2599
|
-
},
|
|
2600
|
-
);
|
|
2601
|
-
|
|
2602
2471
|
server.tool(
|
|
2603
2472
|
'audit_route_access',
|
|
2604
2473
|
[
|
|
@@ -2772,74 +2641,6 @@ server.tool(
|
|
|
2772
2641
|
},
|
|
2773
2642
|
);
|
|
2774
2643
|
|
|
2775
|
-
server.tool(
|
|
2776
|
-
'create_guard',
|
|
2777
|
-
[
|
|
2778
|
-
'Create a metadata guard with optional rules for REST request gating.',
|
|
2779
|
-
'Root guards attach to one route by path/routeId or globally with isGlobal. pre_auth runs before JWT and only has IP/route context; post_auth runs after auth and can use user id.',
|
|
2780
|
-
'Rule types: rate_limit_by_ip, rate_limit_by_user, rate_limit_by_route, ip_whitelist, ip_blacklist. Rate limits use {"maxRequests":number,"perSeconds":number}; IP lists use {"ips":["127.0.0.1","10.0.0.0/24"]}.',
|
|
2781
|
-
'Do not use rate_limit_by_user or userIds on pre_auth guards. Create risky global/IP whitelist guards disabled first, then inspect and test before enabling.',
|
|
2782
|
-
].join(' '),
|
|
2783
|
-
{
|
|
2784
|
-
name: z.string().describe('Guard name'),
|
|
2785
|
-
position: z.enum(['pre_auth', 'post_auth']).default('pre_auth').describe('Execution position for root guard. pre_auth has only IP/route context; post_auth also has authenticated user id.'),
|
|
2786
|
-
routeId: z.union([z.string(), z.number()]).optional().describe('Optional route id'),
|
|
2787
|
-
path: z.string().optional().describe('Optional route path'),
|
|
2788
|
-
methods: z.array(z.string()).optional().describe('Method names this guard applies to. Empty means all configured behavior for route/global.'),
|
|
2789
|
-
combinator: z.enum(['and', 'or']).default('and').describe('How child guards/rules combine'),
|
|
2790
|
-
priority: z.number().optional().default(0).describe('Lower runs first'),
|
|
2791
|
-
isGlobal: z.boolean().optional().default(false).describe('Apply globally instead of one route'),
|
|
2792
|
-
isEnabled: z.boolean().optional().default(false).describe('Enable immediately. Default false to avoid accidental lockout.'),
|
|
2793
|
-
description: z.string().optional().describe('Admin note'),
|
|
2794
|
-
rules: z.string().optional().describe('Optional rules JSON array: [{type, config, priority?, isEnabled?, description?, userIds?}]. Supported types: rate_limit_by_ip, rate_limit_by_user, rate_limit_by_route, ip_whitelist, ip_blacklist.'),
|
|
2795
|
-
},
|
|
2796
|
-
async ({ name, position, routeId, path, methods, combinator, priority, isGlobal, isEnabled, description, rules }) => {
|
|
2797
|
-
let route = null;
|
|
2798
|
-
if (!isGlobal && (routeId || path)) {
|
|
2799
|
-
const routes = await fetchAll('/enfyra_route?limit=1000');
|
|
2800
|
-
route = routes.find((item) => (
|
|
2801
|
-
routeId ? sameId(getId(item), routeId) : item.path === normalizeRestPath(path)
|
|
2802
|
-
));
|
|
2803
|
-
if (!route) throw new Error(`Route not found: ${routeId || path}`);
|
|
2804
|
-
}
|
|
2805
|
-
const methodMap = await getMethodMap();
|
|
2806
|
-
const guardBody = {
|
|
2807
|
-
name,
|
|
2808
|
-
position,
|
|
2809
|
-
combinator,
|
|
2810
|
-
priority,
|
|
2811
|
-
isGlobal,
|
|
2812
|
-
isEnabled,
|
|
2813
|
-
description,
|
|
2814
|
-
...(route ? { route: { id: getId(route) } } : {}),
|
|
2815
|
-
...(methods?.length ? { methods: resolveMethodIds(methodMap, methods) } : {}),
|
|
2816
|
-
};
|
|
2817
|
-
const guard = await fetchAPI(ENFYRA_API_URL, '/enfyra_guard', {
|
|
2818
|
-
method: 'POST',
|
|
2819
|
-
body: JSON.stringify(guardBody),
|
|
2820
|
-
});
|
|
2821
|
-
const ruleInputs = parseJsonArg(rules, []);
|
|
2822
|
-
const createdRules = [];
|
|
2823
|
-
for (const rule of ruleInputs) {
|
|
2824
|
-
const ruleBody = {
|
|
2825
|
-
type: rule.type,
|
|
2826
|
-
config: rule.config,
|
|
2827
|
-
priority: rule.priority ?? 0,
|
|
2828
|
-
isEnabled: rule.isEnabled ?? true,
|
|
2829
|
-
description: rule.description,
|
|
2830
|
-
guard: { id: resultRecordId(guard) },
|
|
2831
|
-
...(Array.isArray(rule.userIds) && rule.userIds.length ? { users: rule.userIds.map((id) => ({ id })) } : {}),
|
|
2832
|
-
};
|
|
2833
|
-
createdRules.push(await fetchAPI(ENFYRA_API_URL, '/enfyra_guard_rule', {
|
|
2834
|
-
method: 'POST',
|
|
2835
|
-
body: JSON.stringify(ruleBody),
|
|
2836
|
-
}));
|
|
2837
|
-
}
|
|
2838
|
-
await fetchAPI(ENFYRA_API_URL, '/admin/reload/guards', { method: 'POST' }).catch(() => {});
|
|
2839
|
-
return { content: [{ type: 'text', text: `Guard created. Guard cache reloaded.\n${JSON.stringify({ guard, rules: createdRules }, null, 2)}` }] };
|
|
2840
|
-
},
|
|
2841
|
-
);
|
|
2842
|
-
|
|
2843
2644
|
// Register table tools
|
|
2844
2645
|
registerTableTools(server, ENFYRA_API_URL);
|
|
2845
2646
|
registerPlatformOperationTools(server, ENFYRA_API_URL);
|