@enfyra/mcp-server 0.0.118 → 0.0.121
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
|
@@ -586,11 +586,30 @@ update_record({
|
|
|
586
586
|
notes: [
|
|
587
587
|
'Use enfyra_user as the user table.',
|
|
588
588
|
'Use table ids for targetTable when already known; MCP can also resolve exact table names such as "enfyra_user" before schema mutation.',
|
|
589
|
-
'Do not add inverse relations on enfyra_user unless
|
|
589
|
+
'Do not add inverse relations on enfyra_user unless a concrete user-to-record response, UI, or deep query will use it.',
|
|
590
|
+
'Do not add inverse relations just because a parent table exists. If messages are read by querying chat_message with conversation/member filters, conversation.messages and user.messages are not needed.',
|
|
590
591
|
'createdAt, updatedAt, and custom date/datetime/timestamp fields already get auto-generated single-field indexes; add only compound indexes needed by hot filters.',
|
|
591
592
|
'Do not provide physical FK column names; Enfyra derives them.',
|
|
592
593
|
],
|
|
593
594
|
},
|
|
595
|
+
{
|
|
596
|
+
name: 'Add an inverse only for a planned parent child-list query',
|
|
597
|
+
code: `create_relation({
|
|
598
|
+
sourceTableId: "chat_message",
|
|
599
|
+
targetTableId: "chat_conversation",
|
|
600
|
+
propertyName: "conversation",
|
|
601
|
+
inversePropertyName: "messages",
|
|
602
|
+
type: "many-to-one",
|
|
603
|
+
isNullable: false,
|
|
604
|
+
onDelete: "CASCADE"
|
|
605
|
+
})`,
|
|
606
|
+
notes: [
|
|
607
|
+
'Use inversePropertyName only when the parent table will actually expose, deep-load, count, or sort by that child collection.',
|
|
608
|
+
'For example, conversation.messages is justified if a conversation detail response loads the latest message page with deep.messages limit/sort, or if a list sorts by _max(messages.createdAt).',
|
|
609
|
+
'If the app only filters chat_message by conversation.id, omit inversePropertyName and keep the schema one-directional.',
|
|
610
|
+
'Before creating an inverse, inspect existing relations and state why the reverse traversal is needed.',
|
|
611
|
+
],
|
|
612
|
+
},
|
|
594
613
|
{
|
|
595
614
|
name: 'Add chat_conversation.lastMessage after chat_message exists',
|
|
596
615
|
code: `update_table({
|
|
@@ -27,7 +27,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
|
|
|
27
27
|
'- For a quick target/base sanity check, call `get_enfyra_api_context`; do not call broad discovery just to confirm which instance this MCP is connected to.',
|
|
28
28
|
'- Discover before deciding. For architecture/capability questions call `discover_enfyra_system`; for DB/pk/runtime/cache context call `discover_runtime_context`; for filters/deep/sort/relation query shape call `discover_query_capabilities`. Run broad discovery tools sequentially, not in parallel.',
|
|
29
29
|
'- Inspect narrowly. Use `inspect_table`, `inspect_route`, and `inspect_feature` for the table/route/feature being changed instead of loading broad metadata.',
|
|
30
|
-
'- Load examples only when needed. Before generating schemas, app connection code, OAuth, Socket.IO, handlers/hooks, flows, files, guards, permissions, or extensions, call `get_enfyra_examples` with the matching category. Before extension UI work, call `get_extension_theme_contract` and follow the app-shell/theme/security contract.',
|
|
30
|
+
'- Load examples only when needed. Before generating schemas, app connection code, OAuth, Socket.IO, handlers/hooks, flows, files, guards, permissions, or extensions, call `get_enfyra_examples` with the matching category. Before extension UI work, call `get_extension_theme_contract` and follow the app-shell/theme/security contract. For the exact class name or Nuxt UI color mapping, call `get_theme_class_reference`; this is the authoritative theme & color contract and must be followed exactly - never use raw CSS variables, Tailwind palette accents, or concrete palettes like color="violet".',
|
|
31
31
|
'- For server scripts, call `discover_script_contexts` before writing or reviewing handler/hook/flow/websocket/GraphQL logic.',
|
|
32
32
|
'- With non-root API tokens, call `get_permission_profile` before relying on admin helper tools or when debugging 403s. MCP admin helpers require ordinary route permissions for static admin routes such as `/admin/script/validate`, `/admin/test/run`, `/admin/flow/trigger/:id`, and `/admin/reload/*`.',
|
|
33
33
|
'- Prefer the most specific business operation tool over raw metadata CRUD: `api_endpoint_workflow` for step-by-step endpoint work; `create_api_endpoint` only when a one-shot endpoint operation is clearly safe; route tools such as `enable_route`, `disable_route`, `delete_route`, `add_route_methods`, `public_route_methods`, and `private_route_methods`; `set_table_graphql`; `ensure_guard`; permission/rule tools; websocket tools; flow tools such as `ensure_manual_flow`, `ensure_scheduled_flow`, `choose_flow_step_tool`, and fixed-type flow step tools; and extension tools such as `ensure_menu`, `ensure_page_extension`, `ensure_global_extension`, and `ensure_widget_extension`.',
|
|
@@ -42,6 +42,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
|
|
|
42
42
|
'- Mutations return ids/status by default. Re-read with `find_one_record` or `query_table` and explicit `fields` when the saved row matters.',
|
|
43
43
|
'- Dynamic repository reads use `filter`, not `where`: `@REPOS.table.find({ filter: {...} })`, `#table.find({ filter: {...} })`, and `exists(filter)`.',
|
|
44
44
|
'- Use `enfyra_user` as the user table. Model record links as real relations using relation `propertyName` values, not physical FK fields like `userId`, `conversationId`, `senderId`, or `memberId` in generated DB code.',
|
|
45
|
+
'- Relation design must stay minimal. Create the owning relation needed for writes/filters first; add `inversePropertyName` only when a concrete response, UI, deep query, aggregate sort/count, or parent-to-child traversal will use that reverse field. For schema work, explicitly review existing relations and mention which inverses are intentionally present or intentionally omitted.',
|
|
45
46
|
'- Do not call internal/no-route system tables such as `enfyra_column` or `enfyra_session` through generic CRUD. Use table/column/relation tools and route-backed tables discovered from metadata.',
|
|
46
47
|
'- Custom API paths use `api_endpoint_workflow` when a handler is needed and the model should follow returned nextSteps. Use lower-level `create_route` without `mainTableId` only when intentionally creating a route shell; `create_table` is only for new persisted data.',
|
|
47
48
|
'- For canonical table reads and RLS, preserve client-controlled query shape: do not override `@QUERY.fields`, `@QUERY.deep`, `@QUERY.sort`, `@QUERY.limit`, `@QUERY.page`, `@QUERY.meta`, `@QUERY.aggregate`, or `debugMode`. Merge only security filters into `@QUERY.filter`.',
|
|
@@ -305,7 +305,7 @@ function getExtensionThemeContract() {
|
|
|
305
305
|
'Primary color is runtime-configurable through the app color picker and must affect extension identity UI. For Nuxt UI components, choose color="primary" by semantic intent and let the app map it through the primary contract; do not choose a concrete palette. For custom extension UI, first choose whether the element is neutral surface, runtime-primary identity, or status. Regular panels, KPI cards, list rows, and large content blocks should use eapp-surface-card, eapp-surface-muted, eapp-surface-flat, eapp-surface-hover, eapp-divide-y, and eapp-text-* classes. Entity identity, selected/current state, active progress, primary tiles, primary icons, and primary CTA fills should use eapp-primary-surface, eapp-primary-soft, eapp-primary-subtle, eapp-primary-solid, eapp-primary-text, eapp-primary-border, or eapp-primary-ring so the color picker controls them.',
|
|
306
306
|
'Use eapp-primary-surface only for larger entity/feature blocks, selected/current cards, tiles, or cards that should read like normal app cards with a very subtle active-primary tint; it is not a saturated selected-state fill and must not be applied broadly to every KPI/list wrapper. Add eapp-primary-surface-hover when that block is clickable. Use eapp-primary-soft for compact selected entity chips, pills, small icon tiles, and identity callouts; add eapp-primary-soft-hover when compact surfaces are clickable; use eapp-primary-subtle for a slightly stronger selected fill; use eapp-primary-solid only for primary identity fills; use eapp-primary-text for identity icons or inline text. eapp-identity-* remains an alias for the same runtime-primary intent, but eapp-primary-* is preferred in new extension code.',
|
|
307
307
|
'Nuxt UI secondary is still a valid semantic color when the product intentionally wants a secondary action or state. Do not use color="secondary", from-secondary-*, bg-secondary-*, text-secondary-*, or cyan/purple/green palette utilities merely to approximate an entity accent; use eapp-primary-* and let the app decide the color.',
|
|
308
|
-
'
|
|
308
|
+
'The app runs on Tailwind v4. Short Tailwind color utilities are the canonical way to apply contract colors and ARE allowed: bg-primary, text-primary, border-primary, ring-primary, bg-success, text-error, etc., including opacity modifiers (bg-primary/10, ring-success/20) which v4 resolves via color-mix. They are generated from the token-backed config (primary -> --md-primary runtime, success/error/warning/info -> --st-* status, secondary -> --md-tertiary) so they follow the color picker and dark theme. Do NOT use raw CSS-variable utilities (text-[var(--*)], bg-[var(--*)], border-[var(--*)]), hardcoded hex, inline style colors, or concrete palette substitution (color="violet", from-cyan-*, text-violet-*, bg-green-*, bg-emerald-*, text-green-*, dark:bg-zinc-950). For intent surfaces with no Tailwind equivalent (selected identity block, soft/solid/subtle surface, divider, radius, modal chrome) use the eapp-* classes below; the app owns how they map to the active color picker value.',
|
|
309
309
|
'Use UButton color="primary" only for the single main action for the current scope. Refresh, back, navigation, filters, and secondary actions should be neutral variants unless they are the main mutation.',
|
|
310
310
|
'PageHeader gradient must be "none" for generated operational extensions unless the user explicitly asks for a decorative page accent. Do not hardcode cyan, violet, purple, blue, or green PageHeader gradients to force color variety.',
|
|
311
311
|
'Do not inject global CSS, create theme guards, redefine the app palette, or solve one extension by overriding the whole app shell.',
|
|
@@ -399,6 +399,68 @@ function getExtensionThemeContract() {
|
|
|
399
399
|
},
|
|
400
400
|
],
|
|
401
401
|
compactExample: '<template><section class="min-h-full w-full space-y-4"><article class="eapp-surface-card p-4"><div class="flex items-start justify-between gap-3"><div><p class="text-sm eapp-text-tertiary">Neutral KPI</p><p class="mt-2 text-2xl font-semibold eapp-text-primary">24</p></div><span class="eapp-primary-soft eapp-radius-control p-2"><UIcon name="lucide:square-stack" class="size-5 eapp-primary-text" /></span></div><div class="mt-3 h-1.5 overflow-hidden eapp-radius-pill eapp-surface-muted"><div class="eapp-primary-solid h-full w-1/2"></div></div></article><section class="eapp-surface-card p-4"><div class="flex items-center justify-between gap-3"><p class="font-semibold eapp-text-primary">Status block stays neutral</p><UBadge color="success" variant="soft">Healthy</UBadge></div></section></section></template>',
|
|
402
|
+
contractAuthority: [
|
|
403
|
+
'This is the authoritative Enfyra theme & color contract. Source of truth: documents/app/theme-color-contract.md. The app owns color through app/utils/primary-colors.ts (Material You seed-to-role generation), app/assets/css/theme.css (semantic variables and Nuxt UI ramps), app/assets/css/main.css (extension-safe semantic utilities), and app/app.config.ts (Nuxt UI component mapping). Pages and extensions only CONSUME classes/Nuxt UI props; they never define colors.',
|
|
404
|
+
'Every color flows from two base layers: --md-* (Material You, runtime primary picker) and --st-* (status). Runtime primary roles are generated with SchemeTonalSpot. Success/warning/info stay fixed status quarts; error follows the generated Material error role through the single --danger-* lane. All Nuxt UI semantic colors (primary/secondary/success/warning/error/info/neutral) are re-pointed to these, so Nuxt UI is used per its docs but colors are decided by Enfyra. This applies to the shell, system pages, and compiled dynamic extensions.',
|
|
405
|
+
'Call get_theme_class_reference for the full class->variable->Nuxt UI table when you need the exact class name or variable.',
|
|
406
|
+
],
|
|
407
|
+
classReference: {
|
|
408
|
+
surfaces: ['eapp-surface-card (default card; --card-bg/--card-border)', 'eapp-surface-muted (recessed/track; --surface-muted)', 'eapp-surface-flat (flush; --surface-default)', 'eapp-surface-hover (clickable row hover)'],
|
|
409
|
+
text: ['eapp-text-primary', 'eapp-text-secondary', 'eapp-text-tertiary', 'eapp-text-quaternary'],
|
|
410
|
+
primaryIdentity: ['eapp-primary-solid (solid fill/meter)', 'eapp-primary-text (inline/icon)', 'eapp-primary-soft + -hover (compact chip/tile)', 'eapp-primary-subtle (stronger selected fill)', 'eapp-primary-surface + -hover (large selected identity block)', 'eapp-primary-border', 'eapp-primary-ring'],
|
|
411
|
+
status: ['eapp-status-success|warning|danger|info|neutral -soft/-text/-border (badges/small icons/short text only)'],
|
|
412
|
+
radius: ['eapp-radius-card', 'eapp-radius-panel', 'eapp-radius-control', 'eapp-radius-subcontrol', 'eapp-radius-pill'],
|
|
413
|
+
dividers: ['eapp-divider', 'eapp-divide-y'],
|
|
414
|
+
modal: ['eapp-modal-surface (modal content chrome; never surface-card)'],
|
|
415
|
+
nuxtUiMapping: 'primary=--md-primary(runtime), secondary=--md-tertiary(runtime), success=--st-success, warning=--st-warning, error=--st-error, info=--st-info, neutral=neutral surfaces',
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function getThemeClassReference() {
|
|
421
|
+
return {
|
|
422
|
+
action: 'theme_class_reference',
|
|
423
|
+
authority: 'Authoritative Enfyra theme & color contract. Source of truth: documents/app/theme-color-contract.md. App owns color via theme.css + main.css + app.config.ts only; pages/extensions consume classes and Nuxt UI props.',
|
|
424
|
+
baseLayers: {
|
|
425
|
+
material: '--md-* (runtime primary picker, HCT/Material You). Drives identity/brand. Never read directly in templates.',
|
|
426
|
+
status: '--st-success/--st-warning/--st-error/--st-info. Fixed semantic palette. Never read directly in templates.',
|
|
427
|
+
},
|
|
428
|
+
nuxtUiColors: {
|
|
429
|
+
primary: 'runtime --md-primary (main brand action/identity). NEVER substitute a concrete palette.',
|
|
430
|
+
secondary: 'runtime --md-tertiary (intentional secondary accent only).',
|
|
431
|
+
success: '--st-success (healthy/success).',
|
|
432
|
+
warning: '--st-warning (pending/attention).',
|
|
433
|
+
error: 'single --danger-* lane from Material error roles (destructive/error). Ghost danger text uses --danger-on-surface; danger fills use --danger-surface.',
|
|
434
|
+
info: '--st-info (informational).',
|
|
435
|
+
neutral: 'neutral surfaces (secondary chrome, non-actions).',
|
|
436
|
+
},
|
|
437
|
+
classes: [
|
|
438
|
+
{ group: 'Surfaces (large ordinary - keep neutral)', classes: 'eapp-surface-card, eapp-surface-muted, eapp-surface-flat, eapp-surface-hover' },
|
|
439
|
+
{ group: 'Text', classes: 'eapp-text-primary, eapp-text-secondary, eapp-text-tertiary, eapp-text-quaternary' },
|
|
440
|
+
{ group: 'Runtime primary identity', classes: 'eapp-primary-solid, eapp-primary-text, eapp-primary-soft(+hover), eapp-primary-subtle, eapp-primary-surface(+hover), eapp-primary-border, eapp-primary-ring' },
|
|
441
|
+
{ group: 'Status (badges/small icons/short text only)', classes: 'eapp-status-{success|warning|danger|info|neutral}-{soft|text|border}' },
|
|
442
|
+
{ group: 'Radius', classes: 'eapp-radius-card, eapp-radius-panel, eapp-radius-control, eapp-radius-subcontrol, eapp-radius-pill' },
|
|
443
|
+
{ group: 'Dividers', classes: 'eapp-divider, eapp-divide-y' },
|
|
444
|
+
{ group: 'Modal', classes: 'eapp-modal-surface (never surface-card as modal ui.content)' },
|
|
445
|
+
],
|
|
446
|
+
forbidden: [
|
|
447
|
+
'Raw CSS variables in templates: text-[var(--*)], bg-[var(--*)], border-[var(--*)].',
|
|
448
|
+
'Tailwind palette accents: from-cyan-*, text-violet-*, bg-green-*, bg-emerald-*, text-gray-*, bg-slate-*, dark:bg-zinc-950.',
|
|
449
|
+
'Concrete palette substitution (color="violet"/"cyan"/..., from-cyan-*, text-violet-*, bg-green-*, bg-emerald-*, dark:bg-zinc-950).',
|
|
450
|
+
'Hardcoded hex colors or inline style="color:#..." for theme-driven surfaces.',
|
|
451
|
+
'Reading --md-* / --st-* / --badge-* base variables directly from extension templates.',
|
|
452
|
+
],
|
|
453
|
+
allowedShortUtilities: [
|
|
454
|
+
'Tailwind v4 short utilities ARE canonical and preferred: bg-primary, text-primary, border-primary, ring-primary, bg-success, text-error, bg-warning, text-info, bg-secondary.',
|
|
455
|
+
'Opacity modifiers work natively via v4 color-mix: bg-primary/10, ring-primary/20, text-primary/70, bg-success/15.',
|
|
456
|
+
'Use eapp-* classes only for intent surfaces with no Tailwind equivalent (eapp-primary-surface/solid/soft/subtle, eapp-surface-card/muted/flat/hover, eapp-divider/divide-y, eapp-radius-*, eapp-modal-surface).',
|
|
457
|
+
],
|
|
458
|
+
chooseByIntent: [
|
|
459
|
+
'Normal accent / active tab / progress / primary CTA -> primary (eapp-primary-* or color="primary").',
|
|
460
|
+
'True semantic state -> status (eapp-status-* or color="success|warning|error|info").',
|
|
461
|
+
'Large ordinary surface -> eapp-surface-*; put a small status badge inside.',
|
|
462
|
+
'Whole block is active identity -> eapp-primary-surface (+hover), subtle only.',
|
|
463
|
+
],
|
|
402
464
|
};
|
|
403
465
|
}
|
|
404
466
|
|
|
@@ -1114,6 +1176,17 @@ export function registerPlatformOperationTools(server, ENFYRA_API_URL) {
|
|
|
1114
1176
|
async () => jsonText(getExtensionThemeContract()),
|
|
1115
1177
|
);
|
|
1116
1178
|
|
|
1179
|
+
server.tool(
|
|
1180
|
+
'get_theme_class_reference',
|
|
1181
|
+
[
|
|
1182
|
+
'Return the authoritative Enfyra theme & color class reference: class -> CSS variable -> Nuxt UI semantic color -> intent.',
|
|
1183
|
+
'Call this whenever you need the exact eapp-* class name or the Nuxt UI color mapping for shell, system page, or dynamic extension UI.',
|
|
1184
|
+
'Source of truth: documents/app/theme-color-contract.md.',
|
|
1185
|
+
].join(' '),
|
|
1186
|
+
{},
|
|
1187
|
+
async () => jsonText(getThemeClassReference()),
|
|
1188
|
+
);
|
|
1189
|
+
|
|
1117
1190
|
server.tool(
|
|
1118
1191
|
'set_table_graphql',
|
|
1119
1192
|
'Business operation: enable or disable GraphQL for one table through enfyra_graphql, then reload GraphQL. REST route methods do not control GraphQL.',
|
package/src/lib/table-tools.js
CHANGED
|
@@ -465,7 +465,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
465
465
|
targetTableId: z.string().describe('Target table id, exact table name, or alias. MCP resolves names/aliases to ids before mutation.'),
|
|
466
466
|
type: z.enum(['many-to-one', 'one-to-many', 'one-to-one', 'many-to-many']).describe('Relation type.'),
|
|
467
467
|
propertyName: z.string().describe('Property name on source table (e.g., "customer", "items").'),
|
|
468
|
-
inversePropertyName: z.string().optional().describe('Property name on target table for bidirectional relation (e.g., "orders"). Omit unless the reverse field
|
|
468
|
+
inversePropertyName: z.string().optional().describe('Property name on target table for bidirectional relation (e.g., "orders"). Omit unless a concrete response, UI, deep query, aggregate sort/count, or parent-to-child traversal will use the reverse field. Do not add inverses merely because a parent table exists.'),
|
|
469
469
|
mappedBy: z.string().optional().describe('Mapped-by property for inverse relation shapes when required by the backend. Do not use physical FK names.'),
|
|
470
470
|
isNullable: z.boolean().optional().default(true).describe('Whether the relation is nullable.'),
|
|
471
471
|
onDelete: z.enum(['CASCADE', 'SET NULL', 'RESTRICT']).optional().default('SET NULL').describe('On delete behavior.'),
|
|
@@ -546,7 +546,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
546
546
|
'**Not** for adding a custom API path or handler only — for that use **`create_route`** without `mainTableId`. Use **`create_table`** when the user needs new stored data (new entity).',
|
|
547
547
|
'PREFERRED: pass `columns` and `relations` params as JSON arrays to create a table WITH columns and relations in one call (cascade). Only use create_column/create_relation separately when adding to an existing table later.',
|
|
548
548
|
'Indexes and uniques are first-class table metadata. Use `indexes` for query performance and `uniques` for data integrity. Each entry is a logical field group such as [["member","isRead","conversation"]] or [{"value":["message","member"]}]. Relation property names are allowed; Enfyra resolves them to physical FK columns.',
|
|
549
|
-
'Relations are supported in this same create_table call when the target table already exists. Each relation uses { targetTable, type, propertyName, inversePropertyName?, mappedBy?, isNullable?, onDelete? }; targetTable may be a table id, {id}, or an exact table name that MCP resolves to an id before mutation.',
|
|
549
|
+
'Relations are supported in this same create_table call when the target table already exists. Each relation uses { targetTable, type, propertyName, inversePropertyName?, mappedBy?, isNullable?, onDelete? }; targetTable may be a table id, {id}, or an exact table name that MCP resolves to an id before mutation. Omit inversePropertyName unless a concrete parent-to-child query or UI surface needs it.',
|
|
550
550
|
'Do NOT provide physical FK/junction columns. Never include fkCol, fkColumn, foreignKeyColumn, sourceColumn, targetColumn, junctionSourceColumn, or junctionTargetColumn. Enfyra derives and hides those physical columns from relation propertyName/table metadata.',
|
|
551
551
|
'Schema operations (create/update/delete table, add column) must run one at a time — migration locks DB; parallel calls will fail.',
|
|
552
552
|
'Enfyra auto-creates a default REST route at path `/<table_name>` (same segment as `name`, not alias).',
|
|
@@ -562,7 +562,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
562
562
|
description: z.string().optional().describe('Description of what this table stores.'),
|
|
563
563
|
isSingleRecord: z.boolean().optional().describe('Set to true for single-record tables such as settings/config. This is passed directly to enfyra_table create.'),
|
|
564
564
|
columns: z.string().optional().describe('JSON array of column definitions to create with the table (cascade). Each column: { name, type, isNullable?, isUnique?, isPublished?, isUpdatable?, isEncrypted?, defaultValue?, description?, options? }. Set isEncrypted=true for values encrypted at rest; set isUpdatable=false separately only when the field should be immutable. The `id` column is always auto-included. Example: [{"name":"title","type":"varchar"},{"name":"api_key","type":"varchar","isEncrypted":true,"isPublished":false}]'),
|
|
565
|
-
relations: z.string().optional().describe('JSON array of relation definitions to create with the table in the same cascade call. Each relation: { targetTable, type, propertyName, inversePropertyName?, mappedBy?, isNullable?, onDelete?, description? }. targetTable can be an id, {"id": <id>}, or an exact table name that MCP resolves to an id before mutation. Do not include physical FK/junction columns such as fkCol, foreignKeyColumn, sourceColumn, targetColumn, junctionSourceColumn, or junctionTargetColumn; Enfyra derives them and hides FK columns from app schema. Example: [{"targetTable":2,"type":"many-to-one","propertyName":"author","inversePropertyName":"posts","isNullable":false,"onDelete":"CASCADE"}]'),
|
|
565
|
+
relations: z.string().optional().describe('JSON array of relation definitions to create with the table in the same cascade call. Each relation: { targetTable, type, propertyName, inversePropertyName?, mappedBy?, isNullable?, onDelete?, description? }. targetTable can be an id, {"id": <id>}, or an exact table name that MCP resolves to an id before mutation. Do not include physical FK/junction columns such as fkCol, foreignKeyColumn, sourceColumn, targetColumn, junctionSourceColumn, or junctionTargetColumn; Enfyra derives them and hides FK columns from app schema. Omit inversePropertyName unless a concrete response, UI, deep query, aggregate sort/count, or parent-to-child traversal needs the reverse field. Example only when parent posts are queried: [{"targetTable":2,"type":"many-to-one","propertyName":"author","inversePropertyName":"posts","isNullable":false,"onDelete":"CASCADE"}]'),
|
|
566
566
|
indexes: z.string().optional().describe('JSON array of logical index field groups. Each group can be ["fieldA","fieldB"] or {"value":["fieldA","fieldB"]}. Relation property names are allowed. Example: [["member","isRead","conversation"],["conversation","member","isRead"]]'),
|
|
567
567
|
uniques: z.string().optional().describe('JSON array of logical unique field groups. Each group can be ["fieldA","fieldB"] or {"value":["fieldA","fieldB"]}. Example: [["message","member"]]'),
|
|
568
568
|
},
|
package/src/mcp-server-entry.mjs
CHANGED
|
@@ -888,7 +888,7 @@ server.tool(
|
|
|
888
888
|
relations: routeTables.has('enfyra_relation')
|
|
889
889
|
? 'enfyra_relation has a REST route for reads/metadata, but canonical schema migration is create_relation/delete_relation or enfyra_table PATCH with the full relations array. Relation onDelete accepts CASCADE, SET NULL, or RESTRICT.'
|
|
890
890
|
: 'Use create_relation/delete_relation or enfyra_table PATCH with the full relations array. Relation onDelete accepts CASCADE, SET NULL, or RESTRICT.',
|
|
891
|
-
relationCascadeFkContract: 'Do not ask for or send physical FK/junction column names in relation create/update payloads. Enfyra derives fk/junction columns from relation propertyName/table metadata and hides FK columns from app schema/forms. Use targetTable, type, propertyName, inversePropertyName or mappedBy, isNullable, onDelete.',
|
|
891
|
+
relationCascadeFkContract: 'Do not ask for or send physical FK/junction column names in relation create/update payloads. Enfyra derives fk/junction columns from relation propertyName/table metadata and hides FK columns from app schema/forms. Use targetTable, type, propertyName, inversePropertyName or mappedBy, isNullable, onDelete. Add inversePropertyName only when a concrete response, UI, deep query, aggregate sort/count, or parent-to-child traversal will use the reverse field.',
|
|
892
892
|
tableDefinitionRelations: (tableDefinition?.relations || []).map((rel) => rel.propertyName),
|
|
893
893
|
relationDefinitionRelations: (relationTable?.relations || []).map((rel) => rel.propertyName),
|
|
894
894
|
},
|
|
@@ -1068,7 +1068,7 @@ server.tool(
|
|
|
1068
1068
|
? 'Use this table metadata primary column when available.'
|
|
1069
1069
|
: 'SQL commonly uses id; Mongo uses _id. Use table metadata primary column when available.',
|
|
1070
1070
|
relationNames: 'API relation operations use relation propertyName, not physical FK column names.',
|
|
1071
|
-
relationCascadeFkContract: 'When creating relations through create_table/create_relation/enfyra_table PATCH, never provide fkCol/fkColumn/foreignKeyColumn/sourceColumn/targetColumn/junction*Column. These are physical implementation details derived by Enfyra and hidden from app schema/forms.',
|
|
1071
|
+
relationCascadeFkContract: 'When creating relations through create_table/create_relation/enfyra_table PATCH, never provide fkCol/fkColumn/foreignKeyColumn/sourceColumn/targetColumn/junction*Column. These are physical implementation details derived by Enfyra and hidden from app schema/forms. Add inversePropertyName only for a concrete reverse traversal such as parent deep child lists, response fields, UI sections, or aggregate sort/count.',
|
|
1072
1072
|
graphql: 'GraphQL query args also accept filter/sort/page/limit. Table data requires Bearer auth and table enablement via enfyra_graphql; anonymous root/schema probes may still return 200.',
|
|
1073
1073
|
},
|
|
1074
1074
|
table: tableName
|
|
@@ -2088,7 +2088,7 @@ server.tool(
|
|
|
2088
2088
|
fields: 'Use column names and relation propertyName values.',
|
|
2089
2089
|
filter: 'Use query DSL operators on column names or nested relation propertyName objects.',
|
|
2090
2090
|
deep: 'Deep fetch keys are relation propertyName values.',
|
|
2091
|
-
relationMutation: 'For relation schema creation/update use targetTable/type/propertyName/inversePropertyName|mappedBy/isNullable/onDelete only. Do not provide physical FK/junction columns; Enfyra derives and hides them.',
|
|
2091
|
+
relationMutation: 'For relation schema creation/update use targetTable/type/propertyName/inversePropertyName|mappedBy/isNullable/onDelete only. Do not provide physical FK/junction columns; Enfyra derives and hides them. Omit inversePropertyName unless a concrete response, UI, deep query, aggregate sort/count, or parent-to-child traversal needs it.',
|
|
2092
2092
|
},
|
|
2093
2093
|
};
|
|
2094
2094
|
|