@enfyra/mcp-server 0.1.18 → 0.1.19
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/dist/lib/mcp-instructions.js +3 -2
- package/dist/lib/mcp-instructions.js.map +1 -1
- package/dist/lib/mutation-guards.d.ts +11 -0
- package/dist/lib/mutation-guards.js +46 -2
- package/dist/lib/mutation-guards.js.map +1 -1
- package/dist/lib/required-knowledge.d.ts +1 -1
- package/dist/lib/required-knowledge.js +3 -2
- package/dist/lib/required-knowledge.js.map +1 -1
- package/dist/lib/table-tools.js +39 -0
- package/dist/lib/table-tools.js.map +1 -1
- package/dist/lib/tool-routing.js +9 -3
- package/dist/lib/tool-routing.js.map +1 -1
- package/dist/mcp-server-entry.js +67 -4
- package/dist/mcp-server-entry.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp-server-entry.js
CHANGED
|
@@ -26,7 +26,7 @@ import { WORKFLOW_SURFACES, discoverWorkflowRoutes } from './lib/tool-routing.js
|
|
|
26
26
|
import { getSupportedColumnTypesFromMetadata, registerTableTools } from './lib/table-tools.js';
|
|
27
27
|
import { registerPlatformOperationTools, validateExtensionCode } from './lib/platform-operation-tools.js';
|
|
28
28
|
import { registerRuntimeZoneTools } from './lib/runtime-zone-tools.js';
|
|
29
|
-
import { parseRecordData, prepareRecordMutation, validateScriptSourceIfPresent } from './lib/mutation-guards.js';
|
|
29
|
+
import { parseRecordBatchData, parseRecordData, prepareRecordBatchMutation, prepareRecordMutation, validateScriptSourceIfPresent } from './lib/mutation-guards.js';
|
|
30
30
|
import { assertDynamicCodeKnowledgeAck, assertDynamicCodeKnowledgeAckIf, assertExtensionKnowledgeAckIf, assertGlobalRulesAck, buildRequiredKnowledgePayload, dynamicCodeKnowledgeAckParam, extensionKnowledgeAckParam, globalRulesAckParam, } from './lib/required-knowledge.js';
|
|
31
31
|
import { validateMainTableRoutePath } from './lib/route-guards.js';
|
|
32
32
|
import { installColumnarToolFormatter, jsonContent } from './lib/response-format.js';
|
|
@@ -558,11 +558,28 @@ async function prepareGenericMutation(tableName, data) {
|
|
|
558
558
|
data,
|
|
559
559
|
});
|
|
560
560
|
}
|
|
561
|
+
async function prepareGenericBatchMutation(tableName, records) {
|
|
562
|
+
const { tables } = await getMetadataTables();
|
|
563
|
+
return prepareRecordBatchMutation({
|
|
564
|
+
fetchAPI,
|
|
565
|
+
apiUrl: ENFYRA_API_URL,
|
|
566
|
+
tables,
|
|
567
|
+
tableName,
|
|
568
|
+
records,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
561
571
|
function assertKnowledgeForGenericMutation(tableName, data, { knowledgeAckKey, extensionKnowledgeAckKey }) {
|
|
562
572
|
const payload = parseRecordData(data);
|
|
563
573
|
assertDynamicCodeKnowledgeAckIf(SCRIPT_BACKED_TABLE_SET.has(tableName) && typeof payload.sourceCode === 'string', knowledgeAckKey);
|
|
564
574
|
assertExtensionKnowledgeAckIf(tableName === 'enfyra_extension' && typeof payload.code === 'string', extensionKnowledgeAckKey);
|
|
565
575
|
}
|
|
576
|
+
function assertKnowledgeForGenericBatchMutation(tableName, records, { knowledgeAckKey, extensionKnowledgeAckKey }) {
|
|
577
|
+
const payloads = parseRecordBatchData(records);
|
|
578
|
+
for (const payload of payloads) {
|
|
579
|
+
assertDynamicCodeKnowledgeAckIf(SCRIPT_BACKED_TABLE_SET.has(tableName) && typeof payload.sourceCode === 'string', knowledgeAckKey);
|
|
580
|
+
assertExtensionKnowledgeAckIf(tableName === 'enfyra_extension' && typeof payload.code === 'string', extensionKnowledgeAckKey);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
566
583
|
async function validateExtensionCodeForGenericMutation(tableName, payload, fallbackName) {
|
|
567
584
|
if (tableName !== 'enfyra_extension' || typeof payload?.code !== 'string')
|
|
568
585
|
return null;
|
|
@@ -844,7 +861,7 @@ server.tool('discover_enfyra_system', [
|
|
|
844
861
|
publicAccess: 'publicMethods controls anonymous REST access per route/method; otherwise Bearer JWT + routePermissions apply.',
|
|
845
862
|
routeTables: sample(routeTableList),
|
|
846
863
|
noRouteTables: sample(noRouteTableList),
|
|
847
|
-
canonicalCrudTools: 'query_table/create_record/update_record/delete_record use dynamic REST routes and only work for route-backed tables.',
|
|
864
|
+
canonicalCrudTools: 'query_table/create_record/create_records/update_record/delete_record use dynamic REST routes and only work for route-backed tables. create_record is single-object only; create_records accepts an array, preflights every item against live metadata, then posts sequentially.',
|
|
848
865
|
customRouteWorkflow: 'For a new endpoint use create_route without mainTableId, then create_handler/create_pre_hook/create_post_hook. Do not create a table just to get a path.',
|
|
849
866
|
routeSamples: sample(routes, 25),
|
|
850
867
|
detailHint: 'Use get_all_routes({ search, limit }) or inspect_route({ path }) for route details. Use inspect_table({ tableName }) for table detail.',
|
|
@@ -1362,9 +1379,9 @@ server.tool('find_one_record', 'Find a single record by ID or filter. By ID uses
|
|
|
1362
1379
|
// ============================================================================
|
|
1363
1380
|
// CRUD TOOLS
|
|
1364
1381
|
// ============================================================================
|
|
1365
|
-
server.tool('create_record', 'Create
|
|
1382
|
+
server.tool('create_record', 'Create exactly one record in a route-backed table. Pass a single JSON object, not an array. The tool preflights body keys against live metadata before POST, validates sourceCode before saving script-backed records, and validates enfyra_extension.code before saving extension records. For multiple records, use create_records.', {
|
|
1366
1383
|
tableName: z.string().describe('Table name to insert into'),
|
|
1367
|
-
data: z.string().describe('
|
|
1384
|
+
data: z.string().describe('Single record data as a JSON object string. Arrays are intentionally rejected; use create_records for batch seeding.'),
|
|
1368
1385
|
queryParams: z.string().optional().describe('Optional query params as JSON object string, e.g. {"expired_at":"2026-09-20"}. Use for route contracts that intentionally keep workflow fields out of the validated body.'),
|
|
1369
1386
|
globalRulesAckKey: globalRulesAckParam(z),
|
|
1370
1387
|
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required only when data contains sourceCode. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
@@ -1383,6 +1400,52 @@ server.tool('create_record', 'Create a new record in any route-backed table. The
|
|
|
1383
1400
|
extensionValidation,
|
|
1384
1401
|
}, null, 2) }] };
|
|
1385
1402
|
});
|
|
1403
|
+
server.tool('create_records', 'Create multiple records in one MCP call for route-backed table seeding. Pass a JSON array string. MCP preflights every item against live metadata first, then sends one POST per record sequentially; this is not a backend bulk endpoint or transaction.', {
|
|
1404
|
+
tableName: z.string().describe('Table name to insert into'),
|
|
1405
|
+
records: z.string().describe('Records as a JSON array string. Each item must be a JSON object using metadata-backed column names and relation propertyName values.'),
|
|
1406
|
+
queryParams: z.string().optional().describe('Optional query params as JSON object string applied to every POST, for route contracts that intentionally keep workflow fields out of the validated body.'),
|
|
1407
|
+
maxRecords: z.number().int().min(1).max(100).optional().default(100).describe('Safety cap for one MCP batch. Default/max is 100. For larger imports, split intentionally.'),
|
|
1408
|
+
globalRulesAckKey: globalRulesAckParam(z),
|
|
1409
|
+
knowledgeAckKey: dynamicCodeKnowledgeAckParam(z).optional().describe('Required only when any item contains sourceCode. Use dynamicCodeAckKey from get_enfyra_required_knowledge.'),
|
|
1410
|
+
extensionKnowledgeAckKey: extensionKnowledgeAckParam(z).optional().describe('Required only when tableName is enfyra_extension and any item contains code. Use extensionAckKey from get_enfyra_required_knowledge.'),
|
|
1411
|
+
}, async ({ tableName, records, queryParams, maxRecords, globalRulesAckKey, knowledgeAckKey, extensionKnowledgeAckKey }) => {
|
|
1412
|
+
assertGlobalRulesAck(globalRulesAckKey);
|
|
1413
|
+
validateTableName(tableName);
|
|
1414
|
+
const parsedRecords = parseRecordBatchData(records);
|
|
1415
|
+
if (parsedRecords.length > maxRecords) {
|
|
1416
|
+
throw new Error(`create_records received ${parsedRecords.length} records, above maxRecords=${maxRecords}. Split the batch deliberately.`);
|
|
1417
|
+
}
|
|
1418
|
+
assertKnowledgeForGenericBatchMutation(tableName, parsedRecords, { knowledgeAckKey, extensionKnowledgeAckKey });
|
|
1419
|
+
const prepared = await prepareGenericBatchMutation(tableName, parsedRecords);
|
|
1420
|
+
const extensionValidations = [];
|
|
1421
|
+
for (const item of prepared.records) {
|
|
1422
|
+
extensionValidations.push(await validateExtensionCodeForGenericMutation(tableName, item.payload, item.payload?.name || item.index));
|
|
1423
|
+
}
|
|
1424
|
+
const query = parseQueryParamsArg(queryParams);
|
|
1425
|
+
const created = [];
|
|
1426
|
+
for (const item of prepared.records) {
|
|
1427
|
+
const result = await fetchAPI(ENFYRA_API_URL, appendQuery(`/${tableName}`, query), { method: 'POST', body: JSON.stringify(item.payload) });
|
|
1428
|
+
created.push({
|
|
1429
|
+
index: item.index,
|
|
1430
|
+
...summarizeMutationResult(result, 'created', tableName),
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
1434
|
+
action: 'created_records',
|
|
1435
|
+
tableName,
|
|
1436
|
+
requested: parsedRecords.length,
|
|
1437
|
+
createdCount: created.length,
|
|
1438
|
+
sequential: true,
|
|
1439
|
+
transactional: false,
|
|
1440
|
+
preflight: {
|
|
1441
|
+
liveMetadataFieldsValidated: true,
|
|
1442
|
+
scriptValidatedBeforeAnyPost: prepared.records.some((item) => item.scriptValidation?.validated === true),
|
|
1443
|
+
extensionValidatedBeforeAnyPost: extensionValidations.some(Boolean),
|
|
1444
|
+
},
|
|
1445
|
+
created,
|
|
1446
|
+
detailHint: `Use query_table({ tableName: "${tableName}", fields: [...], limit: ${Math.min(created.length, 20)} }) to inspect created records when needed.`,
|
|
1447
|
+
}, null, 2) }] };
|
|
1448
|
+
});
|
|
1386
1449
|
server.tool('update_record', 'Update an existing record by ID using PATCH. The tool validates body keys against live metadata, validates sourceCode before saving script-backed records, and validates enfyra_extension.code before saving extension records. Prefer update_extension_code for normal extension edits.', {
|
|
1387
1450
|
tableName: z.string().describe('Table name'),
|
|
1388
1451
|
id: z.string().describe('Record ID to update'),
|