@enfyra/mcp-server 0.1.58 → 0.1.59
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 +46 -267
- package/dist/lib/dynamic-repository-builder.d.ts +18 -0
- package/dist/lib/dynamic-repository-builder.js +105 -0
- package/dist/lib/dynamic-repository-builder.js.map +1 -0
- package/dist/lib/mcp-examples.d.ts +9 -0
- package/dist/lib/mcp-examples.js +86 -19
- package/dist/lib/mcp-examples.js.map +1 -1
- package/dist/lib/mcp-instructions.js +1 -0
- package/dist/lib/mcp-instructions.js.map +1 -1
- package/dist/lib/mutation-guards.js +3 -10
- package/dist/lib/mutation-guards.js.map +1 -1
- package/dist/lib/platform-operation-tools.d.ts +7 -0
- package/dist/lib/platform-operation-tools.js +112 -13
- package/dist/lib/platform-operation-tools.js.map +1 -1
- package/dist/lib/required-knowledge.d.ts +1 -1
- package/dist/lib/required-knowledge.js +18 -13
- package/dist/lib/required-knowledge.js.map +1 -1
- package/dist/lib/runtime-zone-tools.js +8 -6
- package/dist/lib/runtime-zone-tools.js.map +1 -1
- package/dist/lib/tool-routing.d.ts +64 -4
- package/dist/lib/tool-routing.js +136 -14
- package/dist/lib/tool-routing.js.map +1 -1
- package/dist/lib/toolset-filter.js +8 -0
- package/dist/lib/toolset-filter.js.map +1 -1
- package/dist/mcp-server-entry.js +69 -15
- package/dist/mcp-server-entry.js.map +1 -1
- package/package.json +2 -2
|
@@ -522,6 +522,10 @@ function attrStaticOrBound(name, value, expression) {
|
|
|
522
522
|
return `${name}="${String(value).replace(/"/g, '"')}"`;
|
|
523
523
|
}
|
|
524
524
|
function buildHeaderActionLiteral(action) {
|
|
525
|
+
const bareAssignment = String(action.onClick || '').match(/^\s*\(\s*\)\s*=>\s*\(?\s*([A-Za-z_$][\w$]*)\s*=(?!=)/);
|
|
526
|
+
if (bareAssignment) {
|
|
527
|
+
throw new Error(`Invalid header action onClick: assign ${bareAssignment[1]}.value inside script callbacks, or pass a handler name. Template ref auto-unwrapping does not apply in registry callbacks.`);
|
|
528
|
+
}
|
|
525
529
|
const entries = [
|
|
526
530
|
action.id ? `id: ${quoteJsString(action.id)}` : null,
|
|
527
531
|
action.label ? `label: ${quoteJsString(action.label)}` : null,
|
|
@@ -553,14 +557,14 @@ export function buildExtensionPageShellSnippet(input) {
|
|
|
553
557
|
];
|
|
554
558
|
if (actions.length) {
|
|
555
559
|
lines.push('const { register: registerHeaderActions } = useHeaderActionRegistry();');
|
|
556
|
-
lines.push(`registerHeaderActions([\n${actions.map((action) => `
|
|
560
|
+
lines.push(`onMounted(() => {\n registerHeaderActions([\n${actions.map((action) => ` ${buildHeaderActionLiteral(action)}`).join(',\n')}\n ]);\n});`);
|
|
557
561
|
}
|
|
558
562
|
return {
|
|
559
563
|
action: 'extension_page_shell_built',
|
|
560
564
|
snippet: lines.join('\n'),
|
|
561
565
|
contract: [
|
|
562
566
|
'Use usePageHeaderRegistry so the app shell renders the page header.',
|
|
563
|
-
'Use useHeaderActionRegistry for toolbar actions instead of rendering duplicate page headers or local top bars.',
|
|
567
|
+
'Use useHeaderActionRegistry for toolbar actions instead of rendering duplicate page headers or local top bars; register dynamic extension actions in onMounted after setup state exists.',
|
|
564
568
|
'Use primary solid only for the main scope action; secondary actions default to neutral outline.',
|
|
565
569
|
],
|
|
566
570
|
};
|
|
@@ -601,10 +605,10 @@ export function buildExtensionEmptyStateSnippet(input) {
|
|
|
601
605
|
: '';
|
|
602
606
|
return {
|
|
603
607
|
action: 'extension_empty_state_built',
|
|
604
|
-
component: '
|
|
605
|
-
snippet: `<
|
|
608
|
+
component: 'EmptyState',
|
|
609
|
+
snippet: `<EmptyState\n title="${String(input.title || 'No items found').replace(/"/g, '"')}"\n description="${String(input.description || '').replace(/"/g, '"')}"\n icon="${input.icon || 'lucide:inbox'}"\n size="${input.size || 'sm'}"\n variant="${input.variant || 'naked'}"${action}\n/>`,
|
|
606
610
|
contract: [
|
|
607
|
-
'
|
|
611
|
+
'Dynamic extensions expose the app empty-state component as EmptyState.',
|
|
608
612
|
'Use variant="naked" inside framed panels/lists and outline/subtle for standalone framed empty surfaces.',
|
|
609
613
|
],
|
|
610
614
|
};
|
|
@@ -653,6 +657,51 @@ export function buildExtensionResourceListSnippet(input) {
|
|
|
653
657
|
],
|
|
654
658
|
};
|
|
655
659
|
}
|
|
660
|
+
export function buildExtensionResourceGridSnippet(input) {
|
|
661
|
+
const itemsExpression = input.itemsExpression || 'items';
|
|
662
|
+
const itemName = input.itemName || 'item';
|
|
663
|
+
const keyExpression = input.keyExpression || `${itemName}.id`;
|
|
664
|
+
const defaultBody = [
|
|
665
|
+
`<h2 class="font-semibold eapp-text-primary">{{ ${itemName}.title || 'Untitled' }}</h2>`,
|
|
666
|
+
`<p v-if="${itemName}.description" class="text-sm eapp-text-secondary line-clamp-2">{{ ${itemName}.description }}</p>`,
|
|
667
|
+
].join('\n');
|
|
668
|
+
const normalized = normalizeVueBodySnippet(input.cardBody || defaultBody);
|
|
669
|
+
const frame = [
|
|
670
|
+
'<CommonResourceListFrame',
|
|
671
|
+
' variant="plain"',
|
|
672
|
+
` :loading="${input.loadingExpression || 'pending'}"`,
|
|
673
|
+
` :has-items="${itemsExpression}.length > 0"`,
|
|
674
|
+
` :total="${input.totalExpression || `${itemsExpression}.length`}"`,
|
|
675
|
+
` :items-per-page="${input.itemsPerPageExpression || '0'}"`,
|
|
676
|
+
` empty-title="${String(input.emptyTitle || 'No items found').replace(/"/g, '"')}"`,
|
|
677
|
+
` empty-description="${String(input.emptyDescription || '').replace(/"/g, '"')}"`,
|
|
678
|
+
` empty-icon="${input.emptyIcon || 'lucide:inbox'}"`,
|
|
679
|
+
'>',
|
|
680
|
+
' <div class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">',
|
|
681
|
+
` <UCard v-for="${itemName} in ${itemsExpression}" :key="${keyExpression}" class="h-full eapp-surface-card eapp-radius-panel border eapp-divider">`,
|
|
682
|
+
' <div class="flex h-full flex-col gap-4">',
|
|
683
|
+
indentLines(normalized.code, 8),
|
|
684
|
+
' </div>',
|
|
685
|
+
' </UCard>',
|
|
686
|
+
' </div>',
|
|
687
|
+
'</CommonResourceListFrame>',
|
|
688
|
+
].join('\n');
|
|
689
|
+
const constrained = input.constrained === false
|
|
690
|
+
? frame
|
|
691
|
+
: ['<section class="eapp-page-constrained-wide space-y-4">', indentLines(frame, 2), '</section>'].join('\n');
|
|
692
|
+
return {
|
|
693
|
+
action: 'extension_resource_grid_built',
|
|
694
|
+
component: 'CommonResourceListFrame',
|
|
695
|
+
snippet: constrained,
|
|
696
|
+
normalizedBodyChanges: normalized.changes,
|
|
697
|
+
contract: [
|
|
698
|
+
'Use this card grid for dashboard/workboard/catalog collections; use resource_list for dense operational rows.',
|
|
699
|
+
'The default desktop layout uses three columns only at xl because the admin sidebar consumes viewport width.',
|
|
700
|
+
'Keep the page constrained unless the workflow intentionally owns a canvas or other full-bleed surface.',
|
|
701
|
+
'Keep card actions inside cardBody and align them with flex layout rather than floating them at the viewport edge.',
|
|
702
|
+
],
|
|
703
|
+
};
|
|
704
|
+
}
|
|
656
705
|
export function buildExtensionFormEditorSnippet(input) {
|
|
657
706
|
const tag = input.lazy === false ? 'FormEditor' : 'FormEditorLazy';
|
|
658
707
|
const attrs = [
|
|
@@ -1133,6 +1182,32 @@ function collectExtensionRuntimeIssues(code) {
|
|
|
1133
1182
|
if (/\b(?:query|body|filter|deep|aggregate)\s*:\s*JSON\.stringify\s*\(/.test(source)) {
|
|
1134
1183
|
push('error', 'use-api-json-stringify-options', 'useApi query/body/filter/deep/aggregate options must be plain objects or computed objects, not JSON strings.', 'Pass the object directly to useApi or execute().');
|
|
1135
1184
|
}
|
|
1185
|
+
if (/<(?:CommonModal|UModal)\b[^>]*\bv-model\s*=/.test(source)) {
|
|
1186
|
+
push('error', 'modal-open-model', 'CommonModal/UModal must bind v-model:open, not the default v-model contract.', 'Call build_extension_ui kind=modal and preserve its v-model:open binding.');
|
|
1187
|
+
}
|
|
1188
|
+
if (/<CommonEmptyState\b/.test(source)) {
|
|
1189
|
+
push('error', 'unavailable-common-empty-state', 'CommonEmptyState is not registered in the dynamic extension runtime.', 'Use the injected EmptyState alias or build_extension_ui kind=empty_state/resource_list/resource_grid.');
|
|
1190
|
+
}
|
|
1191
|
+
const scriptBlocks = [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/gi)].map((match) => match[1]);
|
|
1192
|
+
const scriptSource = scriptBlocks.length ? scriptBlocks.join('\n') : (/<template\b/i.test(source) ? '' : source);
|
|
1193
|
+
const refNames = new Set([...scriptSource.matchAll(/\bconst\s+([A-Za-z_$][\w$]*)\s*=\s*(?:ref|shallowRef)\s*\(/g)].map((match) => match[1]));
|
|
1194
|
+
const bareRefActionPattern = /\bonClick\s*:\s*\(\s*\)\s*=>\s*\(?\s*([A-Za-z_$][\w$]*)\s*=(?!=)/g;
|
|
1195
|
+
let bareRefActionMatch;
|
|
1196
|
+
while ((bareRefActionMatch = bareRefActionPattern.exec(scriptSource))) {
|
|
1197
|
+
const refName = bareRefActionMatch[1];
|
|
1198
|
+
if (refNames.has(refName)) {
|
|
1199
|
+
push('error', 'script-ref-assignment', `Script callback tries to reassign const ref ${refName}.`, `Assign ${refName}.value or call a handler; template ref auto-unwrapping does not apply inside registry/action callbacks.`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
const executeAliasPattern = /\bexecute\s*:\s*([A-Za-z_$][\w$]*)/g;
|
|
1203
|
+
let executeAliasMatch;
|
|
1204
|
+
while ((executeAliasMatch = executeAliasPattern.exec(source))) {
|
|
1205
|
+
const executeAlias = executeAliasMatch[1];
|
|
1206
|
+
const references = source.match(new RegExp(`\\b${escapeRegExp(executeAlias)}\\b`, 'g'))?.length || 0;
|
|
1207
|
+
if (references === 1) {
|
|
1208
|
+
push('error', 'use-api-unused-execute', `useApi execute alias ${executeAlias} is never used.`, `Call or reference ${executeAlias} from onMounted, a watcher, or a user action; for read builders keep autoLoad enabled.`);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1136
1211
|
if (/\buseApi\s*\(/.test(source) && !/\bexecute\s*:/.test(source) && !/\brefresh\s*:/.test(source) && !/\.\s*(?:execute|refresh)\s*\(/.test(source)) {
|
|
1137
1212
|
push('warning', 'use-api-no-execute-alias', 'useApi() appears without an execute/refresh alias or call.', 'Call useApi() as a top-level setup composable, then call or await execute()/refresh() from onMounted, watchers, or user actions when the request should run.');
|
|
1138
1213
|
}
|
|
@@ -1342,6 +1417,9 @@ export function buildExtensionUiSnippet(kind, input = {}) {
|
|
|
1342
1417
|
case 'resource_list':
|
|
1343
1418
|
result = buildExtensionResourceListSnippet(input);
|
|
1344
1419
|
break;
|
|
1420
|
+
case 'resource_grid':
|
|
1421
|
+
result = buildExtensionResourceGridSnippet(input);
|
|
1422
|
+
break;
|
|
1345
1423
|
case 'form_editor':
|
|
1346
1424
|
result = buildExtensionFormEditorSnippet(input);
|
|
1347
1425
|
break;
|
|
@@ -1420,6 +1498,7 @@ function getExtensionThemeContract() {
|
|
|
1420
1498
|
'The extension is already mounted inside the Enfyra app shell. Do not add a duplicate page header, centered page wrapper, or root-level page padding.',
|
|
1421
1499
|
'Page extensions should be full-bleed, responsive, and split large operations into focused pages or UTabs.',
|
|
1422
1500
|
'Use usePageHeaderRegistry for the shell title and useHeaderActionRegistry/useSubHeaderActionRegistry for page actions.',
|
|
1501
|
+
'Register dynamic extension header actions inside onMounted after setup refs and handlers exist; build_extension_ui kind=page_shell generates this lifecycle shape.',
|
|
1423
1502
|
'Use build_extension_ui kind=menu_notification for sidebar menu notification registration snippets.',
|
|
1424
1503
|
'For shell menu notifications, first decide the signal source. Use a count only when the source already owns an exact count, such as a notification summary endpoint or bounded unread-notification query. Use a dot when a realtime event only proves that something new exists. Do not poll a domain list such as messages, tickets, orders, or jobs solely to decorate the menu; the destination page owns domain fetching.',
|
|
1425
1504
|
'Use build_extension_ui kind=account_panel_item for account panel row registration snippets.',
|
|
@@ -1456,8 +1535,10 @@ function getExtensionThemeContract() {
|
|
|
1456
1535
|
'Use auto-injected components directly in the template with PascalCase names. Do not call resolveComponent() to manually resolve Nuxt UI/eApp components inside extension SFCs; it can compile but render unresolved lowercase DOM tags such as <ubutton>.',
|
|
1457
1536
|
'Buttons should have stable geometry: hover may change color, border, or shadow but must not move the button or resize its content. Disabled buttons keep disabled cursor/visual state.',
|
|
1458
1537
|
'Inputs and textareas should not add hover movement or decorative hover states; focus, invalid, disabled, and loading states must be explicit.',
|
|
1459
|
-
'For drawers, modals, page shell headers/actions, permission gates, empty states, resource lists, form editors, widgets, menu/account panel registries, tabs, upload modals, api_usage, notify, and runtime/theming reviews, call build_extension_ui with the matching kind after extension acknowledgement before patching raw Vue.',
|
|
1538
|
+
'For drawers, modals, page shell headers/actions, permission gates, empty states, resource lists, resource grids, form editors, widgets, menu/account panel registries, tabs, upload modals, api_usage, notify, and runtime/theming reviews, call build_extension_ui with the matching kind after extension acknowledgement before patching raw Vue.',
|
|
1460
1539
|
'Use build_extension_ui kind=theme_classes for theme classes by intent, and kind=runtime_review, theme_review, or review before saving generated snippets that include composables, theme classes, high-contract UI, or native buttons.',
|
|
1540
|
+
'Use build_extension_ui kind=resource_grid for workboards, dashboards, catalogs, and responsive card collections instead of placing UCard children directly into a full-width list frame.',
|
|
1541
|
+
'Use the EmptyState runtime alias returned by build_extension_ui kind=empty_state; CommonEmptyState is not registered as a dynamic extension tag.',
|
|
1461
1542
|
'Extension validation rejects UInput, UTextarea, USelect, USelectMenu, UInputMenu, UInputNumber, UInputTags, UInputTime, and UInputDate without class="w-full" unless marked data-compact or data-inline.',
|
|
1462
1543
|
'Use UBadge or token-backed badge spans for status. Keep badges legible in both themes with tokenized background, text, and border.',
|
|
1463
1544
|
],
|
|
@@ -2910,13 +2991,13 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2910
2991
|
loading: z.string().optional().describe('Raw Vue expression/ref name for loading state.'),
|
|
2911
2992
|
disabled: z.string().optional().describe('Raw Vue expression/ref name for disabled state.'),
|
|
2912
2993
|
to: z.string().optional().describe('Route path for visible navigation actions.'),
|
|
2913
|
-
onClick: z.string().optional().describe('
|
|
2994
|
+
onClick: z.string().optional().describe('Script callback expression or handler reference. Prefer a handler name; bare ref assignments must use ref.value, e.g. () => (modalOpen.value = true).'),
|
|
2914
2995
|
order: z.number().optional().describe('Sort order in the shell header action area.'),
|
|
2915
2996
|
side: z.enum(['left', 'right']).optional().describe('Optional shell side.'),
|
|
2916
2997
|
});
|
|
2917
2998
|
server.tool('validate_dynamic_script', [
|
|
2918
2999
|
'Validate Enfyra dynamic script code before saving it to any script-backed metadata record.',
|
|
2919
|
-
'Use this before create/update of handlers, hooks, flow steps, websocket scripts,
|
|
3000
|
+
'Use this before create/update of handlers, hooks, flow steps, websocket scripts, OAuth provisioning scripts, or bootstrap scripts when the user is iterating on code.',
|
|
2920
3001
|
'This calls the same server compiler contract used by Enfyra, but does not save anything.',
|
|
2921
3002
|
].join(' '), {
|
|
2922
3003
|
sourceCode: z.string().describe('Raw dynamic script sourceCode.'),
|
|
@@ -2990,7 +3071,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2990
3071
|
server.tool('build_extension_ui', [
|
|
2991
3072
|
'Lazy gateway for Enfyra admin extension UI builders.',
|
|
2992
3073
|
'Use this after get_enfyra_required_knowledge(scope="extension") when a high-contract extension UI snippet is needed.',
|
|
2993
|
-
'It keeps guided startup small by dispatching drawer, modal, page_shell, permission_gate, empty_state, resource_list, form_editor, widget, menu_notification, account_panel_item, tabs, upload_modal, api_usage, notify, confirm, runtime_review, theme_classes, theme_review, or review internally instead of exposing every builder tool up front.',
|
|
3074
|
+
'It keeps guided startup small by dispatching drawer, modal, page_shell, permission_gate, empty_state, resource_list, resource_grid, form_editor, widget, menu_notification, account_panel_item, tabs, upload_modal, api_usage, notify, confirm, runtime_review, theme_classes, theme_review, or review internally instead of exposing every builder tool up front.',
|
|
2994
3075
|
].join(' '), {
|
|
2995
3076
|
kind: z.enum([
|
|
2996
3077
|
'drawer',
|
|
@@ -2999,6 +3080,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
2999
3080
|
'permission_gate',
|
|
3000
3081
|
'empty_state',
|
|
3001
3082
|
'resource_list',
|
|
3083
|
+
'resource_grid',
|
|
3002
3084
|
'form_editor',
|
|
3003
3085
|
'widget',
|
|
3004
3086
|
'menu_notification',
|
|
@@ -3115,7 +3197,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
3115
3197
|
body: z.string().describe('Vue template content to render inside PermissionGate. Field controls are normalized to w-full.'),
|
|
3116
3198
|
}, async (input) => jsonText(buildExtensionPermissionGateSnippet(input)));
|
|
3117
3199
|
server.tool('build_extension_empty_state', [
|
|
3118
|
-
'Generate
|
|
3200
|
+
'Generate an EmptyState snippet for Enfyra admin extensions.',
|
|
3119
3201
|
'Use this for app-matched empty/error/no-results states instead of hand-rolled blank panels.',
|
|
3120
3202
|
].join(' '), {
|
|
3121
3203
|
title: z.string().optional().describe('Empty state title.'),
|
|
@@ -3147,6 +3229,23 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
3147
3229
|
emptyDescription: z.string().optional().describe('Empty description.'),
|
|
3148
3230
|
emptyIcon: z.string().optional().describe('Empty icon.'),
|
|
3149
3231
|
}, async (input) => jsonText(buildExtensionResourceListSnippet(input)));
|
|
3232
|
+
server.tool('build_extension_resource_grid', [
|
|
3233
|
+
'Generate a constrained responsive CommonResourceListFrame card grid for Enfyra admin extensions.',
|
|
3234
|
+
'Use this for workboards, catalogs, dashboards, and other card collections so generated pages do not become full-width horizontal strips.',
|
|
3235
|
+
'The tool owns the page constraint, plain list-frame chrome, md/two-column and xl/three-column breakpoints, semantic card surface, loading/empty frame, and stable card height.',
|
|
3236
|
+
].join(' '), {
|
|
3237
|
+
itemsExpression: z.string().optional().default('items').describe('Vue expression for the card array, e.g. notes.'),
|
|
3238
|
+
itemName: z.string().optional().default('item').describe('Loop variable name.'),
|
|
3239
|
+
keyExpression: z.string().optional().describe('Vue expression for :key. Defaults to item.id.'),
|
|
3240
|
+
cardBody: z.string().optional().describe('Card body Vue template. Defaults to semantic title/description. Fields and native buttons are normalized.'),
|
|
3241
|
+
loadingExpression: z.string().optional().default('pending').describe('Vue expression for frame loading.'),
|
|
3242
|
+
totalExpression: z.string().optional().describe('Vue expression for total cards.'),
|
|
3243
|
+
itemsPerPageExpression: z.string().optional().describe('Vue expression for items per page; use 0 to hide pagination.'),
|
|
3244
|
+
emptyTitle: z.string().optional().describe('Empty state title.'),
|
|
3245
|
+
emptyDescription: z.string().optional().describe('Empty state description.'),
|
|
3246
|
+
emptyIcon: z.string().optional().describe('Empty state icon.'),
|
|
3247
|
+
constrained: z.boolean().optional().default(true).describe('Wrap in eapp-page-constrained-wide. Disable only for intentional full-bleed surfaces.'),
|
|
3248
|
+
}, async (input) => jsonText(buildExtensionResourceGridSnippet(input)));
|
|
3150
3249
|
server.tool('build_extension_form_editor', [
|
|
3151
3250
|
'Generate a FormEditor/FormEditorLazy snippet for Enfyra table-backed extension forms.',
|
|
3152
3251
|
'Use this instead of hand-writing UInput/UTextarea fields when the form maps directly to a table record.',
|
|
@@ -3392,7 +3491,7 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
3392
3491
|
].join(' '), {
|
|
3393
3492
|
path: z.string().describe('Custom route path, e.g. /sum. Must not be a full URL.'),
|
|
3394
3493
|
method: z.string().describe('HTTP method for the handler, e.g. GET or POST.'),
|
|
3395
|
-
sourceCode: z.string().describe('Handler sourceCode. Use macros such as @QUERY, @BODY, @THROW400, @REPOS, @USER
|
|
3494
|
+
sourceCode: z.string().describe('Handler sourceCode. Use macros such as @QUERY, @BODY, @THROW400, @REPOS, and @USER. Repository calls are async and reads return result.data. Use @REPOS.main or #secure.table_name/@REPOS.secure.table_name for user-facing access; reserve trusted repos for intentional field-permission bypass. Do not send compiledCode.'),
|
|
3396
3495
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language.'),
|
|
3397
3496
|
anonymousAccess: z.enum(['public', 'private']).optional().default('private').describe('public adds the method to publicMethods; private removes this method from publicMethods.'),
|
|
3398
3497
|
public: z.boolean().optional().describe('Compatibility alias for anonymousAccess. true means public, false means private.'),
|
|
@@ -3416,12 +3515,12 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
3416
3515
|
'Prefer api_endpoint_workflow when route access, role/user permissions, overwrite decisions, or multi-step planning matter.',
|
|
3417
3516
|
'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.',
|
|
3418
3517
|
'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.',
|
|
3419
|
-
'For sourceCode, call discover_script_contexts first. Use #table_name or @REPOS.table_name for explicit table
|
|
3518
|
+
'For sourceCode, call discover_script_contexts first. Use #secure.table_name or @REPOS.secure.table_name for explicit user-facing table access; reserve #table_name/@REPOS.table_name for intentional trusted internal access.',
|
|
3420
3519
|
'Use table/schema tools separately when the user needs persisted data. This tool is for custom behavior endpoints.',
|
|
3421
3520
|
].join(' '), {
|
|
3422
3521
|
path: z.string().describe('Custom route path, e.g. /sum. Must not be a full URL.'),
|
|
3423
3522
|
method: z.string().describe('HTTP method for the handler, e.g. GET or POST.'),
|
|
3424
|
-
sourceCode: z.string().describe('Handler sourceCode. Use macros such as @QUERY, @BODY, @THROW400, @REPOS, @USER
|
|
3523
|
+
sourceCode: z.string().describe('Handler sourceCode. Use macros such as @QUERY, @BODY, @THROW400, @REPOS, and @USER. Repository calls are async and reads return result.data. Use @REPOS.main or #secure.table_name/@REPOS.secure.table_name for user-facing access; reserve trusted repos for intentional field-permission bypass. Do not send compiledCode.'),
|
|
3425
3524
|
scriptLanguage: z.enum(['javascript', 'typescript']).optional().default('javascript').describe('Script language.'),
|
|
3426
3525
|
public: z.boolean().optional().default(false).describe('When true, the method is added to publicMethods for anonymous access.'),
|
|
3427
3526
|
description: z.string().optional().describe('Route description.'),
|