@bpmnkit/proxy 0.0.17 → 0.0.22

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.
@@ -0,0 +1,846 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AIKit MCP server — exposes BPMNKit capabilities as MCP tools for Claude Code skills.
4
+ *
5
+ * Tools:
6
+ * bpmn_create, bpmn_read, bpmn_update, bpmn_validate, bpmn_deploy,
7
+ * bpmn_simulate, bpmn_run_history,
8
+ * worker_list, worker_scaffold,
9
+ * pattern_list, pattern_get
10
+ *
11
+ * Usage:
12
+ * node dist/aikit-mcp.js
13
+ *
14
+ * Environment:
15
+ * BPMNKIT_PROXY_URL — proxy base URL (default: http://localhost:3033)
16
+ * ZEEBE_ADDRESS — Zeebe/reebe REST URL (default: http://localhost:26500)
17
+ * ZEEBE_CLIENT_ID — OAuth client ID (Camunda SaaS)
18
+ * ZEEBE_CLIENT_SECRET — OAuth client secret (Camunda SaaS)
19
+ */
20
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
21
+ import { homedir } from "node:os";
22
+ import { basename, join } from "node:path";
23
+ import { createInterface } from "node:readline";
24
+ import { Bpmn, compactify, optimize } from "@bpmnkit/core";
25
+ import { ALL_PATTERNS, findPattern } from "@bpmnkit/patterns";
26
+ import { getActiveProfile, getAuthHeader } from "@bpmnkit/profiles";
27
+ // ── Config ────────────────────────────────────────────────────────────────────
28
+ const PROXY_URL = (process.env.BPMNKIT_PROXY_URL ?? "http://localhost:3033").replace(/\/$/, "");
29
+ const ZEEBE_ADDRESS = (process.env.ZEEBE_ADDRESS ?? "http://localhost:26500").replace(/\/$/, "");
30
+ // ── Helpers ───────────────────────────────────────────────────────────────────
31
+ function expandHome(p) {
32
+ if (p === "~" || p.startsWith("~/"))
33
+ return homedir() + p.slice(1);
34
+ return p;
35
+ }
36
+ function slugify(text) {
37
+ return text
38
+ .toLowerCase()
39
+ .replace(/[^a-z0-9]+/g, "-")
40
+ .replace(/^-+|-+$/g, "")
41
+ .slice(0, 64);
42
+ }
43
+ /**
44
+ * Call a proxy SSE endpoint and collect the resulting XML and response text.
45
+ * The proxy emits `{ type: "xml", xml: "..." }` events when a diagram is produced.
46
+ */
47
+ async function fetchProxyXml(endpoint, body) {
48
+ let res;
49
+ try {
50
+ res = await fetch(`${PROXY_URL}${endpoint}`, {
51
+ method: "POST",
52
+ headers: { "Content-Type": "application/json" },
53
+ body: JSON.stringify(body),
54
+ });
55
+ }
56
+ catch (err) {
57
+ throw new Error(`Cannot reach proxy at ${PROXY_URL}. Is it running? (${err instanceof Error ? err.message : String(err)})`);
58
+ }
59
+ if (!res.ok)
60
+ throw new Error(`Proxy ${endpoint} returned ${res.status}`);
61
+ if (!res.body)
62
+ throw new Error("No response body from proxy");
63
+ const reader = res.body.getReader();
64
+ const decoder = new TextDecoder();
65
+ let xml;
66
+ let errorMsg;
67
+ const tokens = [];
68
+ let buffer = "";
69
+ while (true) {
70
+ const { done, value } = await reader.read();
71
+ if (done)
72
+ break;
73
+ buffer += decoder.decode(value, { stream: true });
74
+ const lines = buffer.split("\n");
75
+ buffer = lines.pop() ?? "";
76
+ for (const line of lines) {
77
+ const trimmed = line.trim();
78
+ if (!trimmed.startsWith("data: "))
79
+ continue;
80
+ try {
81
+ const event = JSON.parse(trimmed.slice(6));
82
+ if (event.type === "token" && event.text)
83
+ tokens.push(event.text);
84
+ if (event.type === "xml" && event.xml)
85
+ xml = event.xml;
86
+ if (event.type === "error")
87
+ errorMsg = event.message;
88
+ }
89
+ catch {
90
+ /* skip malformed events */
91
+ }
92
+ }
93
+ }
94
+ if (errorMsg)
95
+ throw new Error(errorMsg);
96
+ return { xml, text: tokens.join("") };
97
+ }
98
+ /** Write BPMN XML to disk and return the absolute path. */
99
+ function writeBpmn(dir, name, xml) {
100
+ const safeDir = expandHome(dir);
101
+ if (!existsSync(safeDir))
102
+ mkdirSync(safeDir, { recursive: true });
103
+ const filePath = join(safeDir, name.endsWith(".bpmn") ? name : `${name}.bpmn`);
104
+ writeFileSync(filePath, xml, "utf8");
105
+ return filePath;
106
+ }
107
+ const BUILTIN_WORKERS = [
108
+ {
109
+ jobType: "io.bpmnkit:cli:1",
110
+ name: "CLI",
111
+ description: "Run a shell command",
112
+ source: "built-in",
113
+ },
114
+ {
115
+ jobType: "io.bpmnkit:llm:1",
116
+ name: "LLM",
117
+ description: "Call Claude, Copilot, or Gemini with a prompt",
118
+ source: "built-in",
119
+ },
120
+ {
121
+ jobType: "io.bpmnkit:fs:read:1",
122
+ name: "FS Read",
123
+ description: "Read a file from disk",
124
+ source: "built-in",
125
+ },
126
+ {
127
+ jobType: "io.bpmnkit:fs:write:1",
128
+ name: "FS Write",
129
+ description: "Write a file to disk",
130
+ source: "built-in",
131
+ },
132
+ {
133
+ jobType: "io.bpmnkit:fs:append:1",
134
+ name: "FS Append",
135
+ description: "Append to a file",
136
+ source: "built-in",
137
+ },
138
+ {
139
+ jobType: "io.bpmnkit:fs:list:1",
140
+ name: "FS List",
141
+ description: "List files in a directory",
142
+ source: "built-in",
143
+ },
144
+ {
145
+ jobType: "io.bpmnkit:js:1",
146
+ name: "JavaScript",
147
+ description: "Evaluate a JavaScript expression",
148
+ source: "built-in",
149
+ },
150
+ {
151
+ jobType: "io.bpmnkit:http:scrape:1",
152
+ name: "HTTP Scrape",
153
+ description: "Fetch a URL and extract text/HTML",
154
+ source: "built-in",
155
+ },
156
+ {
157
+ jobType: "io.bpmnkit:email:fetch:1",
158
+ name: "Email Fetch",
159
+ description: "Fetch emails from IMAP",
160
+ source: "built-in",
161
+ },
162
+ {
163
+ jobType: "io.bpmnkit:email:send:1",
164
+ name: "Email Send",
165
+ description: "Send an email via SMTP",
166
+ source: "built-in",
167
+ },
168
+ ];
169
+ /** Scan the local ./workers/ directory for scaffolded workers. */
170
+ function scanScaffoldedWorkers(cwd) {
171
+ const workersDir = join(cwd, "workers");
172
+ if (!existsSync(workersDir))
173
+ return [];
174
+ const entries = [];
175
+ try {
176
+ for (const entry of readdirSync(workersDir, { withFileTypes: true })) {
177
+ if (!entry.isDirectory())
178
+ continue;
179
+ const workerDir = join(workersDir, entry.name);
180
+ const pkgPath = join(workerDir, "package.json");
181
+ if (!existsSync(pkgPath))
182
+ continue;
183
+ try {
184
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
185
+ const jobType = pkg.bpmnkit?.jobType;
186
+ if (!jobType)
187
+ continue;
188
+ entries.push({
189
+ jobType,
190
+ name: entry.name,
191
+ description: pkg.bpmnkit?.description ?? "",
192
+ source: "scaffolded",
193
+ path: workerDir,
194
+ });
195
+ }
196
+ catch {
197
+ /* skip unreadable package.json */
198
+ }
199
+ }
200
+ }
201
+ catch {
202
+ /* workers dir not accessible */
203
+ }
204
+ return entries;
205
+ }
206
+ function generateWorkerCode(slug, spec) {
207
+ const inputFields = Object.entries(spec.inputs ?? {})
208
+ .map(([k, v]) => `\t${k}: unknown // ${v}`)
209
+ .join("\n");
210
+ const outputFields = Object.entries(spec.outputs ?? {})
211
+ .map(([k, v]) => `\t${k}: unknown // ${v}`)
212
+ .join("\n");
213
+ return `/**
214
+ * Generated by BPMNKit AIKit
215
+ * Worker: ${slug}
216
+ * Job type: ${spec.jobType}
217
+ *
218
+ * Setup:
219
+ * npm install
220
+ * npm start # development (tsx, no build needed)
221
+ * npm run build && npm run start:prod # production (compiled JS)
222
+ *
223
+ * Required env: ZEEBE_ADDRESS (default: http://localhost:26500)
224
+ */
225
+
226
+ import { createWorkerClient } from "@bpmnkit/worker-client"
227
+
228
+ const JOB_TYPE = ${JSON.stringify(spec.jobType)}
229
+ const WORKER_NAME = ${JSON.stringify(slug)}
230
+
231
+ const client = createWorkerClient({ workerName: WORKER_NAME })
232
+
233
+ // ── Types ─────────────────────────────────────────────────────────────────────
234
+
235
+ interface Inputs {
236
+ ${inputFields || "\t// (no inputs defined)"}
237
+ }
238
+
239
+ interface Outputs {
240
+ ${outputFields || "\t// (no outputs defined)"}
241
+ }
242
+
243
+ // ── Business logic ────────────────────────────────────────────────────────────
244
+
245
+ async function handle(variables: Inputs): Promise<Outputs> {
246
+ \t// TODO: implement ${spec.description ?? "business logic"}
247
+ \tthrow new Error("Not implemented")
248
+ }
249
+
250
+ // ── Poll loop ─────────────────────────────────────────────────────────────────
251
+
252
+ console.log(\`[\${WORKER_NAME}] polling \${JOB_TYPE} on \${process.env.ZEEBE_ADDRESS ?? "http://localhost:26500"}\`)
253
+
254
+ for await (const job of client.poll(JOB_TYPE)) {
255
+ \ttry {
256
+ \t\tconst outputs = await handle(job.variables as Inputs)
257
+ \t\tawait job.complete(outputs)
258
+ \t\tconsole.log(\`[\${WORKER_NAME}] completed \${job.key}\`)
259
+ \t} catch (err) {
260
+ \t\tconst msg = err instanceof Error ? err.message : String(err)
261
+ \t\tawait job.fail(msg, job.retries - 1)
262
+ \t\tconsole.error(\`[\${WORKER_NAME}] failed \${job.key}: \${msg}\`)
263
+ \t}
264
+ }
265
+ `;
266
+ }
267
+ function generateWorkerPackageJson(slug, spec) {
268
+ return JSON.stringify({
269
+ name: `${slug}-worker`,
270
+ version: "1.0.0",
271
+ type: "module",
272
+ scripts: {
273
+ start: "tsx index.ts",
274
+ build: "tsc",
275
+ "start:prod": "node dist/index.js",
276
+ },
277
+ bpmnkit: {
278
+ jobType: spec.jobType,
279
+ description: spec.description ?? "",
280
+ },
281
+ dependencies: {
282
+ "@bpmnkit/worker-client": "latest",
283
+ },
284
+ devDependencies: {
285
+ tsx: "latest",
286
+ typescript: "latest",
287
+ },
288
+ }, null, 2);
289
+ }
290
+ function generateWorkerTsConfig() {
291
+ return JSON.stringify({
292
+ compilerOptions: {
293
+ target: "ES2022",
294
+ module: "NodeNext",
295
+ moduleResolution: "NodeNext",
296
+ strict: true,
297
+ outDir: "dist",
298
+ rootDir: ".",
299
+ skipLibCheck: true,
300
+ },
301
+ include: ["index.ts"],
302
+ }, null, 2);
303
+ }
304
+ function generateWorkerReadme(slug, spec) {
305
+ const envVars = [
306
+ "ZEEBE_ADDRESS — Zeebe/reebe REST URL (default: http://localhost:26500)",
307
+ "ZEEBE_CLIENT_ID — OAuth2 client ID (Camunda SaaS only)",
308
+ "ZEEBE_CLIENT_SECRET — OAuth2 client secret (Camunda SaaS only)",
309
+ ];
310
+ const inputList = Object.entries(spec.inputs ?? {})
311
+ .map(([k, v]) => `- \`${k}\`: ${v}`)
312
+ .join("\n");
313
+ const outputList = Object.entries(spec.outputs ?? {})
314
+ .map(([k, v]) => `- \`${k}\`: ${v}`)
315
+ .join("\n");
316
+ return `# ${slug} worker
317
+
318
+ ${spec.description ?? "BPMNKit worker"}
319
+
320
+ **Job type**: \`${spec.jobType}\`
321
+
322
+ ## Setup
323
+
324
+ \`\`\`bash
325
+ npm install
326
+
327
+ # Development (runs TypeScript directly, no build needed)
328
+ npm start
329
+
330
+ # Production (compiled JS)
331
+ npm run build && npm run start:prod
332
+ \`\`\`
333
+
334
+ ## Environment variables
335
+
336
+ ${envVars.map((e) => `- \`${e}\``).join("\n")}
337
+
338
+ ## Inputs
339
+
340
+ ${inputList || "_(none defined)_"}
341
+
342
+ ## Outputs
343
+
344
+ ${outputList || "_(none defined)_"}
345
+
346
+ ## Docker (production)
347
+
348
+ \`\`\`dockerfile
349
+ FROM node:22-alpine AS build
350
+ WORKDIR /app
351
+ COPY package*.json ./
352
+ RUN npm install
353
+ COPY index.ts tsconfig.json ./
354
+ RUN npm run build
355
+
356
+ FROM node:22-alpine
357
+ WORKDIR /app
358
+ COPY package*.json ./
359
+ RUN npm install --omit=dev
360
+ COPY --from=build /app/dist ./dist
361
+ CMD ["node", "dist/index.js"]
362
+ \`\`\`
363
+ `;
364
+ }
365
+ // ── Tool implementations ──────────────────────────────────────────────────────
366
+ async function toolBpmnCreate(description, outputDir) {
367
+ const dir = outputDir ? expandHome(outputDir) : process.cwd();
368
+ const slug = slugify(description);
369
+ // Load a matching pattern for context
370
+ const pattern = findPattern(description);
371
+ const context = pattern
372
+ ? `\nDomain context:\n${pattern.readme}\n\nTypical service tasks:\n${pattern.workers.map((w) => `- ${w.name} (${w.jobType}): ${w.description}`).join("\n")}`
373
+ : "";
374
+ const { xml } = await fetchProxyXml("/chat", {
375
+ messages: [
376
+ {
377
+ role: "user",
378
+ content: `Create a BPMN process: ${description}${context}`,
379
+ },
380
+ ],
381
+ });
382
+ if (!xml)
383
+ throw new Error("AI did not produce a BPMN diagram. Try a more specific description.");
384
+ const filePath = writeBpmn(dir, slug, xml);
385
+ return JSON.stringify({ path: filePath, patternMatched: pattern?.id ?? null });
386
+ }
387
+ function toolBpmnRead(path) {
388
+ const absPath = expandHome(path);
389
+ if (!existsSync(absPath))
390
+ throw new Error(`File not found: ${path}`);
391
+ const xml = readFileSync(absPath, "utf8");
392
+ const defs = Bpmn.parse(xml);
393
+ const compact = compactify(defs);
394
+ return JSON.stringify(compact, null, 2);
395
+ }
396
+ async function toolBpmnUpdate(path, instruction) {
397
+ const absPath = expandHome(path);
398
+ if (!existsSync(absPath))
399
+ throw new Error(`File not found: ${path}`);
400
+ const xml = readFileSync(absPath, "utf8");
401
+ const defs = Bpmn.parse(xml);
402
+ const compact = compactify(defs);
403
+ const { xml: updatedXml } = await fetchProxyXml("/chat", {
404
+ messages: [{ role: "user", content: instruction }],
405
+ context: compact,
406
+ action: "improve",
407
+ });
408
+ if (!updatedXml)
409
+ throw new Error("AI did not produce an updated diagram.");
410
+ writeFileSync(absPath, updatedXml, "utf8");
411
+ return JSON.stringify({ path: absPath, updated: true });
412
+ }
413
+ function toolBpmnValidate(path) {
414
+ const absPath = expandHome(path);
415
+ if (!existsSync(absPath))
416
+ throw new Error(`File not found: ${path}`);
417
+ const xml = readFileSync(absPath, "utf8");
418
+ const defs = Bpmn.parse(xml);
419
+ const report = optimize(defs);
420
+ const findings = report.findings.map((f) => ({
421
+ severity: f.severity,
422
+ category: f.category,
423
+ message: f.message,
424
+ suggestion: f.suggestion,
425
+ elementIds: f.elementIds,
426
+ autoFixable: Boolean(f.applyFix),
427
+ }));
428
+ const summary = {
429
+ total: findings.length,
430
+ errors: findings.filter((f) => f.severity === "error").length,
431
+ warnings: findings.filter((f) => f.severity === "warning").length,
432
+ info: findings.filter((f) => f.severity === "info").length,
433
+ autoFixable: findings.filter((f) => f.autoFixable).length,
434
+ };
435
+ return JSON.stringify({ summary, findings }, null, 2);
436
+ }
437
+ async function toolBpmnDeploy(path, target) {
438
+ const absPath = expandHome(path);
439
+ if (!existsSync(absPath))
440
+ throw new Error(`File not found: ${path}`);
441
+ const xml = readFileSync(absPath, "utf8");
442
+ const fileName = basename(absPath);
443
+ const blob = new Blob([xml], { type: "application/octet-stream" });
444
+ const formData = new FormData();
445
+ formData.append("resources[]", blob, fileName);
446
+ if (target === "local") {
447
+ const res = await fetch(`${ZEEBE_ADDRESS}/v2/deployments`, {
448
+ method: "POST",
449
+ body: formData,
450
+ });
451
+ if (!res.ok)
452
+ throw new Error(`Local deploy failed: ${res.status} ${await res.text()}`);
453
+ const result = (await res.json());
454
+ return JSON.stringify({ success: true, target: "local", result });
455
+ }
456
+ // camunda8 — use active profile
457
+ const profile = getActiveProfile();
458
+ if (!profile?.config.baseUrl) {
459
+ throw new Error("No active Camunda 8 profile. Run: casen profile create");
460
+ }
461
+ const authHeader = await getAuthHeader(profile.config);
462
+ const baseUrl = profile.config.baseUrl.replace(/\/$/, "");
463
+ const res = await fetch(`${baseUrl}/v2/deployments`, {
464
+ method: "POST",
465
+ headers: { authorization: authHeader },
466
+ body: formData,
467
+ });
468
+ if (!res.ok)
469
+ throw new Error(`Camunda 8 deploy failed: ${res.status} ${await res.text()}`);
470
+ const result = (await res.json());
471
+ return JSON.stringify({ success: true, target: "camunda8", result });
472
+ }
473
+ async function toolBpmnSimulate(path, scenarios) {
474
+ const absPath = expandHome(path);
475
+ if (!existsSync(absPath))
476
+ throw new Error(`File not found: ${path}`);
477
+ const xml = readFileSync(absPath, "utf8");
478
+ const defs = Bpmn.parse(xml);
479
+ // Collect all service task job types referenced in the diagram
480
+ const referencedJobTypes = new Set();
481
+ for (const proc of defs.processes) {
482
+ for (const el of proc.flowElements) {
483
+ if (el.type !== "serviceTask")
484
+ continue;
485
+ const ext = el.extensionElements ?? [];
486
+ for (const e of ext) {
487
+ if (typeof e === "object" &&
488
+ e !== null &&
489
+ "$type" in e &&
490
+ e.$type === "zeebe:TaskDefinition") {
491
+ const typeProp = e.type;
492
+ if (typeof typeProp === "string")
493
+ referencedJobTypes.add(typeProp);
494
+ }
495
+ }
496
+ }
497
+ }
498
+ const allWorkers = [...BUILTIN_WORKERS, ...scanScaffoldedWorkers(process.cwd())];
499
+ const knownJobTypes = new Set(allWorkers.map((w) => w.jobType));
500
+ const missingWorkers = [...referencedJobTypes].filter((jt) => !knownJobTypes.has(jt));
501
+ const coveredWorkers = [...referencedJobTypes].filter((jt) => knownJobTypes.has(jt));
502
+ // Validation findings
503
+ const validationReport = optimize(defs);
504
+ const errors = validationReport.findings.filter((f) => f.severity === "error");
505
+ return JSON.stringify({
506
+ note: "Phase 1: structural analysis. Full simulation (with process execution) coming in a future phase.",
507
+ validation: {
508
+ errors: errors.length,
509
+ findings: errors.map((f) => ({ message: f.message, elementIds: f.elementIds })),
510
+ },
511
+ workerCoverage: {
512
+ total: referencedJobTypes.size,
513
+ covered: coveredWorkers.length,
514
+ missing: missingWorkers,
515
+ },
516
+ scenariosRequested: scenarios.length,
517
+ }, null, 2);
518
+ }
519
+ async function toolBpmnRunHistory(processId) {
520
+ const params = new URLSearchParams({ limit: "20" });
521
+ let res;
522
+ try {
523
+ res = await fetch(`${PROXY_URL}/run-history?${params.toString()}`);
524
+ }
525
+ catch {
526
+ throw new Error(`Cannot reach proxy at ${PROXY_URL}. Is it running?`);
527
+ }
528
+ if (!res.ok)
529
+ throw new Error(`Run history request failed: ${res.status}`);
530
+ const data = (await res.json());
531
+ const runs = processId ? data.runs.filter((r) => r.processId === processId) : data.runs;
532
+ return JSON.stringify({ runs }, null, 2);
533
+ }
534
+ function toolWorkerList() {
535
+ const scaffolded = scanScaffoldedWorkers(process.cwd());
536
+ const all = [...BUILTIN_WORKERS, ...scaffolded];
537
+ return JSON.stringify({ workers: all, total: all.length }, null, 2);
538
+ }
539
+ function toolWorkerScaffold(jobType, spec) {
540
+ const slug = slugify(jobType.split(":").slice(-2).join("-").replace(":", "-"));
541
+ const workerDir = join(process.cwd(), "workers", slug);
542
+ if (!existsSync(workerDir))
543
+ mkdirSync(workerDir, { recursive: true });
544
+ const fullSpec = { ...spec, jobType };
545
+ writeFileSync(join(workerDir, "index.ts"), generateWorkerCode(slug, fullSpec), "utf8");
546
+ writeFileSync(join(workerDir, "package.json"), generateWorkerPackageJson(slug, fullSpec), "utf8");
547
+ writeFileSync(join(workerDir, "tsconfig.json"), generateWorkerTsConfig(), "utf8");
548
+ writeFileSync(join(workerDir, "README.md"), generateWorkerReadme(slug, fullSpec), "utf8");
549
+ return JSON.stringify({
550
+ path: workerDir,
551
+ files: ["index.ts", "package.json", "tsconfig.json", "README.md"],
552
+ jobType,
553
+ note: "Run `npm install` then `npm start` in the worker directory. Edit index.ts to implement handle().",
554
+ });
555
+ }
556
+ function toolPatternList() {
557
+ const patterns = ALL_PATTERNS.map((p) => ({
558
+ id: p.id,
559
+ name: p.name,
560
+ description: p.description,
561
+ keywords: p.keywords,
562
+ }));
563
+ return JSON.stringify({ patterns, total: patterns.length }, null, 2);
564
+ }
565
+ function toolPatternGet(domain) {
566
+ const pattern = findPattern(domain) ?? ALL_PATTERNS.find((p) => p.id === domain);
567
+ if (!pattern)
568
+ throw new Error(`No pattern found for: "${domain}". Call pattern_list to see available patterns.`);
569
+ return JSON.stringify({
570
+ id: pattern.id,
571
+ name: pattern.name,
572
+ description: pattern.description,
573
+ keywords: pattern.keywords,
574
+ readme: pattern.readme,
575
+ workers: pattern.workers,
576
+ variations: pattern.variations,
577
+ template: pattern.template,
578
+ }, null, 2);
579
+ }
580
+ // ── Tool definitions ──────────────────────────────────────────────────────────
581
+ const TOOLS = [
582
+ {
583
+ name: "bpmn_create",
584
+ description: "Generate a new BPMN process from a natural language description. " +
585
+ "Automatically loads a domain pattern if one matches, then calls the AI to generate the diagram. " +
586
+ "Writes the result to disk and returns the file path.",
587
+ inputSchema: {
588
+ type: "object",
589
+ properties: {
590
+ description: {
591
+ type: "string",
592
+ description: "Natural language description of the process to create",
593
+ },
594
+ outputDir: {
595
+ type: "string",
596
+ description: "Directory to write the BPMN file (default: current working directory)",
597
+ },
598
+ },
599
+ required: ["description"],
600
+ },
601
+ },
602
+ {
603
+ name: "bpmn_read",
604
+ description: "Read a BPMN file and return its compact JSON representation.",
605
+ inputSchema: {
606
+ type: "object",
607
+ properties: {
608
+ path: { type: "string", description: "Path to the .bpmn file" },
609
+ },
610
+ required: ["path"],
611
+ },
612
+ },
613
+ {
614
+ name: "bpmn_update",
615
+ description: "Update an existing BPMN file by describing the change in natural language. " +
616
+ "Reads the file, sends it to the AI with the instruction, and writes the result back.",
617
+ inputSchema: {
618
+ type: "object",
619
+ properties: {
620
+ path: { type: "string", description: "Path to the .bpmn file" },
621
+ instruction: {
622
+ type: "string",
623
+ description: "Natural language instruction for the change to make",
624
+ },
625
+ },
626
+ required: ["path", "instruction"],
627
+ },
628
+ },
629
+ {
630
+ name: "bpmn_validate",
631
+ description: "Validate a BPMN file using the BPMNKit pattern advisor. " +
632
+ "Returns a list of findings with severity (error/warning/info), category, message, and whether they can be auto-fixed.",
633
+ inputSchema: {
634
+ type: "object",
635
+ properties: {
636
+ path: { type: "string", description: "Path to the .bpmn file" },
637
+ },
638
+ required: ["path"],
639
+ },
640
+ },
641
+ {
642
+ name: "bpmn_deploy",
643
+ description: "Deploy a BPMN process to a running engine. " +
644
+ 'target "local" deploys to the local reebe instance (ZEEBE_ADDRESS). ' +
645
+ 'target "camunda8" deploys to the active Camunda 8 profile.',
646
+ inputSchema: {
647
+ type: "object",
648
+ properties: {
649
+ path: { type: "string", description: "Path to the .bpmn file" },
650
+ target: {
651
+ type: "string",
652
+ enum: ["local", "camunda8"],
653
+ description: "Deployment target",
654
+ },
655
+ },
656
+ required: ["path", "target"],
657
+ },
658
+ },
659
+ {
660
+ name: "bpmn_simulate",
661
+ description: "Analyse a BPMN process structurally: checks validation findings and worker coverage. " +
662
+ "Full process execution simulation is planned for a future phase.",
663
+ inputSchema: {
664
+ type: "object",
665
+ properties: {
666
+ path: { type: "string", description: "Path to the .bpmn file" },
667
+ scenarios: {
668
+ type: "array",
669
+ description: "Test scenarios (reserved for future use)",
670
+ items: { type: "object" },
671
+ },
672
+ },
673
+ required: ["path"],
674
+ },
675
+ },
676
+ {
677
+ name: "bpmn_run_history",
678
+ description: "Query the run history from the local proxy. Returns recent process executions.",
679
+ inputSchema: {
680
+ type: "object",
681
+ properties: {
682
+ processId: {
683
+ type: "string",
684
+ description: "Filter by process definition ID (optional)",
685
+ },
686
+ },
687
+ },
688
+ },
689
+ {
690
+ name: "worker_list",
691
+ description: "List all available workers: built-in BPMNKit workers and any scaffolded workers " +
692
+ "found in the ./workers/ directory of the current working directory.",
693
+ inputSchema: {
694
+ type: "object",
695
+ properties: {},
696
+ },
697
+ },
698
+ {
699
+ name: "worker_scaffold",
700
+ description: "Scaffold a standalone TypeScript worker for a given Zeebe job type. " +
701
+ "Generates index.ts, package.json, tsconfig.json, and README.md in ./workers/<slug>/. " +
702
+ "The worker uses @bpmnkit/worker-client — no other BPMNKit dependency required at runtime.",
703
+ inputSchema: {
704
+ type: "object",
705
+ properties: {
706
+ jobType: {
707
+ type: "string",
708
+ description: "Zeebe job type string, e.g. com.example:send-invoice:1",
709
+ },
710
+ description: {
711
+ type: "string",
712
+ description: "What this worker does",
713
+ },
714
+ inputs: {
715
+ type: "object",
716
+ description: "Input variable names mapped to type descriptions",
717
+ additionalProperties: { type: "string" },
718
+ },
719
+ outputs: {
720
+ type: "object",
721
+ description: "Output variable names mapped to type descriptions",
722
+ additionalProperties: { type: "string" },
723
+ },
724
+ },
725
+ required: ["jobType"],
726
+ },
727
+ },
728
+ {
729
+ name: "pattern_list",
730
+ description: "List all available domain process patterns with their id, name, description, and keywords. " +
731
+ "Use this at the start of /implement to check whether a relevant pattern exists.",
732
+ inputSchema: {
733
+ type: "object",
734
+ properties: {},
735
+ },
736
+ },
737
+ {
738
+ name: "pattern_get",
739
+ description: "Get the full content of a domain pattern: readme, worker specs, variations, and compact BPMN template. " +
740
+ "Match by pattern id or by a free-text query (keyword matching).",
741
+ inputSchema: {
742
+ type: "object",
743
+ properties: {
744
+ domain: {
745
+ type: "string",
746
+ description: 'Pattern id (e.g. "invoice-approval") or free-text query (e.g. "employee onboarding")',
747
+ },
748
+ },
749
+ required: ["domain"],
750
+ },
751
+ },
752
+ ];
753
+ async function callTool(name, args) {
754
+ process.stderr.write(`[aikit-mcp] tool: ${name} args: ${JSON.stringify(args)}\n`);
755
+ switch (name) {
756
+ case "bpmn_create":
757
+ return toolBpmnCreate(args.description, args.outputDir);
758
+ case "bpmn_read":
759
+ return toolBpmnRead(args.path);
760
+ case "bpmn_update":
761
+ return toolBpmnUpdate(args.path, args.instruction);
762
+ case "bpmn_validate":
763
+ return toolBpmnValidate(args.path);
764
+ case "bpmn_deploy":
765
+ return toolBpmnDeploy(args.path, args.target);
766
+ case "bpmn_simulate":
767
+ return toolBpmnSimulate(args.path, args.scenarios ?? []);
768
+ case "bpmn_run_history":
769
+ return toolBpmnRunHistory(args.processId);
770
+ case "worker_list":
771
+ return toolWorkerList();
772
+ case "worker_scaffold":
773
+ return toolWorkerScaffold(args.jobType, {
774
+ jobType: args.jobType,
775
+ description: args.description,
776
+ inputs: args.inputs,
777
+ outputs: args.outputs,
778
+ });
779
+ case "pattern_list":
780
+ return toolPatternList();
781
+ case "pattern_get":
782
+ return toolPatternGet(args.domain);
783
+ default:
784
+ throw new Error(`Unknown tool: ${name}`);
785
+ }
786
+ }
787
+ const rl = createInterface({ input: process.stdin, crlfDelay: Number.POSITIVE_INFINITY });
788
+ rl.on("line", (line) => {
789
+ const trimmed = line.trim();
790
+ if (!trimmed)
791
+ return;
792
+ let req;
793
+ try {
794
+ req = JSON.parse(trimmed);
795
+ }
796
+ catch {
797
+ return;
798
+ }
799
+ if (!("id" in req))
800
+ return;
801
+ void (async () => {
802
+ let result;
803
+ let error;
804
+ try {
805
+ switch (req.method) {
806
+ case "initialize":
807
+ result = {
808
+ protocolVersion: "2024-11-05",
809
+ capabilities: { tools: {} },
810
+ serverInfo: { name: "bpmnkit-aikit", version: "1.0.0" },
811
+ };
812
+ break;
813
+ case "tools/list":
814
+ result = { tools: TOOLS };
815
+ break;
816
+ case "tools/call": {
817
+ const params = req.params;
818
+ const text = await callTool(params.name, params.arguments ?? {});
819
+ result = { content: [{ type: "text", text }], isError: false };
820
+ break;
821
+ }
822
+ case "ping":
823
+ result = {};
824
+ break;
825
+ default:
826
+ error = { code: -32601, message: "Method not found" };
827
+ }
828
+ }
829
+ catch (err) {
830
+ if (req.method === "tools/call") {
831
+ result = {
832
+ content: [{ type: "text", text: String(err) }],
833
+ isError: true,
834
+ };
835
+ }
836
+ else {
837
+ error = { code: -32603, message: String(err) };
838
+ }
839
+ }
840
+ const response = error
841
+ ? { jsonrpc: "2.0", id: req.id, error }
842
+ : { jsonrpc: "2.0", id: req.id, result };
843
+ process.stdout.write(`${JSON.stringify(response)}\n`);
844
+ })();
845
+ });
846
+ //# sourceMappingURL=aikit-mcp.js.map