@enfyra/mcp-server 0.1.6 → 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/package.json
CHANGED
package/src/lib/mcp-examples.js
CHANGED
|
@@ -1597,26 +1597,14 @@ ensure_page_extension({
|
|
|
1597
1597
|
'Use enfyra_menu.label, not title.',
|
|
1598
1598
|
'Sensitive admin menus should include a permission condition at creation time.',
|
|
1599
1599
|
'For page extensions, create the menu first with ensure_menu and pass its id to ensure_page_extension.',
|
|
1600
|
-
'Call get_extension_theme_contract before writing or reviewing page/widget/global extension UI.',
|
|
1600
|
+
'Call get_extension_theme_contract before writing or reviewing page/widget/global extension UI; that tool is the authority for theme, color, layout, modal, drawer, and shell registry details.',
|
|
1601
1601
|
'Call get_enfyra_required_knowledge before saving extension code, pass globalRulesAckKey as globalRulesAckKey, and pass extensionAckKey as extensionKnowledgeAckKey.',
|
|
1602
1602
|
'Page extensions must register the app-shell PageHeader with usePageHeaderRegistry instead of rendering a custom top header.',
|
|
1603
|
-
'Use variant: "minimal" for operational pages unless a larger header is intentionally needed.',
|
|
1604
|
-
'Do not put ordinary KPI cards in PageHeader.stats; render metrics in the extension body.',
|
|
1605
1603
|
'Put page-level actions in useHeaderActionRegistry or useSubHeaderActionRegistry, destructure register first, then call it with one action or an array.',
|
|
1606
|
-
'Page extensions should be full-bleed
|
|
1607
|
-
'
|
|
1608
|
-
'Use
|
|
1609
|
-
'
|
|
1610
|
-
'Decision cases: normal decorative accents, feature icons, non-state tiles, active tabs, progress fills, selected segments, and primary actions use runtime primary/identity classes; true semantic states use their matching status colors such as error, warning, success, or info; large ordinary surfaces stay neutral and carry only small badges/icons for status; selected/current entity blocks may use eapp-primary-surface.',
|
|
1611
|
-
'Pattern examples: KPI/metric cards should be eapp-surface-card with a small icon tile using eapp-primary-soft eapp-icon-tile; selected/current entity cards may use eapp-primary-surface; progress bars use eapp-surface-muted tracks plus eapp-primary-solid fills; list rows use eapp-surface-card/eapp-divide-y/eapp-surface-hover and only small chips inside; primary scope actions use UButton color="primary" variant="solid"; secondary actions use neutral variants.',
|
|
1612
|
-
'Status colors belong only in UBadge/UAlert semantic colors or eapp-status-*-soft/text/border classes for badges, small icons, or short status text. Do not read --badge-* variables directly in extension templates. Do not color large panels, alert-like success blocks, KPI cards, list containers, or attention/reconciliation blocks green/yellow/red because of state; keep the block neutral and place the status badge/icon inside.',
|
|
1613
|
-
'Use PageHeader gradient: "none" for generated operational pages unless the user explicitly asks for a decorative page accent; do not hardcode cyan/violet/purple/blue/green gradients.',
|
|
1614
|
-
'For general card grids inside the shell, use md:grid-cols-2 xl:grid-cols-3 instead of lg:grid-cols-3 because the desktop sidebar leaves tablet-width content at 1024px.',
|
|
1615
|
-
'Do not use Nuxt UI neutral semantic classes such as bg-default, text-muted, text-dimmed, border-default, or divide-default inside extension code; use eApp class tokens instead. Do not write text-[var(...)], bg-[var(...)], or border-[var(...)] in generated extension templates unless no class token exists for that exact primitive.',
|
|
1616
|
-
'Do not pass ui.content: "eapp-surface-card" to UModal/CommonModal; modal content uses the app modal surface and caller content classes should only append z-index or width.',
|
|
1617
|
-
'CommonModal and CommonDrawer own action-only footers through cancelAction, primaryAction, dangerAction, leadingActions, and footerHint. Pass action intent through props instead of styling footer buttons manually; cancelAction defaults to neutral outline, dangerAction is for irreversible destructive work, and Keep editing should use tone: "primary" in discard dialogs.',
|
|
1618
|
-
'Use UTabs for page sections instead of custom tab bars so the app-level active/inactive indicators, spacing, focus rings, and theme contrast stay consistent.',
|
|
1619
|
-
'Do not inject global CSS, create theme guards, redefine the app palette, or solve one extension by overriding the whole app shell.',
|
|
1604
|
+
'Page extensions should be full-bleed and responsive from the first version; the extension root is already inside the Enfyra admin page main.',
|
|
1605
|
+
'Render ordinary metrics and lists in the body, not PageHeader.stats, unless the user explicitly wants a compact overview header.',
|
|
1606
|
+
'Use app theme tokens and Nuxt UI semantic colors by intent; do not hard-code concrete palettes or redefine the app palette inside extension code.',
|
|
1607
|
+
'Use app-owned primitives such as UTabs, CommonModal, CommonDrawer, Widget, useMenuNotificationRegistry, and useAccountPanelRegistry when the workflow matches them.',
|
|
1620
1608
|
'Keep list selection local and fetch detail rows only; do not refetch the whole list after a row click unless the list data changed.',
|
|
1621
1609
|
'Page extension paths are admin app UI routes. Do not verify them with test_rest_endpoint against ENFYRA_API_URL unless inspect_route shows an API route with the same path.',
|
|
1622
1610
|
'After saving, open Enfyra admin tabs should update through the server/Enfyra admin UI realtime reload contract; do not tell the user to refresh unless that contract is proven broken.',
|
|
@@ -1441,7 +1441,8 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1441
1441
|
'create_api_endpoint',
|
|
1442
1442
|
[
|
|
1443
1443
|
'Business operation: create or update a custom REST endpoint with a handler in one safe operation.',
|
|
1444
|
-
'
|
|
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.',
|
|
1445
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.',
|
|
1446
1447
|
'Use table/schema tools separately when the user needs persisted data. This tool is for custom behavior endpoints.',
|
|
1447
1448
|
].join(' '),
|
|
@@ -1591,8 +1592,10 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1591
1592
|
message: z.string().optional().describe('Custom validation error message.'),
|
|
1592
1593
|
description: z.string().optional().describe('Admin note.'),
|
|
1593
1594
|
isEnabled: z.boolean().optional().default(true).describe('Enable the rule.'),
|
|
1595
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1594
1596
|
},
|
|
1595
|
-
async ({ tableName, columnName, ruleType, value, message, description, isEnabled }) => {
|
|
1597
|
+
async ({ tableName, columnName, ruleType, value, message, description, isEnabled, globalRulesAckKey }) => {
|
|
1598
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1596
1599
|
const table = resolveTable(await getMetadataTables(ENFYRA_API_URL), tableName);
|
|
1597
1600
|
const column = resolveColumn(table, columnName);
|
|
1598
1601
|
const existing = await findRecord(ENFYRA_API_URL, 'enfyra_column_rule', {
|
|
@@ -1632,8 +1635,10 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1632
1635
|
condition: z.string().optional().describe('Condition JSON object using field permission DSL.'),
|
|
1633
1636
|
description: z.string().optional().describe('Admin note.'),
|
|
1634
1637
|
isEnabled: z.boolean().optional().default(true).describe('Enable the permission.'),
|
|
1638
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1635
1639
|
},
|
|
1636
|
-
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);
|
|
1637
1642
|
if (!!columnName === !!relationName) throw new Error('Provide exactly one of columnName or relationName.');
|
|
1638
1643
|
assertOneScope({ roleId, roleName, allowedUserIds });
|
|
1639
1644
|
const [tables, role] = await Promise.all([
|
|
@@ -1691,8 +1696,10 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1691
1696
|
description: z.string().optional().describe('Admin note.'),
|
|
1692
1697
|
rules: z.string().optional().describe('Rules JSON array: [{type, config, priority, isEnabled, description, userIds}].'),
|
|
1693
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),
|
|
1694
1700
|
},
|
|
1695
|
-
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);
|
|
1696
1703
|
if (path && routeId) throw new Error('Provide path or routeId, not both.');
|
|
1697
1704
|
const ruleInputs = parseJsonArrayArg('rules', rules, []);
|
|
1698
1705
|
if (position === 'pre_auth') {
|
|
@@ -49,7 +49,7 @@ export function assertExtensionKnowledgeAckIf(condition, key) {
|
|
|
49
49
|
export function buildRequiredKnowledgePayload() {
|
|
50
50
|
return {
|
|
51
51
|
version: REQUIRED_KNOWLEDGE_VERSION,
|
|
52
|
-
purpose: 'Read this before mutating Enfyra metadata, schema, routes, permissions, dynamic server code, or extension UI 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
53
|
globalRulesAckKey: GLOBAL_RULES_ACK_KEY,
|
|
54
54
|
dynamicCodeAckKey: DYNAMIC_CODE_KNOWLEDGE_ACK_KEY,
|
|
55
55
|
extensionAckKey: EXTENSION_KNOWLEDGE_ACK_KEY,
|
package/src/lib/table-tools.js
CHANGED
|
@@ -313,7 +313,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
313
313
|
return withSchemaQueue(async () => {
|
|
314
314
|
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, args.tableId);
|
|
315
315
|
if (!tableData) {
|
|
316
|
-
|
|
316
|
+
throw new Error(`Table with ID ${args.tableId} not found.`);
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
const existingColumns = getPatchableColumns(tableData.columns);
|
|
@@ -325,9 +325,12 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
325
325
|
columnName: args.name,
|
|
326
326
|
});
|
|
327
327
|
|
|
328
|
-
return {
|
|
329
|
-
|
|
330
|
-
|
|
328
|
+
return jsonContent({
|
|
329
|
+
action: 'column_created',
|
|
330
|
+
tableId: args.tableId,
|
|
331
|
+
columnName: args.name,
|
|
332
|
+
result,
|
|
333
|
+
});
|
|
331
334
|
});
|
|
332
335
|
}
|
|
333
336
|
|
|
@@ -341,7 +344,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
341
344
|
const resolvedTargetTableId = resolveTableIdentifierFromMetadata(metadata, targetTableId, 'targetTableId');
|
|
342
345
|
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, resolvedSourceTableId);
|
|
343
346
|
if (!tableData) {
|
|
344
|
-
|
|
347
|
+
throw new Error(`Table ${sourceTableId} not found.`);
|
|
345
348
|
}
|
|
346
349
|
const existingRelations = (tableData.relations || []).map(sanitizeExistingRelationForTablePatch);
|
|
347
350
|
const beforeIds = existingRelations.map((relation) => String(getId(relation))).filter((id) => id !== 'null');
|
|
@@ -356,9 +359,11 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
356
359
|
action: 'create',
|
|
357
360
|
propertyName,
|
|
358
361
|
});
|
|
359
|
-
return {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
+
return jsonContent({
|
|
363
|
+
action: 'relation_created',
|
|
364
|
+
relation: { propertyName, type, sourceTableId: resolvedSourceTableId, targetTableId: resolvedTargetTableId },
|
|
365
|
+
result,
|
|
366
|
+
});
|
|
362
367
|
});
|
|
363
368
|
}
|
|
364
369
|
|
|
@@ -366,7 +371,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
366
371
|
return withSchemaQueue(async () => {
|
|
367
372
|
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, tableId);
|
|
368
373
|
if (!tableData) {
|
|
369
|
-
|
|
374
|
+
throw new Error(`Table with ID ${tableId} not found.`);
|
|
370
375
|
}
|
|
371
376
|
|
|
372
377
|
const existingColumns = getPatchableColumns(tableData.columns);
|
|
@@ -399,9 +404,12 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
399
404
|
columnId,
|
|
400
405
|
});
|
|
401
406
|
|
|
402
|
-
return {
|
|
403
|
-
|
|
404
|
-
|
|
407
|
+
return jsonContent({
|
|
408
|
+
action: 'column_deleted',
|
|
409
|
+
tableId,
|
|
410
|
+
columnId,
|
|
411
|
+
result,
|
|
412
|
+
});
|
|
405
413
|
});
|
|
406
414
|
}
|
|
407
415
|
|
|
@@ -409,7 +417,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
409
417
|
return withSchemaQueue(async () => {
|
|
410
418
|
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, tableId);
|
|
411
419
|
if (!tableData) {
|
|
412
|
-
|
|
420
|
+
throw new Error(`Table with ID ${tableId} not found.`);
|
|
413
421
|
}
|
|
414
422
|
|
|
415
423
|
const existingRelations = (tableData.relations || []).map(sanitizeExistingRelationForTablePatch);
|
|
@@ -442,9 +450,12 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
442
450
|
relationId,
|
|
443
451
|
});
|
|
444
452
|
|
|
445
|
-
return {
|
|
446
|
-
|
|
447
|
-
|
|
453
|
+
return jsonContent({
|
|
454
|
+
action: 'relation_deleted',
|
|
455
|
+
tableId,
|
|
456
|
+
relationId,
|
|
457
|
+
result,
|
|
458
|
+
});
|
|
448
459
|
});
|
|
449
460
|
}
|
|
450
461
|
|
|
@@ -613,9 +624,25 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
613
624
|
indexes.length ? `Index group(s): ${indexes.length}.` : null,
|
|
614
625
|
uniques.length ? `Unique group(s): ${uniques.length}.` : null,
|
|
615
626
|
].filter(Boolean).join(' ');
|
|
616
|
-
return {
|
|
617
|
-
|
|
618
|
-
|
|
627
|
+
return jsonContent({
|
|
628
|
+
action: 'table_created',
|
|
629
|
+
table: { id: createdTableId, name, routePath },
|
|
630
|
+
summary: {
|
|
631
|
+
columnCount: userColumns.length + 1,
|
|
632
|
+
createdColumnCount: userColumns.length,
|
|
633
|
+
relationCount: userRelations.length,
|
|
634
|
+
indexGroupCount: indexes.length,
|
|
635
|
+
uniqueGroupCount: uniques.length,
|
|
636
|
+
},
|
|
637
|
+
rest: {
|
|
638
|
+
base,
|
|
639
|
+
routePath,
|
|
640
|
+
operations: ['GET /<table>', 'POST /<table>', 'PATCH /<table>/:id', 'DELETE /<table>/:id'],
|
|
641
|
+
noGetById: true,
|
|
642
|
+
},
|
|
643
|
+
message: [colHint, relHint, constraintHint, restHint].filter(Boolean).join('\n'),
|
|
644
|
+
result,
|
|
645
|
+
});
|
|
619
646
|
})
|
|
620
647
|
);
|
|
621
648
|
|
|
@@ -652,9 +679,11 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
652
679
|
if (uniquesJson !== undefined) body.uniques = normalizeConstraintGroups('uniques', parseJsonArrayParam('uniques', uniquesJson));
|
|
653
680
|
|
|
654
681
|
const result = await patchTableAutoConfirm(ENFYRA_API_URL, tableId, body);
|
|
655
|
-
return {
|
|
656
|
-
|
|
657
|
-
|
|
682
|
+
return jsonContent({
|
|
683
|
+
action: 'table_updated',
|
|
684
|
+
tableId,
|
|
685
|
+
result,
|
|
686
|
+
});
|
|
658
687
|
})
|
|
659
688
|
);
|
|
660
689
|
|
|
@@ -691,9 +720,11 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
691
720
|
const result = await fetchAPI(ENFYRA_API_URL, `/enfyra_table/${tableId}`, {
|
|
692
721
|
method: 'DELETE',
|
|
693
722
|
});
|
|
694
|
-
return {
|
|
695
|
-
|
|
696
|
-
|
|
723
|
+
return jsonContent({
|
|
724
|
+
action: 'table_deleted',
|
|
725
|
+
tableId,
|
|
726
|
+
result,
|
|
727
|
+
});
|
|
697
728
|
})
|
|
698
729
|
);
|
|
699
730
|
|
|
@@ -741,7 +772,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
741
772
|
assertGlobalRulesAck(globalRulesAckKey);
|
|
742
773
|
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, tableId);
|
|
743
774
|
if (!tableData) {
|
|
744
|
-
|
|
775
|
+
throw new Error(`Table with ID ${tableId} not found.`);
|
|
745
776
|
}
|
|
746
777
|
|
|
747
778
|
const existingColumns = getPatchableColumns(tableData.columns);
|
|
@@ -771,9 +802,12 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
771
802
|
columnId,
|
|
772
803
|
});
|
|
773
804
|
|
|
774
|
-
return {
|
|
775
|
-
|
|
776
|
-
|
|
805
|
+
return jsonContent({
|
|
806
|
+
action: 'column_updated',
|
|
807
|
+
tableId,
|
|
808
|
+
columnId,
|
|
809
|
+
result,
|
|
810
|
+
});
|
|
777
811
|
})
|
|
778
812
|
);
|
|
779
813
|
|
package/src/mcp-server-entry.mjs
CHANGED
|
@@ -331,7 +331,7 @@ function summarizeRoutes(routesResult) {
|
|
|
331
331
|
}));
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
-
function summarizeMetadata(metadata, { search, limit } = {}) {
|
|
334
|
+
function summarizeMetadata(metadata, { search, limit, all = false } = {}) {
|
|
335
335
|
const tables = normalizeTables(metadata);
|
|
336
336
|
const q = search ? search.toLowerCase() : null;
|
|
337
337
|
const summarized = tables.map((table) => ({
|
|
@@ -346,11 +346,13 @@ function summarizeMetadata(metadata, { search, limit } = {}) {
|
|
|
346
346
|
const matched = q
|
|
347
347
|
? summarized.filter((table) => JSON.stringify(table).toLowerCase().includes(q))
|
|
348
348
|
: summarized;
|
|
349
|
-
const outputLimit = limit || 30;
|
|
349
|
+
const outputLimit = all ? matched.length : (limit || 30);
|
|
350
350
|
return {
|
|
351
351
|
tableCount: tables.length,
|
|
352
352
|
matchedTableCount: matched.length,
|
|
353
353
|
returnedTableCount: Math.min(matched.length, outputLimit),
|
|
354
|
+
complete: all || outputLimit >= matched.length,
|
|
355
|
+
hardCap: all ? null : outputLimit,
|
|
354
356
|
search: search || null,
|
|
355
357
|
tables: matched.slice(0, outputLimit),
|
|
356
358
|
};
|
|
@@ -807,15 +809,21 @@ server.tool('get_all_metadata', 'Get concise metadata summary for all tables. Us
|
|
|
807
809
|
includeFull: z.boolean().optional().default(false).describe('Return full raw metadata. Default false to keep MCP context small.'),
|
|
808
810
|
search: z.string().optional().describe('Optional table-name/alias substring filter.'),
|
|
809
811
|
limit: z.number().optional().describe('Maximum tables returned after search. Default 30.'),
|
|
810
|
-
|
|
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
|
+
}
|
|
811
817
|
const result = await fetchAPI(ENFYRA_API_URL, '/metadata');
|
|
812
818
|
const payload = includeFull
|
|
813
819
|
? result
|
|
814
820
|
: {
|
|
815
821
|
statusCode: result?.statusCode,
|
|
816
822
|
success: result?.success,
|
|
817
|
-
...summarizeMetadata(result, { search, limit }),
|
|
818
|
-
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.',
|
|
819
827
|
};
|
|
820
828
|
return jsonContent(payload);
|
|
821
829
|
});
|
|
@@ -2109,16 +2117,11 @@ server.tool(
|
|
|
2109
2117
|
tableName: z.string().describe('Table name or alias to inspect'),
|
|
2110
2118
|
},
|
|
2111
2119
|
async ({ tableName }) => {
|
|
2112
|
-
|
|
2113
|
-
|
|
2120
|
+
const state = await collectRestDefinitionState();
|
|
2121
|
+
const table = state.tables.find((item) => item?.name === tableName || item?.alias === tableName);
|
|
2114
2122
|
if (!table) {
|
|
2115
|
-
|
|
2116
|
-
await fetchAPI(ENFYRA_API_URL, '/admin/reload/routes', { method: 'POST' }).catch(() => {});
|
|
2117
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
2118
|
-
state = await collectRestDefinitionState();
|
|
2119
|
-
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.`);
|
|
2120
2124
|
}
|
|
2121
|
-
if (!table) throw new Error(`Unknown table "${tableName}"`);
|
|
2122
2125
|
const tableId = getId(table);
|
|
2123
2126
|
const columnIds = new Set((table.columns || []).map((column) => String(getId(column))));
|
|
2124
2127
|
const relationIds = new Set((table.relations || []).map((relation) => String(getId(relation))));
|
|
@@ -2420,7 +2423,7 @@ server.tool('get_all_routes', 'List route definitions with minimal fields. Every
|
|
|
2420
2423
|
const queryParams = new URLSearchParams({
|
|
2421
2424
|
filter: JSON.stringify(filter),
|
|
2422
2425
|
fields: 'id,path,mainTable.name,availableMethods.*,publicMethods.*,isEnabled',
|
|
2423
|
-
limit: '1000',
|
|
2426
|
+
limit: all ? '0' : '1000',
|
|
2424
2427
|
});
|
|
2425
2428
|
const result = await fetchAPI(ENFYRA_API_URL, `/enfyra_route?${queryParams.toString()}`);
|
|
2426
2429
|
const q = search ? search.toLowerCase() : null;
|
|
@@ -2439,6 +2442,8 @@ server.tool('get_all_routes', 'List route definitions with minimal fields. Every
|
|
|
2439
2442
|
matchedRouteCount: matchedRoutes.length,
|
|
2440
2443
|
returnedRouteCount: Math.min(matchedRoutes.length, routeLimit),
|
|
2441
2444
|
all: !!all,
|
|
2445
|
+
complete: all || routeLimit >= matchedRoutes.length,
|
|
2446
|
+
hardCap: all ? null : routeLimit,
|
|
2442
2447
|
search: search || null,
|
|
2443
2448
|
routes: matchedRoutes.slice(0, routeLimit),
|
|
2444
2449
|
detailHint: matchedRoutes.length > routeLimit
|
|
@@ -2885,7 +2890,7 @@ server.tool('reload_all', 'Reload all caches (metadata, routes, GraphQL)', {
|
|
|
2885
2890
|
}, async ({ globalRulesAckKey }) => {
|
|
2886
2891
|
assertGlobalRulesAck(globalRulesAckKey);
|
|
2887
2892
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload', { method: 'POST' });
|
|
2888
|
-
return {
|
|
2893
|
+
return jsonContent({ action: 'reloaded_all', result });
|
|
2889
2894
|
});
|
|
2890
2895
|
|
|
2891
2896
|
server.tool('reload_metadata', 'Reload metadata cache only', {
|
|
@@ -2893,7 +2898,7 @@ server.tool('reload_metadata', 'Reload metadata cache only', {
|
|
|
2893
2898
|
}, async ({ globalRulesAckKey }) => {
|
|
2894
2899
|
assertGlobalRulesAck(globalRulesAckKey);
|
|
2895
2900
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload/metadata', { method: 'POST' });
|
|
2896
|
-
return {
|
|
2901
|
+
return jsonContent({ action: 'reloaded_metadata', result });
|
|
2897
2902
|
});
|
|
2898
2903
|
|
|
2899
2904
|
server.tool('reload_routes', 'Reload routes cache only', {
|
|
@@ -2901,7 +2906,7 @@ server.tool('reload_routes', 'Reload routes cache only', {
|
|
|
2901
2906
|
}, async ({ globalRulesAckKey }) => {
|
|
2902
2907
|
assertGlobalRulesAck(globalRulesAckKey);
|
|
2903
2908
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload/routes', { method: 'POST' });
|
|
2904
|
-
return {
|
|
2909
|
+
return jsonContent({ action: 'reloaded_routes', result });
|
|
2905
2910
|
});
|
|
2906
2911
|
|
|
2907
2912
|
server.tool('reload_graphql', 'Reload GraphQL schema', {
|
|
@@ -2909,7 +2914,7 @@ server.tool('reload_graphql', 'Reload GraphQL schema', {
|
|
|
2909
2914
|
}, async ({ globalRulesAckKey }) => {
|
|
2910
2915
|
assertGlobalRulesAck(globalRulesAckKey);
|
|
2911
2916
|
const result = await fetchAPI(ENFYRA_API_URL, '/admin/reload/graphql', { method: 'POST' });
|
|
2912
|
-
return {
|
|
2917
|
+
return jsonContent({ action: 'reloaded_graphql', result });
|
|
2913
2918
|
});
|
|
2914
2919
|
|
|
2915
2920
|
// ============================================================================
|
|
@@ -3080,12 +3085,15 @@ server.tool(
|
|
|
3080
3085
|
const checkFilter = JSON.stringify({ name: { _eq: name }, type: { _eq: type } });
|
|
3081
3086
|
const existing = await fetchAPI(ENFYRA_API_URL, `/enfyra_package?filter=${encodeURIComponent(checkFilter)}&limit=1`);
|
|
3082
3087
|
if (existing.data && existing.data.length > 0) {
|
|
3083
|
-
return {
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
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
|
+
});
|
|
3089
3097
|
}
|
|
3090
3098
|
|
|
3091
3099
|
// Step 3: Get current user for installedBy
|
|
@@ -3107,12 +3115,11 @@ server.tool(
|
|
|
3107
3115
|
body: JSON.stringify(body),
|
|
3108
3116
|
});
|
|
3109
3117
|
|
|
3110
|
-
return {
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
};
|
|
3118
|
+
return jsonContent({
|
|
3119
|
+
action: 'package_installed',
|
|
3120
|
+
package: { name, version: pkgVersion, type },
|
|
3121
|
+
result,
|
|
3122
|
+
});
|
|
3116
3123
|
},
|
|
3117
3124
|
);
|
|
3118
3125
|
|