@enfyra/mcp-server 0.0.73 → 0.0.75

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.73",
3
+ "version": "0.0.75",
4
4
  "description": "MCP server for Enfyra - manage your Enfyra instance via Claude Code",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -135,7 +135,7 @@ export function buildMcpServerInstructions(apiBaseUrl) {
135
135
  '- Each route has **publishedMethods** (which HTTP verbs are “public”) and **routePermissions** (roles/users for protected access).',
136
136
  '- If the **current request method** is listed in **publishedMethods** for that route, the server allows the call **without** a Bearer token (`RoleGuard`).',
137
137
  '- Otherwise the client must send an **Authorization** header with **Bearer** JWT from login. Then the user must satisfy **routePermissions** (unless root admin).',
138
- '- Owner-scoped GET handlers must preserve caller filters and merge the owner scope for normal users, while root admin operational views may bypass that owner scope with `@USER.isRootAdmin` when the product explicitly needs global admin visibility.',
138
+ '- Owner-scoped GET handlers must preserve caller filters and merge the owner scope for normal users, while root admin operational views may bypass only the owner scope with `@USER.isRootAdmin` and must keep caller filters intact. Never replace `@QUERY.filter` with `{}` for root admins; doing so breaks detail reads, pagination, and `debugMode=true` explain checks.',
139
139
  '- MCP tools that use `fetchAPI` authenticate with the configured `ENFYRA_API_TOKEN`. Explain to users that **direct HTTP** calls need a Bearer token unless the route/method is published.',
140
140
  '',
141
141
  '### Post-hooks (REST)',
@@ -16,8 +16,11 @@ const FORBIDDEN_RELATION_KEYS = [
16
16
  'fkCol',
17
17
  'fkColumn',
18
18
  'foreignKeyColumn',
19
+ 'referencedColumn',
20
+ 'constraintName',
19
21
  'sourceColumn',
20
22
  'targetColumn',
23
+ 'junctionTableName',
21
24
  'junctionSourceColumn',
22
25
  'junctionTargetColumn',
23
26
  ];
@@ -144,6 +147,23 @@ export function normalizeRelationForTablePatch(relation) {
144
147
  return normalized;
145
148
  }
146
149
 
150
+ export function sanitizeExistingRelationForTablePatch(relation) {
151
+ const {
152
+ fkCol,
153
+ fkColumn,
154
+ foreignKeyColumn,
155
+ referencedColumn,
156
+ constraintName,
157
+ sourceColumn,
158
+ targetColumn,
159
+ junctionTableName,
160
+ junctionSourceColumn,
161
+ junctionTargetColumn,
162
+ ...rest
163
+ } = relation;
164
+ return normalizeRelationForTablePatch(rest);
165
+ }
166
+
147
167
  export function resolveRelationTargetsFromMetadata(metadata, relations) {
148
168
  return relations.map((relation) => {
149
169
  const targetTable = relation.targetTable;
@@ -210,7 +230,7 @@ async function verifyRelationCascade(ENFYRA_API_URL, tableId, beforeIds, {
210
230
  propertyName,
211
231
  }) {
212
232
  const tableData = await fetchTableWithDetails(ENFYRA_API_URL, tableId);
213
- const afterRelations = (tableData.relations || []).map(normalizeRelationForTablePatch);
233
+ const afterRelations = (tableData.relations || []).map(sanitizeExistingRelationForTablePatch);
214
234
  const afterIds = afterRelations.map((relation) => String(getId(relation))).filter((id) => id !== 'null');
215
235
  const excludedIds = action === 'delete' ? [relationId] : [];
216
236
  const missingIds = getMissingIds(beforeIds, afterIds, excludedIds);
@@ -293,7 +313,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
293
313
  if (!tableData) {
294
314
  return { content: [{ type: 'text', text: `Error: Table with ID ${sourceTableId} not found.` }] };
295
315
  }
296
- const existingRelations = (tableData.relations || []).map(normalizeRelationForTablePatch);
316
+ const existingRelations = (tableData.relations || []).map(sanitizeExistingRelationForTablePatch);
297
317
  const beforeIds = existingRelations.map((relation) => String(getId(relation))).filter((id) => id !== 'null');
298
318
  const newRelation = { targetTable: targetTableId, type, propertyName };
299
319
  if (inversePropertyName !== undefined) newRelation.inversePropertyName = inversePropertyName || null;
@@ -361,7 +381,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
361
381
  return { content: [{ type: 'text', text: `Error: Table with ID ${tableId} not found.` }] };
362
382
  }
363
383
 
364
- const existingRelations = (tableData.relations || []).map(normalizeRelationForTablePatch);
384
+ const existingRelations = (tableData.relations || []).map(sanitizeExistingRelationForTablePatch);
365
385
  const beforeIds = existingRelations.map((relation) => String(getId(relation))).filter((id) => id !== 'null');
366
386
  if (!beforeIds.includes(String(relationId))) {
367
387
  throw new Error(`Relation ${relationId} was not found on table ${tableId}; refusing schema cascade patch.`);