flowzap-mcp 1.4.0 → 1.4.1
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/index.js +2 -2
- package/dist/tools/artifactToDiagram (# Edit conflict 2026-05-24 a7hs75C #).js +487 -0
- package/dist/tools/complianceCheck.d.ts.map +1 -1
- package/dist/tools/complianceCheck.js +24 -2
- package/dist/tools/complianceCheck.js.map +1 -1
- package/dist/tools/playgroundApi.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -95,7 +95,7 @@ async function secureFetch(url, options) {
|
|
|
95
95
|
signal: controller.signal,
|
|
96
96
|
headers: {
|
|
97
97
|
...options.headers,
|
|
98
|
-
"User-Agent": "flowzap-mcp/1.4.
|
|
98
|
+
"User-Agent": "flowzap-mcp/1.4.1",
|
|
99
99
|
"X-MCP-Client": "flowzap-mcp",
|
|
100
100
|
},
|
|
101
101
|
});
|
|
@@ -387,7 +387,7 @@ async function handleCreatePlayground(code, view) {
|
|
|
387
387
|
// Main server setup
|
|
388
388
|
const server = new Server({
|
|
389
389
|
name: "flowzap-mcp",
|
|
390
|
-
version: "1.4.
|
|
390
|
+
version: "1.4.1",
|
|
391
391
|
}, {
|
|
392
392
|
capabilities: {
|
|
393
393
|
tools: {},
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArtifactToDiagram Tool
|
|
3
|
+
*
|
|
4
|
+
* Parses real artifacts (HTTP logs, OpenAPI specs, code snippets) into FlowZap Code.
|
|
5
|
+
* Gives AI agents a capability they don't have natively: structured extraction from logs/specs.
|
|
6
|
+
*/
|
|
7
|
+
import { createPlaygroundUrl } from "./playgroundApi.js";
|
|
8
|
+
export const artifactToDiagramTool = {
|
|
9
|
+
name: "flowzap_artifact_to_diagram",
|
|
10
|
+
description: "Parse real artifacts (HTTP logs, OpenAPI specs, code snippets) into FlowZap Code diagrams. Use this to convert raw technical data into visual workflows that can be explained and refined.",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
artifactType: {
|
|
15
|
+
type: "string",
|
|
16
|
+
enum: ["http_logs", "openapi", "code"],
|
|
17
|
+
description: "Type of artifact: http_logs (request/response sequences), openapi (API specs), code (function call traces)",
|
|
18
|
+
},
|
|
19
|
+
content: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Raw artifact content to parse",
|
|
22
|
+
},
|
|
23
|
+
view: {
|
|
24
|
+
type: "string",
|
|
25
|
+
enum: ["workflow", "sequence"],
|
|
26
|
+
description: "Preferred diagram view (default: sequence for logs, workflow for openapi)",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
required: ["artifactType", "content"],
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Parse HTTP logs into FlowZap Code
|
|
34
|
+
*
|
|
35
|
+
* Supports formats:
|
|
36
|
+
* - Apache/Nginx combined log format
|
|
37
|
+
* - Simple request/response pairs
|
|
38
|
+
* - HAR-like JSON
|
|
39
|
+
*/
|
|
40
|
+
function parseHttpLogs(content) {
|
|
41
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
42
|
+
const actors = new Set(["Client"]);
|
|
43
|
+
const steps = [];
|
|
44
|
+
// Try to detect format
|
|
45
|
+
let nodeId = 1;
|
|
46
|
+
// Pattern 1: Simple "METHOD URL -> STATUS" or "CLIENT -> SERVER: MESSAGE"
|
|
47
|
+
const simplePattern = /^(\w+)?\s*(?:->|→)\s*(\w+)(?:\s*:\s*(.+))?$/;
|
|
48
|
+
const httpPattern = /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\S+)(?:\s+(\d{3}))?/i;
|
|
49
|
+
const responsePattern = /^(\d{3})\s+(.+)/;
|
|
50
|
+
let currentServer = "Server";
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
const trimmed = line.trim();
|
|
53
|
+
if (!trimmed)
|
|
54
|
+
continue;
|
|
55
|
+
// Try HTTP method pattern
|
|
56
|
+
const httpMatch = trimmed.match(httpPattern);
|
|
57
|
+
if (httpMatch) {
|
|
58
|
+
const [, method, path, status] = httpMatch;
|
|
59
|
+
// Extract server from path if it's a full URL
|
|
60
|
+
try {
|
|
61
|
+
const url = new URL(path.startsWith("http") ? path : `https://example.com${path}`);
|
|
62
|
+
currentServer = url.hostname.split(".")[0] || "Server";
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Keep current server
|
|
66
|
+
}
|
|
67
|
+
actors.add(currentServer);
|
|
68
|
+
// Request
|
|
69
|
+
steps.push({
|
|
70
|
+
from: "Client",
|
|
71
|
+
to: currentServer,
|
|
72
|
+
label: `${method} ${path.length > 30 ? path.substring(0, 30) + "..." : path}`,
|
|
73
|
+
});
|
|
74
|
+
// Response if status included
|
|
75
|
+
if (status) {
|
|
76
|
+
steps.push({
|
|
77
|
+
from: currentServer,
|
|
78
|
+
to: "Client",
|
|
79
|
+
label: `${status} Response`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
// Try simple arrow pattern
|
|
85
|
+
const simpleMatch = trimmed.match(simplePattern);
|
|
86
|
+
if (simpleMatch) {
|
|
87
|
+
const [, from = "Client", to, message = "Request"] = simpleMatch;
|
|
88
|
+
actors.add(from);
|
|
89
|
+
actors.add(to);
|
|
90
|
+
steps.push({ from, to, label: message });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// Try response pattern (just status code)
|
|
94
|
+
const responseMatch = trimmed.match(responsePattern);
|
|
95
|
+
if (responseMatch && steps.length > 0) {
|
|
96
|
+
const lastStep = steps[steps.length - 1];
|
|
97
|
+
steps.push({
|
|
98
|
+
from: lastStep.to,
|
|
99
|
+
to: lastStep.from,
|
|
100
|
+
label: `${responseMatch[1]} ${responseMatch[2]}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// If no steps parsed, create a placeholder
|
|
105
|
+
if (steps.length === 0) {
|
|
106
|
+
return {
|
|
107
|
+
code: `client {\n # Client\n n1: circle label:"No parseable requests found"\n}`,
|
|
108
|
+
view: "sequence",
|
|
109
|
+
notes: "Could not parse HTTP log format. Try a simpler format like 'GET /api/users 200' or 'Client -> Server: Request'",
|
|
110
|
+
actors: ["Client"],
|
|
111
|
+
stepCount: 0,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// Generate FlowZap Code
|
|
115
|
+
const actorList = Array.from(actors);
|
|
116
|
+
const laneNodes = new Map();
|
|
117
|
+
const allEdges = [];
|
|
118
|
+
// Initialize lanes
|
|
119
|
+
for (const actor of actorList) {
|
|
120
|
+
laneNodes.set(actor, []);
|
|
121
|
+
}
|
|
122
|
+
// Create nodes and edges
|
|
123
|
+
for (const step of steps) {
|
|
124
|
+
const fromLane = step.from;
|
|
125
|
+
const toLane = step.to;
|
|
126
|
+
// Create source node if this is first action from this actor
|
|
127
|
+
const fromNodes = laneNodes.get(fromLane) || [];
|
|
128
|
+
let fromNodeId;
|
|
129
|
+
if (fromNodes.length === 0) {
|
|
130
|
+
fromNodeId = `n${nodeId++}`;
|
|
131
|
+
fromNodes.push(` ${fromNodeId}: rectangle label:"${escapeLabel(step.label)}"`);
|
|
132
|
+
laneNodes.set(fromLane, fromNodes);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Use last node as source
|
|
136
|
+
const lastNode = fromNodes[fromNodes.length - 1];
|
|
137
|
+
const match = lastNode.match(/^\s*(n\d+):/);
|
|
138
|
+
fromNodeId = match ? match[1] : `n${nodeId++}`;
|
|
139
|
+
}
|
|
140
|
+
// Create target node
|
|
141
|
+
const toNodes = laneNodes.get(toLane) || [];
|
|
142
|
+
const toNodeId = `n${nodeId++}`;
|
|
143
|
+
toNodes.push(` ${toNodeId}: rectangle label:"${escapeLabel(step.label)}"`);
|
|
144
|
+
laneNodes.set(toLane, toNodes);
|
|
145
|
+
// Create edge
|
|
146
|
+
if (fromLane === toLane) {
|
|
147
|
+
allEdges.push(` ${fromNodeId}.handle(right) -> ${toNodeId}.handle(left)`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
allEdges.push(` ${fromNodeId}.handle(bottom) -> ${toLane.toLowerCase().replace(/\s+/g, "")}.${toNodeId}.handle(top) [label="${escapeLabel(step.label)}"]`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Build code
|
|
154
|
+
let code = "";
|
|
155
|
+
for (const [actor, nodes] of laneNodes) {
|
|
156
|
+
const laneId = actor.toLowerCase().replace(/\s+/g, "");
|
|
157
|
+
code += `${laneId} {\n # ${actor}\n`;
|
|
158
|
+
code += nodes.join("\n") + "\n";
|
|
159
|
+
// Add edges that originate from this lane
|
|
160
|
+
const laneEdges = allEdges.filter((e) => e.includes(`${laneId}.`) || e.match(new RegExp(`^\\s*n\\d+\\.handle`)));
|
|
161
|
+
if (laneEdges.length > 0) {
|
|
162
|
+
code += laneEdges.join("\n") + "\n";
|
|
163
|
+
}
|
|
164
|
+
code += "}\n\n";
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
code: code.trim(),
|
|
168
|
+
view: "sequence",
|
|
169
|
+
notes: `Inferred ${actorList.length} actors and ${steps.length} steps from HTTP logs`,
|
|
170
|
+
actors: actorList,
|
|
171
|
+
stepCount: steps.length,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Parse OpenAPI spec into FlowZap Code
|
|
176
|
+
*/
|
|
177
|
+
function parseOpenAPI(content) {
|
|
178
|
+
let spec;
|
|
179
|
+
try {
|
|
180
|
+
spec = JSON.parse(content);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Try YAML-like parsing (basic)
|
|
184
|
+
try {
|
|
185
|
+
// Very basic YAML parsing for common patterns
|
|
186
|
+
const lines = content.split("\n");
|
|
187
|
+
spec = { paths: {}, info: { title: "API" } };
|
|
188
|
+
let currentPath = "";
|
|
189
|
+
let currentMethod = "";
|
|
190
|
+
for (const line of lines) {
|
|
191
|
+
const pathMatch = line.match(/^\/[\w\-\/{}]+:/);
|
|
192
|
+
if (pathMatch) {
|
|
193
|
+
currentPath = pathMatch[0].replace(":", "");
|
|
194
|
+
spec.paths[currentPath] = {};
|
|
195
|
+
}
|
|
196
|
+
const methodMatch = line.match(/^\s+(get|post|put|delete|patch):/i);
|
|
197
|
+
if (methodMatch && currentPath) {
|
|
198
|
+
currentMethod = methodMatch[1].toLowerCase();
|
|
199
|
+
spec.paths[currentPath][currentMethod] = { summary: "" };
|
|
200
|
+
}
|
|
201
|
+
const summaryMatch = line.match(/^\s+summary:\s*(.+)/);
|
|
202
|
+
if (summaryMatch && currentPath && currentMethod) {
|
|
203
|
+
spec.paths[currentPath][currentMethod].summary = summaryMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return {
|
|
209
|
+
code: `api {\n # API\n n1: circle label:"Could not parse OpenAPI spec"\n}`,
|
|
210
|
+
view: "workflow",
|
|
211
|
+
notes: "Failed to parse OpenAPI content. Ensure it's valid JSON or YAML.",
|
|
212
|
+
actors: ["API"],
|
|
213
|
+
stepCount: 0,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const paths = spec.paths || {};
|
|
218
|
+
const title = spec.info?.title || "API";
|
|
219
|
+
const actors = new Set(["Client", title]);
|
|
220
|
+
const steps = [];
|
|
221
|
+
// Extract endpoints
|
|
222
|
+
for (const [path, methods] of Object.entries(paths)) {
|
|
223
|
+
if (typeof methods !== "object" || methods === null)
|
|
224
|
+
continue;
|
|
225
|
+
for (const [method, details] of Object.entries(methods)) {
|
|
226
|
+
if (!["get", "post", "put", "delete", "patch"].includes(method.toLowerCase()))
|
|
227
|
+
continue;
|
|
228
|
+
const summary = details?.summary || details?.operationId || `${method.toUpperCase()} ${path}`;
|
|
229
|
+
steps.push({
|
|
230
|
+
method: method.toUpperCase(),
|
|
231
|
+
path,
|
|
232
|
+
summary: String(summary),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (steps.length === 0) {
|
|
237
|
+
return {
|
|
238
|
+
code: `api {\n # ${title}\n n1: circle label:"No endpoints found"\n}`,
|
|
239
|
+
view: "workflow",
|
|
240
|
+
notes: "No API endpoints found in the OpenAPI spec.",
|
|
241
|
+
actors: [title],
|
|
242
|
+
stepCount: 0,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// Generate workflow diagram
|
|
246
|
+
const apiLaneId = title.toLowerCase().replace(/\s+/g, "").replace(/[^a-z0-9]/g, "");
|
|
247
|
+
let code = `client {\n # Client\n n1: circle label:"Start"\n}\n\n`;
|
|
248
|
+
code += `${apiLaneId} {\n # ${title}\n`;
|
|
249
|
+
let nodeId = 2;
|
|
250
|
+
const nodeIds = [];
|
|
251
|
+
for (const step of steps) {
|
|
252
|
+
const label = step.summary.length > 40 ? step.summary.substring(0, 40) + "..." : step.summary;
|
|
253
|
+
code += ` n${nodeId}: rectangle label:"${escapeLabel(label)}"\n`;
|
|
254
|
+
nodeIds.push(`n${nodeId}`);
|
|
255
|
+
nodeId++;
|
|
256
|
+
}
|
|
257
|
+
// Add edges between consecutive endpoints
|
|
258
|
+
for (let i = 0; i < nodeIds.length - 1; i++) {
|
|
259
|
+
code += ` ${nodeIds[i]}.handle(right) -> ${nodeIds[i + 1]}.handle(left)\n`;
|
|
260
|
+
}
|
|
261
|
+
// Add end node
|
|
262
|
+
code += ` n${nodeId}: circle label:"End"\n`;
|
|
263
|
+
if (nodeIds.length > 0) {
|
|
264
|
+
code += ` ${nodeIds[nodeIds.length - 1]}.handle(right) -> n${nodeId}.handle(left)\n`;
|
|
265
|
+
}
|
|
266
|
+
code += "}\n";
|
|
267
|
+
// Add cross-lane edge from client to first API node
|
|
268
|
+
code = code.replace("n1: circle label:\"Start\"\n}", `n1: circle label:"Start"\n n1.handle(bottom) -> ${apiLaneId}.n2.handle(top)\n}`);
|
|
269
|
+
return {
|
|
270
|
+
code,
|
|
271
|
+
view: "workflow",
|
|
272
|
+
notes: `Extracted ${steps.length} endpoints from OpenAPI spec "${title}"`,
|
|
273
|
+
actors: Array.from(actors),
|
|
274
|
+
stepCount: steps.length,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Parse code snippets into FlowZap Code (function call trace)
|
|
279
|
+
*/
|
|
280
|
+
function parseCode(content) {
|
|
281
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
282
|
+
const actors = new Set();
|
|
283
|
+
const calls = [];
|
|
284
|
+
// Patterns for function calls
|
|
285
|
+
const patterns = [
|
|
286
|
+
// JavaScript/TypeScript: object.method() or await object.method()
|
|
287
|
+
/(?:await\s+)?(\w+)\.(\w+)\s*\(/g,
|
|
288
|
+
// Python: object.method() or Class.method()
|
|
289
|
+
/(\w+)\.(\w+)\s*\(/g,
|
|
290
|
+
// Function definition: function name() or def name():
|
|
291
|
+
/(?:function|def|async\s+function)\s+(\w+)/g,
|
|
292
|
+
// Class method: class.method or self.method
|
|
293
|
+
/(?:this|self)\.(\w+)\s*\(/g,
|
|
294
|
+
];
|
|
295
|
+
let currentModule = "Main";
|
|
296
|
+
for (const line of lines) {
|
|
297
|
+
const trimmed = line.trim();
|
|
298
|
+
// Detect class/module definitions
|
|
299
|
+
const classMatch = trimmed.match(/^(?:class|module|namespace)\s+(\w+)/);
|
|
300
|
+
if (classMatch) {
|
|
301
|
+
currentModule = classMatch[1];
|
|
302
|
+
actors.add(currentModule);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
// Detect function definitions
|
|
306
|
+
const funcMatch = trimmed.match(/^(?:function|def|async\s+function|async\s+def)\s+(\w+)/);
|
|
307
|
+
if (funcMatch) {
|
|
308
|
+
actors.add(currentModule);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
// Detect method calls
|
|
312
|
+
const callMatch = trimmed.match(/(?:await\s+)?(\w+)\.(\w+)\s*\(/);
|
|
313
|
+
if (callMatch) {
|
|
314
|
+
const [, obj, method] = callMatch;
|
|
315
|
+
// Skip common non-actor objects
|
|
316
|
+
if (["console", "Math", "JSON", "Object", "Array", "String", "this", "self"].includes(obj)) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
actors.add(obj);
|
|
320
|
+
calls.push({
|
|
321
|
+
caller: currentModule,
|
|
322
|
+
callee: obj,
|
|
323
|
+
method,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (calls.length === 0) {
|
|
328
|
+
return {
|
|
329
|
+
code: `main {\n # Main\n n1: circle label:"No function calls detected"\n}`,
|
|
330
|
+
view: "sequence",
|
|
331
|
+
notes: "Could not detect function call patterns in the code.",
|
|
332
|
+
actors: ["Main"],
|
|
333
|
+
stepCount: 0,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// Generate sequence diagram
|
|
337
|
+
const actorList = Array.from(actors);
|
|
338
|
+
if (!actorList.includes("Main")) {
|
|
339
|
+
actorList.unshift("Main");
|
|
340
|
+
}
|
|
341
|
+
let code = "";
|
|
342
|
+
const laneNodes = new Map();
|
|
343
|
+
let nodeId = 1;
|
|
344
|
+
// Initialize lanes
|
|
345
|
+
for (const actor of actorList) {
|
|
346
|
+
const laneId = actor.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
347
|
+
code += `${laneId} {\n # ${actor}\n`;
|
|
348
|
+
laneNodes.set(actor, 0);
|
|
349
|
+
code += "}\n\n";
|
|
350
|
+
}
|
|
351
|
+
// We'll rebuild with nodes and edges
|
|
352
|
+
code = "";
|
|
353
|
+
const allNodes = [];
|
|
354
|
+
const allEdges = [];
|
|
355
|
+
for (const call of calls) {
|
|
356
|
+
const fromLane = call.caller.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
357
|
+
const toLane = call.callee.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
358
|
+
const fromNodeId = `n${nodeId++}`;
|
|
359
|
+
const toNodeId = `n${nodeId++}`;
|
|
360
|
+
allNodes.push({ lane: fromLane, id: fromNodeId, label: `Call ${call.method}` });
|
|
361
|
+
allNodes.push({ lane: toLane, id: toNodeId, label: call.method });
|
|
362
|
+
if (fromLane === toLane) {
|
|
363
|
+
allEdges.push({ lane: fromLane, edge: ` ${fromNodeId}.handle(right) -> ${toNodeId}.handle(left)` });
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
allEdges.push({ lane: fromLane, edge: ` ${fromNodeId}.handle(bottom) -> ${toLane}.${toNodeId}.handle(top) [label="${call.method}"]` });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Group nodes by lane
|
|
370
|
+
const nodesByLane = new Map();
|
|
371
|
+
for (const node of allNodes) {
|
|
372
|
+
const existing = nodesByLane.get(node.lane) || [];
|
|
373
|
+
existing.push(node);
|
|
374
|
+
nodesByLane.set(node.lane, existing);
|
|
375
|
+
}
|
|
376
|
+
// Build code
|
|
377
|
+
for (const actor of actorList) {
|
|
378
|
+
const laneId = actor.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
379
|
+
code += `${laneId} {\n # ${actor}\n`;
|
|
380
|
+
const nodes = nodesByLane.get(laneId) || [];
|
|
381
|
+
for (const node of nodes) {
|
|
382
|
+
code += ` ${node.id}: rectangle label:"${escapeLabel(node.label)}"\n`;
|
|
383
|
+
}
|
|
384
|
+
const edges = allEdges.filter((e) => e.lane === laneId);
|
|
385
|
+
for (const e of edges) {
|
|
386
|
+
code += e.edge + "\n";
|
|
387
|
+
}
|
|
388
|
+
code += "}\n\n";
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
code: code.trim(),
|
|
392
|
+
view: "sequence",
|
|
393
|
+
notes: `Detected ${calls.length} function calls across ${actorList.length} modules`,
|
|
394
|
+
actors: actorList,
|
|
395
|
+
stepCount: calls.length,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Escape label text for FlowZap Code
|
|
400
|
+
*/
|
|
401
|
+
function escapeLabel(text) {
|
|
402
|
+
return text
|
|
403
|
+
.replace(/\\/g, "\\\\")
|
|
404
|
+
.replace(/"/g, '\\"')
|
|
405
|
+
.replace(/\n/g, " ")
|
|
406
|
+
.trim();
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Main handler for artifact_to_diagram tool
|
|
410
|
+
*/
|
|
411
|
+
export async function handleArtifactToDiagram(artifactType, content, view) {
|
|
412
|
+
// Validate inputs
|
|
413
|
+
if (typeof artifactType !== "string") {
|
|
414
|
+
return JSON.stringify({
|
|
415
|
+
success: false,
|
|
416
|
+
error: "artifactType must be a string",
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
if (!["http_logs", "openapi", "code"].includes(artifactType)) {
|
|
420
|
+
return JSON.stringify({
|
|
421
|
+
success: false,
|
|
422
|
+
error: `Invalid artifactType "${artifactType}". Must be one of: http_logs, openapi, code`,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
if (typeof content !== "string") {
|
|
426
|
+
return JSON.stringify({
|
|
427
|
+
success: false,
|
|
428
|
+
error: "content must be a string",
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
if (content.trim().length === 0) {
|
|
432
|
+
return JSON.stringify({
|
|
433
|
+
success: false,
|
|
434
|
+
error: "content cannot be empty",
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
if (content.length > 100000) {
|
|
438
|
+
return JSON.stringify({
|
|
439
|
+
success: false,
|
|
440
|
+
error: "content exceeds maximum length of 100,000 characters",
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
let result;
|
|
445
|
+
switch (artifactType) {
|
|
446
|
+
case "http_logs":
|
|
447
|
+
result = parseHttpLogs(content);
|
|
448
|
+
break;
|
|
449
|
+
case "openapi":
|
|
450
|
+
result = parseOpenAPI(content);
|
|
451
|
+
break;
|
|
452
|
+
case "code":
|
|
453
|
+
result = parseCode(content);
|
|
454
|
+
break;
|
|
455
|
+
default:
|
|
456
|
+
return JSON.stringify({
|
|
457
|
+
success: false,
|
|
458
|
+
error: `Unsupported artifact type: ${artifactType}`,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
// Override view if specified
|
|
462
|
+
if (view === "workflow" || view === "sequence") {
|
|
463
|
+
result.view = view;
|
|
464
|
+
}
|
|
465
|
+
// Auto-create playground URL
|
|
466
|
+
const playground = await createPlaygroundUrl(result.code);
|
|
467
|
+
return JSON.stringify({
|
|
468
|
+
success: true,
|
|
469
|
+
code: result.code,
|
|
470
|
+
url: playground.url || null,
|
|
471
|
+
view: result.view,
|
|
472
|
+
notes: result.notes,
|
|
473
|
+
stats: {
|
|
474
|
+
actors: result.actors,
|
|
475
|
+
stepCount: result.stepCount,
|
|
476
|
+
},
|
|
477
|
+
...(playground.error && { playgroundError: playground.error }),
|
|
478
|
+
}, null, 2);
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
return JSON.stringify({
|
|
482
|
+
success: false,
|
|
483
|
+
error: `Failed to parse artifact: ${error instanceof Error ? error.message : String(error)}`,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
//# sourceMappingURL=artifactToDiagram.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"complianceCheck.d.ts","sourceRoot":"","sources":["../../src/tools/complianceCheck.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAmB/D,eAAO,MAAM,mBAAmB,EAAE,IAmBjC,CAAC;
|
|
1
|
+
{"version":3,"file":"complianceCheck.d.ts","sourceRoot":"","sources":["../../src/tools/complianceCheck.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAmB/D,eAAO,MAAM,mBAAmB,EAAE,IAmBjC,CAAC;AAgFF,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,MAAM,CAAC,CA6DjB"}
|
|
@@ -40,6 +40,21 @@ export const complianceCheckTool = {
|
|
|
40
40
|
required: ["code"],
|
|
41
41
|
},
|
|
42
42
|
};
|
|
43
|
+
const ALLOWED_RESULT_HOST = "flowzap.xyz";
|
|
44
|
+
function isSafeResultUrl(url) {
|
|
45
|
+
if (!url || typeof url !== "string")
|
|
46
|
+
return false;
|
|
47
|
+
try {
|
|
48
|
+
const u = new URL(url);
|
|
49
|
+
if (u.protocol !== "https:")
|
|
50
|
+
return false;
|
|
51
|
+
// Allow only the production host (and subdomains) to prevent open-redirect-style leaks.
|
|
52
|
+
return u.hostname === ALLOWED_RESULT_HOST || u.hostname.endsWith(`.${ALLOWED_RESULT_HOST}`);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
43
58
|
function renderFindings(findings) {
|
|
44
59
|
if (!findings || findings.length === 0) {
|
|
45
60
|
return "_No specific findings flagged for this framework._";
|
|
@@ -55,7 +70,14 @@ function renderFindings(findings) {
|
|
|
55
70
|
}
|
|
56
71
|
function renderMarkdown(payload) {
|
|
57
72
|
const fw = payload.frameworks || {};
|
|
58
|
-
const sections = [
|
|
73
|
+
const sections = [];
|
|
74
|
+
// Lead with the shareable rendered-result URL when available so agents (and end users)
|
|
75
|
+
// can deep-link directly to a styled audit page instead of relying on inline markdown.
|
|
76
|
+
if (isSafeResultUrl(payload.resultUrl)) {
|
|
77
|
+
sections.push(`A compliance check to SOC2, PIPL and GDPR is ready and is available here: ${payload.resultUrl}`);
|
|
78
|
+
sections.push("");
|
|
79
|
+
}
|
|
80
|
+
sections.push("# Compliance Check Results (SOC2 · GDPR · PIPL)\n");
|
|
59
81
|
for (const key of ["gdpr", "soc2", "pipl"]) {
|
|
60
82
|
const findings = fw[key]?.findings || [];
|
|
61
83
|
sections.push(`## ${FRAMEWORK_LABELS[key]} (${findings.length} finding${findings.length === 1 ? "" : "s"})`);
|
|
@@ -91,7 +113,7 @@ export async function handleComplianceCheck(rawCode, rawLng) {
|
|
|
91
113
|
method: "POST",
|
|
92
114
|
headers: {
|
|
93
115
|
"Content-Type": "application/json",
|
|
94
|
-
"User-Agent": "flowzap-mcp/1.4.
|
|
116
|
+
"User-Agent": "flowzap-mcp/1.4.1",
|
|
95
117
|
"X-MCP-Client": "flowzap-mcp",
|
|
96
118
|
},
|
|
97
119
|
body: JSON.stringify({ code: sanitizedCode, lng }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"complianceCheck.js","sourceRoot":"","sources":["../../src/tools/complianceCheck.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,gBAAgB,GAAG,qBAAqB,CAAC;AAC/C,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC;AAGhD,MAAM,WAAW,GAA2B;IAC1C,OAAO,EAAE,GAAG;IACZ,OAAO,EAAE,IAAI;IACb,OAAO,EAAE,GAAG;CACb,CAAC;AAEF,MAAM,gBAAgB,GAA2B;IAC/C,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;CACb,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAS;IACvC,IAAI,EAAE,0BAA0B;IAChC,WAAW,EACT,oYAAoY;IACtY,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,8DAA8D;aAC5E;YACD,GAAG,EAAE;gBACH,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;gBACxB,WAAW,EAAE,iCAAiC;aAC/C;SACF;QACD,QAAQ,EAAE,CAAC,MAAM,CAAC;KACnB;CACF,CAAC;
|
|
1
|
+
{"version":3,"file":"complianceCheck.js","sourceRoot":"","sources":["../../src/tools/complianceCheck.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@ export async function createPlaygroundUrl(code) {
|
|
|
16
16
|
method: "POST",
|
|
17
17
|
headers: {
|
|
18
18
|
"Content-Type": "application/json",
|
|
19
|
-
"User-Agent": "flowzap-mcp/1.4.
|
|
19
|
+
"User-Agent": "flowzap-mcp/1.4.1",
|
|
20
20
|
"X-MCP-Client": "flowzap-mcp",
|
|
21
21
|
},
|
|
22
22
|
body: JSON.stringify({ code, source: 'mcp' }),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowzap-mcp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"mcpName": "io.github.flowzap-xyz/flowzap",
|
|
5
5
|
"description": "MCP server for FlowZap - Create workflow, sequence, and architecture diagrams via AI assistants. Works with Claude Desktop, Claude Code, Cursor, Windsurf, OpenAI Codex, Warp, Zed, Cline, Roo Code, Continue.dev, and Sourcegraph Cody.",
|
|
6
6
|
"main": "dist/index.js",
|