@enfyra/mcp-server 0.0.64 → 0.0.66

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfyra/mcp-server",
3
- "version": "0.0.64",
3
+ "version": "0.0.66",
4
4
  "description": "MCP server for Enfyra - manage your Enfyra instance via Claude Code",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -213,13 +213,13 @@ window.location.href = url.toString()`,
213
213
  }
214
214
  ]),
215
215
  indexes: JSON.stringify([
216
- ["conversation", "createdAt"],
217
- ["sender", "createdAt"]
216
+ ["conversation", "createdAt"]
218
217
  ])
219
218
  })`,
220
219
  notes: [
221
220
  'Use user_definition as the user table.',
222
221
  'Do not add inverse relations on user_definition unless the user explicitly asks.',
222
+ 'createdAt, updatedAt, and custom date/datetime/timestamp fields already get auto-generated single-field indexes; add only compound indexes needed by hot filters.',
223
223
  'Do not provide physical FK column names; Enfyra derives them.',
224
224
  ],
225
225
  },
@@ -270,6 +270,7 @@ window.location.href = url.toString()`,
270
270
  })`,
271
271
  notes: [
272
272
  'Unread is per user and per message; do not put global read state on conversation.',
273
+ 'readAt is a datetime field and gets its own auto index; the explicit indexes here are compound unread lookup indexes.',
273
274
  'For chat-list UX, default to a boolean unread dot instead of exact counts.',
274
275
  ],
275
276
  },
