@enfyra/mcp-server 0.1.62 → 0.1.64
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/README.md +1 -0
- package/dist/lib/dynamic-endpoint-contract.d.ts +12 -0
- package/dist/lib/dynamic-endpoint-contract.js +148 -0
- package/dist/lib/dynamic-endpoint-contract.js.map +1 -0
- package/dist/lib/dynamic-repository-builder.d.ts +7 -0
- package/dist/lib/dynamic-repository-builder.js +8 -0
- package/dist/lib/dynamic-repository-builder.js.map +1 -1
- package/dist/lib/extension-search-tools.js +2 -1
- package/dist/lib/extension-search-tools.js.map +1 -1
- package/dist/lib/extension-sfc-analyzer.d.ts +4 -0
- package/dist/lib/extension-sfc-analyzer.js +137 -0
- package/dist/lib/extension-sfc-analyzer.js.map +1 -0
- package/dist/lib/mcp-examples.js +117 -8
- package/dist/lib/mcp-examples.js.map +1 -1
- package/dist/lib/mcp-instructions.js +5 -15
- package/dist/lib/mcp-instructions.js.map +1 -1
- package/dist/lib/mcp-usage-telemetry.js +41 -2
- package/dist/lib/mcp-usage-telemetry.js.map +1 -1
- package/dist/lib/mutation-guards.d.ts +1 -0
- package/dist/lib/mutation-guards.js +42 -0
- package/dist/lib/mutation-guards.js.map +1 -1
- package/dist/lib/platform-operation-tools.d.ts +160 -3
- package/dist/lib/platform-operation-tools.js +454 -95
- package/dist/lib/platform-operation-tools.js.map +1 -1
- package/dist/lib/required-knowledge.d.ts +9 -2
- package/dist/lib/required-knowledge.js +60 -17
- package/dist/lib/required-knowledge.js.map +1 -1
- package/dist/lib/response-format.js +39 -25
- package/dist/lib/response-format.js.map +1 -1
- package/dist/lib/runtime-zone-tools.js +4 -3
- package/dist/lib/runtime-zone-tools.js.map +1 -1
- package/dist/lib/session-safety.d.ts +9 -0
- package/dist/lib/session-safety.js +89 -0
- package/dist/lib/session-safety.js.map +1 -0
- package/dist/lib/source-artifacts.js +3 -1
- package/dist/lib/source-artifacts.js.map +1 -1
- package/dist/lib/table-tools.d.ts +9 -1
- package/dist/lib/table-tools.js +69 -9
- package/dist/lib/table-tools.js.map +1 -1
- package/dist/lib/tool-input-normalization.d.ts +4 -0
- package/dist/lib/tool-input-normalization.js +35 -0
- package/dist/lib/tool-input-normalization.js.map +1 -0
- package/dist/lib/tool-routing.d.ts +6 -2
- package/dist/lib/tool-routing.js +52 -16
- package/dist/lib/tool-routing.js.map +1 -1
- package/dist/lib/toolset-filter.d.ts +7 -3
- package/dist/lib/toolset-filter.js +144 -86
- package/dist/lib/toolset-filter.js.map +1 -1
- package/dist/lib/types.d.ts +62 -0
- package/dist/mcp-server-entry.js +79 -23
- package/dist/mcp-server-entry.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const DESTRUCTIVE_TOOLS = new Set([
|
|
2
|
+
'delete_records',
|
|
3
|
+
'delete_tables',
|
|
4
|
+
'delete_columns',
|
|
5
|
+
'delete_relations',
|
|
6
|
+
'delete_method',
|
|
7
|
+
'delete_route',
|
|
8
|
+
]);
|
|
9
|
+
const MUTATION_TOOL_PATTERN = /^(?:create|update|delete|ensure|patch|install|enable|disable|reorder|reload|trigger|set|add|remove)_/;
|
|
10
|
+
const MUTATION_TOOLS = new Set([
|
|
11
|
+
'api_endpoint_workflow',
|
|
12
|
+
'extension_workflow',
|
|
13
|
+
'flow_workflow',
|
|
14
|
+
'public_route_methods',
|
|
15
|
+
'private_route_methods',
|
|
16
|
+
'replace_route_methods',
|
|
17
|
+
'run_admin_test',
|
|
18
|
+
'test_flow_step',
|
|
19
|
+
'test_graphql',
|
|
20
|
+
'test_rest_endpoint',
|
|
21
|
+
]);
|
|
22
|
+
const PREVIEW_IGNORED_KEYS = new Set([
|
|
23
|
+
'confirm',
|
|
24
|
+
'expectedPath',
|
|
25
|
+
'globalRulesAckKey',
|
|
26
|
+
'maxItems',
|
|
27
|
+
'skipNotFound',
|
|
28
|
+
]);
|
|
29
|
+
const ID_KEYS = new Set(['id', '_id', 'columnId', 'flowId', 'relationId', 'routeId', 'tableId']);
|
|
30
|
+
let targetConfirmed = false;
|
|
31
|
+
const destructivePreviews = new Set();
|
|
32
|
+
function isRecord(value) {
|
|
33
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
34
|
+
}
|
|
35
|
+
function normalizeFingerprintValue(value, key) {
|
|
36
|
+
if (Array.isArray(value))
|
|
37
|
+
return value.map((entry) => normalizeFingerprintValue(entry));
|
|
38
|
+
if (isRecord(value)) {
|
|
39
|
+
return Object.fromEntries(Object.keys(value)
|
|
40
|
+
.filter((entryKey) => !PREVIEW_IGNORED_KEYS.has(entryKey))
|
|
41
|
+
.sort()
|
|
42
|
+
.map((entryKey) => [entryKey, normalizeFingerprintValue(value[entryKey], entryKey)]));
|
|
43
|
+
}
|
|
44
|
+
if (key && ID_KEYS.has(key) && value !== undefined && value !== null)
|
|
45
|
+
return String(value);
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
function destructivePreviewKey(toolName, input) {
|
|
49
|
+
return `${toolName}:${JSON.stringify(normalizeFingerprintValue(input))}`;
|
|
50
|
+
}
|
|
51
|
+
function isMutationTool(toolName) {
|
|
52
|
+
return MUTATION_TOOL_PATTERN.test(toolName) || MUTATION_TOOLS.has(toolName);
|
|
53
|
+
}
|
|
54
|
+
export function resetMcpSafetySession() {
|
|
55
|
+
targetConfirmed = false;
|
|
56
|
+
destructivePreviews.clear();
|
|
57
|
+
}
|
|
58
|
+
export function getMcpSafetySessionState() {
|
|
59
|
+
return {
|
|
60
|
+
targetConfirmed,
|
|
61
|
+
destructivePreviewCount: destructivePreviews.size,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function beforeMcpToolExecution(toolName, input = {}) {
|
|
65
|
+
if (isMutationTool(toolName) && !targetConfirmed) {
|
|
66
|
+
throw new Error(`Target is not confirmed for this MCP process session. Call get_enfyra_api_context before ${toolName}, verify the API base, then retry.`);
|
|
67
|
+
}
|
|
68
|
+
if (DESTRUCTIVE_TOOLS.has(toolName) && input.confirm === true) {
|
|
69
|
+
const key = destructivePreviewKey(toolName, input);
|
|
70
|
+
if (!destructivePreviews.has(key)) {
|
|
71
|
+
throw new Error(`Missing matching destructive preview for ${toolName}. Call the same tool first with confirm=false, inspect the preview, then retry with confirm=true in this MCP process session.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function afterMcpToolExecution(toolName, input = {}) {
|
|
76
|
+
if (toolName === 'get_enfyra_api_context') {
|
|
77
|
+
targetConfirmed = true;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!DESTRUCTIVE_TOOLS.has(toolName))
|
|
81
|
+
return;
|
|
82
|
+
const key = destructivePreviewKey(toolName, input);
|
|
83
|
+
if (input.confirm === true) {
|
|
84
|
+
destructivePreviews.delete(key);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
destructivePreviews.add(key);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=session-safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-safety.js","sourceRoot":"","sources":["../../src/lib/session-safety.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
const DEFAULT_PREVIEW_CHARS =
|
|
5
|
+
const DEFAULT_PREVIEW_CHARS = 480;
|
|
6
6
|
const DEFAULT_INLINE_LIMIT = 1400;
|
|
7
7
|
const SOURCE_FIELD_NAMES = new Set([
|
|
8
8
|
'sourceCode',
|
|
@@ -21,6 +21,8 @@ function safePart(value) {
|
|
|
21
21
|
function extensionForField(fieldName) {
|
|
22
22
|
if (fieldName === 'code')
|
|
23
23
|
return '.vue';
|
|
24
|
+
if (fieldName.endsWith('.diff') || fieldName === 'diff')
|
|
25
|
+
return '.diff';
|
|
24
26
|
return '.js';
|
|
25
27
|
}
|
|
26
28
|
export function writeSourceArtifact({ tableName, id, fieldName, source }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-artifacts.js","sourceRoot":"","sources":["../../src/lib/source-artifacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,MAAM,qBAAqB,GAAG,
|
|
1
|
+
{"version":3,"file":"source-artifacts.js","sourceRoot":"","sources":["../../src/lib/source-artifacts.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,6 +29,7 @@ type RelationPatch = AnyRecord & {
|
|
|
29
29
|
onDelete?: string;
|
|
30
30
|
description?: string;
|
|
31
31
|
};
|
|
32
|
+
export declare function assertColumnContractBroadening(existingColumn: AnyRecord, requested: AnyRecord, toolset?: string): string[];
|
|
32
33
|
export declare function buildPrimaryColumnForDbType(dbType: string | null | undefined): ColumnPatch;
|
|
33
34
|
export declare function normalizeTablesFromMetadata(metadata: any): any[];
|
|
34
35
|
export declare function resolveTableFromMetadata(metadata: any, tableId: any): any;
|
|
@@ -40,6 +41,11 @@ export declare function resolveTableIdentifierFromMetadata(metadata: any, tableR
|
|
|
40
41
|
* complete permission-projected schema from /metadata/:name.
|
|
41
42
|
*/
|
|
42
43
|
export declare function fetchTableWithDetails(ENFYRA_API_URL: any, tableId: any): Promise<AnyRecord>;
|
|
44
|
+
export declare function normalizeCreateTableDefinitions(items: AnyRecord[]): {
|
|
45
|
+
_requestedTableName?: string;
|
|
46
|
+
relations?: any;
|
|
47
|
+
name: string;
|
|
48
|
+
}[];
|
|
43
49
|
export declare function computeBatchCleanupOrder(items: AnyRecord[]): string[];
|
|
44
50
|
export declare function assertIndexesDoNotReferenceUniqueFields(indexes: ConstraintGroup[], uniques: ConstraintGroup[]): void;
|
|
45
51
|
export declare function normalizeRelationForTablePatch(relation: AnyRecord): RelationPatch;
|
|
@@ -75,5 +81,7 @@ export declare function buildColumnDefinition({ name, type, supportedTypes, isNu
|
|
|
75
81
|
/**
|
|
76
82
|
* Register table tools with MCP server
|
|
77
83
|
*/
|
|
78
|
-
export declare function registerTableTools(server: any, ENFYRA_API_URL: any
|
|
84
|
+
export declare function registerTableTools(server: any, ENFYRA_API_URL: any, options?: {
|
|
85
|
+
toolset?: string;
|
|
86
|
+
}): void;
|
|
79
87
|
export {};
|
package/dist/lib/table-tools.js
CHANGED
|
@@ -6,10 +6,22 @@ import { fetchAPI } from './fetch.js';
|
|
|
6
6
|
import { fetchMetadataContext, fetchTableCatalog, fetchTableMetadata, resolveTableCatalogEntry, } from './metadata-client.js';
|
|
7
7
|
import { jsonContent } from './response-format.js';
|
|
8
8
|
import { assertGlobalRulesAck, globalRulesAckParam } from './required-knowledge.js';
|
|
9
|
+
import { normalizeTableName } from './tool-input-normalization.js';
|
|
9
10
|
function bulkObjectArrayParam(z, label) {
|
|
10
11
|
return z.array(z.record(z.any())).describe(`${label} as a native JSON array of objects. Pass one object in the array for a single mutation.`);
|
|
11
12
|
}
|
|
12
13
|
let schemaQueue = Promise.resolve();
|
|
14
|
+
export function assertColumnContractBroadening(existingColumn, requested, toolset = 'guided') {
|
|
15
|
+
const broadened = [];
|
|
16
|
+
if (existingColumn?.isUpdatable === false && requested?.isUpdatable === true)
|
|
17
|
+
broadened.push('isUpdatable false→true');
|
|
18
|
+
if (existingColumn?.isPublished === false && requested?.isPublished === true)
|
|
19
|
+
broadened.push('isPublished false→true');
|
|
20
|
+
if (broadened.length > 0 && (toolset !== 'full' || requested?.allowContractBroadening !== true)) {
|
|
21
|
+
throw new Error(`Column contract broadening is blocked in the guided toolset: ${broadened.join(', ')}. Do not broaden canonical metadata merely for a custom action or E2E fixture; use an exact trusted internal write after authorization instead. Expert full-toolset changes additionally require allowContractBroadening=true.`);
|
|
22
|
+
}
|
|
23
|
+
return broadened;
|
|
24
|
+
}
|
|
13
25
|
function withSchemaQueue(operation) {
|
|
14
26
|
const run = schemaQueue.then(operation, operation);
|
|
15
27
|
schemaQueue = run.catch(() => { });
|
|
@@ -283,6 +295,35 @@ function preflightCreateTableDefinitions(items) {
|
|
|
283
295
|
}
|
|
284
296
|
});
|
|
285
297
|
}
|
|
298
|
+
export function normalizeCreateTableDefinitions(items) {
|
|
299
|
+
const batchNameMap = new Map(items
|
|
300
|
+
.map((item) => String(item?.name ?? ''))
|
|
301
|
+
.filter(Boolean)
|
|
302
|
+
.map((name) => [name.toLowerCase(), normalizeTableName(name)]));
|
|
303
|
+
return items.map((item) => {
|
|
304
|
+
const originalName = String(item?.name ?? '');
|
|
305
|
+
const name = normalizeTableName(originalName);
|
|
306
|
+
const relations = Array.isArray(item?.relations)
|
|
307
|
+
? item.relations.map((relation) => {
|
|
308
|
+
const target = relation?.targetTable ?? relation?.targetTableId;
|
|
309
|
+
if (typeof target !== 'string')
|
|
310
|
+
return relation;
|
|
311
|
+
const normalizedTarget = batchNameMap.get(target.toLowerCase());
|
|
312
|
+
if (!normalizedTarget || normalizedTarget === target)
|
|
313
|
+
return relation;
|
|
314
|
+
return relation?.targetTable !== undefined
|
|
315
|
+
? { ...relation, targetTable: normalizedTarget }
|
|
316
|
+
: { ...relation, targetTableId: normalizedTarget };
|
|
317
|
+
})
|
|
318
|
+
: item?.relations;
|
|
319
|
+
return {
|
|
320
|
+
...item,
|
|
321
|
+
name,
|
|
322
|
+
...(relations !== undefined ? { relations } : {}),
|
|
323
|
+
...(name !== originalName ? { _requestedTableName: originalName } : {}),
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
}
|
|
286
327
|
function relationTargetName(relation) {
|
|
287
328
|
const target = relation?.targetTable ?? relation?.targetTableId;
|
|
288
329
|
if (target && typeof target === 'object')
|
|
@@ -639,7 +680,8 @@ export function buildColumnDefinition({ name, type, supportedTypes, isNullable,
|
|
|
639
680
|
/**
|
|
640
681
|
* Register table tools with MCP server
|
|
641
682
|
*/
|
|
642
|
-
export function registerTableTools(server, ENFYRA_API_URL) {
|
|
683
|
+
export function registerTableTools(server, ENFYRA_API_URL, options = {}) {
|
|
684
|
+
const toolset = options.toolset || 'guided';
|
|
643
685
|
const apiBase = ENFYRA_API_URL.replace(/\/$/, '');
|
|
644
686
|
async function appendColumnToTable(args) {
|
|
645
687
|
assertGlobalRulesAck(args.globalRulesAckKey);
|
|
@@ -861,6 +903,10 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
861
903
|
const idColumn = buildPrimaryColumnForDbType(metadataContext.dbType);
|
|
862
904
|
const { columns: userColumnsWithoutAuto, skippedAutoColumns } = stripAutoManagedColumns(userColumns);
|
|
863
905
|
const { columns: normalizedUserColumns, normalizations } = normalizeColumnsForLiveMetadata(userColumnsWithoutAuto, supportedTypes);
|
|
906
|
+
const schemaNormalizations = [
|
|
907
|
+
...(args._requestedTableName ? [{ field: 'name', from: args._requestedTableName, to: args.name, reason: 'Enfyra table names are lowercase.' }] : []),
|
|
908
|
+
...normalizations,
|
|
909
|
+
];
|
|
864
910
|
const deferredRelations = arrayValue('relations', args.relations).map(normalizeRelationForTablePatch);
|
|
865
911
|
const relationNames = new Set(deferredRelations.map((relation) => relation.propertyName).filter(Boolean));
|
|
866
912
|
assertNoColumnRelationNameCollision(normalizedUserColumns.map((column) => String(column.name || '')).filter(Boolean), deferredRelations.map((relation) => String(relation.propertyName || '')).filter(Boolean), `create_tables item "${args.name}"`);
|
|
@@ -900,7 +946,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
900
946
|
uniqueGroupCount: uniques.length,
|
|
901
947
|
deferredConstraintCount: splitIndexes.deferred.length + splitUniques.deferred.length,
|
|
902
948
|
},
|
|
903
|
-
schemaNormalization:
|
|
949
|
+
schemaNormalization: schemaNormalizations,
|
|
904
950
|
skippedAutoColumns,
|
|
905
951
|
schema: {
|
|
906
952
|
intended: {
|
|
@@ -1029,7 +1075,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
1029
1075
|
result,
|
|
1030
1076
|
};
|
|
1031
1077
|
}
|
|
1032
|
-
async function updateOneColumn({ tableId, columnId, name, type, isNullable, isPublished, isUpdatable, defaultValue, description, options }) {
|
|
1078
|
+
async function updateOneColumn({ tableId, columnId, name, type, isNullable, isPublished, isUpdatable, defaultValue, description, options, allowContractBroadening }) {
|
|
1033
1079
|
const tableData = await fetchTableWithDetails(ENFYRA_API_URL, tableId);
|
|
1034
1080
|
if (!tableData) {
|
|
1035
1081
|
throw new Error(`Table with ID ${tableId} not found.`);
|
|
@@ -1039,6 +1085,12 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
1039
1085
|
if (!beforeIds.includes(String(columnId))) {
|
|
1040
1086
|
throw new Error(`Column ${columnId} was not found on table ${tableId}; refusing schema cascade patch.`);
|
|
1041
1087
|
}
|
|
1088
|
+
const existingColumn = existingColumns.find((column) => String(getId(column)) === String(columnId));
|
|
1089
|
+
const contractBroadening = assertColumnContractBroadening(existingColumn || {}, {
|
|
1090
|
+
isPublished,
|
|
1091
|
+
isUpdatable,
|
|
1092
|
+
allowContractBroadening,
|
|
1093
|
+
}, toolset);
|
|
1042
1094
|
const columns = existingColumns.map(col => {
|
|
1043
1095
|
const rest = normalizeColumnForTablePatch(col);
|
|
1044
1096
|
if (String(getId(col)) === String(columnId)) {
|
|
@@ -1070,6 +1122,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
1070
1122
|
action: 'column_updated',
|
|
1071
1123
|
tableId,
|
|
1072
1124
|
columnId,
|
|
1125
|
+
contractBroadening,
|
|
1073
1126
|
result,
|
|
1074
1127
|
};
|
|
1075
1128
|
}
|
|
@@ -1230,7 +1283,7 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
1230
1283
|
assertGlobalRulesAck(globalRulesAckKey);
|
|
1231
1284
|
if (items !== undefined && tables !== undefined)
|
|
1232
1285
|
throw new Error('Pass either items or tables to create_tables, not both.');
|
|
1233
|
-
const parsedItems = parseBulkItemsParam('items', items ?? tables);
|
|
1286
|
+
const parsedItems = normalizeCreateTableDefinitions(parseBulkItemsParam('items', items ?? tables));
|
|
1234
1287
|
assertBulkLimit('create_tables', parsedItems, maxItems);
|
|
1235
1288
|
preflightCreateTableDefinitions(parsedItems);
|
|
1236
1289
|
const recordDeleteOrder = computeBatchCleanupOrder(parsedItems);
|
|
@@ -1300,9 +1353,16 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
1300
1353
|
recordRule: 'If you delete seeded records before deleting test tables, delete record batches sequentially in recordDeleteOrder; do not parallelize parent/child deletes.',
|
|
1301
1354
|
tableRule: 'For full test cleanup, prefer delete_tables with tableDeleteOrder after deleting custom routes/flows; table deletion removes the remaining table data.',
|
|
1302
1355
|
},
|
|
1303
|
-
created,
|
|
1304
|
-
|
|
1305
|
-
|
|
1356
|
+
created: toolset === 'full' ? created : created.map(({ result, supportedColumnTypes, schema, ...item }) => ({
|
|
1357
|
+
...item,
|
|
1358
|
+
schema: {
|
|
1359
|
+
intended: schema?.intended,
|
|
1360
|
+
liveMetadataAvailable: schema?.liveMetadataAvailable,
|
|
1361
|
+
liveMetadataError: schema?.liveMetadataError,
|
|
1362
|
+
},
|
|
1363
|
+
})),
|
|
1364
|
+
createdRelations: toolset === 'full' ? createdRelations : createdRelations.map(({ result, responseFormat, ...item }) => item),
|
|
1365
|
+
appliedDeferredConstraints: toolset === 'full' ? appliedDeferredConstraints : appliedDeferredConstraints.map(({ result, ...item }) => item),
|
|
1306
1366
|
});
|
|
1307
1367
|
});
|
|
1308
1368
|
server.tool('update_tables', 'Update one or more table definitions. Always pass items as a native JSON array; for one table, pass one item. Items run sequentially through the schema queue.', {
|
|
@@ -1367,8 +1427,8 @@ export function registerTableTools(server, ENFYRA_API_URL) {
|
|
|
1367
1427
|
}
|
|
1368
1428
|
return jsonContent({ action: 'columns_created', requested: parsedItems.length, createdCount: created.length, sequential: true, created });
|
|
1369
1429
|
});
|
|
1370
|
-
server.tool('update_columns', 'Update one or more columns. Always pass items as a native JSON array; for one column, pass one item. Items run sequentially through the schema queue.', {
|
|
1371
|
-
items: bulkObjectArrayParam(z, 'Column update items').describe('Native JSON array of column update items: [{ tableId, columnId, name?, type?, isNullable?, isPublished?, isUpdatable?, defaultValue?, description?, options? }].'),
|
|
1430
|
+
server.tool('update_columns', 'Update one or more columns. Always pass items as a native JSON array; for one column, pass one item. Items run sequentially through the schema queue. Guided mode blocks isUpdatable/isPublished false→true broadening. Do not set isUpdatable=true merely to seed E2E data or let a custom action change a server-owned field; use an exact trusted internal write after authorization and preserve the canonical metadata contract.', {
|
|
1431
|
+
items: bulkObjectArrayParam(z, 'Column update items').describe('Native JSON array of column update items: [{ tableId, columnId, name?, type?, isNullable?, isPublished?, isUpdatable?, defaultValue?, description?, options?, allowContractBroadening? }]. Guided mode blocks false→true isUpdatable/isPublished changes. Expert full mode requires allowContractBroadening=true; never use broadening only for custom-action writes or test fixtures.'),
|
|
1372
1432
|
maxItems: z.number().int().min(1).max(100).optional().default(100).describe('Safety cap for one schema batch. Default/max is 100.'),
|
|
1373
1433
|
globalRulesAckKey: globalRulesAckParam(z),
|
|
1374
1434
|
}, async ({ items, maxItems, globalRulesAckKey }) => {
|