n8n-mcp 2.75.0 → 2.76.0
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 +7 -5
- package/dist/http-server-single-session.d.ts.map +1 -1
- package/dist/http-server-single-session.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/agents-action-map.d.ts +1 -0
- package/dist/mcp/agents-action-map.d.ts.map +1 -1
- package/dist/mcp/agents-action-map.js +2 -1
- package/dist/mcp/agents-action-map.js.map +1 -1
- package/dist/mcp/handlers-n8n-manager.d.ts +3 -0
- package/dist/mcp/handlers-n8n-manager.d.ts.map +1 -1
- package/dist/mcp/handlers-n8n-manager.js +564 -103
- package/dist/mcp/handlers-n8n-manager.js.map +1 -1
- package/dist/mcp/handlers-official-tools.d.ts +17 -0
- package/dist/mcp/handlers-official-tools.d.ts.map +1 -1
- package/dist/mcp/handlers-official-tools.js +78 -34
- package/dist/mcp/handlers-official-tools.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +10 -76
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool-docs/workflow_management/n8n-manage-datatable.d.ts.map +1 -1
- package/dist/mcp/tool-docs/workflow_management/n8n-manage-datatable.js +31 -9
- package/dist/mcp/tool-docs/workflow_management/n8n-manage-datatable.js.map +1 -1
- package/dist/mcp/tool-docs/workflow_management/n8n-test-workflow.d.ts.map +1 -1
- package/dist/mcp/tool-docs/workflow_management/n8n-test-workflow.js +92 -19
- package/dist/mcp/tool-docs/workflow_management/n8n-test-workflow.js.map +1 -1
- package/dist/mcp/tool-docs/workflow_management/n8n-workflow-versions.d.ts.map +1 -1
- package/dist/mcp/tool-docs/workflow_management/n8n-workflow-versions.js +129 -28
- package/dist/mcp/tool-docs/workflow_management/n8n-workflow-versions.js.map +1 -1
- package/dist/mcp/tool-policy.d.ts +9 -0
- package/dist/mcp/tool-policy.d.ts.map +1 -0
- package/dist/mcp/tool-policy.js +138 -0
- package/dist/mcp/tool-policy.js.map +1 -0
- package/dist/mcp/tools-documentation.js +3 -3
- package/dist/mcp/tools-n8n-manager.d.ts +1 -0
- package/dist/mcp/tools-n8n-manager.d.ts.map +1 -1
- package/dist/mcp/tools-n8n-manager.js +103 -21
- package/dist/mcp/tools-n8n-manager.js.map +1 -1
- package/dist/services/mcp-exposure.d.ts +21 -0
- package/dist/services/mcp-exposure.d.ts.map +1 -0
- package/dist/services/mcp-exposure.js +131 -0
- package/dist/services/mcp-exposure.js.map +1 -0
- package/dist/services/n8n-official-mcp-client.d.ts.map +1 -1
- package/dist/services/n8n-official-mcp-client.js +25 -3
- package/dist/services/n8n-official-mcp-client.js.map +1 -1
- package/dist/services/workflow-versioning-service.d.ts +2 -1
- package/dist/services/workflow-versioning-service.d.ts.map +1 -1
- package/dist/services/workflow-versioning-service.js +8 -2
- package/dist/services/workflow-versioning-service.js.map +1 -1
- package/dist/triggers/index.d.ts +1 -1
- package/dist/triggers/index.d.ts.map +1 -1
- package/dist/triggers/index.js +2 -1
- package/dist/triggers/index.js.map +1 -1
- package/dist/triggers/trigger-detector.d.ts +3 -2
- package/dist/triggers/trigger-detector.d.ts.map +1 -1
- package/dist/triggers/trigger-detector.js +12 -0
- package/dist/triggers/trigger-detector.js.map +1 -1
- package/dist/types/n8n-api.d.ts +6 -0
- package/dist/types/n8n-api.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -74,6 +74,9 @@ exports.handleInsertRows = handleInsertRows;
|
|
|
74
74
|
exports.handleUpdateRows = handleUpdateRows;
|
|
75
75
|
exports.handleUpsertRows = handleUpsertRows;
|
|
76
76
|
exports.handleDeleteRows = handleDeleteRows;
|
|
77
|
+
exports.handleAddColumn = handleAddColumn;
|
|
78
|
+
exports.handleDeleteColumn = handleDeleteColumn;
|
|
79
|
+
exports.handleRenameColumn = handleRenameColumn;
|
|
77
80
|
exports.handleListCredentials = handleListCredentials;
|
|
78
81
|
exports.handleGetCredential = handleGetCredential;
|
|
79
82
|
exports.handleCreateCredential = handleCreateCredential;
|
|
@@ -112,6 +115,10 @@ const execution_processor_1 = require("../services/execution-processor");
|
|
|
112
115
|
const npm_version_checker_1 = require("../utils/npm-version-checker");
|
|
113
116
|
const mcp_input_normalizer_1 = require("../utils/mcp-input-normalizer");
|
|
114
117
|
const official_mcp_access_1 = require("./official-mcp-access");
|
|
118
|
+
const handlers_official_tools_1 = require("./handlers-official-tools");
|
|
119
|
+
const mcp_exposure_1 = require("../services/mcp-exposure");
|
|
120
|
+
const tool_policy_1 = require("./tool-policy");
|
|
121
|
+
const agents_action_map_1 = require("./agents-action-map");
|
|
115
122
|
let defaultApiClient = null;
|
|
116
123
|
let lastDefaultConfigUrl = null;
|
|
117
124
|
const cacheMutex = new cache_utils_1.CacheMutex();
|
|
@@ -298,6 +305,7 @@ const autofixWorkflowSchema = zod_1.z.object({
|
|
|
298
305
|
});
|
|
299
306
|
const testWorkflowSchema = zod_1.z.object({
|
|
300
307
|
workflowId: zod_1.z.string(),
|
|
308
|
+
method: optionalEmptyAware(zod_1.z.enum(['auto', 'trigger', 'prepare', 'pinned', 'direct'])),
|
|
301
309
|
triggerType: optionalEmptyAware(zod_1.z.enum(['webhook', 'form', 'chat'])),
|
|
302
310
|
httpMethod: optionalEmptyAware(zod_1.z.enum(['GET', 'POST', 'PUT', 'DELETE'])),
|
|
303
311
|
webhookPath: optionalEmptyAware(zod_1.z.string()),
|
|
@@ -307,6 +315,11 @@ const testWorkflowSchema = zod_1.z.object({
|
|
|
307
315
|
headers: zod_1.z.record(zod_1.z.string()).optional(),
|
|
308
316
|
timeout: zod_1.z.number().optional(),
|
|
309
317
|
waitForResponse: zod_1.z.boolean().optional(),
|
|
318
|
+
exposeToMcp: zod_1.z.boolean().optional(),
|
|
319
|
+
timeoutMs: zod_1.z.number().int().min(agents_action_map_1.MIN_TIMEOUT_MS).max(agents_action_map_1.MAX_TIMEOUT_MS).optional(),
|
|
320
|
+
pinData: zod_1.z.record(zod_1.z.array(zod_1.z.unknown())).optional(),
|
|
321
|
+
triggerNodeName: optionalEmptyAware(zod_1.z.string()),
|
|
322
|
+
executionMode: optionalEmptyAware(zod_1.z.enum(['manual', 'production'])),
|
|
310
323
|
});
|
|
311
324
|
const listExecutionsSchema = zod_1.z.object({
|
|
312
325
|
limit: zod_1.z.number().min(1).max(100).optional(),
|
|
@@ -340,14 +353,20 @@ const cancelTestRunSchema = zod_1.z.object({
|
|
|
340
353
|
workflowId: testRunPathId,
|
|
341
354
|
runId: testRunPathId,
|
|
342
355
|
});
|
|
356
|
+
const versionIdValue = zod_1.z.union([zod_1.z.number().int(), zod_1.z.string().min(1)]);
|
|
343
357
|
const workflowVersionsSchema = zod_1.z.object({
|
|
344
|
-
mode: zod_1.z.enum(['list', 'get', 'rollback', 'delete', 'prune']),
|
|
358
|
+
mode: zod_1.z.enum(['list', 'get', 'rollback', 'delete', 'prune', 'diff']),
|
|
359
|
+
source: zod_1.z.enum(['local', 'native']).optional(),
|
|
345
360
|
workflowId: zod_1.z.string().optional(),
|
|
346
|
-
versionId:
|
|
361
|
+
versionId: versionIdValue.optional(),
|
|
362
|
+
toVersionId: versionIdValue.optional(),
|
|
347
363
|
limit: zod_1.z.number().default(10).optional(),
|
|
364
|
+
offset: zod_1.z.number().int().min(0).optional(),
|
|
348
365
|
validateBefore: zod_1.z.boolean().default(true).optional(),
|
|
349
366
|
deleteAll: zod_1.z.boolean().default(false).optional(),
|
|
350
367
|
maxVersions: zod_1.z.number().default(10).optional(),
|
|
368
|
+
exposeToMcp: zod_1.z.boolean().optional(),
|
|
369
|
+
timeoutMs: zod_1.z.number().int().min(5000).max(600000).optional(),
|
|
351
370
|
});
|
|
352
371
|
async function handleCreateWorkflow(args, context) {
|
|
353
372
|
try {
|
|
@@ -1173,16 +1192,151 @@ async function handleAutofixWorkflow(args, repository, context) {
|
|
|
1173
1192
|
};
|
|
1174
1193
|
}
|
|
1175
1194
|
}
|
|
1195
|
+
const OFFICIAL_TEST_ACTION = 'test_workflow';
|
|
1196
|
+
const FAILED_RUN_STATUSES = new Set(['error', 'crashed', 'canceled']);
|
|
1197
|
+
const LEGACY_TIMEOUT_SCOPE_WARNING = 'timeout applies to the HTTP trigger path only; use timeoutMs for method prepare/pinned/direct';
|
|
1198
|
+
const OFFICIAL_TEST_METHODS = new Set(['prepare', 'pinned', 'direct']);
|
|
1176
1199
|
async function handleTestWorkflow(args, context) {
|
|
1177
1200
|
try {
|
|
1178
|
-
const client = ensureApiConfigured(context);
|
|
1179
1201
|
const input = testWorkflowSchema.parse(args);
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1202
|
+
const method = input.method ?? 'auto';
|
|
1203
|
+
const apiClient = getN8nApiClient(context);
|
|
1204
|
+
const officialTimeoutMs = input.timeoutMs ?? (method === 'prepare' ? agents_action_map_1.DEFAULT_TIMEOUT_MS : agents_action_map_1.PINNED_TIMEOUT_MS);
|
|
1205
|
+
const routeOfficial = async (aliases, officialArgs, idempotent, resolvedMethod) => {
|
|
1206
|
+
const response = await (0, mcp_exposure_1.withMcpExposure)({
|
|
1207
|
+
apiClient,
|
|
1208
|
+
workflowId: input.workflowId,
|
|
1209
|
+
exposeToMcp: input.exposeToMcp,
|
|
1210
|
+
action: OFFICIAL_TEST_ACTION,
|
|
1211
|
+
toolName: 'n8n_test_workflow',
|
|
1212
|
+
context,
|
|
1213
|
+
}, () => (0, handlers_official_tools_1.callOfficialTool)(context, aliases, officialArgs, officialTimeoutMs, OFFICIAL_TEST_ACTION, idempotent));
|
|
1214
|
+
const decorated = { ...response, method: resolvedMethod, backend: 'official-mcp' };
|
|
1215
|
+
if (input.timeout !== undefined && decorated.success) {
|
|
1216
|
+
decorated.warnings = [...(decorated.warnings ?? []), LEGACY_TIMEOUT_SCOPE_WARNING];
|
|
1217
|
+
}
|
|
1218
|
+
return decorated;
|
|
1219
|
+
};
|
|
1220
|
+
if (method === 'pinned' && Object.keys(input.pinData ?? {}).length === 0) {
|
|
1221
|
+
return {
|
|
1222
|
+
success: false,
|
|
1223
|
+
code: 'INVALID_ARGS',
|
|
1224
|
+
method: 'pinned',
|
|
1225
|
+
error: 'pinData is required for method: pinned (keys are node names, values are arrays of items wrapped as { "json": { ... } }). Run method: prepare first to see which nodes need pinned data.',
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
if ((method === 'auto' || method === 'trigger') && (0, tool_policy_1.isOperationDisabled)('n8n_test_workflow', 'trigger')) {
|
|
1229
|
+
return {
|
|
1230
|
+
success: false,
|
|
1231
|
+
code: 'OPERATION_DISABLED',
|
|
1232
|
+
method: 'trigger',
|
|
1233
|
+
backend: 'public-api',
|
|
1234
|
+
error: "Operation 'trigger' on tool 'n8n_test_workflow' is disabled by server policy.",
|
|
1235
|
+
details: { requestedMethod: method },
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
const isPlainPrepare = method === 'prepare' && input.exposeToMcp !== true;
|
|
1239
|
+
if (!isPlainPrepare && !(0, mcp_exposure_1.publicApiMatchesContext)(context)) {
|
|
1240
|
+
return {
|
|
1241
|
+
success: false,
|
|
1242
|
+
code: 'NOT_CONFIGURED',
|
|
1243
|
+
method,
|
|
1244
|
+
backend: OFFICIAL_TEST_METHODS.has(method) ? 'official-mcp' : 'public-api',
|
|
1245
|
+
error: mcp_exposure_1.PUBLIC_API_CONTEXT_HINT,
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
if (method === 'prepare') {
|
|
1249
|
+
return routeOfficial(['prepare_workflow_pin_data'], { workflowId: input.workflowId }, true, 'prepare');
|
|
1250
|
+
}
|
|
1251
|
+
const client = ensureApiConfigured(context);
|
|
1252
|
+
const { detectTriggerFromWorkflow, classifyTriggerNode, ensureRegistryInitialized, TriggerRegistry, } = await Promise.resolve().then(() => __importStar(require('../triggers')));
|
|
1182
1253
|
const workflow = await client.getWorkflow(input.workflowId);
|
|
1254
|
+
const detection = detectTriggerFromWorkflow(workflow);
|
|
1255
|
+
const detectedNodeName = detection.trigger?.node.name;
|
|
1256
|
+
if (method === 'pinned') {
|
|
1257
|
+
const pinnedTriggerNode = input.triggerNodeName ?? detectedNodeName;
|
|
1258
|
+
const response = await routeOfficial(['test_workflow'], {
|
|
1259
|
+
workflowId: input.workflowId,
|
|
1260
|
+
pinData: input.pinData,
|
|
1261
|
+
...(pinnedTriggerNode ? { triggerNodeName: pinnedTriggerNode } : {}),
|
|
1262
|
+
timeout: Math.max(1, Math.floor(officialTimeoutMs / 1000) - 5),
|
|
1263
|
+
}, false, 'pinned');
|
|
1264
|
+
if (!response.success)
|
|
1265
|
+
return response;
|
|
1266
|
+
const run = (response.data ?? {});
|
|
1267
|
+
const executionId = typeof run.executionId === 'string' ? run.executionId : undefined;
|
|
1268
|
+
const status = typeof run.status === 'string' ? run.status : undefined;
|
|
1269
|
+
if (status && FAILED_RUN_STATUSES.has(status)) {
|
|
1270
|
+
return {
|
|
1271
|
+
...response,
|
|
1272
|
+
success: false,
|
|
1273
|
+
code: 'EXECUTION_FAILED',
|
|
1274
|
+
error: typeof run.error === 'string' ? run.error : `Run finished with status ${status}`,
|
|
1275
|
+
...(executionId ? { executionId } : {}),
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
return { ...response, ...(executionId ? { executionId } : {}) };
|
|
1279
|
+
}
|
|
1280
|
+
if (method === 'direct') {
|
|
1281
|
+
const namedNode = input.triggerNodeName
|
|
1282
|
+
? (workflow.nodes ?? []).find(node => node.name === input.triggerNodeName)
|
|
1283
|
+
: undefined;
|
|
1284
|
+
if (input.triggerNodeName && !namedNode) {
|
|
1285
|
+
return {
|
|
1286
|
+
success: false,
|
|
1287
|
+
code: 'INVALID_ARGS',
|
|
1288
|
+
method: 'direct',
|
|
1289
|
+
backend: 'official-mcp',
|
|
1290
|
+
error: `triggerNodeName "${input.triggerNodeName}" is not a node of workflow ${input.workflowId}`,
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
const triggerKind = input.triggerNodeName
|
|
1294
|
+
? (namedNode ? classifyTriggerNode(namedNode) : null)
|
|
1295
|
+
: (detection.trigger?.type ?? null);
|
|
1296
|
+
let inputs;
|
|
1297
|
+
if (input.message !== undefined) {
|
|
1298
|
+
inputs = { chatInput: input.message };
|
|
1299
|
+
}
|
|
1300
|
+
else if (input.data && triggerKind === 'form') {
|
|
1301
|
+
inputs = { formData: input.data };
|
|
1302
|
+
}
|
|
1303
|
+
else if (input.data || input.headers || input.httpMethod) {
|
|
1304
|
+
inputs = {
|
|
1305
|
+
webhookData: {
|
|
1306
|
+
method: input.httpMethod ?? 'POST',
|
|
1307
|
+
...(input.data ? { body: input.data } : {}),
|
|
1308
|
+
...(input.headers ? { headers: input.headers } : {}),
|
|
1309
|
+
},
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
const directTriggerNode = input.triggerNodeName ?? (inputs ? detectedNodeName : undefined);
|
|
1313
|
+
if (inputs && !directTriggerNode) {
|
|
1314
|
+
return {
|
|
1315
|
+
success: false,
|
|
1316
|
+
code: 'INVALID_ARGS',
|
|
1317
|
+
method: 'direct',
|
|
1318
|
+
error: 'triggerNodeName is required when inputs are given and no trigger node could be detected',
|
|
1319
|
+
details: { workflowId: input.workflowId, reason: detection.reason },
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
const response = await routeOfficial(['execute_workflow'], {
|
|
1323
|
+
workflowId: input.workflowId,
|
|
1324
|
+
executionMode: input.executionMode ?? 'manual',
|
|
1325
|
+
...(directTriggerNode ? { triggerNodeName: directTriggerNode } : {}),
|
|
1326
|
+
...(inputs ? { inputs } : {}),
|
|
1327
|
+
}, false, 'direct');
|
|
1328
|
+
if (!response.success)
|
|
1329
|
+
return response;
|
|
1330
|
+
const executionId = response.data?.executionId;
|
|
1331
|
+
return {
|
|
1332
|
+
...response,
|
|
1333
|
+
...(typeof executionId === 'string' ? { executionId } : {}),
|
|
1334
|
+
hint: 'execute_workflow returns as soon as the run starts; poll n8n_executions with the executionId for the result.',
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
await ensureRegistryInitialized();
|
|
1183
1338
|
let triggerType = input.triggerType;
|
|
1184
1339
|
let triggerInfo;
|
|
1185
|
-
const detection = detectTriggerFromWorkflow(workflow);
|
|
1186
1340
|
if (!triggerType) {
|
|
1187
1341
|
if (detection.detected && detection.trigger) {
|
|
1188
1342
|
triggerType = detection.trigger.type;
|
|
@@ -1192,10 +1346,13 @@ async function handleTestWorkflow(args, context) {
|
|
|
1192
1346
|
return {
|
|
1193
1347
|
success: false,
|
|
1194
1348
|
error: 'Workflow cannot be triggered externally',
|
|
1349
|
+
method,
|
|
1350
|
+
backend: 'public-api',
|
|
1195
1351
|
details: {
|
|
1196
1352
|
workflowId: input.workflowId,
|
|
1197
1353
|
reason: detection.reason,
|
|
1198
|
-
hint: 'Only workflows with webhook, form, or chat triggers can be executed via the API. Add one of these trigger nodes to your workflow.'
|
|
1354
|
+
hint: 'Only workflows with webhook, form, or chat triggers can be executed via the API. Add one of these trigger nodes to your workflow.'
|
|
1355
|
+
+ ' To run it anyway through n8n\'s MCP server, call again with method: direct (executionMode manual) or method: pinned with pinData from method: prepare — both need N8N_MCP_ACCESS_TOKEN.',
|
|
1199
1356
|
},
|
|
1200
1357
|
};
|
|
1201
1358
|
}
|
|
@@ -1208,6 +1365,8 @@ async function handleTestWorkflow(args, context) {
|
|
|
1208
1365
|
return {
|
|
1209
1366
|
success: false,
|
|
1210
1367
|
error: `Workflow does not have a ${triggerType} trigger`,
|
|
1368
|
+
method: 'trigger',
|
|
1369
|
+
backend: 'public-api',
|
|
1211
1370
|
details: {
|
|
1212
1371
|
workflowId: input.workflowId,
|
|
1213
1372
|
requestedTrigger: triggerType,
|
|
@@ -1224,6 +1383,8 @@ async function handleTestWorkflow(args, context) {
|
|
|
1224
1383
|
return {
|
|
1225
1384
|
success: false,
|
|
1226
1385
|
error: `No handler registered for trigger type: ${triggerType}`,
|
|
1386
|
+
method: 'trigger',
|
|
1387
|
+
backend: 'public-api',
|
|
1227
1388
|
details: {
|
|
1228
1389
|
supportedTypes: TriggerRegistry.getRegisteredTypes(),
|
|
1229
1390
|
},
|
|
@@ -1233,6 +1394,8 @@ async function handleTestWorkflow(args, context) {
|
|
|
1233
1394
|
return {
|
|
1234
1395
|
success: false,
|
|
1235
1396
|
error: 'Workflow must be active to trigger via this method',
|
|
1397
|
+
method: 'trigger',
|
|
1398
|
+
backend: 'public-api',
|
|
1236
1399
|
details: {
|
|
1237
1400
|
workflowId: input.workflowId,
|
|
1238
1401
|
triggerType,
|
|
@@ -1244,6 +1407,8 @@ async function handleTestWorkflow(args, context) {
|
|
|
1244
1407
|
return {
|
|
1245
1408
|
success: false,
|
|
1246
1409
|
error: 'Chat trigger requires a message parameter',
|
|
1410
|
+
method: 'trigger',
|
|
1411
|
+
backend: 'public-api',
|
|
1247
1412
|
details: {
|
|
1248
1413
|
hint: 'Provide message="your message" for chat triggers',
|
|
1249
1414
|
},
|
|
@@ -1271,6 +1436,8 @@ async function handleTestWorkflow(args, context) {
|
|
|
1271
1436
|
: response.error,
|
|
1272
1437
|
executionId: response.executionId,
|
|
1273
1438
|
workflowId: input.workflowId,
|
|
1439
|
+
method: 'trigger',
|
|
1440
|
+
backend: 'public-api',
|
|
1274
1441
|
details: {
|
|
1275
1442
|
triggerType,
|
|
1276
1443
|
metadata: response.metadata,
|
|
@@ -1279,9 +1446,16 @@ async function handleTestWorkflow(args, context) {
|
|
|
1279
1446
|
};
|
|
1280
1447
|
}
|
|
1281
1448
|
catch (error) {
|
|
1449
|
+
const raw = (args && typeof args === 'object' ? args : {});
|
|
1450
|
+
const rawMethod = typeof raw.method === 'string' && raw.method.trim() !== '' ? raw.method : 'auto';
|
|
1451
|
+
const label = {
|
|
1452
|
+
method: rawMethod,
|
|
1453
|
+
backend: OFFICIAL_TEST_METHODS.has(rawMethod) ? 'official-mcp' : 'public-api',
|
|
1454
|
+
};
|
|
1282
1455
|
if (error instanceof zod_1.z.ZodError) {
|
|
1283
1456
|
return {
|
|
1284
1457
|
success: false,
|
|
1458
|
+
...label,
|
|
1285
1459
|
error: 'Invalid input',
|
|
1286
1460
|
details: { errors: error.errors },
|
|
1287
1461
|
};
|
|
@@ -1289,6 +1463,7 @@ async function handleTestWorkflow(args, context) {
|
|
|
1289
1463
|
if (error instanceof n8n_errors_1.N8nApiError) {
|
|
1290
1464
|
return {
|
|
1291
1465
|
success: false,
|
|
1466
|
+
...label,
|
|
1292
1467
|
error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error),
|
|
1293
1468
|
code: error.code,
|
|
1294
1469
|
details: error.details,
|
|
@@ -1296,6 +1471,7 @@ async function handleTestWorkflow(args, context) {
|
|
|
1296
1471
|
}
|
|
1297
1472
|
return {
|
|
1298
1473
|
success: false,
|
|
1474
|
+
...label,
|
|
1299
1475
|
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
1300
1476
|
};
|
|
1301
1477
|
}
|
|
@@ -2110,146 +2286,322 @@ async function handleDiagnostic(request, context) {
|
|
|
2110
2286
|
data: diagnostic
|
|
2111
2287
|
};
|
|
2112
2288
|
}
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2289
|
+
const VERSIONS_ACTION = 'workflow_versions';
|
|
2290
|
+
const VERSIONS_TIMEOUT_MS = 30000;
|
|
2291
|
+
const NATIVE_VERSIONS_LIMIT_CAP = 50;
|
|
2292
|
+
const NATIVE_VALIDATION_NOTE = 'not available for native versions';
|
|
2293
|
+
function withDiffFormat(data, format) {
|
|
2294
|
+
return data && typeof data === 'object' && !Array.isArray(data)
|
|
2295
|
+
? { ...data, format }
|
|
2296
|
+
: { diff: data, format };
|
|
2297
|
+
}
|
|
2298
|
+
const LOCAL_VERSION_ID_PATTERN = /^-?\d+$/;
|
|
2299
|
+
function parseLocalVersionId(value, field) {
|
|
2300
|
+
if (value === undefined)
|
|
2301
|
+
return { ok: true, value: undefined };
|
|
2302
|
+
if (typeof value === 'string' && value.trim() === '') {
|
|
2303
|
+
return { ok: false, error: `${field} must be an integer version id for source: local` };
|
|
2304
|
+
}
|
|
2305
|
+
if (typeof value === 'string' && !LOCAL_VERSION_ID_PATTERN.test(value.trim())) {
|
|
2306
|
+
return {
|
|
2307
|
+
ok: false,
|
|
2308
|
+
error: `${field} must be an integer version id for source: local (got ${JSON.stringify(value)}). n8n's own string version ids need source: native.`,
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
const parsed = typeof value === 'number' ? value : Number(value.trim());
|
|
2312
|
+
if (!Number.isInteger(parsed)) {
|
|
2313
|
+
return {
|
|
2314
|
+
ok: false,
|
|
2315
|
+
error: `${field} must be an integer version id for source: local (got ${JSON.stringify(value)}). n8n's own string version ids need source: native.`,
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
return { ok: true, value: parsed };
|
|
2319
|
+
}
|
|
2320
|
+
async function handleNativeWorkflowVersions(input, context) {
|
|
2321
|
+
const mode = input.mode;
|
|
2322
|
+
const label = (response) => ({
|
|
2323
|
+
...response,
|
|
2324
|
+
mode,
|
|
2325
|
+
source: 'native',
|
|
2326
|
+
backend: 'official-mcp',
|
|
2327
|
+
});
|
|
2328
|
+
const invalid = (error) => label({ success: false, action: VERSIONS_ACTION, code: 'INVALID_ARGS', error });
|
|
2329
|
+
if (mode === 'delete' || mode === 'prune') {
|
|
2330
|
+
return label({
|
|
2331
|
+
success: false,
|
|
2332
|
+
action: VERSIONS_ACTION,
|
|
2333
|
+
code: 'MODE_NOT_SUPPORTED_FOR_SOURCE',
|
|
2334
|
+
error: `n8n's own version history cannot be ${mode === 'delete' ? 'deleted' : 'pruned'} through MCP; use source: local for n8n-mcp snapshots`,
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
const workflowId = input.workflowId;
|
|
2338
|
+
if (!workflowId) {
|
|
2339
|
+
return invalid(`workflowId is required for source: native (mode: ${mode})`);
|
|
2340
|
+
}
|
|
2341
|
+
if (mode !== 'list' && input.versionId === undefined) {
|
|
2342
|
+
return invalid(`versionId is required for mode: ${mode}`);
|
|
2343
|
+
}
|
|
2344
|
+
if (mode === 'diff' && input.toVersionId === undefined) {
|
|
2345
|
+
return invalid('toVersionId is required for mode: diff');
|
|
2346
|
+
}
|
|
2347
|
+
const versionId = input.versionId === undefined ? undefined : String(input.versionId);
|
|
2348
|
+
const toVersionId = input.toVersionId === undefined ? undefined : String(input.toVersionId);
|
|
2349
|
+
let aliases;
|
|
2350
|
+
let officialArgs;
|
|
2351
|
+
let idempotent = true;
|
|
2352
|
+
switch (mode) {
|
|
2353
|
+
case 'list':
|
|
2354
|
+
aliases = ['get_workflow_history'];
|
|
2355
|
+
officialArgs = {
|
|
2356
|
+
workflowId,
|
|
2357
|
+
limit: Math.max(1, Math.min(NATIVE_VERSIONS_LIMIT_CAP, Math.floor(input.limit ?? 10))),
|
|
2358
|
+
offset: Math.max(0, Math.floor(input.offset ?? 0)),
|
|
2359
|
+
};
|
|
2360
|
+
break;
|
|
2361
|
+
case 'get':
|
|
2362
|
+
aliases = ['get_workflow_version'];
|
|
2363
|
+
officialArgs = { workflowId, versionId };
|
|
2364
|
+
break;
|
|
2365
|
+
case 'diff':
|
|
2366
|
+
aliases = ['get_workflow_versions_diff'];
|
|
2367
|
+
officialArgs = { workflowId, fromVersionId: versionId, toVersionId };
|
|
2368
|
+
break;
|
|
2369
|
+
case 'rollback':
|
|
2370
|
+
aliases = ['restore_workflow_version'];
|
|
2371
|
+
officialArgs = { workflowId, versionId };
|
|
2372
|
+
idempotent = false;
|
|
2373
|
+
break;
|
|
2374
|
+
default:
|
|
2375
|
+
return invalid(`Unknown mode: ${mode}`);
|
|
2376
|
+
}
|
|
2377
|
+
const response = await (0, mcp_exposure_1.withMcpExposure)({
|
|
2378
|
+
apiClient: getN8nApiClient(context),
|
|
2379
|
+
workflowId,
|
|
2380
|
+
exposeToMcp: input.exposeToMcp,
|
|
2381
|
+
action: VERSIONS_ACTION,
|
|
2382
|
+
toolName: 'n8n_workflow_versions',
|
|
2383
|
+
context,
|
|
2384
|
+
}, () => (0, handlers_official_tools_1.callOfficialTool)(context, aliases, officialArgs, input.timeoutMs ?? VERSIONS_TIMEOUT_MS, VERSIONS_ACTION, idempotent));
|
|
2385
|
+
const labelled = label(response);
|
|
2386
|
+
if (mode === 'diff' && labelled.success) {
|
|
2387
|
+
labelled.data = withDiffFormat(labelled.data, 'n8n');
|
|
2388
|
+
}
|
|
2389
|
+
if (mode === 'rollback' && labelled.success) {
|
|
2390
|
+
labelled.validation = NATIVE_VALIDATION_NOTE;
|
|
2391
|
+
}
|
|
2392
|
+
return labelled;
|
|
2393
|
+
}
|
|
2394
|
+
async function handleLocalWorkflowVersions(input, versionId, toVersionId, repository, context) {
|
|
2395
|
+
const client = getN8nApiClient(context);
|
|
2396
|
+
const versioningService = new workflow_versioning_service_1.WorkflowVersioningService(repository, client || undefined, (0, instance_context_1.getInstanceScopeId)(context));
|
|
2397
|
+
switch (input.mode) {
|
|
2398
|
+
case 'list': {
|
|
2399
|
+
if (!input.workflowId) {
|
|
2400
|
+
return {
|
|
2401
|
+
success: false,
|
|
2402
|
+
error: 'workflowId is required for list mode'
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
const versions = await versioningService.getVersionHistory(input.workflowId, input.limit);
|
|
2117
2406
|
return {
|
|
2118
|
-
success:
|
|
2119
|
-
|
|
2407
|
+
success: true,
|
|
2408
|
+
data: {
|
|
2409
|
+
workflowId: input.workflowId,
|
|
2410
|
+
versions,
|
|
2411
|
+
count: versions.length,
|
|
2412
|
+
message: `Found ${versions.length} version(s) for workflow ${input.workflowId}`
|
|
2413
|
+
}
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
case 'get': {
|
|
2417
|
+
if (!versionId) {
|
|
2418
|
+
return {
|
|
2419
|
+
success: false,
|
|
2420
|
+
error: 'versionId is required for get mode'
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
const version = await versioningService.getVersion(versionId);
|
|
2424
|
+
if (!version) {
|
|
2425
|
+
return {
|
|
2426
|
+
success: false,
|
|
2427
|
+
error: `Version ${versionId} not found`
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
return {
|
|
2431
|
+
success: true,
|
|
2432
|
+
data: version
|
|
2120
2433
|
};
|
|
2121
2434
|
}
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2435
|
+
case 'rollback': {
|
|
2436
|
+
if (!input.workflowId) {
|
|
2437
|
+
return {
|
|
2438
|
+
success: false,
|
|
2439
|
+
error: 'workflowId is required for rollback mode'
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
if (!client) {
|
|
2443
|
+
return {
|
|
2444
|
+
success: false,
|
|
2445
|
+
error: 'n8n API not configured. Cannot perform rollback without API access.'
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
const result = await versioningService.restoreVersion(input.workflowId, versionId, input.validateBefore);
|
|
2449
|
+
return {
|
|
2450
|
+
success: result.success,
|
|
2451
|
+
data: result.success ? result : undefined,
|
|
2452
|
+
error: result.success ? undefined : result.message,
|
|
2453
|
+
details: result.success ? undefined : {
|
|
2454
|
+
validationErrors: result.validationErrors
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
case 'delete': {
|
|
2459
|
+
if (input.deleteAll) {
|
|
2126
2460
|
if (!input.workflowId) {
|
|
2127
2461
|
return {
|
|
2128
2462
|
success: false,
|
|
2129
|
-
error: 'workflowId is required for
|
|
2463
|
+
error: 'workflowId is required for deleteAll mode'
|
|
2130
2464
|
};
|
|
2131
2465
|
}
|
|
2132
|
-
const
|
|
2466
|
+
const result = await versioningService.deleteAllVersions(input.workflowId);
|
|
2133
2467
|
return {
|
|
2134
2468
|
success: true,
|
|
2135
2469
|
data: {
|
|
2136
2470
|
workflowId: input.workflowId,
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
message: `Found ${versions.length} version(s) for workflow ${input.workflowId}`
|
|
2471
|
+
deleted: result.deleted,
|
|
2472
|
+
message: result.message
|
|
2140
2473
|
}
|
|
2141
2474
|
};
|
|
2142
2475
|
}
|
|
2143
|
-
|
|
2144
|
-
if (!
|
|
2145
|
-
return {
|
|
2146
|
-
success: false,
|
|
2147
|
-
error: 'versionId is required for get mode'
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
const version = await versioningService.getVersion(input.versionId);
|
|
2151
|
-
if (!version) {
|
|
2476
|
+
else {
|
|
2477
|
+
if (!versionId) {
|
|
2152
2478
|
return {
|
|
2153
2479
|
success: false,
|
|
2154
|
-
error:
|
|
2480
|
+
error: 'versionId is required for single version delete'
|
|
2155
2481
|
};
|
|
2156
2482
|
}
|
|
2483
|
+
const result = await versioningService.deleteVersion(versionId);
|
|
2157
2484
|
return {
|
|
2158
|
-
success:
|
|
2159
|
-
data:
|
|
2485
|
+
success: result.success,
|
|
2486
|
+
data: result.success ? { message: result.message } : undefined,
|
|
2487
|
+
error: result.success ? undefined : result.message
|
|
2160
2488
|
};
|
|
2161
2489
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
success: false,
|
|
2166
|
-
error: 'workflowId is required for rollback mode'
|
|
2167
|
-
};
|
|
2168
|
-
}
|
|
2169
|
-
if (!client) {
|
|
2170
|
-
return {
|
|
2171
|
-
success: false,
|
|
2172
|
-
error: 'n8n API not configured. Cannot perform rollback without API access.'
|
|
2173
|
-
};
|
|
2174
|
-
}
|
|
2175
|
-
const result = await versioningService.restoreVersion(input.workflowId, input.versionId, input.validateBefore);
|
|
2490
|
+
}
|
|
2491
|
+
case 'prune': {
|
|
2492
|
+
if (!input.workflowId) {
|
|
2176
2493
|
return {
|
|
2177
|
-
success:
|
|
2178
|
-
|
|
2179
|
-
error: result.success ? undefined : result.message,
|
|
2180
|
-
details: result.success ? undefined : {
|
|
2181
|
-
validationErrors: result.validationErrors
|
|
2182
|
-
}
|
|
2494
|
+
success: false,
|
|
2495
|
+
error: 'workflowId is required for prune mode'
|
|
2183
2496
|
};
|
|
2184
2497
|
}
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
}
|
|
2193
|
-
const result = await versioningService.deleteAllVersions(input.workflowId);
|
|
2194
|
-
return {
|
|
2195
|
-
success: true,
|
|
2196
|
-
data: {
|
|
2197
|
-
workflowId: input.workflowId,
|
|
2198
|
-
deleted: result.deleted,
|
|
2199
|
-
message: result.message
|
|
2200
|
-
}
|
|
2201
|
-
};
|
|
2202
|
-
}
|
|
2203
|
-
else {
|
|
2204
|
-
if (!input.versionId) {
|
|
2205
|
-
return {
|
|
2206
|
-
success: false,
|
|
2207
|
-
error: 'versionId is required for single version delete'
|
|
2208
|
-
};
|
|
2209
|
-
}
|
|
2210
|
-
const result = await versioningService.deleteVersion(input.versionId);
|
|
2211
|
-
return {
|
|
2212
|
-
success: result.success,
|
|
2213
|
-
data: result.success ? { message: result.message } : undefined,
|
|
2214
|
-
error: result.success ? undefined : result.message
|
|
2215
|
-
};
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
case 'prune': {
|
|
2219
|
-
if (!input.workflowId) {
|
|
2220
|
-
return {
|
|
2221
|
-
success: false,
|
|
2222
|
-
error: 'workflowId is required for prune mode'
|
|
2223
|
-
};
|
|
2498
|
+
const result = await versioningService.pruneVersions(input.workflowId, input.maxVersions || 10);
|
|
2499
|
+
return {
|
|
2500
|
+
success: true,
|
|
2501
|
+
data: {
|
|
2502
|
+
workflowId: input.workflowId,
|
|
2503
|
+
pruned: result.pruned,
|
|
2504
|
+
remaining: result.remaining,
|
|
2505
|
+
message: `Pruned ${result.pruned} old version(s), ${result.remaining} version(s) remaining`
|
|
2224
2506
|
}
|
|
2225
|
-
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
case 'diff': {
|
|
2510
|
+
if (!input.workflowId) {
|
|
2226
2511
|
return {
|
|
2227
|
-
success:
|
|
2228
|
-
|
|
2229
|
-
workflowId: input.workflowId,
|
|
2230
|
-
pruned: result.pruned,
|
|
2231
|
-
remaining: result.remaining,
|
|
2232
|
-
message: `Pruned ${result.pruned} old version(s), ${result.remaining} version(s) remaining`
|
|
2233
|
-
}
|
|
2512
|
+
success: false,
|
|
2513
|
+
error: 'workflowId is required for diff mode'
|
|
2234
2514
|
};
|
|
2235
2515
|
}
|
|
2236
|
-
|
|
2516
|
+
if (versionId === undefined || toVersionId === undefined) {
|
|
2237
2517
|
return {
|
|
2238
2518
|
success: false,
|
|
2239
|
-
|
|
2519
|
+
code: 'INVALID_ARGS',
|
|
2520
|
+
error: 'versionId and toVersionId are both required for diff mode'
|
|
2240
2521
|
};
|
|
2522
|
+
}
|
|
2523
|
+
try {
|
|
2524
|
+
const diff = await versioningService.compareVersions(versionId, toVersionId, input.workflowId);
|
|
2525
|
+
return {
|
|
2526
|
+
success: true,
|
|
2527
|
+
data: withDiffFormat(diff, 'n8n-mcp')
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
catch (error) {
|
|
2531
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2532
|
+
if (message.includes(workflow_versioning_service_1.VERSION_OWNERSHIP_ERROR_PREFIX)) {
|
|
2533
|
+
return {
|
|
2534
|
+
success: false,
|
|
2535
|
+
code: 'INVALID_ARGS',
|
|
2536
|
+
error: message
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
throw error;
|
|
2540
|
+
}
|
|
2241
2541
|
}
|
|
2542
|
+
default:
|
|
2543
|
+
return {
|
|
2544
|
+
success: false,
|
|
2545
|
+
error: `Unknown mode: ${input.mode}`
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
async function handleWorkflowVersions(args, repository, context) {
|
|
2550
|
+
try {
|
|
2551
|
+
const input = workflowVersionsSchema.parse(args);
|
|
2552
|
+
if (process.env.ENABLE_MULTI_TENANT === 'true' && (0, instance_context_1.getInstanceScopeId)(context) === '') {
|
|
2553
|
+
const source = input.source ?? 'local';
|
|
2554
|
+
return {
|
|
2555
|
+
success: false,
|
|
2556
|
+
mode: input.mode,
|
|
2557
|
+
source,
|
|
2558
|
+
backend: source === 'native' ? 'official-mcp' : 'n8n-mcp',
|
|
2559
|
+
error: source === 'native'
|
|
2560
|
+
? "Reading n8n's own version history needs an instance-scoped context for this tenant"
|
|
2561
|
+
: 'Workflow version storage is not available for this tenant context'
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
if ((input.source ?? 'local') === 'native') {
|
|
2565
|
+
return handleNativeWorkflowVersions(input, context);
|
|
2566
|
+
}
|
|
2567
|
+
const invalidLocalVersionId = (error) => ({
|
|
2568
|
+
success: false,
|
|
2569
|
+
mode: input.mode,
|
|
2570
|
+
source: 'local',
|
|
2571
|
+
backend: 'n8n-mcp',
|
|
2572
|
+
code: 'INVALID_ARGS',
|
|
2573
|
+
error,
|
|
2574
|
+
});
|
|
2575
|
+
const parsedVersionId = parseLocalVersionId(input.versionId, 'versionId');
|
|
2576
|
+
if (!parsedVersionId.ok)
|
|
2577
|
+
return invalidLocalVersionId(parsedVersionId.error);
|
|
2578
|
+
const parsedToVersionId = parseLocalVersionId(input.toVersionId, 'toVersionId');
|
|
2579
|
+
if (!parsedToVersionId.ok)
|
|
2580
|
+
return invalidLocalVersionId(parsedToVersionId.error);
|
|
2581
|
+
const versionId = parsedVersionId.value;
|
|
2582
|
+
const toVersionId = parsedToVersionId.value;
|
|
2583
|
+
const localResult = await handleLocalWorkflowVersions(input, versionId, toVersionId, repository, context);
|
|
2584
|
+
return { ...localResult, mode: input.mode, source: 'local', backend: 'n8n-mcp' };
|
|
2242
2585
|
}
|
|
2243
2586
|
catch (error) {
|
|
2587
|
+
const raw = (args && typeof args === 'object' ? args : {});
|
|
2588
|
+
const source = raw.source === 'native' ? 'native' : 'local';
|
|
2589
|
+
const label = {
|
|
2590
|
+
...(typeof raw.mode === 'string' ? { mode: raw.mode } : {}),
|
|
2591
|
+
source,
|
|
2592
|
+
backend: source === 'native' ? 'official-mcp' : 'n8n-mcp',
|
|
2593
|
+
};
|
|
2244
2594
|
if (error instanceof zod_1.z.ZodError) {
|
|
2245
2595
|
return {
|
|
2246
2596
|
success: false,
|
|
2597
|
+
...label,
|
|
2247
2598
|
error: 'Invalid input',
|
|
2248
2599
|
details: { errors: error.errors }
|
|
2249
2600
|
};
|
|
2250
2601
|
}
|
|
2251
2602
|
return {
|
|
2252
2603
|
success: false,
|
|
2604
|
+
...label,
|
|
2253
2605
|
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
2254
2606
|
};
|
|
2255
2607
|
}
|
|
@@ -2716,6 +3068,115 @@ async function handleDeleteRows(args, context) {
|
|
|
2716
3068
|
return handleCrudError(error);
|
|
2717
3069
|
}
|
|
2718
3070
|
}
|
|
3071
|
+
const DATATABLE_ACTION = 'manage_datatable';
|
|
3072
|
+
const DATATABLE_TIMEOUT_MS = agents_action_map_1.DEFAULT_TIMEOUT_MS;
|
|
3073
|
+
const COLUMN_TOOLS = {
|
|
3074
|
+
addColumn: ['add_data_table_column'],
|
|
3075
|
+
deleteColumn: ['delete_data_table_column'],
|
|
3076
|
+
renameColumn: ['rename_data_table_column'],
|
|
3077
|
+
};
|
|
3078
|
+
const columnNameSchema = zod_1.z
|
|
3079
|
+
.string()
|
|
3080
|
+
.min(1, 'Column name cannot be empty')
|
|
3081
|
+
.max(63, 'Column name must be at most 63 characters')
|
|
3082
|
+
.regex(/^[a-zA-Z][a-zA-Z0-9_]*$/, 'Column name must start with a letter and contain only letters, digits and underscores');
|
|
3083
|
+
const columnTargetSchema = tableIdSchema.extend({
|
|
3084
|
+
projectId: optionalEmptyAware(zod_1.z.string()),
|
|
3085
|
+
timeoutMs: zod_1.z.number().int().min(agents_action_map_1.MIN_TIMEOUT_MS).max(agents_action_map_1.MAX_TIMEOUT_MS).optional(),
|
|
3086
|
+
});
|
|
3087
|
+
const addColumnSchema = columnTargetSchema.extend({
|
|
3088
|
+
column: zod_1.z.preprocess(tryParseJson, zod_1.z.object({
|
|
3089
|
+
name: columnNameSchema,
|
|
3090
|
+
type: zod_1.z.enum(['string', 'number', 'boolean', 'date']),
|
|
3091
|
+
})),
|
|
3092
|
+
});
|
|
3093
|
+
const deleteColumnSchema = columnTargetSchema.extend({
|
|
3094
|
+
columnId: zod_1.z.string().min(1, 'columnId is required'),
|
|
3095
|
+
});
|
|
3096
|
+
const renameColumnSchema = deleteColumnSchema.extend({
|
|
3097
|
+
name: columnNameSchema,
|
|
3098
|
+
});
|
|
3099
|
+
function columnInvalidArgs(action, error) {
|
|
3100
|
+
return {
|
|
3101
|
+
success: false,
|
|
3102
|
+
action,
|
|
3103
|
+
code: 'INVALID_ARGS',
|
|
3104
|
+
error: error.issues.map(i => `${i.path.join('.') || 'input'}: ${i.message}`).join('; '),
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
async function resolveDataTableProjectId(input, action, context) {
|
|
3108
|
+
if (input.projectId)
|
|
3109
|
+
return { projectId: input.projectId };
|
|
3110
|
+
const resolved = await (0, handlers_official_tools_1.resolveProjectChoices)(context);
|
|
3111
|
+
if ('failure' in resolved)
|
|
3112
|
+
return { failure: { ...resolved.failure, action } };
|
|
3113
|
+
const items = resolved.choices.items;
|
|
3114
|
+
if (items.length === 1)
|
|
3115
|
+
return { projectId: items[0].id };
|
|
3116
|
+
const backend = resolved.choices.backend;
|
|
3117
|
+
if (items.length === 0) {
|
|
3118
|
+
return {
|
|
3119
|
+
failure: {
|
|
3120
|
+
success: false,
|
|
3121
|
+
action,
|
|
3122
|
+
backend,
|
|
3123
|
+
code: 'PROJECT_REQUIRED',
|
|
3124
|
+
error: 'No project could be resolved for this instance; pass projectId',
|
|
3125
|
+
},
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
return {
|
|
3129
|
+
failure: {
|
|
3130
|
+
success: false,
|
|
3131
|
+
action,
|
|
3132
|
+
backend,
|
|
3133
|
+
code: 'PROJECT_REQUIRED',
|
|
3134
|
+
error: 'Several projects are accessible; pass projectId',
|
|
3135
|
+
details: { candidates: items },
|
|
3136
|
+
},
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
async function callColumnTool(action, officialArgs, timeoutMs, context) {
|
|
3140
|
+
const response = await (0, handlers_official_tools_1.callOfficialTool)(context, COLUMN_TOOLS[action], officialArgs, timeoutMs ?? DATATABLE_TIMEOUT_MS, DATATABLE_ACTION, false);
|
|
3141
|
+
return { ...response, action, backend: 'official-mcp' };
|
|
3142
|
+
}
|
|
3143
|
+
async function handleAddColumn(args, context) {
|
|
3144
|
+
const parsed = addColumnSchema.safeParse(args);
|
|
3145
|
+
if (!parsed.success)
|
|
3146
|
+
return columnInvalidArgs('addColumn', parsed.error);
|
|
3147
|
+
const resolved = await resolveDataTableProjectId(parsed.data, 'addColumn', context);
|
|
3148
|
+
if ('failure' in resolved)
|
|
3149
|
+
return resolved.failure;
|
|
3150
|
+
return callColumnTool('addColumn', {
|
|
3151
|
+
dataTableId: parsed.data.tableId,
|
|
3152
|
+
projectId: resolved.projectId,
|
|
3153
|
+
name: parsed.data.column.name,
|
|
3154
|
+
type: parsed.data.column.type,
|
|
3155
|
+
}, parsed.data.timeoutMs, context);
|
|
3156
|
+
}
|
|
3157
|
+
async function handleDeleteColumn(args, context) {
|
|
3158
|
+
const parsed = deleteColumnSchema.safeParse(args);
|
|
3159
|
+
if (!parsed.success)
|
|
3160
|
+
return columnInvalidArgs('deleteColumn', parsed.error);
|
|
3161
|
+
const resolved = await resolveDataTableProjectId(parsed.data, 'deleteColumn', context);
|
|
3162
|
+
if ('failure' in resolved)
|
|
3163
|
+
return resolved.failure;
|
|
3164
|
+
return callColumnTool('deleteColumn', { dataTableId: parsed.data.tableId, projectId: resolved.projectId, columnId: parsed.data.columnId }, parsed.data.timeoutMs, context);
|
|
3165
|
+
}
|
|
3166
|
+
async function handleRenameColumn(args, context) {
|
|
3167
|
+
const parsed = renameColumnSchema.safeParse(args);
|
|
3168
|
+
if (!parsed.success)
|
|
3169
|
+
return columnInvalidArgs('renameColumn', parsed.error);
|
|
3170
|
+
const resolved = await resolveDataTableProjectId(parsed.data, 'renameColumn', context);
|
|
3171
|
+
if ('failure' in resolved)
|
|
3172
|
+
return resolved.failure;
|
|
3173
|
+
return callColumnTool('renameColumn', {
|
|
3174
|
+
dataTableId: parsed.data.tableId,
|
|
3175
|
+
projectId: resolved.projectId,
|
|
3176
|
+
columnId: parsed.data.columnId,
|
|
3177
|
+
name: parsed.data.name,
|
|
3178
|
+
}, parsed.data.timeoutMs, context);
|
|
3179
|
+
}
|
|
2719
3180
|
const listCredentialsSchema = zod_1.z.object({
|
|
2720
3181
|
includeUsage: zod_1.z.boolean().optional(),
|
|
2721
3182
|
cursor: optionalEmptyAware(zod_1.z.string()),
|