@bpmnkit/proxy 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/claude.js +108 -0
- package/dist/adapters/copilot.js +46 -0
- package/dist/adapters/gemini.js +43 -0
- package/dist/apply-ops.js +79 -0
- package/dist/bridge.bundle.js +5298 -0
- package/dist/bridge.js +226 -0
- package/dist/index.js +592 -0
- package/dist/mcp-server.js +615 -0
- package/dist/prompt.js +189 -0
- package/package.json +35 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Minimal stdio MCP server for BPMN/DMN/Form editing.
|
|
4
|
+
* Zero external dependencies — pure Node.js built-ins + @bpmnkit/core (workspace package).
|
|
5
|
+
*
|
|
6
|
+
* State is stored as the native model (BpmnDefinitions, DmnDefinitions, or FormDefinition)
|
|
7
|
+
* so core builder APIs produce the correct structure.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node dist/mcp-server.js [--input <file>] [--output <file>]
|
|
11
|
+
*
|
|
12
|
+
* File type is detected from input file content:
|
|
13
|
+
* - DMN XML → DmnDefinitions
|
|
14
|
+
* - Form JSON → FormDefinition
|
|
15
|
+
* - BPMN XML → BpmnDefinitions (default)
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { createInterface } from "node:readline";
|
|
19
|
+
import vm from "node:vm";
|
|
20
|
+
import { Bpmn, Dmn, Form, compactify, compactifyDmn, compactifyForm, expand, expandDmn, expandForm, layoutDmn, layoutProcess, } from "@bpmnkit/core";
|
|
21
|
+
// ── CLI args ──────────────────────────────────────────────────────────────────
|
|
22
|
+
function getArg(flag) {
|
|
23
|
+
const idx = process.argv.indexOf(flag);
|
|
24
|
+
return idx !== -1 ? process.argv[idx + 1] : undefined;
|
|
25
|
+
}
|
|
26
|
+
const inputFile = getArg("--input");
|
|
27
|
+
const outputFile = getArg("--output");
|
|
28
|
+
function detectStateKind(content) {
|
|
29
|
+
const trimmed = content.trimStart();
|
|
30
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("["))
|
|
31
|
+
return "form";
|
|
32
|
+
if (trimmed.includes("https://www.omg.org/spec/DMN") ||
|
|
33
|
+
(trimmed.includes("<definitions") && trimmed.includes("decision")))
|
|
34
|
+
return "dmn";
|
|
35
|
+
return "bpmn";
|
|
36
|
+
}
|
|
37
|
+
let state = {
|
|
38
|
+
kind: "bpmn",
|
|
39
|
+
data: Bpmn.parse(Bpmn.makeEmpty("Process_1", "New Process")),
|
|
40
|
+
};
|
|
41
|
+
if (inputFile) {
|
|
42
|
+
try {
|
|
43
|
+
const content = readFileSync(inputFile, "utf8");
|
|
44
|
+
const kind = detectStateKind(content);
|
|
45
|
+
if (kind === "dmn") {
|
|
46
|
+
state = { kind: "dmn", data: Dmn.parse(content) };
|
|
47
|
+
}
|
|
48
|
+
else if (kind === "form") {
|
|
49
|
+
state = { kind: "form", data: Form.parse(content) };
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
state = { kind: "bpmn", data: Bpmn.parse(content) };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
/* start with empty BPMN diagram if file is unreadable */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// ── BPMN helpers ──────────────────────────────────────────────────────────────
|
|
60
|
+
function buildBpmnDiagram(proc) {
|
|
61
|
+
const layout = layoutProcess(proc);
|
|
62
|
+
const shapes = layout.nodes.map((n) => ({
|
|
63
|
+
id: `${n.id}_di`,
|
|
64
|
+
bpmnElement: n.id,
|
|
65
|
+
isExpanded: n.isExpanded,
|
|
66
|
+
bounds: n.bounds,
|
|
67
|
+
label: n.labelBounds ? { bounds: n.labelBounds } : undefined,
|
|
68
|
+
unknownAttributes: {},
|
|
69
|
+
}));
|
|
70
|
+
const edges = layout.edges.map((e) => ({
|
|
71
|
+
id: `${e.id}_di`,
|
|
72
|
+
bpmnElement: e.id,
|
|
73
|
+
waypoints: e.waypoints,
|
|
74
|
+
unknownAttributes: {},
|
|
75
|
+
}));
|
|
76
|
+
return {
|
|
77
|
+
id: `BPMNDiagram_${proc.id}`,
|
|
78
|
+
plane: {
|
|
79
|
+
id: `BPMNPlane_${proc.id}`,
|
|
80
|
+
bpmnElement: proc.id,
|
|
81
|
+
shapes,
|
|
82
|
+
edges,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function saveState() {
|
|
87
|
+
if (!outputFile)
|
|
88
|
+
return;
|
|
89
|
+
if (state.kind === "bpmn") {
|
|
90
|
+
state.data.diagrams = state.data.processes.map((proc) => buildBpmnDiagram(proc));
|
|
91
|
+
writeFileSync(outputFile, Bpmn.export(state.data));
|
|
92
|
+
}
|
|
93
|
+
else if (state.kind === "dmn") {
|
|
94
|
+
const laid = layoutDmn(state.data);
|
|
95
|
+
writeFileSync(outputFile, Dmn.export(laid));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
writeFileSync(outputFile, Form.export(state.data));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function recomputeIncomingOutgoing(proc) {
|
|
102
|
+
const elementMap = new Map();
|
|
103
|
+
for (const el of proc.flowElements) {
|
|
104
|
+
el.incoming = [];
|
|
105
|
+
el.outgoing = [];
|
|
106
|
+
elementMap.set(el.id, el);
|
|
107
|
+
}
|
|
108
|
+
for (const sf of proc.sequenceFlows) {
|
|
109
|
+
const src = elementMap.get(sf.sourceRef);
|
|
110
|
+
const tgt = elementMap.get(sf.targetRef);
|
|
111
|
+
if (src)
|
|
112
|
+
src.outgoing.push(sf.id);
|
|
113
|
+
if (tgt)
|
|
114
|
+
tgt.incoming.push(sf.id);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function findProcess(processId) {
|
|
118
|
+
if (state.kind !== "bpmn")
|
|
119
|
+
return undefined;
|
|
120
|
+
return state.data.processes.find((p) => p.id === processId);
|
|
121
|
+
}
|
|
122
|
+
function ensureProcess(processId) {
|
|
123
|
+
if (state.kind !== "bpmn")
|
|
124
|
+
throw new Error("Current file is not a BPMN diagram");
|
|
125
|
+
let proc = state.data.processes.find((p) => p.id === processId);
|
|
126
|
+
if (!proc) {
|
|
127
|
+
proc = {
|
|
128
|
+
id: processId,
|
|
129
|
+
isExecutable: true,
|
|
130
|
+
extensionElements: [],
|
|
131
|
+
flowElements: [],
|
|
132
|
+
sequenceFlows: [],
|
|
133
|
+
textAnnotations: [],
|
|
134
|
+
associations: [],
|
|
135
|
+
unknownAttributes: {},
|
|
136
|
+
};
|
|
137
|
+
state.data.processes.push(proc);
|
|
138
|
+
}
|
|
139
|
+
return proc;
|
|
140
|
+
}
|
|
141
|
+
// ── Tool definitions ──────────────────────────────────────────────────────────
|
|
142
|
+
const ELEMENT_SCHEMA = {
|
|
143
|
+
type: "object",
|
|
144
|
+
properties: {
|
|
145
|
+
id: { type: "string", description: "Unique element ID" },
|
|
146
|
+
type: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "BPMN element type. Events: startEvent | endEvent | intermediateThrowEvent | intermediateCatchEvent | boundaryEvent. " +
|
|
149
|
+
"Tasks: serviceTask | userTask | businessRuleTask | callActivity | scriptTask | sendTask | manualTask. " +
|
|
150
|
+
"Gateways: exclusiveGateway | parallelGateway | inclusiveGateway | eventBasedGateway. " +
|
|
151
|
+
"Containers: subProcess | adHocSubProcess.",
|
|
152
|
+
},
|
|
153
|
+
name: { type: "string", description: "Display name shown on the diagram" },
|
|
154
|
+
eventType: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "For events: timer | message | signal | error | escalation | cancel | terminate | conditional | link | compensate",
|
|
157
|
+
},
|
|
158
|
+
attachedTo: { type: "string", description: "boundaryEvent only: ID of the host activity" },
|
|
159
|
+
interrupting: {
|
|
160
|
+
type: "boolean",
|
|
161
|
+
description: "boundaryEvent only: false = non-interrupting (default true)",
|
|
162
|
+
},
|
|
163
|
+
jobType: {
|
|
164
|
+
type: "string",
|
|
165
|
+
description: "serviceTask only: Zeebe worker job type. " +
|
|
166
|
+
"⚠️ For HTTP/REST API calls do NOT set this here — use the add_http_call tool instead.",
|
|
167
|
+
},
|
|
168
|
+
formId: { type: "string", description: "userTask only: linked Camunda form ID" },
|
|
169
|
+
calledProcess: { type: "string", description: "callActivity only: ID of the called process" },
|
|
170
|
+
decisionId: { type: "string", description: "businessRuleTask only: DMN decision ID" },
|
|
171
|
+
resultVariable: {
|
|
172
|
+
type: "string",
|
|
173
|
+
description: "businessRuleTask / serviceTask: process variable to store the task output",
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
required: ["id", "type"],
|
|
177
|
+
};
|
|
178
|
+
const FLOW_SCHEMA = {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
id: { type: "string" },
|
|
182
|
+
from: { type: "string", description: "Source element ID" },
|
|
183
|
+
to: { type: "string", description: "Target element ID" },
|
|
184
|
+
name: { type: "string" },
|
|
185
|
+
condition: { type: "string", description: "FEEL condition expression" },
|
|
186
|
+
},
|
|
187
|
+
required: ["id", "from", "to"],
|
|
188
|
+
};
|
|
189
|
+
const COMPOSE_TOOL = {
|
|
190
|
+
name: "compose_diagram",
|
|
191
|
+
description: "Run a JavaScript snippet to make complex multi-step changes in a single call.\n" +
|
|
192
|
+
"Prefer this over multiple separate tool calls when building from scratch or doing batch edits.\n" +
|
|
193
|
+
"\n" +
|
|
194
|
+
"BPMN Bridge API:\n" +
|
|
195
|
+
" Bridge.mcpGetDiagram() → CompactDiagram JSON\n" +
|
|
196
|
+
" Bridge.mcpAddElements(processId, elementsJson, flowsJson) → result\n" +
|
|
197
|
+
" Bridge.mcpRemoveElements(processId, elementIdsJson, flowIdsJson) → result\n" +
|
|
198
|
+
" Bridge.mcpUpdateElement(processId, elementId, changesJson) → result\n" +
|
|
199
|
+
" Bridge.mcpSetCondition(processId, flowId, conditionJson) → result\n" +
|
|
200
|
+
" Bridge.mcpReplaceDiagram(compactJson) → result\n" +
|
|
201
|
+
" Bridge.mcpAddHttpCall(processId, configJson) → result\n" +
|
|
202
|
+
" Bridge.mcpExportXml() → XML string\n" +
|
|
203
|
+
"\n" +
|
|
204
|
+
"DMN Bridge API:\n" +
|
|
205
|
+
" Bridge.mcpGetDiagram() → CompactDmn JSON\n" +
|
|
206
|
+
" Bridge.mcpReplaceDiagram(compactDmnJson) → result\n" +
|
|
207
|
+
" Bridge.mcpExportXml() → DMN XML string\n" +
|
|
208
|
+
"\n" +
|
|
209
|
+
"Form Bridge API:\n" +
|
|
210
|
+
" Bridge.mcpGetDiagram() → CompactForm JSON\n" +
|
|
211
|
+
" Bridge.mcpReplaceDiagram(compactFormJson) → result\n" +
|
|
212
|
+
" Bridge.mcpExportXml() → Form JSON string\n" +
|
|
213
|
+
"\n" +
|
|
214
|
+
"Use `return` to return the final result string.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
code: { type: "string", description: "JavaScript snippet to run against the Bridge API" },
|
|
219
|
+
},
|
|
220
|
+
required: ["code"],
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
const BPMN_TOOLS = [
|
|
224
|
+
{
|
|
225
|
+
name: "get_diagram",
|
|
226
|
+
description: "Return the current BPMN diagram as a compact JSON. Call this first before making any changes.",
|
|
227
|
+
inputSchema: { type: "object", properties: {} },
|
|
228
|
+
},
|
|
229
|
+
COMPOSE_TOOL,
|
|
230
|
+
{
|
|
231
|
+
name: "add_http_call",
|
|
232
|
+
description: "⚠️ ALWAYS use this tool — not add_elements — for any HTTP/REST API call, webhook, or external service integration.\n" +
|
|
233
|
+
"Adds a Camunda HTTP connector service task (jobType: io.camunda:http-json:1) with the correct zeebe:ioMapping inputs.",
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: "object",
|
|
236
|
+
properties: {
|
|
237
|
+
processId: { type: "string" },
|
|
238
|
+
id: { type: "string" },
|
|
239
|
+
name: { type: "string" },
|
|
240
|
+
url: { type: "string" },
|
|
241
|
+
method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
|
|
242
|
+
headers: { type: "string" },
|
|
243
|
+
body: { type: "string" },
|
|
244
|
+
resultVariable: { type: "string" },
|
|
245
|
+
},
|
|
246
|
+
required: ["processId", "id", "name", "url", "method"],
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: "add_elements",
|
|
251
|
+
description: "Add BPMN elements (tasks, events, gateways) and/or sequence flows to a process.\n" +
|
|
252
|
+
"⚠️ NOT for HTTP/REST API calls — use add_http_call for those.",
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: "object",
|
|
255
|
+
properties: {
|
|
256
|
+
processId: { type: "string" },
|
|
257
|
+
elements: { type: "array", items: ELEMENT_SCHEMA },
|
|
258
|
+
flows: { type: "array", items: FLOW_SCHEMA },
|
|
259
|
+
},
|
|
260
|
+
required: ["processId"],
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "remove_elements",
|
|
265
|
+
description: "Remove BPMN elements and/or sequence flows.",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
processId: { type: "string" },
|
|
270
|
+
elementIds: { type: "array", items: { type: "string" } },
|
|
271
|
+
flowIds: { type: "array", items: { type: "string" } },
|
|
272
|
+
},
|
|
273
|
+
required: ["processId"],
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "update_element",
|
|
278
|
+
description: "Rename an existing BPMN element or change its display name.",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
processId: { type: "string" },
|
|
283
|
+
elementId: { type: "string" },
|
|
284
|
+
changes: {
|
|
285
|
+
type: "object",
|
|
286
|
+
properties: { name: { type: "string" } },
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
required: ["processId", "elementId", "changes"],
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: "set_condition",
|
|
294
|
+
description: "Set or clear a FEEL condition expression on a sequence flow.",
|
|
295
|
+
inputSchema: {
|
|
296
|
+
type: "object",
|
|
297
|
+
properties: {
|
|
298
|
+
processId: { type: "string" },
|
|
299
|
+
flowId: { type: "string" },
|
|
300
|
+
condition: { description: "FEEL expression string, or null to remove" },
|
|
301
|
+
},
|
|
302
|
+
required: ["processId", "flowId", "condition"],
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: "replace_diagram",
|
|
307
|
+
description: "Replace the entire BPMN diagram. Use only when creating from scratch.",
|
|
308
|
+
inputSchema: {
|
|
309
|
+
type: "object",
|
|
310
|
+
properties: {
|
|
311
|
+
diagram: {
|
|
312
|
+
type: "object",
|
|
313
|
+
description: "CompactDiagram: { id, processes: [{ id, name?, elements: [...], flows: [...] }] }",
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
required: ["diagram"],
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
const DMN_TOOLS = [
|
|
321
|
+
{
|
|
322
|
+
name: "get_diagram",
|
|
323
|
+
description: "Return the current DMN diagram as a compact JSON. Call this first before making any changes.",
|
|
324
|
+
inputSchema: { type: "object", properties: {} },
|
|
325
|
+
},
|
|
326
|
+
COMPOSE_TOOL,
|
|
327
|
+
{
|
|
328
|
+
name: "replace_diagram",
|
|
329
|
+
description: "Replace the entire DMN diagram. Use when creating from scratch or doing a full rewrite.",
|
|
330
|
+
inputSchema: {
|
|
331
|
+
type: "object",
|
|
332
|
+
properties: {
|
|
333
|
+
diagram: {
|
|
334
|
+
type: "object",
|
|
335
|
+
description: "CompactDmn: { id, name, decisions: [{ id, name?, inputs, outputs, rules, requires? }], inputData: [...] }",
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
required: ["diagram"],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
];
|
|
342
|
+
const FORM_TOOLS = [
|
|
343
|
+
{
|
|
344
|
+
name: "get_diagram",
|
|
345
|
+
description: "Return the current form as a compact JSON. Call this first before making any changes.",
|
|
346
|
+
inputSchema: { type: "object", properties: {} },
|
|
347
|
+
},
|
|
348
|
+
COMPOSE_TOOL,
|
|
349
|
+
{
|
|
350
|
+
name: "replace_diagram",
|
|
351
|
+
description: "Replace the entire form. Use when creating from scratch or doing a full rewrite.",
|
|
352
|
+
inputSchema: {
|
|
353
|
+
type: "object",
|
|
354
|
+
properties: {
|
|
355
|
+
diagram: {
|
|
356
|
+
type: "object",
|
|
357
|
+
description: "CompactForm: { id, fields: [{ type, id, label?, key?, required?, values?, fields? }] }",
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
required: ["diagram"],
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
];
|
|
364
|
+
function getTools() {
|
|
365
|
+
if (state.kind === "dmn")
|
|
366
|
+
return DMN_TOOLS;
|
|
367
|
+
if (state.kind === "form")
|
|
368
|
+
return FORM_TOOLS;
|
|
369
|
+
return BPMN_TOOLS;
|
|
370
|
+
}
|
|
371
|
+
// ── Tool execution ────────────────────────────────────────────────────────────
|
|
372
|
+
function callTool(name, args) {
|
|
373
|
+
process.stderr.write(`[mcp] tool: ${name} args: ${JSON.stringify(args)}\n`);
|
|
374
|
+
switch (name) {
|
|
375
|
+
case "get_diagram": {
|
|
376
|
+
if (state.kind === "dmn")
|
|
377
|
+
return JSON.stringify(compactifyDmn(state.data), null, 2);
|
|
378
|
+
if (state.kind === "form")
|
|
379
|
+
return JSON.stringify(compactifyForm(state.data), null, 2);
|
|
380
|
+
return JSON.stringify(compactify(state.data), null, 2);
|
|
381
|
+
}
|
|
382
|
+
case "add_elements": {
|
|
383
|
+
const proc = ensureProcess(args.processId);
|
|
384
|
+
const elements = args.elements ?? [];
|
|
385
|
+
const flows = args.flows ?? [];
|
|
386
|
+
const miniCompact = {
|
|
387
|
+
id: "__temp__",
|
|
388
|
+
processes: [{ id: proc.id, elements, flows }],
|
|
389
|
+
};
|
|
390
|
+
const tempDefs = expand(miniCompact);
|
|
391
|
+
const tempProc = tempDefs.processes[0];
|
|
392
|
+
if (!tempProc)
|
|
393
|
+
return "Failed to expand elements.";
|
|
394
|
+
let addedEls = 0;
|
|
395
|
+
let addedFlows = 0;
|
|
396
|
+
for (const el of tempProc.flowElements) {
|
|
397
|
+
if (!proc.flowElements.some((e) => e.id === el.id)) {
|
|
398
|
+
proc.flowElements.push(el);
|
|
399
|
+
addedEls++;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
for (const sf of tempProc.sequenceFlows) {
|
|
403
|
+
if (!proc.sequenceFlows.some((f) => f.id === sf.id)) {
|
|
404
|
+
proc.sequenceFlows.push(sf);
|
|
405
|
+
addedFlows++;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
recomputeIncomingOutgoing(proc);
|
|
409
|
+
saveState();
|
|
410
|
+
return `Added ${addedEls} element(s) and ${addedFlows} flow(s) to ${args.processId}.`;
|
|
411
|
+
}
|
|
412
|
+
case "remove_elements": {
|
|
413
|
+
const proc = findProcess(args.processId);
|
|
414
|
+
if (!proc)
|
|
415
|
+
return `Process ${args.processId} not found.`;
|
|
416
|
+
const dropEls = new Set(args.elementIds ?? []);
|
|
417
|
+
const dropFlows = new Set(args.flowIds ?? []);
|
|
418
|
+
const removedEls = proc.flowElements.filter((e) => dropEls.has(e.id)).length;
|
|
419
|
+
proc.flowElements = proc.flowElements.filter((e) => !dropEls.has(e.id));
|
|
420
|
+
const removedFlows = proc.sequenceFlows.filter((f) => dropFlows.has(f.id) || dropEls.has(f.sourceRef) || dropEls.has(f.targetRef)).length;
|
|
421
|
+
proc.sequenceFlows = proc.sequenceFlows.filter((f) => !dropFlows.has(f.id) && !dropEls.has(f.sourceRef) && !dropEls.has(f.targetRef));
|
|
422
|
+
recomputeIncomingOutgoing(proc);
|
|
423
|
+
saveState();
|
|
424
|
+
return `Removed ${removedEls} element(s) and ${removedFlows} flow(s).`;
|
|
425
|
+
}
|
|
426
|
+
case "update_element": {
|
|
427
|
+
const proc = findProcess(args.processId);
|
|
428
|
+
if (!proc)
|
|
429
|
+
return `Process ${args.processId} not found.`;
|
|
430
|
+
const el = proc.flowElements.find((e) => e.id === args.elementId);
|
|
431
|
+
if (!el)
|
|
432
|
+
return `Element ${args.elementId} not found in ${args.processId}.`;
|
|
433
|
+
const changes = args.changes;
|
|
434
|
+
if (changes.name !== undefined)
|
|
435
|
+
el.name = changes.name;
|
|
436
|
+
saveState();
|
|
437
|
+
return `Updated element ${args.elementId}.`;
|
|
438
|
+
}
|
|
439
|
+
case "set_condition": {
|
|
440
|
+
const proc = findProcess(args.processId);
|
|
441
|
+
if (!proc)
|
|
442
|
+
return `Process ${args.processId} not found.`;
|
|
443
|
+
const sf = proc.sequenceFlows.find((f) => f.id === args.flowId);
|
|
444
|
+
if (!sf)
|
|
445
|
+
return `Flow ${args.flowId} not found in ${args.processId}.`;
|
|
446
|
+
if (args.condition === null) {
|
|
447
|
+
sf.conditionExpression = undefined;
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
sf.conditionExpression = { text: args.condition, attributes: {} };
|
|
451
|
+
}
|
|
452
|
+
saveState();
|
|
453
|
+
return `Condition set on flow ${args.flowId}.`;
|
|
454
|
+
}
|
|
455
|
+
case "add_http_call": {
|
|
456
|
+
const proc = ensureProcess(args.processId);
|
|
457
|
+
const config = {
|
|
458
|
+
name: args.name,
|
|
459
|
+
method: args.method,
|
|
460
|
+
url: args.url,
|
|
461
|
+
};
|
|
462
|
+
if (args.headers)
|
|
463
|
+
config.headers = args.headers;
|
|
464
|
+
if (args.body)
|
|
465
|
+
config.body = args.body;
|
|
466
|
+
if (args.resultVariable)
|
|
467
|
+
config.resultVariable = args.resultVariable;
|
|
468
|
+
const tempDefs = Bpmn.createProcess("__temp__")
|
|
469
|
+
.restConnector(args.id, config)
|
|
470
|
+
.build();
|
|
471
|
+
const tempProc = tempDefs.processes[0];
|
|
472
|
+
const el = tempProc?.flowElements.find((e) => e.id === args.id);
|
|
473
|
+
if (!el)
|
|
474
|
+
return "Failed to create REST connector element.";
|
|
475
|
+
if (!proc.flowElements.some((e) => e.id === el.id)) {
|
|
476
|
+
el.incoming = [];
|
|
477
|
+
el.outgoing = [];
|
|
478
|
+
proc.flowElements.push(el);
|
|
479
|
+
}
|
|
480
|
+
saveState();
|
|
481
|
+
return `Added HTTP task "${args.name}" (${args.method} ${args.url}).`;
|
|
482
|
+
}
|
|
483
|
+
case "replace_diagram": {
|
|
484
|
+
const raw = args.diagram;
|
|
485
|
+
if (state.kind === "dmn") {
|
|
486
|
+
state = { kind: "dmn", data: expandDmn(raw) };
|
|
487
|
+
}
|
|
488
|
+
else if (state.kind === "form") {
|
|
489
|
+
state = { kind: "form", data: expandForm(raw) };
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
state = { kind: "bpmn", data: expand(raw) };
|
|
493
|
+
}
|
|
494
|
+
saveState();
|
|
495
|
+
return "Diagram replaced.";
|
|
496
|
+
}
|
|
497
|
+
case "compose_diagram": {
|
|
498
|
+
const code = args.code;
|
|
499
|
+
// Build Bridge object that mirrors bridge.ts API, extended for DMN/Form types.
|
|
500
|
+
const bridge = buildBridge();
|
|
501
|
+
const ctx = vm.createContext({ Bridge: bridge });
|
|
502
|
+
try {
|
|
503
|
+
const result = vm.runInContext(`(function(){\n${code}\n})()`, ctx, { timeout: 5000 });
|
|
504
|
+
return typeof result === "string" ? result : JSON.stringify(result ?? null);
|
|
505
|
+
}
|
|
506
|
+
catch (err) {
|
|
507
|
+
throw new Error(`Code execution failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
default:
|
|
511
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function buildBridge() {
|
|
515
|
+
const base = {
|
|
516
|
+
mcpGetDiagram: () => callTool("get_diagram", {}),
|
|
517
|
+
mcpReplaceDiagram: (compactJson) => callTool("replace_diagram", { diagram: JSON.parse(compactJson) }),
|
|
518
|
+
mcpExportXml: () => {
|
|
519
|
+
if (state.kind === "dmn") {
|
|
520
|
+
return Dmn.export(layoutDmn(state.data));
|
|
521
|
+
}
|
|
522
|
+
if (state.kind === "form") {
|
|
523
|
+
return Form.export(state.data);
|
|
524
|
+
}
|
|
525
|
+
state.data.diagrams = state.data.processes.map((proc) => buildBpmnDiagram(proc));
|
|
526
|
+
return Bpmn.export(state.data);
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
if (state.kind !== "bpmn")
|
|
530
|
+
return base;
|
|
531
|
+
// BPMN-specific methods
|
|
532
|
+
return {
|
|
533
|
+
...base,
|
|
534
|
+
mcpAddElements: (processId, elementsJson, flowsJson) => callTool("add_elements", {
|
|
535
|
+
processId,
|
|
536
|
+
elements: JSON.parse(elementsJson),
|
|
537
|
+
flows: JSON.parse(flowsJson),
|
|
538
|
+
}),
|
|
539
|
+
mcpRemoveElements: (processId, elementIdsJson, flowIdsJson) => callTool("remove_elements", {
|
|
540
|
+
processId,
|
|
541
|
+
elementIds: JSON.parse(elementIdsJson),
|
|
542
|
+
flowIds: JSON.parse(flowIdsJson),
|
|
543
|
+
}),
|
|
544
|
+
mcpUpdateElement: (processId, elementId, changesJson) => callTool("update_element", {
|
|
545
|
+
processId,
|
|
546
|
+
elementId,
|
|
547
|
+
changes: JSON.parse(changesJson),
|
|
548
|
+
}),
|
|
549
|
+
mcpSetCondition: (processId, flowId, conditionJson) => callTool("set_condition", {
|
|
550
|
+
processId,
|
|
551
|
+
flowId,
|
|
552
|
+
condition: JSON.parse(conditionJson),
|
|
553
|
+
}),
|
|
554
|
+
mcpAddHttpCall: (processId, configJson) => {
|
|
555
|
+
const cfg = JSON.parse(configJson);
|
|
556
|
+
return callTool("add_http_call", { processId, ...cfg });
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
const rl = createInterface({ input: process.stdin, crlfDelay: Number.POSITIVE_INFINITY });
|
|
561
|
+
rl.on("line", (line) => {
|
|
562
|
+
const trimmed = line.trim();
|
|
563
|
+
if (!trimmed)
|
|
564
|
+
return;
|
|
565
|
+
let req;
|
|
566
|
+
try {
|
|
567
|
+
req = JSON.parse(trimmed);
|
|
568
|
+
}
|
|
569
|
+
catch {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
// Notifications have no id — ignore them (no response needed)
|
|
573
|
+
if (!("id" in req))
|
|
574
|
+
return;
|
|
575
|
+
let result;
|
|
576
|
+
let error;
|
|
577
|
+
try {
|
|
578
|
+
switch (req.method) {
|
|
579
|
+
case "initialize":
|
|
580
|
+
result = {
|
|
581
|
+
protocolVersion: "2024-11-05",
|
|
582
|
+
capabilities: { tools: {} },
|
|
583
|
+
serverInfo: { name: "bpmnkit-mcp", version: "1.0.0" },
|
|
584
|
+
};
|
|
585
|
+
break;
|
|
586
|
+
case "tools/list":
|
|
587
|
+
result = { tools: getTools() };
|
|
588
|
+
break;
|
|
589
|
+
case "tools/call": {
|
|
590
|
+
const params = req.params;
|
|
591
|
+
const text = callTool(params.name, params.arguments ?? {});
|
|
592
|
+
result = { content: [{ type: "text", text }], isError: false };
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
case "ping":
|
|
596
|
+
result = {};
|
|
597
|
+
break;
|
|
598
|
+
default:
|
|
599
|
+
error = { code: -32601, message: "Method not found" };
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
catch (err) {
|
|
603
|
+
if (req.method === "tools/call") {
|
|
604
|
+
result = { content: [{ type: "text", text: String(err) }], isError: true };
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
error = { code: -32603, message: String(err) };
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const response = error
|
|
611
|
+
? { jsonrpc: "2.0", id: req.id, error }
|
|
612
|
+
: { jsonrpc: "2.0", id: req.id, result };
|
|
613
|
+
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
614
|
+
});
|
|
615
|
+
//# sourceMappingURL=mcp-server.js.map
|