@enfyra/mcp-server 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/lib/mcp-examples.js +29 -20
- package/src/lib/mcp-instructions.js +1 -1
- package/src/lib/platform-operation-tools.js +85 -21
- package/src/lib/required-knowledge.js +63 -2
- package/src/lib/table-tools.js +86 -35
- package/src/mcp-server-entry.mjs +97 -48
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';
|
|
@@ -329,7 +331,7 @@ function summarizeRoutes(routesResult) {
|
|
|
329
331
|
}));
|
|
330
332
|
}
|
|
331
333
|
|
|
332
|
-
function summarizeMetadata(metadata, { search, limit } = {}) {
|
|
334
|
+
function summarizeMetadata(metadata, { search, limit, all = false } = {}) {
|
|
333
335
|
const tables = normalizeTables(metadata);
|
|
334
336
|
const q = search ? search.toLowerCase() : null;
|
|
335
337
|
const summarized = tables.map((table) => ({
|
|
@@ -344,11 +346,13 @@ function summarizeMetadata(metadata, { search, limit } = {}) {
|
|
|
344
346
|
const matched = q
|
|
345
347
|
? summarized.filter((table) => JSON.stringify(table).toLowerCase().includes(q))
|
|
346
348
|
: summarized;
|
|
347
|
-
const outputLimit = limit || 30;
|
|
349
|
+
const outputLimit = all ? matched.length : (limit || 30);
|
|
348
350
|
return {
|
|
349
351
|
tableCount: tables.length,
|
|
350
352
|
matchedTableCount: matched.length,
|
|
351
353
|
returnedTableCount: Math.min(matched.length, outputLimit),
|
|
354
|
+
complete: all || outputLimit >= matched.length,
|
|
355
|
+
hardCap: all ? null : outputLimit,
|
|
352
356
|
search: search || null,
|
|
353
357
|
tables: matched.slice(0, outputLimit),
|
|
354
358
|
};
|
|
@@ -805,15 +809,21 @@ server.tool('get_all_metadata', 'Get concise metadata summary for all tables. Us
|
|
|
805
809
|
includeFull: z.boolean().optional().default(false).describe('Return full raw metadata. Default false to keep MCP context small.'),
|
|
806
810
|
search: z.string().optional().describe('Optional table-name/alias substring filter.'),
|
|
807
811
|
limit: z.number().optional().describe('Maximum tables returned after search. Default 30.'),
|
|
808
|
-
|
|
812
|
+
all: z.boolean().optional().default(false).describe('Return every matched table summary. Use when a complete table list is required.'),
|
|
813
|
+
}, async ({ includeFull, search, limit, all }) => {
|
|
814
|
+
if (all && limit !== undefined) {
|
|
815
|
+
throw new Error('get_all_metadata accepts either all=true or limit, not both.');
|
|
816
|
+
}
|
|
809
817
|
const result = await fetchAPI(ENFYRA_API_URL, '/metadata');
|
|
810
818
|
const payload = includeFull
|
|
811
819
|
? result
|
|
812
820
|
: {
|
|
813
821
|
statusCode: result?.statusCode,
|
|
814
822
|
success: result?.success,
|
|
815
|
-
...summarizeMetadata(result, { search, limit }),
|
|
816
|
-
detailHint:
|
|
823
|
+
...summarizeMetadata(result, { search, limit, all }),
|
|
824
|
+
detailHint: all
|
|
825
|
+
? 'Complete summary returned. Call get_table_metadata({ tableName }) or inspect_table({ tableName }) for columns, relations, and route context.'
|
|
826
|
+
: 'Default response is capped and minimal. Pass all=true for a complete summary, or call get_table_metadata({ tableName }) / inspect_table({ tableName }) for detail.',
|
|
817
827
|
};
|
|
818
828
|
return jsonContent(payload);
|
|
819
829
|
});
|
|
@@ -1471,9 +1481,11 @@ server.tool('create_record', 'Create a new record in any route-backed table. The
|
|
|
1471
1481
|
tableName: z.string().describe('Table name to insert into'),
|
|
1472
1482
|
data: z.string().describe('Record data as JSON string'),
|
|
1473
1483
|
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.'),
|
|
1484
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1474
1485
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required only when data contains sourceCode. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
1475
1486
|
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 }) => {
|
|
1487
|
+
}, async ({ tableName, data, queryParams, globalRulesAckKey, knowledgeAckKey, extensionKnowledgeAckKey }) => {
|
|
1488
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1477
1489
|
validateTableName(tableName);
|
|
1478
1490
|
assertKnowledgeForGenericMutation(tableName, data, { knowledgeAckKey, extensionKnowledgeAckKey });
|
|
1479
1491
|
const prepared = await prepareGenericMutation(tableName, data);
|
|
@@ -1490,9 +1502,11 @@ server.tool('update_record', 'Update an existing record by ID using PATCH. The t
|
|
|
1490
1502
|
id: z.string().describe('Record ID to update'),
|
|
1491
1503
|
data: z.string().describe('Fields to update as JSON string'),
|
|
1492
1504
|
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.'),
|
|
1505
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1493
1506
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required only when data contains sourceCode. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
1494
1507
|
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 }) => {
|
|
1508
|
+
}, async ({ tableName, id, data, queryParams, globalRulesAckKey, knowledgeAckKey, extensionKnowledgeAckKey }) => {
|
|
1509
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1496
1510
|
validateTableName(tableName);
|
|
1497
1511
|
assertKnowledgeForGenericMutation(tableName, data, { knowledgeAckKey, extensionKnowledgeAckKey });
|
|
1498
1512
|
const prepared = await prepareGenericMutation(tableName, data);
|
|
@@ -1548,9 +1562,10 @@ server.tool(
|
|
|
1548
1562
|
expectedSourceSha256: z.string().optional().describe('Optional SHA-256 from get_script_source; fails if source changed.'),
|
|
1549
1563
|
scriptLanguage: z.string().optional().describe('Script language to save. Defaults to existing scriptLanguage or javascript.'),
|
|
1550
1564
|
apply: z.boolean().optional().default(false).describe('false returns preview only; true validates and saves.'),
|
|
1565
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when apply=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
1551
1566
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required when apply=true. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
1552
1567
|
},
|
|
1553
|
-
async ({ tableName, id, oldText, newText, occurrence, expectedSourceSha256, scriptLanguage, apply, knowledgeAckKey }) => {
|
|
1568
|
+
async ({ tableName, id, oldText, newText, occurrence, expectedSourceSha256, scriptLanguage, apply, globalRulesAckKey, knowledgeAckKey }) => {
|
|
1554
1569
|
const { record, sourceField, sourceCode } = await fetchScriptRecord(tableName, id);
|
|
1555
1570
|
if (sourceField !== 'sourceCode') {
|
|
1556
1571
|
throw new Error(`patch_script_source only saves sourceCode records. Record uses "${sourceField}"; use update_record intentionally for this legacy field.`);
|
|
@@ -1581,6 +1596,7 @@ server.tool(
|
|
|
1581
1596
|
if (!apply) {
|
|
1582
1597
|
return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
|
|
1583
1598
|
}
|
|
1599
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1584
1600
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
1585
1601
|
const language = scriptLanguage || record.scriptLanguage || 'javascript';
|
|
1586
1602
|
const prepared = await prepareGenericMutation(
|
|
@@ -1623,9 +1639,11 @@ server.tool(
|
|
|
1623
1639
|
id: z.string().describe('Record ID to update'),
|
|
1624
1640
|
sourceCode: z.string().describe('Editable script sourceCode. Pass the raw code string; do not JSON-escape it yourself.'),
|
|
1625
1641
|
scriptLanguage: z.string().optional().default('javascript').describe('Script language, usually javascript or typescript'),
|
|
1642
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1626
1643
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
1627
1644
|
},
|
|
1628
|
-
async ({ tableName, id, sourceCode, scriptLanguage, knowledgeAckKey }) => {
|
|
1645
|
+
async ({ tableName, id, sourceCode, scriptLanguage, globalRulesAckKey, knowledgeAckKey }) => {
|
|
1646
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1629
1647
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
1630
1648
|
validateTableName(tableName);
|
|
1631
1649
|
const prepared = await prepareGenericMutation(
|
|
@@ -1652,7 +1670,8 @@ server.tool('delete_record', 'Delete a record by ID', {
|
|
|
1652
1670
|
id: z.string().describe('Record ID to delete'),
|
|
1653
1671
|
queryParams: z.string().optional().describe('Optional query params as JSON object string for route-specific confirmation contracts.'),
|
|
1654
1672
|
confirm: z.boolean().optional().default(false).describe('Required true to apply the destructive delete. Omit/false returns a preview only.'),
|
|
1655
|
-
|
|
1673
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when confirm=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
1674
|
+
}, async ({ tableName, id, queryParams, confirm, globalRulesAckKey }) => {
|
|
1656
1675
|
validateTableName(tableName);
|
|
1657
1676
|
const primaryKey = await getPrimaryFieldName(tableName);
|
|
1658
1677
|
if (!confirm) {
|
|
@@ -1673,6 +1692,7 @@ server.tool('delete_record', 'Delete a record by ID', {
|
|
|
1673
1692
|
next: 'Call delete_record again with confirm=true to delete this route-backed record.',
|
|
1674
1693
|
}, null, 2) }] };
|
|
1675
1694
|
}
|
|
1695
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1676
1696
|
const query = parseQueryParamsArg(queryParams);
|
|
1677
1697
|
const result = await fetchAPI(ENFYRA_API_URL, appendQuery(`/${tableName}/${id}`, query), { method: 'DELETE' });
|
|
1678
1698
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
@@ -1713,8 +1733,10 @@ server.tool(
|
|
|
1713
1733
|
buttonColor: z.string().describe('Badge background color as full hex, e.g. #dbeafe.'),
|
|
1714
1734
|
textColor: z.string().describe('Badge text color as full hex, e.g. #1d4ed8.'),
|
|
1715
1735
|
isSystem: z.boolean().optional().default(false).describe('Set true only for built-in/runtime-owned methods. Normal app methods should leave this false.'),
|
|
1736
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1716
1737
|
},
|
|
1717
|
-
async ({ method, buttonColor, textColor, isSystem }) => {
|
|
1738
|
+
async ({ method, buttonColor, textColor, isSystem, globalRulesAckKey }) => {
|
|
1739
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1718
1740
|
const normalizedMethod = normalizeMethodNameInput(method);
|
|
1719
1741
|
const existing = await findMethodRecordByName(normalizedMethod);
|
|
1720
1742
|
if (existing) {
|
|
@@ -1747,8 +1769,10 @@ server.tool(
|
|
|
1747
1769
|
method: z.string().optional().describe('Existing method name to find, or new name when id is provided.'),
|
|
1748
1770
|
buttonColor: z.string().optional().describe('Badge background color as full hex, e.g. #dbeafe.'),
|
|
1749
1771
|
textColor: z.string().optional().describe('Badge text color as full hex, e.g. #1d4ed8.'),
|
|
1772
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1750
1773
|
},
|
|
1751
|
-
async ({ id, method, buttonColor, textColor }) => {
|
|
1774
|
+
async ({ id, method, buttonColor, textColor, globalRulesAckKey }) => {
|
|
1775
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1752
1776
|
let targetId = id;
|
|
1753
1777
|
let existing = null;
|
|
1754
1778
|
if (!targetId) {
|
|
@@ -1793,8 +1817,9 @@ server.tool(
|
|
|
1793
1817
|
id: z.string().optional().describe('Method record id. If omitted, method is used to find the record.'),
|
|
1794
1818
|
method: z.string().optional().describe('Method name to find when id is omitted.'),
|
|
1795
1819
|
confirm: z.boolean().optional().default(false).describe('Required true to apply the destructive delete. Omit/false returns a preview only.'),
|
|
1820
|
+
globalRulesAckKey: globalRulesAckParam(z).optional().describe('Required when confirm=true. Use globalRulesAckKey from get_enfyra_required_knowledge.'),
|
|
1796
1821
|
},
|
|
1797
|
-
async ({ id, method, confirm }) => {
|
|
1822
|
+
async ({ id, method, confirm, globalRulesAckKey }) => {
|
|
1798
1823
|
let targetId = id;
|
|
1799
1824
|
let target = null;
|
|
1800
1825
|
if (!targetId) {
|
|
@@ -1820,6 +1845,7 @@ server.tool(
|
|
|
1820
1845
|
next: 'Call delete_method again with confirm=true to delete.',
|
|
1821
1846
|
}, null, 2) }] };
|
|
1822
1847
|
}
|
|
1848
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1823
1849
|
const result = await fetchAPI(ENFYRA_API_URL, `/enfyra_method/${encodeURIComponent(String(targetId))}`, { method: 'DELETE' });
|
|
1824
1850
|
_methodMap = null;
|
|
1825
1851
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
@@ -2091,16 +2117,11 @@ server.tool(
|
|
|
2091
2117
|
tableName: z.string().describe('Table name or alias to inspect'),
|
|
2092
2118
|
},
|
|
2093
2119
|
async ({ tableName }) => {
|
|
2094
|
-
|
|
2095
|
-
|
|
2120
|
+
const state = await collectRestDefinitionState();
|
|
2121
|
+
const table = state.tables.find((item) => item?.name === tableName || item?.alias === tableName);
|
|
2096
2122
|
if (!table) {
|
|
2097
|
-
|
|
2098
|
-
await fetchAPI(ENFYRA_API_URL, '/admin/reload/routes', { method: 'POST' }).catch(() => {});
|
|
2099
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
2100
|
-
state = await collectRestDefinitionState();
|
|
2101
|
-
table = state.tables.find((item) => item?.name === tableName || item?.alias === tableName);
|
|
2123
|
+
throw new Error(`Unknown table "${tableName}". Use get_all_tables({ search, limit }) or get_all_metadata({ search, all: true }) to confirm the table name. If a just-created table is missing, verify the create response/reload event before calling manual reload tools.`);
|
|
2102
2124
|
}
|
|
2103
|
-
if (!table) throw new Error(`Unknown table "${tableName}"`);
|
|
2104
2125
|
const tableId = getId(table);
|
|
2105
2126
|
const columnIds = new Set((table.columns || []).map((column) => String(getId(column))));
|
|
2106
2127
|
const relationIds = new Set((table.relations || []).map((relation) => String(getId(relation))));
|
|
@@ -2402,7 +2423,7 @@ server.tool('get_all_routes', 'List route definitions with minimal fields. Every
|
|
|
2402
2423
|
const queryParams = new URLSearchParams({
|
|
2403
2424
|
filter: JSON.stringify(filter),
|
|
2404
2425
|
fields: 'id,path,mainTable.name,availableMethods.*,publicMethods.*,isEnabled',
|
|
2405
|
-
limit: '1000',
|
|
2426
|
+
limit: all ? '0' : '1000',
|
|
2406
2427
|
});
|
|
2407
2428
|
const result = await fetchAPI(ENFYRA_API_URL, `/enfyra_route?${queryParams.toString()}`);
|
|
2408
2429
|
const q = search ? search.toLowerCase() : null;
|
|
@@ -2421,6 +2442,8 @@ server.tool('get_all_routes', 'List route definitions with minimal fields. Every
|
|
|
2421
2442
|
matchedRouteCount: matchedRoutes.length,
|
|
2422
2443
|
returnedRouteCount: Math.min(matchedRoutes.length, routeLimit),
|
|
2423
2444
|
all: !!all,
|
|
2445
|
+
complete: all || routeLimit >= matchedRoutes.length,
|
|
2446
|
+
hardCap: all ? null : routeLimit,
|
|
2424
2447
|
search: search || null,
|
|
2425
2448
|
routes: matchedRoutes.slice(0, routeLimit),
|
|
2426
2449
|
detailHint: matchedRoutes.length > routeLimit
|
|
@@ -2449,8 +2472,10 @@ server.tool(
|
|
|
2449
2472
|
.describe('Methods accessible WITHOUT auth token. Omit = all methods require auth.'),
|
|
2450
2473
|
isEnabled: z.boolean().optional().default(true).describe('Enable route immediately'),
|
|
2451
2474
|
description: z.string().optional().describe('Route description'),
|
|
2475
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2452
2476
|
},
|
|
2453
|
-
async ({ path: routePath, mainTableId, methods, publicMethods, isEnabled, description }) => {
|
|
2477
|
+
async ({ path: routePath, mainTableId, methods, publicMethods, isEnabled, description, globalRulesAckKey }) => {
|
|
2478
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2454
2479
|
const methodMap = await getMethodMap();
|
|
2455
2480
|
const normalizedPath = normalizeRestPath(routePath);
|
|
2456
2481
|
|
|
@@ -2513,9 +2538,11 @@ server.tool(
|
|
|
2513
2538
|
sourceCode: z.string().describe('Handler JavaScript sourceCode. Do not use logic; backend CRUD rejects logic.'),
|
|
2514
2539
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language for compiler. Default javascript.'),
|
|
2515
2540
|
timeout: z.number().optional().describe('Timeout in ms (default: system DEFAULT_HANDLER_TIMEOUT, usually 30000)'),
|
|
2541
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2516
2542
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
2517
2543
|
},
|
|
2518
|
-
async ({ routeId, method, methods, sourceCode, scriptLanguage, timeout, knowledgeAckKey }) => {
|
|
2544
|
+
async ({ routeId, method, methods, sourceCode, scriptLanguage, timeout, globalRulesAckKey, knowledgeAckKey }) => {
|
|
2545
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2519
2546
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
2520
2547
|
const methodNames = methods && methods.length > 0 ? methods : method ? [method] : [];
|
|
2521
2548
|
if (methodNames.length === 0) throw new Error('Provide method or methods');
|
|
@@ -2576,9 +2603,11 @@ server.tool(
|
|
|
2576
2603
|
.describe('Method names this hook applies to. Default: built-in REST methods GET, POST, PATCH, DELETE.'),
|
|
2577
2604
|
priority: z.number().optional().default(0).describe('Execution order (lower = first)'),
|
|
2578
2605
|
isEnabled: z.boolean().optional().default(true).describe('Enable hook immediately'),
|
|
2606
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2579
2607
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
2580
2608
|
},
|
|
2581
|
-
async ({ routeId, name, code, scriptLanguage, methods, priority, isEnabled, knowledgeAckKey }) => {
|
|
2609
|
+
async ({ routeId, name, code, scriptLanguage, methods, priority, isEnabled, globalRulesAckKey, knowledgeAckKey }) => {
|
|
2610
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2582
2611
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
2583
2612
|
const methodMap = await getMethodMap();
|
|
2584
2613
|
const methodNames = methods || ['GET', 'POST', 'PATCH', 'DELETE'];
|
|
@@ -2632,9 +2661,11 @@ server.tool(
|
|
|
2632
2661
|
.describe('Method names this hook applies to. Default: built-in REST methods GET, POST, PATCH, DELETE.'),
|
|
2633
2662
|
priority: z.number().optional().default(0).describe('Execution order (lower = first)'),
|
|
2634
2663
|
isEnabled: z.boolean().optional().default(true).describe('Enable hook immediately'),
|
|
2664
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2635
2665
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z),
|
|
2636
2666
|
},
|
|
2637
|
-
async ({ routeId, name, code, scriptLanguage, methods, priority, isEnabled, knowledgeAckKey }) => {
|
|
2667
|
+
async ({ routeId, name, code, scriptLanguage, methods, priority, isEnabled, globalRulesAckKey, knowledgeAckKey }) => {
|
|
2668
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2638
2669
|
assertDynamicCodeKnowledgeAck(knowledgeAckKey);
|
|
2639
2670
|
const methodMap = await getMethodMap();
|
|
2640
2671
|
const methodNames = methods || ['GET', 'POST', 'PATCH', 'DELETE'];
|
|
@@ -2751,8 +2782,10 @@ server.tool(
|
|
|
2751
2782
|
mode: z.enum(['merge', 'replace']).optional().default('merge').describe('merge adds methods to an existing permission; replace overwrites methods on the matched permission.'),
|
|
2752
2783
|
description: z.string().optional().describe('Admin note'),
|
|
2753
2784
|
isEnabled: z.boolean().optional().default(true).describe('Enable the permission'),
|
|
2785
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2754
2786
|
},
|
|
2755
|
-
async ({ path, routeId, methods, roleId, roleName, allowedUserIds, mode, description, isEnabled }) => {
|
|
2787
|
+
async ({ path, routeId, methods, roleId, roleName, allowedUserIds, mode, description, isEnabled, globalRulesAckKey }) => {
|
|
2788
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2756
2789
|
if (!path && !routeId) throw new Error('Provide path or routeId.');
|
|
2757
2790
|
if (path && routeId) throw new Error('Provide path or routeId, not both.');
|
|
2758
2791
|
if (roleId && roleName) throw new Error('Provide roleId or roleName, not both.');
|
|
@@ -2852,24 +2885,36 @@ registerPlatformOperationTools(server, ENFYRA_API_URL);
|
|
|
2852
2885
|
// CACHE & SYSTEM TOOLS
|
|
2853
2886
|
// ============================================================================
|
|
2854
2887
|
|
|
2855
|
-
server.tool('reload_all', 'Reload all caches (metadata, routes, GraphQL)', {
|
|
2888
|
+
server.tool('reload_all', 'Reload all caches (metadata, routes, GraphQL)', {
|
|
2889
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2890
|
+
}, async ({ globalRulesAckKey }) => {
|
|
2891
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2856
2892
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload', { method: 'POST' });
|
|
2857
|
-
return {
|
|
2893
|
+
return jsonContent({ action: 'reloaded_all', result });
|
|
2858
2894
|
});
|
|
2859
2895
|
|
|
2860
|
-
server.tool('reload_metadata', 'Reload metadata cache only', {
|
|
2896
|
+
server.tool('reload_metadata', 'Reload metadata cache only', {
|
|
2897
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2898
|
+
}, async ({ globalRulesAckKey }) => {
|
|
2899
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2861
2900
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload/metadata', { method: 'POST' });
|
|
2862
|
-
return {
|
|
2901
|
+
return jsonContent({ action: 'reloaded_metadata', result });
|
|
2863
2902
|
});
|
|
2864
2903
|
|
|
2865
|
-
server.tool('reload_routes', 'Reload routes cache only', {
|
|
2904
|
+
server.tool('reload_routes', 'Reload routes cache only', {
|
|
2905
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2906
|
+
}, async ({ globalRulesAckKey }) => {
|
|
2907
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2866
2908
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload/routes', { method: 'POST' });
|
|
2867
|
-
return {
|
|
2909
|
+
return jsonContent({ action: 'reloaded_routes', result });
|
|
2868
2910
|
});
|
|
2869
2911
|
|
|
2870
|
-
server.tool('reload_graphql', 'Reload GraphQL schema', {
|
|
2912
|
+
server.tool('reload_graphql', 'Reload GraphQL schema', {
|
|
2913
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
2914
|
+
}, async ({ globalRulesAckKey }) => {
|
|
2915
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
2871
2916
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload/graphql', { method: 'POST' });
|
|
2872
|
-
return {
|
|
2917
|
+
return jsonContent({ action: 'reloaded_graphql', result });
|
|
2873
2918
|
});
|
|
2874
2919
|
|
|
2875
2920
|
// ============================================================================
|
|
@@ -3015,8 +3060,10 @@ server.tool(
|
|
|
3015
3060
|
name: z.string().describe('Exact NPM package name (e.g., "node-ssh", "axios")'),
|
|
3016
3061
|
type: z.enum(['Server', 'App']).default('Server').describe('Where to install: Server (handlers/hooks) or App (extensions)'),
|
|
3017
3062
|
version: z.string().optional().describe('Specific version. If omitted, fetches latest from NPM.'),
|
|
3063
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
3018
3064
|
},
|
|
3019
|
-
async ({ name, type, version }) => {
|
|
3065
|
+
async ({ name, type, version, globalRulesAckKey }) => {
|
|
3066
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
3020
3067
|
// Step 1: Get package info from NPM if version not specified
|
|
3021
3068
|
let pkgVersion = version;
|
|
3022
3069
|
let pkgDescription = '';
|
|
@@ -3038,12 +3085,15 @@ server.tool(
|
|
|
3038
3085
|
const checkFilter = JSON.stringify({ name: { _eq: name }, type: { _eq: type } });
|
|
3039
3086
|
const existing = await fetchAPI(ENFYRA_API_URL, `/enfyra_package?filter=${encodeURIComponent(checkFilter)}&limit=1`);
|
|
3040
3087
|
if (existing.data && existing.data.length > 0) {
|
|
3041
|
-
return {
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3088
|
+
return jsonContent({
|
|
3089
|
+
action: 'package_already_installed',
|
|
3090
|
+
package: {
|
|
3091
|
+
name,
|
|
3092
|
+
version: existing.data[0].version,
|
|
3093
|
+
type: existing.data[0].type,
|
|
3094
|
+
},
|
|
3095
|
+
record: existing.data[0],
|
|
3096
|
+
});
|
|
3047
3097
|
}
|
|
3048
3098
|
|
|
3049
3099
|
// Step 3: Get current user for installedBy
|
|
@@ -3065,12 +3115,11 @@ server.tool(
|
|
|
3065
3115
|
body: JSON.stringify(body),
|
|
3066
3116
|
});
|
|
3067
3117
|
|
|
3068
|
-
return {
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
};
|
|
3118
|
+
return jsonContent({
|
|
3119
|
+
action: 'package_installed',
|
|
3120
|
+
package: { name, version: pkgVersion, type },
|
|
3121
|
+
result,
|
|
3122
|
+
});
|
|
3074
3123
|
},
|
|
3075
3124
|
);
|
|
3076
3125
|
|