@walkeros/mcp 3.1.0 → 3.2.0-next-1775064795590
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 +215 -160
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ var SimulateOutputShape = {
|
|
|
56
56
|
z.object({
|
|
57
57
|
received: z.boolean().describe("Whether destination received the event"),
|
|
58
58
|
calls: z.number().describe("Number of API calls made"),
|
|
59
|
-
payload: z.unknown().optional().describe("
|
|
59
|
+
payload: z.unknown().optional().describe("All intercepted API calls (only when verbose: true)")
|
|
60
60
|
})
|
|
61
61
|
).optional().describe("Per-destination results"),
|
|
62
62
|
capturedEvents: z.array(z.record(z.string(), z.unknown())).optional().describe("Events captured by source simulation"),
|
|
@@ -114,7 +114,6 @@ function registerFlowValidateTool(server2) {
|
|
|
114
114
|
flow,
|
|
115
115
|
path
|
|
116
116
|
});
|
|
117
|
-
const summary = result.valid ? "Valid" : `Invalid: ${result.errors.length} errors, ${result.warnings.length} warnings`;
|
|
118
117
|
const hints = result.valid ? {
|
|
119
118
|
next: [
|
|
120
119
|
"Use flow_simulate to test event flow",
|
|
@@ -126,7 +125,7 @@ function registerFlowValidateTool(server2) {
|
|
|
126
125
|
"Read walkeros://reference/flow-schema for correct structure"
|
|
127
126
|
]
|
|
128
127
|
};
|
|
129
|
-
return mcpResult(result,
|
|
128
|
+
return mcpResult(result, hints);
|
|
130
129
|
} catch (error) {
|
|
131
130
|
return mcpError(
|
|
132
131
|
error,
|
|
@@ -172,16 +171,15 @@ function registerFlowBundleTool(server2) {
|
|
|
172
171
|
content,
|
|
173
172
|
flowName: flow
|
|
174
173
|
});
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
});
|
|
174
|
+
return mcpResult2(
|
|
175
|
+
{ success: true, ...result2 },
|
|
176
|
+
{
|
|
177
|
+
next: [
|
|
178
|
+
"Use flow_simulate to test",
|
|
179
|
+
"Use api({ action: 'deploy' }) to publish"
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
);
|
|
185
183
|
}
|
|
186
184
|
const result = await bundle(configPath, {
|
|
187
185
|
flowName: flow,
|
|
@@ -191,7 +189,6 @@ function registerFlowBundleTool(server2) {
|
|
|
191
189
|
if (!result) {
|
|
192
190
|
return mcpResult2(
|
|
193
191
|
{ success: false, message: "Bundle produced no output" },
|
|
194
|
-
"Bundle produced no output",
|
|
195
192
|
{
|
|
196
193
|
warnings: [
|
|
197
194
|
"The build returned no result. The flow may be empty or misconfigured."
|
|
@@ -201,29 +198,29 @@ function registerFlowBundleTool(server2) {
|
|
|
201
198
|
);
|
|
202
199
|
}
|
|
203
200
|
const output_ = result;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
201
|
+
return mcpResult2(
|
|
202
|
+
{ success: true, ...output_ },
|
|
203
|
+
{
|
|
204
|
+
next: [
|
|
205
|
+
"Use flow_simulate to test",
|
|
206
|
+
"Use api({ action: 'deploy' }) to publish"
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
);
|
|
213
210
|
} catch (error) {
|
|
214
211
|
return mcpError2(error, "Run flow_validate for detailed error messages");
|
|
215
212
|
}
|
|
216
213
|
}
|
|
217
214
|
);
|
|
218
215
|
}
|
|
219
|
-
function formatBytes(bytes) {
|
|
220
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
221
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
222
|
-
}
|
|
223
216
|
|
|
224
217
|
// src/tools/simulate.ts
|
|
225
218
|
import { z as z3 } from "zod";
|
|
226
|
-
import {
|
|
219
|
+
import {
|
|
220
|
+
simulateSource,
|
|
221
|
+
simulateTransformer,
|
|
222
|
+
simulateDestination
|
|
223
|
+
} from "@walkeros/cli";
|
|
227
224
|
import { schemas as schemas3 } from "@walkeros/cli/dev";
|
|
228
225
|
import { mcpResult as mcpResult3, mcpError as mcpError3 } from "@walkeros/core";
|
|
229
226
|
function registerFlowSimulateTool(server2) {
|
|
@@ -231,11 +228,11 @@ function registerFlowSimulateTool(server2) {
|
|
|
231
228
|
"flow_simulate",
|
|
232
229
|
{
|
|
233
230
|
title: "Simulate Flow",
|
|
234
|
-
description: 'Simulate events through a walkerOS flow without making real API calls. For destinations: event is a walkerOS event { name: "entity action", data: {...} }. For sources: event is { content: ..., trigger?: { type?, options? }, env?: {...} }. Use step to target a specific step. Use flow_examples to discover available test data.',
|
|
231
|
+
description: 'Simulate events through a walkerOS flow without making real API calls. For destinations: event is a walkerOS event { name: "entity action", data: {...} }. For sources: event is { content: ..., trigger?: { type?, options? }, env?: {...} }. Use step to target a specific step. Use flow_examples to discover available test data. IMPORTANT: Destinations with require (e.g. require: ["consent"]) stay pending until that collector event fires \u2014 simulation will error "not found" if require is not satisfied. Remove require from config or provide consent/user events before simulating. Separately, destinations with consent (e.g. consent: { marketing: true }) only receive events where the event includes matching consent. Mapping transforms event names and data at the destination level. Policy redacts or injects fields before mapping runs.',
|
|
235
232
|
inputSchema: {
|
|
236
233
|
configPath: schemas3.SimulateInputShape.configPath,
|
|
237
234
|
event: z3.union([z3.record(z3.string(), z3.unknown()), z3.string()]).optional().describe(
|
|
238
|
-
"For destinations: { name, data }. For sources: { content, trigger?, env? }. Can also be a JSON string or file path."
|
|
235
|
+
"For destinations: { name, data, consent? }. Include consent (e.g. { marketing: true }) to satisfy destination consent requirements. For sources: { content, trigger?, env? }. Can also be a JSON string or file path."
|
|
239
236
|
),
|
|
240
237
|
flow: schemas3.SimulateInputShape.flow,
|
|
241
238
|
platform: schemas3.SimulateInputShape.platform,
|
|
@@ -257,24 +254,76 @@ function registerFlowSimulateTool(server2) {
|
|
|
257
254
|
"event is required. For sources provide { content, trigger? }, for destinations provide { name, data }."
|
|
258
255
|
);
|
|
259
256
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
|
|
257
|
+
if (!step) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
'step is required. Specify a target like "source.browser", "destination.gtag", or "transformer.demo".'
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
let resolvedEvent = event;
|
|
263
|
+
if (typeof event === "string") {
|
|
264
|
+
try {
|
|
265
|
+
resolvedEvent = JSON.parse(event);
|
|
266
|
+
} catch {
|
|
267
|
+
throw new Error(
|
|
268
|
+
"Event string must be valid JSON. Got: " + event.substring(0, 50)
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const dotIndex = step.indexOf(".");
|
|
273
|
+
if (dotIndex === -1) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Invalid step format "${step}". Use "type.name" (e.g. "source.browser", "destination.gtag").`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
const stepType = step.substring(0, dotIndex);
|
|
279
|
+
const stepId = step.substring(dotIndex + 1);
|
|
280
|
+
let result;
|
|
281
|
+
switch (stepType) {
|
|
282
|
+
case "source":
|
|
283
|
+
result = await simulateSource(configPath, resolvedEvent, {
|
|
284
|
+
sourceId: stepId,
|
|
285
|
+
flow,
|
|
286
|
+
silent: true
|
|
287
|
+
});
|
|
288
|
+
break;
|
|
289
|
+
case "transformer":
|
|
290
|
+
result = await simulateTransformer(
|
|
291
|
+
configPath,
|
|
292
|
+
resolvedEvent,
|
|
293
|
+
{
|
|
294
|
+
transformerId: stepId,
|
|
295
|
+
flow,
|
|
296
|
+
silent: true
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
break;
|
|
300
|
+
case "destination":
|
|
301
|
+
result = await simulateDestination(
|
|
302
|
+
configPath,
|
|
303
|
+
resolvedEvent,
|
|
304
|
+
{
|
|
305
|
+
destinationId: stepId,
|
|
306
|
+
flow,
|
|
307
|
+
silent: true
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
break;
|
|
311
|
+
default:
|
|
312
|
+
throw new Error(
|
|
313
|
+
`Unknown step type "${stepType}". Use "source", "transformer", or "destination".`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
if (result.captured && result.captured.length > 0) {
|
|
317
|
+
const eventCount = result.captured.length;
|
|
268
318
|
const summary2 = `Source captured ${eventCount} event${eventCount !== 1 ? "s" : ""}`;
|
|
269
319
|
return mcpResult3(
|
|
270
320
|
{
|
|
271
|
-
success:
|
|
272
|
-
error:
|
|
321
|
+
success: result.success,
|
|
322
|
+
error: result.error,
|
|
273
323
|
summary: summary2,
|
|
274
|
-
capturedEvents:
|
|
275
|
-
duration:
|
|
324
|
+
capturedEvents: result.captured,
|
|
325
|
+
duration: result.duration
|
|
276
326
|
},
|
|
277
|
-
summary2,
|
|
278
327
|
{
|
|
279
328
|
next: eventCount > 0 ? [
|
|
280
329
|
"Use flow_simulate with a destination step to test downstream processing"
|
|
@@ -285,14 +334,20 @@ function registerFlowSimulateTool(server2) {
|
|
|
285
334
|
);
|
|
286
335
|
}
|
|
287
336
|
const destinations = {};
|
|
288
|
-
if (
|
|
289
|
-
|
|
337
|
+
if (result.elbResult && typeof result.elbResult === "object" && "done" in result.elbResult && result.elbResult.done) {
|
|
338
|
+
const done = result.elbResult.done;
|
|
339
|
+
for (const name of Object.keys(done)) {
|
|
340
|
+
destinations[name] = { received: true, calls: 0 };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (result.usage) {
|
|
344
|
+
for (const [name, calls] of Object.entries(result.usage)) {
|
|
290
345
|
const summary2 = {
|
|
291
346
|
received: calls.length > 0,
|
|
292
347
|
calls: calls.length
|
|
293
348
|
};
|
|
294
349
|
if (verbose && calls.length > 0) {
|
|
295
|
-
summary2.payload = calls
|
|
350
|
+
summary2.payload = calls;
|
|
296
351
|
}
|
|
297
352
|
destinations[name] = summary2;
|
|
298
353
|
}
|
|
@@ -302,28 +357,30 @@ function registerFlowSimulateTool(server2) {
|
|
|
302
357
|
(d) => d.received
|
|
303
358
|
).length;
|
|
304
359
|
const warnings = [];
|
|
305
|
-
if (destCount === 0) {
|
|
306
|
-
warnings.push("No destinations found in flow configuration.");
|
|
307
|
-
}
|
|
308
|
-
if (destCount > 0 && receivedCount === 0) {
|
|
360
|
+
if (stepType === "destination" && destCount === 0) {
|
|
309
361
|
warnings.push(
|
|
310
|
-
|
|
362
|
+
'Destination did not receive the event. Common causes: (1) destination config has consent: { marketing: true } but event lacks matching consent, (2) mapping rules do not match the event name, (3) policy redacted required fields. Add consent to the event: { name: "...", data: {...}, consent: { marketing: true } }.'
|
|
311
363
|
);
|
|
312
364
|
}
|
|
313
|
-
const summary = `${receivedCount}/${destCount} destinations received the event`;
|
|
314
|
-
const
|
|
315
|
-
success:
|
|
316
|
-
error:
|
|
365
|
+
const summary = stepType === "transformer" ? `Transformer processed event` : `${receivedCount}/${destCount} destinations received the event`;
|
|
366
|
+
const resultObj = {
|
|
367
|
+
success: result.success,
|
|
368
|
+
error: result.error,
|
|
317
369
|
summary,
|
|
318
370
|
destinations: destCount > 0 ? destinations : void 0,
|
|
319
|
-
duration:
|
|
371
|
+
duration: result.duration
|
|
320
372
|
};
|
|
321
|
-
return mcpResult3(
|
|
373
|
+
return mcpResult3(resultObj, {
|
|
322
374
|
next: ["Use flow_bundle to build for production"],
|
|
323
375
|
...warnings.length > 0 ? { warnings } : {}
|
|
324
376
|
});
|
|
325
377
|
} catch (error) {
|
|
326
|
-
|
|
378
|
+
const msg = error instanceof Error ? error.message : "";
|
|
379
|
+
let hint = "Run flow_validate for detailed error messages";
|
|
380
|
+
if (msg.includes("not found in collector")) {
|
|
381
|
+
hint = 'If this destination has require: ["consent"] or require: ["user"], it stays pending until that event fires. For simulation, either remove require from the config or simulate with a flow that omits require on the target destination.';
|
|
382
|
+
}
|
|
383
|
+
return mcpError3(error, hint);
|
|
327
384
|
}
|
|
328
385
|
}
|
|
329
386
|
);
|
|
@@ -369,8 +426,7 @@ function registerFlowPushTool(server2) {
|
|
|
369
426
|
"Check destination configuration and connectivity."
|
|
370
427
|
);
|
|
371
428
|
}
|
|
372
|
-
|
|
373
|
-
return mcpResult4(result, summary);
|
|
429
|
+
return mcpResult4(result);
|
|
374
430
|
} catch (error) {
|
|
375
431
|
return mcpError4(
|
|
376
432
|
error,
|
|
@@ -456,23 +512,20 @@ function registerFlowExamplesTool(server2) {
|
|
|
456
512
|
}
|
|
457
513
|
}
|
|
458
514
|
}
|
|
459
|
-
const stepSet = new Set(examples.map((e) => e.step));
|
|
460
515
|
const result = {
|
|
461
516
|
flow: flowName,
|
|
462
517
|
count: examples.length,
|
|
463
518
|
examples
|
|
464
519
|
};
|
|
465
|
-
const totalExamples = examples.length;
|
|
466
|
-
const summary = `${totalExamples} examples across ${stepSet.size} steps`;
|
|
467
520
|
const hints = {
|
|
468
521
|
next: ["Use flow_simulate with step and event to simulate"]
|
|
469
522
|
};
|
|
470
|
-
if (
|
|
523
|
+
if (examples.length === 0) {
|
|
471
524
|
hints.warnings = [
|
|
472
525
|
"No examples found. Add examples to step definitions in your flow config for testing."
|
|
473
526
|
];
|
|
474
527
|
}
|
|
475
|
-
return mcpResult5(result,
|
|
528
|
+
return mcpResult5(result, hints);
|
|
476
529
|
} catch (error) {
|
|
477
530
|
return mcpError5(error, "Check configPath \u2014 expected a flow.json file");
|
|
478
531
|
}
|
|
@@ -511,13 +564,27 @@ async function fetchCatalog(filters) {
|
|
|
511
564
|
}
|
|
512
565
|
let entries;
|
|
513
566
|
try {
|
|
514
|
-
entries = await fetchFromNpm();
|
|
567
|
+
entries = filters?.baseUrl ? await fetchCatalogFrom(filters.baseUrl, filters) : await fetchFromNpm();
|
|
515
568
|
} catch {
|
|
516
|
-
|
|
569
|
+
try {
|
|
570
|
+
entries = await fetchFromNpm();
|
|
571
|
+
} catch {
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
517
574
|
}
|
|
518
575
|
cache = { entries, timestamp: Date.now() };
|
|
519
576
|
return applyFilters(entries, filters);
|
|
520
577
|
}
|
|
578
|
+
async function fetchCatalogFrom(baseUrl, filters) {
|
|
579
|
+
const params = new URLSearchParams();
|
|
580
|
+
if (filters?.type) params.set("type", filters.type);
|
|
581
|
+
if (filters?.platform) params.set("platform", filters.platform);
|
|
582
|
+
const url = `${baseUrl}/api/packages${params.toString() ? `?${params}` : ""}`;
|
|
583
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(15e3) });
|
|
584
|
+
if (!res.ok) throw new Error(`Catalog fetch failed: ${res.status}`);
|
|
585
|
+
const data = await res.json();
|
|
586
|
+
return data.catalog;
|
|
587
|
+
}
|
|
521
588
|
async function fetchFromNpm() {
|
|
522
589
|
const res = await fetch(`${NPM_SEARCH_URL}?text=@walkeros/&size=250`, {
|
|
523
590
|
signal: AbortSignal.timeout(1e4)
|
|
@@ -571,7 +638,7 @@ function registerPackageSearchTool(server2) {
|
|
|
571
638
|
"package_search",
|
|
572
639
|
{
|
|
573
640
|
title: "Search Package",
|
|
574
|
-
description: "
|
|
641
|
+
description: "Start here for package discovery. Never guess package names \u2014 use this tool first to find exact names. Without package name: returns catalog filtered by type/platform. With package name: returns metadata, hint keys, and example summaries.",
|
|
575
642
|
inputSchema: {
|
|
576
643
|
package: z6.string().min(1).optional().describe(
|
|
577
644
|
"Exact npm package name for detailed lookup (e.g., @walkeros/web-destination-snowplow)"
|
|
@@ -591,16 +658,16 @@ function registerPackageSearchTool(server2) {
|
|
|
591
658
|
}
|
|
592
659
|
},
|
|
593
660
|
async ({ package: packageName, type, platform, version }) => {
|
|
661
|
+
const baseUrl = process.env.APP_URL || void 0;
|
|
594
662
|
if (!packageName) {
|
|
595
|
-
const catalog = await fetchCatalog({ type, platform });
|
|
663
|
+
const catalog = await fetchCatalog({ type, platform, baseUrl });
|
|
596
664
|
const result = { catalog, count: catalog.length };
|
|
597
|
-
|
|
598
|
-
return mcpResult6(result, summary, {
|
|
665
|
+
return mcpResult6(result, {
|
|
599
666
|
next: ["Use package_get for schemas and examples"]
|
|
600
667
|
});
|
|
601
668
|
}
|
|
602
669
|
try {
|
|
603
|
-
const info = await fetchPackage(packageName, { version });
|
|
670
|
+
const info = await fetchPackage(packageName, { version, baseUrl });
|
|
604
671
|
const result = {
|
|
605
672
|
package: info.packageName,
|
|
606
673
|
version: info.version,
|
|
@@ -610,8 +677,7 @@ function registerPackageSearchTool(server2) {
|
|
|
610
677
|
hintKeys: info.hintKeys,
|
|
611
678
|
exampleSummaries: info.exampleSummaries
|
|
612
679
|
};
|
|
613
|
-
|
|
614
|
-
return mcpResult6(result, summary, {
|
|
680
|
+
return mcpResult6(result, {
|
|
615
681
|
next: ["Use package_get for schemas and examples"]
|
|
616
682
|
});
|
|
617
683
|
} catch (error) {
|
|
@@ -628,7 +694,7 @@ function registerGetPackageSchemaTool(server2) {
|
|
|
628
694
|
"package_get",
|
|
629
695
|
{
|
|
630
696
|
title: "Get Package",
|
|
631
|
-
description: '
|
|
697
|
+
description: 'Requires exact package name \u2014 do not guess names, use package_search first to find them. Returns schemas + hint texts + example summaries by default (lightweight). Use section parameter for full content: "hints" (with code blocks), "examples" (full in/out data), or "all".',
|
|
632
698
|
inputSchema: {
|
|
633
699
|
package: z6.string().min(1).describe(
|
|
634
700
|
"Exact npm package name (e.g., @walkeros/web-destination-snowplow)"
|
|
@@ -647,8 +713,9 @@ function registerGetPackageSchemaTool(server2) {
|
|
|
647
713
|
}
|
|
648
714
|
},
|
|
649
715
|
async ({ package: packageName, version, section }) => {
|
|
716
|
+
const baseUrl = process.env.APP_URL || void 0;
|
|
650
717
|
try {
|
|
651
|
-
const info = await fetchPackage(packageName, { version });
|
|
718
|
+
const info = await fetchPackage(packageName, { version, baseUrl });
|
|
652
719
|
const mergedSchemas = {};
|
|
653
720
|
if (info.type) {
|
|
654
721
|
mergedSchemas.config = mergeConfigSchema(
|
|
@@ -685,10 +752,7 @@ function registerGetPackageSchemaTool(server2) {
|
|
|
685
752
|
} else {
|
|
686
753
|
result.exampleSummaries = info.exampleSummaries;
|
|
687
754
|
}
|
|
688
|
-
|
|
689
|
-
const exampleCount = info.exampleSummaries.length;
|
|
690
|
-
const summary = `${info.packageName} \u2014 ${schemaCount} schemas, ${exampleCount} examples`;
|
|
691
|
-
return mcpResult6(result, summary);
|
|
755
|
+
return mcpResult6(result);
|
|
692
756
|
} catch (error) {
|
|
693
757
|
return mcpError6(
|
|
694
758
|
error,
|
|
@@ -754,16 +818,12 @@ function registerFlowLoadTool(server2) {
|
|
|
754
818
|
try {
|
|
755
819
|
if (source) {
|
|
756
820
|
const config = await loadJsonConfig2(source);
|
|
757
|
-
return mcpResult7(
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
"Use add-step prompt to modify"
|
|
764
|
-
]
|
|
765
|
-
}
|
|
766
|
-
);
|
|
821
|
+
return mcpResult7(config, {
|
|
822
|
+
next: [
|
|
823
|
+
"Use flow_validate to check",
|
|
824
|
+
"Use add-step prompt to modify"
|
|
825
|
+
]
|
|
826
|
+
});
|
|
767
827
|
}
|
|
768
828
|
if (!platform) {
|
|
769
829
|
return mcpError7(
|
|
@@ -773,16 +833,12 @@ function registerFlowLoadTool(server2) {
|
|
|
773
833
|
);
|
|
774
834
|
}
|
|
775
835
|
const skeleton = platform === "web" ? WEB_SKELETON : SERVER_SKELETON;
|
|
776
|
-
return mcpResult7(
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
"Use add-step prompt to add sources and destinations"
|
|
783
|
-
]
|
|
784
|
-
}
|
|
785
|
-
);
|
|
836
|
+
return mcpResult7(skeleton, {
|
|
837
|
+
next: [
|
|
838
|
+
"Read walkeros://reference/flow-schema for config structure",
|
|
839
|
+
"Use add-step prompt to add sources and destinations"
|
|
840
|
+
]
|
|
841
|
+
});
|
|
786
842
|
} catch (error) {
|
|
787
843
|
const msg = error instanceof Error ? error.message : "";
|
|
788
844
|
if (msg.includes("not found") || msg.includes("ENOENT"))
|
|
@@ -827,7 +883,6 @@ function registerFeedbackTool(server2) {
|
|
|
827
883
|
if (anonymous === void 0 && explicitAnonymous === void 0) {
|
|
828
884
|
return mcpResult8(
|
|
829
885
|
{ needsConsent: true },
|
|
830
|
-
'Before sending feedback, ask the user: "Would you like to include your user and project info with feedback? This is a one-time choice." Then call feedback again with the anonymous parameter set.',
|
|
831
886
|
{
|
|
832
887
|
next: [
|
|
833
888
|
"Ask the user if they want to include their info",
|
|
@@ -842,8 +897,8 @@ function registerFeedbackTool(server2) {
|
|
|
842
897
|
writeConfig({ ...base, anonymousFeedback: anonymous });
|
|
843
898
|
}
|
|
844
899
|
const isAnonymous = explicitAnonymous ?? anonymous ?? true;
|
|
845
|
-
await feedback(text, { anonymous: isAnonymous, version: "3.
|
|
846
|
-
return mcpResult8({ ok: true }
|
|
900
|
+
await feedback(text, { anonymous: isAnonymous, version: "3.2.0-next-1775064795590" });
|
|
901
|
+
return mcpResult8({ ok: true });
|
|
847
902
|
} catch (error) {
|
|
848
903
|
return mcpError8(error);
|
|
849
904
|
}
|
|
@@ -956,40 +1011,33 @@ function registerApiTool(server2) {
|
|
|
956
1011
|
} = params;
|
|
957
1012
|
try {
|
|
958
1013
|
let data;
|
|
959
|
-
let summary;
|
|
960
1014
|
switch (action) {
|
|
961
1015
|
// Auth
|
|
962
1016
|
case "whoami": {
|
|
963
1017
|
data = await whoami();
|
|
964
|
-
summary = `Authenticated as ${data.email}`;
|
|
965
1018
|
break;
|
|
966
1019
|
}
|
|
967
1020
|
// Projects
|
|
968
1021
|
case "project.list": {
|
|
969
1022
|
data = await listProjects();
|
|
970
|
-
summary = `${(data.projects ?? []).length} projects`;
|
|
971
1023
|
break;
|
|
972
1024
|
}
|
|
973
1025
|
case "project.get": {
|
|
974
1026
|
data = await getProject({ projectId });
|
|
975
|
-
summary = `Project "${data.name}"`;
|
|
976
1027
|
break;
|
|
977
1028
|
}
|
|
978
1029
|
case "project.create": {
|
|
979
1030
|
if (!name) throw new Error("name required for project.create");
|
|
980
1031
|
data = await createProject({ name });
|
|
981
|
-
summary = `Created project "${name}"`;
|
|
982
1032
|
break;
|
|
983
1033
|
}
|
|
984
1034
|
case "project.update": {
|
|
985
1035
|
if (!name) throw new Error("name required for project.update");
|
|
986
1036
|
data = await updateProject({ projectId, name });
|
|
987
|
-
summary = `Updated project "${name}"`;
|
|
988
1037
|
break;
|
|
989
1038
|
}
|
|
990
1039
|
case "project.delete": {
|
|
991
1040
|
data = await deleteProject({ projectId });
|
|
992
|
-
summary = `Deleted project ${projectId ?? "default"}`;
|
|
993
1041
|
break;
|
|
994
1042
|
}
|
|
995
1043
|
// Flows
|
|
@@ -1000,20 +1048,17 @@ function registerApiTool(server2) {
|
|
|
1000
1048
|
order,
|
|
1001
1049
|
includeDeleted
|
|
1002
1050
|
});
|
|
1003
|
-
summary = `${(data.flows ?? []).length} flows`;
|
|
1004
1051
|
break;
|
|
1005
1052
|
}
|
|
1006
1053
|
case "flow.get": {
|
|
1007
1054
|
if (!flowId) throw new Error("flowId required for flow.get");
|
|
1008
1055
|
data = await getFlow({ flowId, projectId, fields });
|
|
1009
|
-
summary = `Flow "${data.name}" (${flowId})`;
|
|
1010
1056
|
break;
|
|
1011
1057
|
}
|
|
1012
1058
|
case "flow.create": {
|
|
1013
1059
|
if (!name) throw new Error("name required for flow.create");
|
|
1014
1060
|
if (!content) throw new Error("content required for flow.create");
|
|
1015
1061
|
data = await createFlow({ name, content, projectId });
|
|
1016
|
-
summary = `Created flow "${name}" (${data.id})`;
|
|
1017
1062
|
break;
|
|
1018
1063
|
}
|
|
1019
1064
|
case "flow.update": {
|
|
@@ -1025,19 +1070,16 @@ function registerApiTool(server2) {
|
|
|
1025
1070
|
projectId,
|
|
1026
1071
|
mergePatch: patch ?? true
|
|
1027
1072
|
});
|
|
1028
|
-
summary = `Updated flow ${flowId}`;
|
|
1029
1073
|
break;
|
|
1030
1074
|
}
|
|
1031
1075
|
case "flow.delete": {
|
|
1032
1076
|
if (!flowId) throw new Error("flowId required for flow.delete");
|
|
1033
1077
|
data = await deleteFlow({ flowId, projectId });
|
|
1034
|
-
summary = `Deleted flow ${flowId}`;
|
|
1035
1078
|
break;
|
|
1036
1079
|
}
|
|
1037
1080
|
case "flow.duplicate": {
|
|
1038
1081
|
if (!flowId) throw new Error("flowId required for flow.duplicate");
|
|
1039
1082
|
data = await duplicateFlow({ flowId, name, projectId });
|
|
1040
|
-
summary = `Duplicated flow ${flowId}`;
|
|
1041
1083
|
break;
|
|
1042
1084
|
}
|
|
1043
1085
|
// Deploy
|
|
@@ -1098,12 +1140,13 @@ function registerApiTool(server2) {
|
|
|
1098
1140
|
const st = data.status;
|
|
1099
1141
|
const deployData = data;
|
|
1100
1142
|
if (st === "failed") {
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1143
|
+
return mcpResult9(
|
|
1144
|
+
{ action, ok: false, data },
|
|
1145
|
+
{
|
|
1146
|
+
next: ["Run flow_validate to check your configuration"]
|
|
1147
|
+
}
|
|
1148
|
+
);
|
|
1105
1149
|
} else {
|
|
1106
|
-
summary = `Deployed flow ${flowId} \u2014 status: ${st}`;
|
|
1107
1150
|
const publicUrl = deployData.publicUrl;
|
|
1108
1151
|
const containerUrl = deployData.containerUrl;
|
|
1109
1152
|
const deployType = deployData.type;
|
|
@@ -1116,9 +1159,12 @@ function registerApiTool(server2) {
|
|
|
1116
1159
|
nextHints.push(`Test: curl ${containerUrl}/health`);
|
|
1117
1160
|
}
|
|
1118
1161
|
if (nextHints.length > 0) {
|
|
1119
|
-
return mcpResult9(
|
|
1120
|
-
|
|
1121
|
-
|
|
1162
|
+
return mcpResult9(
|
|
1163
|
+
{ action, ok: true, data },
|
|
1164
|
+
{
|
|
1165
|
+
next: nextHints
|
|
1166
|
+
}
|
|
1167
|
+
);
|
|
1122
1168
|
}
|
|
1123
1169
|
}
|
|
1124
1170
|
break;
|
|
@@ -1134,7 +1180,6 @@ function registerApiTool(server2) {
|
|
|
1134
1180
|
} catch {
|
|
1135
1181
|
data = await getDeploymentBySlug({ slug: flowId });
|
|
1136
1182
|
}
|
|
1137
|
-
summary = `Deployment ${data.slug ?? flowId} \u2014 ${data.status}`;
|
|
1138
1183
|
break;
|
|
1139
1184
|
}
|
|
1140
1185
|
case "deployment.list": {
|
|
@@ -1143,7 +1188,6 @@ function registerApiTool(server2) {
|
|
|
1143
1188
|
type,
|
|
1144
1189
|
status
|
|
1145
1190
|
});
|
|
1146
|
-
summary = `${(data.deployments ?? []).length} deployments`;
|
|
1147
1191
|
break;
|
|
1148
1192
|
}
|
|
1149
1193
|
case "deployment.create": {
|
|
@@ -1152,14 +1196,12 @@ function registerApiTool(server2) {
|
|
|
1152
1196
|
"type (web/server) required for deployment.create"
|
|
1153
1197
|
);
|
|
1154
1198
|
data = await createDep({ type, label: name, projectId });
|
|
1155
|
-
summary = `Created ${type} deployment ${data.slug}`;
|
|
1156
1199
|
break;
|
|
1157
1200
|
}
|
|
1158
1201
|
case "deployment.delete": {
|
|
1159
1202
|
if (!flowId)
|
|
1160
1203
|
throw new Error("flowId (slug) required for deployment.delete");
|
|
1161
1204
|
data = await deleteDep({ slug: flowId });
|
|
1162
|
-
summary = `Deleted deployment ${flowId}`;
|
|
1163
1205
|
break;
|
|
1164
1206
|
}
|
|
1165
1207
|
default:
|
|
@@ -1167,7 +1209,7 @@ function registerApiTool(server2) {
|
|
|
1167
1209
|
`Unknown action: ${action}. Use one of: ${ACTIONS.join(", ")}`
|
|
1168
1210
|
);
|
|
1169
1211
|
}
|
|
1170
|
-
return mcpResult9({ action, ok: true, data }
|
|
1212
|
+
return mcpResult9({ action, ok: true, data });
|
|
1171
1213
|
} catch (error) {
|
|
1172
1214
|
const msg = error instanceof Error ? error.message : "";
|
|
1173
1215
|
const name2 = error instanceof Error ? error.name : "";
|
|
@@ -1178,7 +1220,6 @@ function registerApiTool(server2) {
|
|
|
1178
1220
|
ok: true,
|
|
1179
1221
|
data: { status: "deploying", flowId }
|
|
1180
1222
|
},
|
|
1181
|
-
`Deploy in progress (timed out waiting). Use deployment.list with projectId to check status.`,
|
|
1182
1223
|
{
|
|
1183
1224
|
next: [
|
|
1184
1225
|
'Use api(action: "deployment.list") to check current status'
|
|
@@ -1504,9 +1545,10 @@ function registerAddStepPrompt(server2) {
|
|
|
1504
1545
|
"4. Scaffold the step config using the package schemas \u2014 include required settings with placeholder values.",
|
|
1505
1546
|
"5. Wire the step into the flow: add to packages section (with version if needed), connect via next/before chains if needed.",
|
|
1506
1547
|
'6. For destinations: configure mapping using nested entity \u2192 action keys. Event "product add" maps to `{ "product": { "add": { name: "AddToCart" } } }`. Use the setup-mapping prompt for guidance.',
|
|
1507
|
-
|
|
1508
|
-
"8.
|
|
1509
|
-
"9.
|
|
1548
|
+
'7. For destinations: if consent-gated loading is needed, add require: ["consent"] to config. Note: require delays initialization until a "walker consent" event fires. When simulating with flow_simulate, destinations with require will error "not found" \u2014 remove require temporarily or test without it. For per-event consent filtering, add consent: { marketing: true } to config.',
|
|
1549
|
+
"8. Use flow_validate to verify the result.",
|
|
1550
|
+
"9. For server sources: check if the package supports `ingest` configuration via package_get. Ingest extracts request metadata (IP, user-agent, headers) using mapping syntax. Transformers like fingerprint depend on ingest data.",
|
|
1551
|
+
"10. When adding a transformer that uses ingest fields, verify the source has `ingest` configured \u2014 otherwise ingest fields resolve to empty values.",
|
|
1510
1552
|
"",
|
|
1511
1553
|
"Important:",
|
|
1512
1554
|
"- Read the walkeros://reference/flow-schema resource to understand connection rules.",
|
|
@@ -1554,7 +1596,13 @@ function registerSetupMappingPrompt(server2) {
|
|
|
1554
1596
|
"",
|
|
1555
1597
|
'Mapping uses nested entity \u2192 action keys. Event "product add" maps to `{ "product": { "add": Rule } }`. Wildcards: `{ "*": { "view": Rule } }`.',
|
|
1556
1598
|
"",
|
|
1557
|
-
"Use $def references for shared mapping patterns across destinations."
|
|
1599
|
+
"Use $def references for shared mapping patterns across destinations.",
|
|
1600
|
+
"",
|
|
1601
|
+
"Policy and consent in mapping:",
|
|
1602
|
+
"- config.policy runs BEFORE mapping rules \u2014 use it to inject or redact fields on the event.",
|
|
1603
|
+
"- rule.policy runs after config.policy but before data transformation \u2014 use for event-specific pre-processing.",
|
|
1604
|
+
"- config.consent gates ALL events to this destination. rule.consent gates specific event types.",
|
|
1605
|
+
"- Individual value configs support consent: { marketing: true } for field-level gating."
|
|
1558
1606
|
].join("\n")
|
|
1559
1607
|
}
|
|
1560
1608
|
}
|
|
@@ -1660,19 +1708,36 @@ function registerUseDefinitionsPrompt(server2) {
|
|
|
1660
1708
|
var server = new McpServer(
|
|
1661
1709
|
{
|
|
1662
1710
|
name: "walkeros-flow",
|
|
1663
|
-
version: "3.
|
|
1711
|
+
version: "3.2.0-next-1775064795590"
|
|
1664
1712
|
},
|
|
1665
1713
|
{
|
|
1666
1714
|
instructions: `walkerOS is an open-source, privacy-first event data collection platform. Define event pipelines as code using JSON flow configurations.
|
|
1667
1715
|
|
|
1716
|
+
## Rules
|
|
1717
|
+
|
|
1718
|
+
- **Never guess package names.** Always use \`package_search\` first to find exact names, then \`package_get\` for details.
|
|
1719
|
+
- **Never construct flow configs from memory.** Read \`walkeros://reference/flow-schema\` and use \`package_get\` for package-specific schemas.
|
|
1720
|
+
- **Always validate.** Run \`flow_validate\` after every config change. If validation fails, fix and re-validate.
|
|
1721
|
+
- **Simulate before deploying.** Use \`flow_simulate\` to test with mocked API calls before \`flow_bundle\` or \`flow_push\`.
|
|
1722
|
+
|
|
1723
|
+
## Workflow
|
|
1724
|
+
|
|
1725
|
+
1. \`flow_load({ platform: "web" })\` or \`flow_load({ source: "./flow.json" })\` \u2014 create or load
|
|
1726
|
+
2. \`package_search({ type: "destination", platform: "web" })\` \u2014 discover packages
|
|
1727
|
+
3. \`package_get({ package: "..." })\` \u2014 read schemas, hints, examples
|
|
1728
|
+
4. Use the \`add-step\` prompt \u2014 guided step addition
|
|
1729
|
+
5. Use the \`setup-mapping\` prompt \u2014 event transformation config
|
|
1730
|
+
6. \`flow_validate({ type: "flow", input: "flow.json" })\` \u2014 verify
|
|
1731
|
+
7. \`flow_simulate({ configPath: "flow.json", event: "..." })\` \u2014 test
|
|
1732
|
+
8. \`flow_bundle({ configPath: "flow.json" })\` \u2014 build
|
|
1733
|
+
9. \`api({ action: "deploy", id: "cfg_..." })\` \u2014 deploy (requires WALKEROS_TOKEN)
|
|
1734
|
+
|
|
1668
1735
|
## Architecture: Source \u2192 Collector \u2192 Destination(s)
|
|
1669
1736
|
|
|
1670
1737
|
Every component in a flow is a **step**: sources capture events, transformers process them, destinations deliver them, stores provide shared state. Steps connect via \`next\` (pre-collector) and \`before\` (post-collector) chains.
|
|
1671
1738
|
|
|
1672
1739
|
## Flow Config Structure
|
|
1673
1740
|
|
|
1674
|
-
Every flow config follows this shape:
|
|
1675
|
-
|
|
1676
1741
|
\`\`\`json
|
|
1677
1742
|
{
|
|
1678
1743
|
"version": 3,
|
|
@@ -1686,38 +1751,28 @@ Every flow config follows this shape:
|
|
|
1686
1751
|
}
|
|
1687
1752
|
\`\`\`
|
|
1688
1753
|
|
|
1689
|
-
Event format: \`{ name: "entity action", data: {...}, entity: "...", action: "..." }\`. Sources convert raw input into this format.
|
|
1690
|
-
|
|
1691
|
-
Key rules:
|
|
1692
1754
|
- \`version: 3\` is required
|
|
1693
1755
|
- Each flow must have exactly one of \`web: {}\` or \`server: {}\`
|
|
1694
1756
|
- Destination settings go inside \`config.settings\`, not directly on the destination
|
|
1695
|
-
-
|
|
1696
|
-
|
|
1697
|
-
## Getting Started
|
|
1698
|
-
|
|
1699
|
-
1. \`flow_load({ platform: "web" })\` or \`flow_load({ source: "./flow.json" })\` \u2014 create or load a flow (also accepts inline JSON strings or URLs)
|
|
1700
|
-
2. \`package_search({ type: "destination", platform: "web" })\` \u2014 discover available packages
|
|
1701
|
-
3. Use the \`add-step\` prompt to add sources, destinations, transformers, or stores
|
|
1702
|
-
4. Use the \`setup-mapping\` prompt to configure event transformations
|
|
1703
|
-
5. \`flow_validate({ type: "flow", input: "flow.json" })\` \u2014 verify configuration
|
|
1704
|
-
6. \`flow_simulate({ configPath: "flow.json", event: "..." })\` \u2014 test with mocked API calls
|
|
1705
|
-
7. \`flow_bundle({ configPath: "flow.json" })\` \u2014 build deployable JavaScript
|
|
1706
|
-
8. \`api({ action: "deploy", id: "cfg_..." })\` \u2014 deploy to walkerOS cloud (requires WALKEROS_TOKEN env var; unavailable without it)
|
|
1707
|
-
|
|
1708
|
-
If validation fails, fix the reported errors and re-validate. Do not skip validation.
|
|
1709
|
-
|
|
1710
|
-
## Reference Resources
|
|
1711
|
-
|
|
1712
|
-
Read these before constructing configs manually: \`walkeros://reference/flow-schema\`, \`walkeros://reference/mapping\`, \`walkeros://reference/event-model\`, \`walkeros://reference/consent\`, \`walkeros://reference/variables\`, \`walkeros://reference/contract\`, \`walkeros://reference/examples\`.
|
|
1757
|
+
- Event format: \`{ name: "entity action", data: {...}, entity: "...", action: "..." }\`
|
|
1713
1758
|
|
|
1714
1759
|
## Key Concepts
|
|
1715
1760
|
|
|
1716
|
-
- **Steps** are sources, destinations, transformers, or stores \u2014 each backed by an npm package. Use \`package_search\` to browse, \`package_get\` for schemas and examples. \`package_get\` returns \`schemas.config\` \u2014 a merged JSON Schema combining base config fields (consent, require, logger, mapping, etc.) with the package's typed settings. Non-config schemas (mapping rules, utility schemas) remain as sibling keys.
|
|
1717
1761
|
- **Mapping** transforms events using data/map/loop/set/condition rules. Same syntax on sources and destinations. Mapping rules use NESTED entity \u2192 action keying: event name "product add" maps to \`{ "product": { "add": Rule } }\`. Wildcards: \`{ "*": { "view": Rule } }\`.
|
|
1718
1762
|
- **Contracts** define event schemas using entity-action keying. Can generate FROM mappings or scaffold mappings FROM contracts.
|
|
1719
1763
|
- **Variables** ($var, $env, $def, $code, $store) enable DRY, environment-aware config. Use the \`use-definitions\` prompt to extract shared patterns.
|
|
1720
|
-
- **Consent** gates destinations, mapping rules, and individual fields. Privacy-first by design
|
|
1764
|
+
- **Consent** gates destinations, mapping rules, and individual fields. Privacy-first by design.
|
|
1765
|
+
|
|
1766
|
+
## Simulation Tips
|
|
1767
|
+
|
|
1768
|
+
- Destinations with \`require: ["consent"]\` stay **pending** until a \`"walker consent"\` event fires. Simulation will error "not found" for pending destinations \u2014 remove \`require\` from config when testing with \`flow_simulate\`.
|
|
1769
|
+
- Destinations with \`consent: { marketing: true }\` silently skip events that lack matching consent. Include \`consent\` in the event: \`{ name: "page view", data: {...}, consent: { marketing: true } }\`.
|
|
1770
|
+
- **Mapping** transforms event names and data at the destination level. Events without a matching mapping rule pass through unmodified.
|
|
1771
|
+
- **Policy** modifies the event before mapping runs \u2014 use it to inject computed fields or redact sensitive data.
|
|
1772
|
+
|
|
1773
|
+
## Reference Resources
|
|
1774
|
+
|
|
1775
|
+
Read these before constructing configs manually: \`walkeros://reference/flow-schema\`, \`walkeros://reference/mapping\`, \`walkeros://reference/event-model\`, \`walkeros://reference/consent\`, \`walkeros://reference/variables\`, \`walkeros://reference/contract\`, \`walkeros://reference/examples\`.`
|
|
1721
1776
|
}
|
|
1722
1777
|
);
|
|
1723
1778
|
registerFlowValidateTool(server);
|