@kairos-sdk/core 0.4.5 → 0.5.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 +18 -10
- package/dist/{chunk-6CLI43FI.js → chunk-5GAY7CSJ.js} +109 -13
- package/dist/chunk-5GAY7CSJ.js.map +1 -0
- package/dist/{chunk-CR2NHLOH.js → chunk-EVOAYH2K.js} +57 -11
- package/dist/chunk-EVOAYH2K.js.map +1 -0
- package/dist/{chunk-4TS6GW6O.js → chunk-HBGZTUUZ.js} +26 -13
- package/dist/chunk-HBGZTUUZ.js.map +1 -0
- package/dist/{chunk-6IXW3WCC.js → chunk-KIFT5LA7.js} +532 -76
- package/dist/chunk-KIFT5LA7.js.map +1 -0
- package/dist/cli.cjs +738 -111
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +31 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +711 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -5
- package/dist/mcp-server.cjs +875 -333
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/mcp-server.js +293 -251
- package/dist/mcp-server.js.map +1 -1
- package/dist/{reader-CpUcHhKW.d.ts → reader-B5mV20H6.d.cts} +34 -4
- package/dist/{reader-CpUcHhKW.d.cts → reader-B5mV20H6.d.ts} +34 -4
- package/dist/standalone.cjs +602 -84
- package/dist/standalone.cjs.map +1 -1
- package/dist/standalone.d.cts +2 -1
- package/dist/standalone.d.ts +2 -1
- package/dist/standalone.js +3 -3
- package/package.json +4 -1
- package/dist/chunk-4TS6GW6O.js.map +0 -1
- package/dist/chunk-6CLI43FI.js.map +0 -1
- package/dist/chunk-6IXW3WCC.js.map +0 -1
- package/dist/chunk-CR2NHLOH.js.map +0 -1
package/dist/mcp-server.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
PromptBuilder,
|
|
4
4
|
inferWorkflowType
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-EVOAYH2K.js";
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_REGISTRY,
|
|
8
8
|
FileLibrary,
|
|
9
|
+
GuardError,
|
|
9
10
|
N8nApiClient,
|
|
10
11
|
N8nFieldStripper,
|
|
11
12
|
N8nValidator,
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
TelemetryReader,
|
|
16
17
|
generateUUID,
|
|
17
18
|
nullLogger
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-KIFT5LA7.js";
|
|
19
20
|
|
|
20
21
|
// src/mcp-server.ts
|
|
21
22
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -87,9 +88,13 @@ import { fileURLToPath } from "url";
|
|
|
87
88
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
88
89
|
var pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
89
90
|
var library = new FileLibrary();
|
|
90
|
-
var
|
|
91
|
+
var _validator = new N8nValidator();
|
|
92
|
+
function getValidator() {
|
|
93
|
+
return _validator;
|
|
94
|
+
}
|
|
91
95
|
var nodeSyncer = new NodeSyncer();
|
|
92
96
|
var lastSync = null;
|
|
97
|
+
var AUTO_SYNC_TIMEOUT_MS = 5e3;
|
|
93
98
|
var stripper = new N8nFieldStripper();
|
|
94
99
|
var promptBuilder = new PromptBuilder(getMcpPatternsPath());
|
|
95
100
|
function getMcpTelemetry() {
|
|
@@ -124,11 +129,23 @@ function isAllowed(action) {
|
|
|
124
129
|
const key = `KAIROS_MCP_ALLOW_${action.toUpperCase()}`;
|
|
125
130
|
return process.env[key] === "true";
|
|
126
131
|
}
|
|
132
|
+
function mcpText(text) {
|
|
133
|
+
return { content: [{ type: "text", text }] };
|
|
134
|
+
}
|
|
135
|
+
function mcpError(text) {
|
|
136
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
137
|
+
}
|
|
138
|
+
function checkMcpAuth(provided) {
|
|
139
|
+
const expected = process.env["KAIROS_MCP_SECRET"];
|
|
140
|
+
if (!expected) return null;
|
|
141
|
+
if (provided === expected) return null;
|
|
142
|
+
return mcpError(JSON.stringify({ error: "Unauthorized: missing or incorrect kairos_secret" }));
|
|
143
|
+
}
|
|
127
144
|
function getApiClient() {
|
|
128
145
|
const baseUrl = process.env["N8N_BASE_URL"];
|
|
129
146
|
const apiKey = process.env["N8N_API_KEY"];
|
|
130
147
|
if (!baseUrl || !apiKey) {
|
|
131
|
-
throw new
|
|
148
|
+
throw new GuardError("N8N_BASE_URL and N8N_API_KEY environment variables are required for n8n operations");
|
|
132
149
|
}
|
|
133
150
|
return new N8nApiClient(baseUrl, apiKey, nullLogger);
|
|
134
151
|
}
|
|
@@ -142,7 +159,7 @@ async function autoSync() {
|
|
|
142
159
|
const nodeTypes = await client.getNodeTypes();
|
|
143
160
|
if (nodeTypes.length === 0) return null;
|
|
144
161
|
lastSync = nodeSyncer.sync(nodeTypes);
|
|
145
|
-
|
|
162
|
+
_validator = new N8nValidator(lastSync.registry);
|
|
146
163
|
return lastSync;
|
|
147
164
|
} catch {
|
|
148
165
|
return null;
|
|
@@ -164,21 +181,21 @@ server.tool(
|
|
|
164
181
|
const baseUrl = process.env["N8N_BASE_URL"];
|
|
165
182
|
const apiKey = process.env["N8N_API_KEY"];
|
|
166
183
|
if (!baseUrl || !apiKey) {
|
|
167
|
-
return {
|
|
168
|
-
content: [{
|
|
169
|
-
type: "text",
|
|
170
|
-
text: JSON.stringify({ error: "N8N_BASE_URL and N8N_API_KEY are required. Kairos needs to sync your n8n instance's node types to generate accurate workflows." })
|
|
171
|
-
}],
|
|
172
|
-
isError: true
|
|
173
|
-
};
|
|
184
|
+
return mcpError(JSON.stringify({ error: "N8N_BASE_URL and N8N_API_KEY are required. Kairos needs to sync your n8n instance's node types to generate accurate workflows." }));
|
|
174
185
|
}
|
|
175
186
|
const runId = generateUUID();
|
|
176
187
|
const workflowType = inferWorkflowType(description);
|
|
188
|
+
const syncPromise = autoSync();
|
|
189
|
+
const syncTimeout = new Promise((resolve) => setTimeout(() => resolve(null), AUTO_SYNC_TIMEOUT_MS));
|
|
177
190
|
await library.initialize();
|
|
178
|
-
const syncResult = await
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
191
|
+
const [syncResult, matches, failureRates] = await Promise.all([
|
|
192
|
+
Promise.race([syncPromise, syncTimeout]),
|
|
193
|
+
library.search(description),
|
|
194
|
+
(async () => {
|
|
195
|
+
const reader = getTelemetryReader();
|
|
196
|
+
return reader ? reader.getFailureRates() : [];
|
|
197
|
+
})()
|
|
198
|
+
]);
|
|
182
199
|
const request = { description, ...name ? { name } : {} };
|
|
183
200
|
const built = promptBuilder.build(request, matches, failureRates, syncResult?.catalogText);
|
|
184
201
|
if (mcpTelemetry) {
|
|
@@ -192,43 +209,38 @@ server.tool(
|
|
|
192
209
|
await mcpTelemetry.emit("build_start", { description, model: "mcp-decomposed", dryRun: false }, runId);
|
|
193
210
|
}
|
|
194
211
|
const systemText = built.system.map((block) => block.text).join("\n\n---\n\n");
|
|
195
|
-
return {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}, null, 2)
|
|
225
|
-
}]
|
|
226
|
-
};
|
|
212
|
+
return mcpText(JSON.stringify({
|
|
213
|
+
kairos_run_id: runId,
|
|
214
|
+
mode: built.mode,
|
|
215
|
+
matchCount: matches.length,
|
|
216
|
+
topMatchScore: matches[0]?.score ?? null,
|
|
217
|
+
nodeCatalog: syncResult ? "synced" : "static",
|
|
218
|
+
nodeCount: syncResult?.nodeCount ?? null,
|
|
219
|
+
...syncResult ? {} : { syncWarning: "Could not sync node types from your n8n instance. Using static fallback catalog \u2014 generated workflows may not match your exact n8n setup." },
|
|
220
|
+
systemPrompt: systemText,
|
|
221
|
+
userMessage: built.userMessage,
|
|
222
|
+
outputFormat: {
|
|
223
|
+
description: "Generate a JSON object with this exact structure. The workflow field contains the n8n workflow. credentialsNeeded lists services requiring credentials.",
|
|
224
|
+
schema: {
|
|
225
|
+
workflow: {
|
|
226
|
+
name: "string \u2014 descriptive workflow name",
|
|
227
|
+
nodes: "array \u2014 n8n node objects with id (UUID v4), type, typeVersion, name, position, parameters",
|
|
228
|
+
connections: "object \u2014 keyed by source node NAME, maps to target nodes",
|
|
229
|
+
settings: 'object \u2014 include executionOrder: "v1"'
|
|
230
|
+
},
|
|
231
|
+
credentialsNeeded: [{
|
|
232
|
+
service: 'string \u2014 e.g. "Slack"',
|
|
233
|
+
credentialType: 'string \u2014 e.g. "slackOAuth2Api"',
|
|
234
|
+
description: "string \u2014 what the user needs to set up"
|
|
235
|
+
}]
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}, null, 2));
|
|
227
239
|
}
|
|
228
240
|
);
|
|
229
241
|
server.tool(
|
|
230
242
|
"kairos_validate",
|
|
231
|
-
"Validate n8n workflow JSON against
|
|
243
|
+
"Validate n8n workflow JSON against 34 structural rules. Returns pass/fail with specific issues. If validation fails, fix the issues and call this again. Errors block deployment; warnings are advisory.",
|
|
232
244
|
{
|
|
233
245
|
workflow: z.string().describe("The workflow JSON string to validate"),
|
|
234
246
|
kairos_run_id: z.string().optional().describe("Run ID from kairos_prompt \u2014 enables telemetry correlation")
|
|
@@ -238,17 +250,9 @@ server.tool(
|
|
|
238
250
|
try {
|
|
239
251
|
parsed = JSON.parse(workflowStr);
|
|
240
252
|
} catch (e) {
|
|
241
|
-
return {
|
|
242
|
-
content: [{
|
|
243
|
-
type: "text",
|
|
244
|
-
text: JSON.stringify({
|
|
245
|
-
valid: false,
|
|
246
|
-
error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}`
|
|
247
|
-
}, null, 2)
|
|
248
|
-
}]
|
|
249
|
-
};
|
|
253
|
+
return mcpText(JSON.stringify({ valid: false, error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` }, null, 2));
|
|
250
254
|
}
|
|
251
|
-
const result =
|
|
255
|
+
const result = getValidator().validate(parsed);
|
|
252
256
|
const errors = result.issues.filter((i) => i.severity === "error");
|
|
253
257
|
const warnings = result.issues.filter((i) => i.severity === "warn");
|
|
254
258
|
if (mcpTelemetry && kairos_run_id) {
|
|
@@ -269,27 +273,14 @@ server.tool(
|
|
|
269
273
|
}, kairos_run_id);
|
|
270
274
|
}
|
|
271
275
|
}
|
|
272
|
-
return {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
rule: i.rule,
|
|
281
|
-
message: i.message,
|
|
282
|
-
nodeId: i.nodeId ?? null
|
|
283
|
-
})),
|
|
284
|
-
warnings: warnings.map((i) => ({
|
|
285
|
-
rule: i.rule,
|
|
286
|
-
message: i.message,
|
|
287
|
-
nodeId: i.nodeId ?? null
|
|
288
|
-
})),
|
|
289
|
-
deployable: errors.length === 0
|
|
290
|
-
}, null, 2)
|
|
291
|
-
}]
|
|
292
|
-
};
|
|
276
|
+
return mcpText(JSON.stringify({
|
|
277
|
+
valid: result.valid,
|
|
278
|
+
errorCount: errors.length,
|
|
279
|
+
warningCount: warnings.length,
|
|
280
|
+
errors: errors.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null })),
|
|
281
|
+
warnings: warnings.map((i) => ({ rule: i.rule, message: i.message, nodeId: i.nodeId ?? null })),
|
|
282
|
+
deployable: errors.length === 0
|
|
283
|
+
}, null, 2));
|
|
293
284
|
}
|
|
294
285
|
);
|
|
295
286
|
server.tool(
|
|
@@ -298,101 +289,146 @@ server.tool(
|
|
|
298
289
|
{
|
|
299
290
|
workflow: z.string().describe("The validated workflow JSON string to deploy"),
|
|
300
291
|
activate: z.boolean().default(false).describe("Activate the workflow immediately after deployment"),
|
|
301
|
-
kairos_run_id: z.string().optional().describe("Run ID from kairos_prompt \u2014 enables telemetry correlation")
|
|
292
|
+
kairos_run_id: z.string().optional().describe("Run ID from kairos_prompt \u2014 enables telemetry correlation"),
|
|
293
|
+
kairos_secret: z.string().optional().describe("Required when KAIROS_MCP_SECRET env var is set")
|
|
302
294
|
},
|
|
303
|
-
async ({ workflow: workflowStr, activate, kairos_run_id }) => {
|
|
295
|
+
async ({ workflow: workflowStr, activate, kairos_run_id, kairos_secret }) => {
|
|
296
|
+
const authError = checkMcpAuth(kairos_secret);
|
|
297
|
+
if (authError) return authError;
|
|
304
298
|
if (!isAllowed("deploy")) {
|
|
305
|
-
return {
|
|
306
|
-
content: [{
|
|
307
|
-
type: "text",
|
|
308
|
-
text: JSON.stringify({ error: "Deploy is disabled. Set KAIROS_MCP_ALLOW_DEPLOY=true to enable." })
|
|
309
|
-
}],
|
|
310
|
-
isError: true
|
|
311
|
-
};
|
|
299
|
+
return mcpError(JSON.stringify({ error: "Deploy is disabled. Set KAIROS_MCP_ALLOW_DEPLOY=true to enable." }));
|
|
312
300
|
}
|
|
313
301
|
let parsed;
|
|
314
302
|
try {
|
|
315
303
|
parsed = JSON.parse(workflowStr);
|
|
316
304
|
} catch (e) {
|
|
317
|
-
return {
|
|
318
|
-
content: [{
|
|
319
|
-
type: "text",
|
|
320
|
-
text: JSON.stringify({ error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` })
|
|
321
|
-
}]
|
|
322
|
-
};
|
|
305
|
+
return mcpError(JSON.stringify({ error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` }));
|
|
323
306
|
}
|
|
324
|
-
const validation =
|
|
307
|
+
const validation = getValidator().validate(parsed);
|
|
325
308
|
const errors = validation.issues.filter((i) => i.severity === "error");
|
|
326
309
|
if (errors.length > 0) {
|
|
327
|
-
return {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
error: "Workflow has validation errors \u2014 fix them before deploying",
|
|
332
|
-
errors: errors.map((i) => ({ rule: i.rule, message: i.message }))
|
|
333
|
-
}, null, 2)
|
|
334
|
-
}]
|
|
335
|
-
};
|
|
310
|
+
return mcpError(JSON.stringify({
|
|
311
|
+
error: "Workflow has validation errors \u2014 fix them before deploying",
|
|
312
|
+
errors: errors.map((i) => ({ rule: i.rule, message: i.message }))
|
|
313
|
+
}, null, 2));
|
|
336
314
|
}
|
|
337
315
|
const client = getApiClient();
|
|
338
316
|
const stripped = stripper.stripForCreate(parsed);
|
|
339
317
|
const response = await client.createWorkflow(stripped);
|
|
340
318
|
if (activate) {
|
|
341
319
|
if (!isAllowed("activate")) {
|
|
342
|
-
return {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
warning: "Workflow deployed but activation is disabled. Set KAIROS_MCP_ALLOW_ACTIVATE=true to enable.",
|
|
350
|
-
url: `${process.env["N8N_BASE_URL"]}/workflow/${response.id}`
|
|
351
|
-
}, null, 2)
|
|
352
|
-
}]
|
|
353
|
-
};
|
|
320
|
+
return mcpText(JSON.stringify({
|
|
321
|
+
workflowId: response.id,
|
|
322
|
+
name: response.name,
|
|
323
|
+
activated: false,
|
|
324
|
+
warning: "Workflow deployed but activation is disabled. Set KAIROS_MCP_ALLOW_ACTIVATE=true to enable.",
|
|
325
|
+
url: `${process.env["N8N_BASE_URL"]}/workflow/${response.id}`
|
|
326
|
+
}, null, 2));
|
|
354
327
|
}
|
|
355
328
|
await client.activateWorkflow(response.id);
|
|
356
329
|
}
|
|
330
|
+
const session = kairos_run_id ? mcpSessions.get(kairos_run_id) : void 0;
|
|
331
|
+
const missingSessionWarning = kairos_run_id && !session ? `
|
|
332
|
+
|
|
333
|
+
Note: kairos_run_id "${kairos_run_id}" was provided but no active session was found. This usually means kairos_deploy was called without a prior kairos_prompt call, or the session expired. Telemetry and pattern learning for this build were skipped.` : "";
|
|
357
334
|
await library.initialize();
|
|
358
335
|
await library.save(parsed, {
|
|
359
|
-
description: parsed.name,
|
|
336
|
+
description: session?.description ?? parsed.name,
|
|
360
337
|
generationMode: "scratch",
|
|
361
|
-
generationAttempts: 1
|
|
338
|
+
generationAttempts: session?.validateAttempts ?? 1,
|
|
339
|
+
n8nWorkflowId: response.id
|
|
362
340
|
});
|
|
363
|
-
if (mcpTelemetry && kairos_run_id) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
PatternAnalyzer.fromEnv().analyzeAndSave().catch(() => {
|
|
382
|
-
});
|
|
383
|
-
}
|
|
341
|
+
if (mcpTelemetry && kairos_run_id && session) {
|
|
342
|
+
await mcpTelemetry.emit("build_complete", {
|
|
343
|
+
description: session.description,
|
|
344
|
+
success: true,
|
|
345
|
+
totalAttempts: session.validateAttempts,
|
|
346
|
+
totalDurationMs: Date.now() - session.startTime,
|
|
347
|
+
totalTokensInput: 0,
|
|
348
|
+
totalTokensOutput: 0,
|
|
349
|
+
workflowName: response.name,
|
|
350
|
+
workflowId: response.id,
|
|
351
|
+
dryRun: false,
|
|
352
|
+
credentialsNeeded: 0,
|
|
353
|
+
warnedRules: session.warnedRules,
|
|
354
|
+
workflowType: session.workflowType
|
|
355
|
+
}, kairos_run_id);
|
|
356
|
+
mcpSessions.delete(kairos_run_id);
|
|
357
|
+
PatternAnalyzer.fromEnv().analyzeAndSave().catch(() => {
|
|
358
|
+
});
|
|
384
359
|
}
|
|
385
|
-
return {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
360
|
+
return mcpText(JSON.stringify({
|
|
361
|
+
workflowId: response.id,
|
|
362
|
+
name: response.name,
|
|
363
|
+
activated: activate,
|
|
364
|
+
url: `${process.env["N8N_BASE_URL"]}/workflow/${response.id}`
|
|
365
|
+
}, null, 2) + missingSessionWarning);
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
server.tool(
|
|
369
|
+
"kairos_replace",
|
|
370
|
+
"Replace an existing n8n workflow with a new version. Validates before updating. Use kairos_prompt \u2192 kairos_validate \u2192 kairos_replace for iteration on existing workflows.",
|
|
371
|
+
{
|
|
372
|
+
workflow_id: z.string().describe("The n8n workflow ID to replace"),
|
|
373
|
+
workflow: z.string().describe("The validated workflow JSON string"),
|
|
374
|
+
kairos_run_id: z.string().optional().describe("Run ID from kairos_prompt \u2014 enables telemetry correlation"),
|
|
375
|
+
kairos_secret: z.string().optional().describe("Required when KAIROS_MCP_SECRET env var is set")
|
|
376
|
+
},
|
|
377
|
+
async ({ workflow_id, workflow: workflowStr, kairos_run_id, kairos_secret }) => {
|
|
378
|
+
const authError = checkMcpAuth(kairos_secret);
|
|
379
|
+
if (authError) return authError;
|
|
380
|
+
let parsed;
|
|
381
|
+
try {
|
|
382
|
+
parsed = JSON.parse(workflowStr);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
return mcpError(JSON.stringify({ error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` }));
|
|
385
|
+
}
|
|
386
|
+
const validation = getValidator().validate(parsed);
|
|
387
|
+
const errors = validation.issues.filter((i) => i.severity === "error");
|
|
388
|
+
if (errors.length > 0) {
|
|
389
|
+
return mcpError(JSON.stringify({
|
|
390
|
+
error: "Workflow has validation errors \u2014 fix them before replacing",
|
|
391
|
+
errors: errors.map((i) => ({ rule: i.rule, message: i.message }))
|
|
392
|
+
}, null, 2));
|
|
393
|
+
}
|
|
394
|
+
const client = getApiClient();
|
|
395
|
+
const stripped = stripper.stripForUpdate(parsed);
|
|
396
|
+
const response = await client.updateWorkflow(workflow_id, stripped);
|
|
397
|
+
const session = kairos_run_id ? mcpSessions.get(kairos_run_id) : void 0;
|
|
398
|
+
const missingSessionWarning = kairos_run_id && !session ? `
|
|
399
|
+
|
|
400
|
+
Note: kairos_run_id "${kairos_run_id}" was provided but no active session was found.` : "";
|
|
401
|
+
await library.initialize();
|
|
402
|
+
await library.save(parsed, {
|
|
403
|
+
description: session?.description ?? parsed.name,
|
|
404
|
+
generationMode: "scratch",
|
|
405
|
+
generationAttempts: session?.validateAttempts ?? 1,
|
|
406
|
+
n8nWorkflowId: workflow_id
|
|
407
|
+
});
|
|
408
|
+
if (mcpTelemetry && kairos_run_id && session) {
|
|
409
|
+
await mcpTelemetry.emit("build_complete", {
|
|
410
|
+
description: session.description,
|
|
411
|
+
success: true,
|
|
412
|
+
totalAttempts: session.validateAttempts,
|
|
413
|
+
totalDurationMs: Date.now() - session.startTime,
|
|
414
|
+
totalTokensInput: 0,
|
|
415
|
+
totalTokensOutput: 0,
|
|
416
|
+
workflowName: response.name,
|
|
417
|
+
workflowId: response.id,
|
|
418
|
+
dryRun: false,
|
|
419
|
+
credentialsNeeded: 0,
|
|
420
|
+
warnedRules: session.warnedRules,
|
|
421
|
+
workflowType: session.workflowType
|
|
422
|
+
}, kairos_run_id);
|
|
423
|
+
mcpSessions.delete(kairos_run_id);
|
|
424
|
+
PatternAnalyzer.fromEnv().analyzeAndSave().catch(() => {
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return mcpText(JSON.stringify({
|
|
428
|
+
workflowId: response.id,
|
|
429
|
+
name: response.name,
|
|
430
|
+
url: `${process.env["N8N_BASE_URL"]}/workflow/${response.id}`
|
|
431
|
+
}, null, 2) + missingSessionWarning);
|
|
396
432
|
}
|
|
397
433
|
);
|
|
398
434
|
server.tool(
|
|
@@ -405,23 +441,20 @@ server.tool(
|
|
|
405
441
|
async ({ query, limit }) => {
|
|
406
442
|
await library.initialize();
|
|
407
443
|
const matches = await library.search(query);
|
|
408
|
-
return
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
)
|
|
423
|
-
}]
|
|
424
|
-
};
|
|
444
|
+
return mcpText(JSON.stringify(
|
|
445
|
+
matches.slice(0, limit).map((m) => ({
|
|
446
|
+
id: m.workflow.id,
|
|
447
|
+
score: Number(m.score.toFixed(3)),
|
|
448
|
+
mode: m.mode,
|
|
449
|
+
description: m.workflow.description,
|
|
450
|
+
nodeCount: m.workflow.workflow.nodes.length,
|
|
451
|
+
nodes: m.workflow.workflow.nodes.map((n) => n.name),
|
|
452
|
+
n8nWorkflowId: m.workflow.n8nWorkflowId ?? null,
|
|
453
|
+
failurePatterns: m.workflow.failurePatterns ?? []
|
|
454
|
+
})),
|
|
455
|
+
null,
|
|
456
|
+
2
|
|
457
|
+
));
|
|
425
458
|
}
|
|
426
459
|
);
|
|
427
460
|
server.tool(
|
|
@@ -432,36 +465,19 @@ server.tool(
|
|
|
432
465
|
const baseUrl = process.env["N8N_BASE_URL"];
|
|
433
466
|
const apiKey = process.env["N8N_API_KEY"];
|
|
434
467
|
if (!baseUrl || !apiKey) {
|
|
435
|
-
return {
|
|
436
|
-
content: [{
|
|
437
|
-
type: "text",
|
|
438
|
-
text: JSON.stringify({ error: "N8N_BASE_URL and N8N_API_KEY are required for sync." })
|
|
439
|
-
}],
|
|
440
|
-
isError: true
|
|
441
|
-
};
|
|
468
|
+
return mcpError(JSON.stringify({ error: "N8N_BASE_URL and N8N_API_KEY are required for sync." }));
|
|
442
469
|
}
|
|
443
470
|
lastSync = null;
|
|
444
471
|
const result = await autoSync();
|
|
445
472
|
if (!result) {
|
|
446
|
-
return {
|
|
447
|
-
content: [{
|
|
448
|
-
type: "text",
|
|
449
|
-
text: JSON.stringify({ error: "Failed to fetch node types from n8n. Check your credentials and that your instance is running." })
|
|
450
|
-
}],
|
|
451
|
-
isError: true
|
|
452
|
-
};
|
|
473
|
+
return mcpError(JSON.stringify({ error: "Failed to fetch node types from n8n. Check your credentials and that your instance is running." }));
|
|
453
474
|
}
|
|
454
|
-
return {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
newNodes: result.newNodes,
|
|
461
|
-
message: `Synced ${result.nodeCount} node types from your n8n instance (${result.newNodes} not in default catalog).`
|
|
462
|
-
}, null, 2)
|
|
463
|
-
}]
|
|
464
|
-
};
|
|
475
|
+
return mcpText(JSON.stringify({
|
|
476
|
+
synced: true,
|
|
477
|
+
nodeCount: result.nodeCount,
|
|
478
|
+
newNodes: result.newNodes,
|
|
479
|
+
message: `Synced ${result.nodeCount} node types from your n8n instance (${result.newNodes} not in default catalog).`
|
|
480
|
+
}, null, 2));
|
|
465
481
|
}
|
|
466
482
|
);
|
|
467
483
|
server.tool(
|
|
@@ -477,12 +493,72 @@ server.tool(
|
|
|
477
493
|
if (limit !== void 0 && limit > 0) {
|
|
478
494
|
analysis.topFailureRules = analysis.topFailureRules.slice(0, limit);
|
|
479
495
|
}
|
|
480
|
-
return
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
496
|
+
return mcpText(JSON.stringify(analysis, null, 2));
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
server.tool(
|
|
500
|
+
"kairos_library",
|
|
501
|
+
"Browse the local Kairos workflow library. Returns saved workflow metadata. Use the optional query to search, or omit it to list all entries.",
|
|
502
|
+
{
|
|
503
|
+
query: z.string().optional().describe("Optional search query \u2014 omit to list all entries"),
|
|
504
|
+
limit: z.number().default(20).describe("Maximum entries to return")
|
|
505
|
+
},
|
|
506
|
+
async ({ query, limit }) => {
|
|
507
|
+
await library.initialize();
|
|
508
|
+
if (query) {
|
|
509
|
+
const matches = await library.search(query);
|
|
510
|
+
return mcpText(JSON.stringify(
|
|
511
|
+
matches.slice(0, limit).map((m) => ({
|
|
512
|
+
id: m.workflow.id,
|
|
513
|
+
description: m.workflow.description,
|
|
514
|
+
score: Number(m.score.toFixed(3)),
|
|
515
|
+
mode: m.mode,
|
|
516
|
+
nodeCount: m.workflow.workflow.nodes.length,
|
|
517
|
+
nodes: m.workflow.workflow.nodes.map((n) => n.name),
|
|
518
|
+
deployCount: m.workflow.deployCount,
|
|
519
|
+
n8nWorkflowId: m.workflow.n8nWorkflowId ?? null,
|
|
520
|
+
createdAt: m.workflow.createdAt
|
|
521
|
+
})),
|
|
522
|
+
null,
|
|
523
|
+
2
|
|
524
|
+
));
|
|
525
|
+
}
|
|
526
|
+
const all = await library.list();
|
|
527
|
+
return mcpText(JSON.stringify(
|
|
528
|
+
all.slice(0, limit).map((w) => ({
|
|
529
|
+
id: w.id,
|
|
530
|
+
description: w.description,
|
|
531
|
+
nodeCount: w.workflow.nodes.length,
|
|
532
|
+
nodes: w.workflow.nodes.map((n) => n.name),
|
|
533
|
+
deployCount: w.deployCount,
|
|
534
|
+
n8nWorkflowId: w.n8nWorkflowId ?? null,
|
|
535
|
+
timesRetrieved: w.timesRetrieved ?? 0,
|
|
536
|
+
createdAt: w.createdAt
|
|
537
|
+
})),
|
|
538
|
+
null,
|
|
539
|
+
2
|
|
540
|
+
));
|
|
541
|
+
}
|
|
542
|
+
);
|
|
543
|
+
server.tool(
|
|
544
|
+
"kairos_outcome",
|
|
545
|
+
"Record the outcome of a workflow build against a library entry. Trains the pattern learning system to know what works and what fails over time.",
|
|
546
|
+
{
|
|
547
|
+
library_id: z.string().describe("The Kairos library entry ID (returned by kairos_deploy, kairos_replace, or kairos_library)"),
|
|
548
|
+
attempts: z.number().describe("Number of generation+validation attempts before success"),
|
|
549
|
+
first_try_pass: z.boolean().describe("Whether the first attempt passed validation"),
|
|
550
|
+
failed_rules: z.array(z.number()).describe("Validation rule IDs that failed during generation"),
|
|
551
|
+
mode: z.enum(["direct", "reference"]).describe("How the library entry was used during generation")
|
|
552
|
+
},
|
|
553
|
+
async ({ library_id, attempts, first_try_pass, failed_rules, mode }) => {
|
|
554
|
+
await library.initialize();
|
|
555
|
+
await library.recordOutcome(library_id, {
|
|
556
|
+
attempts,
|
|
557
|
+
firstTryPass: first_try_pass,
|
|
558
|
+
failedRules: failed_rules,
|
|
559
|
+
mode
|
|
560
|
+
});
|
|
561
|
+
return mcpText(JSON.stringify({ recorded: true, libraryId: library_id }));
|
|
486
562
|
}
|
|
487
563
|
);
|
|
488
564
|
server.tool(
|
|
@@ -492,12 +568,7 @@ server.tool(
|
|
|
492
568
|
async () => {
|
|
493
569
|
const client = getApiClient();
|
|
494
570
|
const workflows = await client.listWorkflows();
|
|
495
|
-
return
|
|
496
|
-
content: [{
|
|
497
|
-
type: "text",
|
|
498
|
-
text: JSON.stringify(workflows, null, 2)
|
|
499
|
-
}]
|
|
500
|
-
};
|
|
571
|
+
return mcpText(JSON.stringify(workflows, null, 2));
|
|
501
572
|
}
|
|
502
573
|
);
|
|
503
574
|
server.tool(
|
|
@@ -509,12 +580,7 @@ server.tool(
|
|
|
509
580
|
async ({ workflow_id }) => {
|
|
510
581
|
const client = getApiClient();
|
|
511
582
|
const workflow = await client.getWorkflow(workflow_id);
|
|
512
|
-
return
|
|
513
|
-
content: [{
|
|
514
|
-
type: "text",
|
|
515
|
-
text: JSON.stringify(workflow, null, 2)
|
|
516
|
-
}]
|
|
517
|
-
};
|
|
583
|
+
return mcpText(JSON.stringify(workflow, null, 2));
|
|
518
584
|
}
|
|
519
585
|
);
|
|
520
586
|
server.tool(
|
|
@@ -525,22 +591,11 @@ server.tool(
|
|
|
525
591
|
},
|
|
526
592
|
async ({ workflow_id }) => {
|
|
527
593
|
if (!isAllowed("activate")) {
|
|
528
|
-
return {
|
|
529
|
-
content: [{
|
|
530
|
-
type: "text",
|
|
531
|
-
text: JSON.stringify({ error: "Activate is disabled. Set KAIROS_MCP_ALLOW_ACTIVATE=true to enable." })
|
|
532
|
-
}],
|
|
533
|
-
isError: true
|
|
534
|
-
};
|
|
594
|
+
return mcpError(JSON.stringify({ error: "Activate is disabled. Set KAIROS_MCP_ALLOW_ACTIVATE=true to enable." }));
|
|
535
595
|
}
|
|
536
596
|
const client = getApiClient();
|
|
537
597
|
await client.activateWorkflow(workflow_id);
|
|
538
|
-
return {
|
|
539
|
-
content: [{
|
|
540
|
-
type: "text",
|
|
541
|
-
text: `Activated workflow ${workflow_id}`
|
|
542
|
-
}]
|
|
543
|
-
};
|
|
598
|
+
return mcpText(`Activated workflow ${workflow_id}`);
|
|
544
599
|
}
|
|
545
600
|
);
|
|
546
601
|
server.tool(
|
|
@@ -552,38 +607,25 @@ server.tool(
|
|
|
552
607
|
async ({ workflow_id }) => {
|
|
553
608
|
const client = getApiClient();
|
|
554
609
|
await client.deactivateWorkflow(workflow_id);
|
|
555
|
-
return {
|
|
556
|
-
content: [{
|
|
557
|
-
type: "text",
|
|
558
|
-
text: `Deactivated workflow ${workflow_id}`
|
|
559
|
-
}]
|
|
560
|
-
};
|
|
610
|
+
return mcpText(`Deactivated workflow ${workflow_id}`);
|
|
561
611
|
}
|
|
562
612
|
);
|
|
563
613
|
server.tool(
|
|
564
614
|
"kairos_delete",
|
|
565
615
|
"Delete a workflow from n8n. This is irreversible.",
|
|
566
616
|
{
|
|
567
|
-
workflow_id: z.string().describe("The n8n workflow ID to delete")
|
|
617
|
+
workflow_id: z.string().describe("The n8n workflow ID to delete"),
|
|
618
|
+
kairos_secret: z.string().optional().describe("Required when KAIROS_MCP_SECRET env var is set")
|
|
568
619
|
},
|
|
569
|
-
async ({ workflow_id }) => {
|
|
620
|
+
async ({ workflow_id, kairos_secret }) => {
|
|
621
|
+
const authError = checkMcpAuth(kairos_secret);
|
|
622
|
+
if (authError) return authError;
|
|
570
623
|
if (!isAllowed("delete")) {
|
|
571
|
-
return {
|
|
572
|
-
content: [{
|
|
573
|
-
type: "text",
|
|
574
|
-
text: JSON.stringify({ error: "Delete is disabled. Set KAIROS_MCP_ALLOW_DELETE=true to enable." })
|
|
575
|
-
}],
|
|
576
|
-
isError: true
|
|
577
|
-
};
|
|
624
|
+
return mcpError(JSON.stringify({ error: "Delete is disabled. Set KAIROS_MCP_ALLOW_DELETE=true to enable." }));
|
|
578
625
|
}
|
|
579
626
|
const client = getApiClient();
|
|
580
627
|
await client.deleteWorkflow(workflow_id);
|
|
581
|
-
return {
|
|
582
|
-
content: [{
|
|
583
|
-
type: "text",
|
|
584
|
-
text: `Deleted workflow ${workflow_id}`
|
|
585
|
-
}]
|
|
586
|
-
};
|
|
628
|
+
return mcpText(`Deleted workflow ${workflow_id}`);
|
|
587
629
|
}
|
|
588
630
|
);
|
|
589
631
|
server.tool(
|
|
@@ -596,15 +638,15 @@ server.tool(
|
|
|
596
638
|
async ({ workflow_id, limit }) => {
|
|
597
639
|
const client = getApiClient();
|
|
598
640
|
const executions = await client.getExecutions(workflow_id, { limit });
|
|
599
|
-
return
|
|
600
|
-
content: [{
|
|
601
|
-
type: "text",
|
|
602
|
-
text: JSON.stringify(executions, null, 2)
|
|
603
|
-
}]
|
|
604
|
-
};
|
|
641
|
+
return mcpText(JSON.stringify(executions, null, 2));
|
|
605
642
|
}
|
|
606
643
|
);
|
|
607
644
|
async function main() {
|
|
645
|
+
if (!process.env["ANTHROPIC_API_KEY"]) {
|
|
646
|
+
process.stderr.write(
|
|
647
|
+
"[kairos-mcp] WARNING: ANTHROPIC_API_KEY is not set \u2014 kairos_prompt will fail. Set it before using workflow generation tools.\n"
|
|
648
|
+
);
|
|
649
|
+
}
|
|
608
650
|
const transport = new StdioServerTransport();
|
|
609
651
|
await server.connect(transport);
|
|
610
652
|
}
|