@enfyra/mcp-server 0.0.15 → 0.0.16
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 +1 -1
- package/src/lib/table-tools.js +52 -38
package/package.json
CHANGED
package/src/lib/table-tools.js
CHANGED
|
@@ -13,6 +13,26 @@ async function fetchTableWithDetails(ENFYRA_API_URL, tableId) {
|
|
|
13
13
|
return result?.data?.[0] || result?.[0] || null;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* PATCH table_definition with auto-confirm for schema changes.
|
|
18
|
+
* First PATCH returns preview + requiredConfirmHash; this helper
|
|
19
|
+
* automatically resends with ?schemaConfirmHash= to apply.
|
|
20
|
+
*/
|
|
21
|
+
async function patchTableAutoConfirm(ENFYRA_API_URL, tableId, body) {
|
|
22
|
+
const result = await fetchAPI(ENFYRA_API_URL, `/table_definition/${tableId}`, {
|
|
23
|
+
method: 'PATCH',
|
|
24
|
+
body: JSON.stringify(body),
|
|
25
|
+
});
|
|
26
|
+
const preview = Array.isArray(result?.data) ? result.data[0] : result?.data;
|
|
27
|
+
if (preview?._preview && preview?.requiredConfirmHash) {
|
|
28
|
+
return fetchAPI(ENFYRA_API_URL, `/table_definition/${tableId}?schemaConfirmHash=${preview.requiredConfirmHash}`, {
|
|
29
|
+
method: 'PATCH',
|
|
30
|
+
body: JSON.stringify(body),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
/**
|
|
17
37
|
* Register table tools with MCP server
|
|
18
38
|
*/
|
|
@@ -40,7 +60,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
40
60
|
[
|
|
41
61
|
'Create a new table definition with an auto-included `id` primary key column.',
|
|
42
62
|
'**Not** for adding a custom API path or handler only — for that use **`create_route`** with an existing `mainTableId`. Use **`create_table`** when the user needs new stored data (new entity).',
|
|
43
|
-
'
|
|
63
|
+
'PREFERRED: pass `columns` param as JSON array to create table WITH columns in one call (cascade). Only use create_column separately if adding to an existing table later.',
|
|
44
64
|
'Schema operations (create/update/delete table, add column) must run one at a time — migration locks DB; parallel calls will fail.',
|
|
45
65
|
'Enfyra auto-creates a default REST route at path `/<table_name>` (same segment as `name`, not alias).',
|
|
46
66
|
'REST surface for that route (matches server route engine): 4 HTTP operations — GET `/<table>` (list/filter), POST `/<table>` (create), PATCH `/<table>/:id` (update), DELETE `/<table>/:id` (delete).',
|
|
@@ -53,12 +73,14 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
53
73
|
alias: z.string().optional().describe('Table alias for API. If not provided, the table name will be used.'),
|
|
54
74
|
description: z.string().optional().describe('Description of what this table stores.'),
|
|
55
75
|
isEnabled: z.boolean().optional().default(true).describe('Enable table. Set to false to disable.'),
|
|
76
|
+
columns: z.string().optional().describe('JSON array of column definitions to create with the table (cascade). Each column: { name, type, isNullable?, isUnique?, defaultValue?, description?, options? }. The `id` column is always auto-included. Example: [{"name":"title","type":"varchar"},{"name":"status","type":"enum","options":["draft","published"]}]'),
|
|
56
77
|
},
|
|
57
|
-
async ({ name, alias, description, isEnabled }) => {
|
|
78
|
+
async ({ name, alias, description, isEnabled, columns: columnsJson }) => {
|
|
58
79
|
const idColumn = { name: 'id', type: 'int', isPrimary: true, isGenerated: true, isNullable: false };
|
|
80
|
+
const userColumns = columnsJson ? JSON.parse(columnsJson) : [];
|
|
59
81
|
const result = await fetchAPI(ENFYRA_API_URL, '/table_definition', {
|
|
60
82
|
method: 'POST',
|
|
61
|
-
body: JSON.stringify({ name, alias, description, isEnabled, columns: [idColumn] }),
|
|
83
|
+
body: JSON.stringify({ name, alias, description, isEnabled, columns: [idColumn, ...userColumns] }),
|
|
62
84
|
});
|
|
63
85
|
const base = ENFYRA_API_URL.replace(/\/$/, '');
|
|
64
86
|
const routePath = `/${name}`;
|
|
@@ -66,8 +88,11 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
66
88
|
`Auto route path: ${routePath} → full base for REST: ${base}${routePath}`,
|
|
67
89
|
`REST: GET+POST on ${routePath}; PATCH+DELETE on ${routePath}/:id only. No GET ${routePath}/:id.`,
|
|
68
90
|
].join('\n');
|
|
91
|
+
const colHint = userColumns.length
|
|
92
|
+
? `Table created with ${userColumns.length} column(s) + auto id.`
|
|
93
|
+
: `Table created. Use create_column to add columns (tableId: ${result.id}).`;
|
|
69
94
|
return {
|
|
70
|
-
content: [{ type: 'text', text:
|
|
95
|
+
content: [{ type: 'text', text: `${colHint}\n${restHint}\n\nFull result:\n${JSON.stringify(result, null, 2)}` }],
|
|
71
96
|
};
|
|
72
97
|
}
|
|
73
98
|
);
|
|
@@ -153,17 +178,14 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
153
178
|
return { content: [{ type: 'text', text: `Error: Table with ID ${tableId} not found.` }] };
|
|
154
179
|
}
|
|
155
180
|
|
|
156
|
-
const existingColumns = (tableData.columns || []).map(
|
|
181
|
+
const existingColumns = (tableData.columns || []).map(({ table, ...col }) => col);
|
|
157
182
|
const newCol = { name, type, isNullable: isNullable ?? true };
|
|
158
183
|
if (isUnique) newCol.isUnique = true;
|
|
159
184
|
if (defaultValue !== undefined) newCol.defaultValue = defaultValue;
|
|
160
185
|
if (description) newCol.description = description;
|
|
161
186
|
if (options) newCol.options = JSON.parse(options);
|
|
162
187
|
|
|
163
|
-
const result = await
|
|
164
|
-
method: 'PATCH',
|
|
165
|
-
body: JSON.stringify({ columns: [...existingColumns, newCol] }),
|
|
166
|
-
});
|
|
188
|
+
const result = await patchTableAutoConfirm(ENFYRA_API_URL, tableId, { columns: [...existingColumns, newCol] });
|
|
167
189
|
|
|
168
190
|
return {
|
|
169
191
|
content: [{ type: 'text', text: `Column "${name}" added to table ${tableId}.\n\n${JSON.stringify(result, null, 2)}` }],
|
|
@@ -198,24 +220,20 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
198
220
|
}
|
|
199
221
|
|
|
200
222
|
const columns = (tableData.columns || []).map(col => {
|
|
223
|
+
const { table, ...rest } = col;
|
|
201
224
|
if (String(col.id) === String(columnId)) {
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
if (
|
|
205
|
-
if (
|
|
206
|
-
if (
|
|
207
|
-
if (
|
|
208
|
-
if (
|
|
209
|
-
if (options !== undefined) updated.options = JSON.parse(options);
|
|
210
|
-
return updated;
|
|
225
|
+
if (name !== undefined) rest.name = name;
|
|
226
|
+
if (type !== undefined) rest.type = type;
|
|
227
|
+
if (isNullable !== undefined) rest.isNullable = isNullable;
|
|
228
|
+
if (isHidden !== undefined) rest.isHidden = isHidden;
|
|
229
|
+
if (defaultValue !== undefined) rest.defaultValue = defaultValue;
|
|
230
|
+
if (description !== undefined) rest.description = description;
|
|
231
|
+
if (options !== undefined) rest.options = JSON.parse(options);
|
|
211
232
|
}
|
|
212
|
-
return
|
|
233
|
+
return rest;
|
|
213
234
|
});
|
|
214
235
|
|
|
215
|
-
const result = await
|
|
216
|
-
method: 'PATCH',
|
|
217
|
-
body: JSON.stringify({ columns }),
|
|
218
|
-
});
|
|
236
|
+
const result = await patchTableAutoConfirm(ENFYRA_API_URL, tableId, { columns });
|
|
219
237
|
|
|
220
238
|
return {
|
|
221
239
|
content: [{ type: 'text', text: `Column ${columnId} updated on table ${tableId}.\n\n${JSON.stringify(result, null, 2)}` }],
|
|
@@ -245,12 +263,9 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
245
263
|
|
|
246
264
|
const columns = (tableData.columns || [])
|
|
247
265
|
.filter(col => String(col.id) !== String(columnId))
|
|
248
|
-
.map(
|
|
266
|
+
.map(({ table, ...col }) => col);
|
|
249
267
|
|
|
250
|
-
const result = await
|
|
251
|
-
method: 'PATCH',
|
|
252
|
-
body: JSON.stringify({ columns }),
|
|
253
|
-
});
|
|
268
|
+
const result = await patchTableAutoConfirm(ENFYRA_API_URL, tableId, { columns });
|
|
254
269
|
|
|
255
270
|
return {
|
|
256
271
|
content: [{ type: 'text', text: `Column ${columnId} deleted from table ${tableId}.\n\n${JSON.stringify(result, null, 2)}` }],
|
|
@@ -277,11 +292,13 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
277
292
|
onDelete: z.enum(['CASCADE', 'SET NULL', 'RESTRICT', 'NO ACTION']).optional().default('SET NULL').describe('On delete behavior.'),
|
|
278
293
|
},
|
|
279
294
|
async ({ sourceTableId, targetTableId, type, propertyName, inversePropertyName, isNullable, onDelete }) => {
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
});
|
|
295
|
+
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, sourceTableId);
|
|
296
|
+
if (!tableData) {
|
|
297
|
+
return { content: [{ type: 'text', text: `Error: Table with ID ${sourceTableId} not found.` }] };
|
|
298
|
+
}
|
|
299
|
+
const existingRelations = (tableData.relations || []).map(({ sourceTable, targetTable, ...rel }) => rel);
|
|
300
|
+
const newRelation = { targetTableId, type, propertyName, inversePropertyName: inversePropertyName || null, isNullable, onDelete };
|
|
301
|
+
const result = await patchTableAutoConfirm(ENFYRA_API_URL, sourceTableId, { relations: [...existingRelations, newRelation] });
|
|
285
302
|
return {
|
|
286
303
|
content: [{ type: 'text', text: `Relation created: ${propertyName} (${type}) from table ${sourceTableId} → ${targetTableId}.\n\nFull result:\n${JSON.stringify(result, null, 2)}` }],
|
|
287
304
|
};
|
|
@@ -309,12 +326,9 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
309
326
|
|
|
310
327
|
const relations = (tableData.relations || [])
|
|
311
328
|
.filter(rel => String(rel.id) !== String(relationId))
|
|
312
|
-
.map(
|
|
329
|
+
.map(({ sourceTable, targetTable, ...rel }) => rel);
|
|
313
330
|
|
|
314
|
-
const result = await
|
|
315
|
-
method: 'PATCH',
|
|
316
|
-
body: JSON.stringify({ relations }),
|
|
317
|
-
});
|
|
331
|
+
const result = await patchTableAutoConfirm(ENFYRA_API_URL, tableId, { relations });
|
|
318
332
|
|
|
319
333
|
return {
|
|
320
334
|
content: [{ type: 'text', text: `Relation ${relationId} deleted from table ${tableId}.\n\n${JSON.stringify(result, null, 2)}` }],
|