@@ -445,6 +446,39 @@ GET /enfyra/post?filter={"<primaryKeyFromMetadata>":{"_eq":123}}&limit=1`,
445
446
  'Do not invent deep keys like members unless members is a relation on that table.',
446
447
  ],
447
448
  },
449
+ {
450
+ name: 'Sort parent rows by child relation aggregates',
451
+ code: `query_table({
452
+ tableName: "cloud_support_tickets",
453
+ fields: [
454
+ "id",
455
+ "subject",
456
+ "status",
457
+ "project.id",
458
+ "project.name"
459
+ ],
460
+ sort: "-_max(messages.createdAt),-createdAt",
461
+ limit: 25,
462
+ deep: JSON.stringify({
463
+ messages: {
464
+ fields: "id,authorKind,body,createdAt",
465
+ sort: "-createdAt",
466
+ limit: 3
467
+ }
468
+ })
469
+ })
470
+
471
+ // Other parent aggregate sorts:
472
+ // sort=-_count(messages)
473
+ // sort=_min(messages.createdAt)`,
474
+ notes: [
475
+ 'Use _max(relation.field) for latest-child ordering, _min(relation.field) for earliest-child ordering, and _count(relation) for child-count ordering.',
476
+ 'Aggregate sort helpers only work on direct one-to-many or many-to-many list relations.',
477
+ 'The aggregate field must be a real non-encrypted scalar field on the related table.',
478
+ 'Do not use raw sort=-messages.createdAt for parent ordering; it is ambiguous and rejected.',
479
+ 'deep.messages.sort only orders the loaded message rows inside each ticket, so keep parent sort and child pagination as separate concerns.',
480
+ ],
481
+ },
448
482
  {
449
483
  name: 'Encrypted fields are not lookup fields',
450
484
  code: `// Bad: api_token is isEncrypted=true, so filter/sort cannot use it.
@@ -84,6 +84,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
84
84
  '- MCP **`create_table` supports creating columns and relations in the same call**: pass `columns` and `relations` as JSON arrays. Use `create_relation` only when adding a relation to an existing table later.',
85
85
  '- MCP **`create_table` supports `isSingleRecord` directly**. Set `isSingleRecord: true` in the create call for settings/config tables that should keep only one record; do not create first and then patch only for this flag.',
86
86
  '- MCP **`create_table` and `update_table` support `indexes` and `uniques`** as JSON arrays of logical field groups. Use compound indexes for hot filters and unread/read state, e.g. `indexes: [["member","isRead","conversation"],["conversation","member","isRead"]]` and `uniques: [["message","member"]]`. Relation property names are allowed; Enfyra resolves them to physical FK columns for SQL and Mongo.',
87
+ '- Enfyra auto-generates single-field indexes for `createdAt`, `updatedAt`, and scalar time columns with type `date`, `datetime`, or `timestamp`. SQL appends `id` as the stable tie-breaker, and Mongo appends `_id`. Do not add duplicate single-field indexes for these time fields; add explicit compound indexes only when a hot query combines the time field with other filters such as status, owner, tenant, or relation fields.',
87
88
  '- MCP **`create_table` does not accept `alias`**. Do not invent or send alias during table creation; default route/schema behavior is based on `name`. Use `update_table` later only when alias truly needs to change.',
88
89
  '- In `create_table.relations`, each relation uses `targetTable` (table id or `{id}`), `type`, `propertyName`, optional `inversePropertyName` or `mappedBy`, `isNullable`, `onDelete`, and `description`. The target table must already exist.',
89
90
  '- **Use `user_definition` as the only user table.** Do not create app-specific user/profile mapping tables such as `chat_profile`, `app_user`, `customer_user`, or tables that only mirror/link Enfyra users. If an app needs extra user fields or user relations, add columns/relations directly on `user_definition`.',
@@ -241,6 +242,8 @@ export function buildMcpServerInstructions(apiBaseUrl) {
241
242
  '- Use **`discover_query_capabilities`** before building non-trivial filters/deep queries. It returns supported filter operators, field-permission condition operators, deep shape/rules, table columns/relations, table primary key, route paths, and examples.',
242
243
  '- Full filter operators: `_eq`, `_neq`, `_gt`, `_gte`, `_lt`, `_lte`, `_in`, `_not_in`, `_nin`, `_contains`, `_starts_with`, `_ends_with`, `_between`, `_is_null`, `_is_not_null`, `_and`, `_or`, `_not`.',
243
244
  '- Field permission condition DSL is narrower and does not support `_contains`, `_starts_with`, `_ends_with`, or `_between`.',
245
+ '- Root `sort` accepts local fields such as `-createdAt` plus direct list-relation aggregate helpers: `_count(relationName)`, `_max(relationName.fieldName)`, and `_min(relationName.fieldName)`. Use `-_max(messages.createdAt)` to order parent rows by the latest child row. The relation must be direct `one-to-many` or `many-to-many`, and the aggregate field must be a non-encrypted scalar field on the related table.',
246
+ '- Raw dotted to-many sort such as `messages.createdAt` is invalid for parent ordering. `deep: { messages: { sort: "-createdAt" } }` sorts the loaded child rows inside each parent only; it does not sort the parent list.',
244
247
  '- Deep shape: `{ relationName: { fields?, filter?, sort?, limit?, page?, deep? } }`. Relation keys are relation `propertyName`, not physical FK columns.',
245
248
  '- Use dotted relation fields such as `owner.email` or `lastMessage.text` when the caller only needs basic related record fields. Use `deep` when relation loading needs query options such as `filter`, `sort`, `limit`, `page`, or nested `deep`. Do not use `deep` for simple relation-id filters, one-row lookup, counts, or large child collections that should be loaded separately with pagination.',
246
249
  '- Deep validation rejects unknown relation keys, unknown subkeys, `limit` on many-to-one/one-to-one, and invalid dotted sort through many-side relations.',
@@ -654,7 +654,7 @@ server.tool(
654
654
  queryParams: {
655
655
  fields: 'Comma-separated scalar/relation fields. Relations use relation propertyName, not physical FK column names.',
656
656
  filter: 'JSON object using operators above. Relation filters use nested relation propertyName objects.',
657
- sort: 'Field name or -field. Dotted relation sort is constrained by relation type and deep validation.',
657
+ sort: 'Local field or -field. For direct one-to-many/many-to-many parent ordering, use _count(relation), _max(relation.field), or _min(relation.field); raw dotted to-many sort is invalid.',
658
658
  page: '1-based page.',
659
659
  limit: 'Page size.',
660
660
  meta: 'Request metadata/counts where supported.',
@@ -668,6 +668,7 @@ server.tool(
668
668
  'Unknown deep entry keys are invalid.',
669
669
  'limit on many-to-one/one-to-one relations is invalid.',
670
670
  'Dotted sort through one-to-many/many-to-many is invalid.',
671
+ 'Deep sort orders rows inside the related collection only; use root aggregate sort helpers when parent rows must be ordered by child values.',
671
672
  'Nested deep is recursively validated.',
672
673
  'Field permissions may rewrite filters/sorts and sanitize post-query results.',
673
674
  ],