@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 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("Full payload (only when verbose: true)")
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, summary, hints);
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
- const r = result2;
176
- const size2 = r.totalSize ?? r.size;
177
- const time2 = r.buildTime;
178
- const summary2 = `Bundled${size2 ? ` (${formatBytes(size2)}` : ""}${time2 ? `, ${time2}ms)` : size2 ? ")" : ""}`;
179
- return mcpResult2({ success: true, ...result2 }, summary2, {
180
- next: [
181
- "Use flow_simulate to test",
182
- "Use api({ action: 'deploy' }) to publish"
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
- const size = output_.totalSize;
205
- const time = output_.buildTime;
206
- const summary = `Bundled${size ? ` (${formatBytes(size)}` : ""}${time ? `, ${time}ms)` : size ? ")" : ""}`;
207
- return mcpResult2({ success: true, ...output_ }, summary, {
208
- next: [
209
- "Use flow_simulate to test",
210
- "Use api({ action: 'deploy' }) to publish"
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 { simulate } from "@walkeros/cli";
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
- const raw = await simulate(configPath, event, {
261
- json: true,
262
- flow,
263
- platform,
264
- step
265
- });
266
- if (raw.capturedEvents) {
267
- const eventCount = raw.capturedEvents.length;
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: raw.success,
272
- error: raw.error,
321
+ success: result.success,
322
+ error: result.error,
273
323
  summary: summary2,
274
- capturedEvents: raw.capturedEvents,
275
- duration: raw.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 (raw.usage) {
289
- for (const [name, calls] of Object.entries(raw.usage)) {
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[calls.length - 1];
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
- "No destinations received the event. Check: mapping keys use nested entity\u2192action structure, event name matches, consent is granted."
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 result = {
315
- success: raw.success,
316
- error: raw.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: raw.duration
371
+ duration: result.duration
320
372
  };
321
- return mcpResult3(result, summary, {
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
- return mcpError3(error, "Run flow_validate for detailed error messages");
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
- const summary = `Pushed event${result.duration ? ` (${result.duration}ms)` : ""}`;
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 (totalExamples === 0) {
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, summary, hints);
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
- return [];
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: "Browse walkerOS packages or look up a specific one. Without package name: returns catalog filtered by type/platform. With package name: returns metadata, hint keys, and example summaries.",
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
- const summary = `${catalog.length} packages found`;
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
- const summary = `${info.packageName} v${info.version}`;
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: 'Fetch walkerOS package details from npm. By default returns schemas + hint texts + example summaries (lightweight). Use section parameter to get full content: "hints" (with code blocks), "examples" (full in/out data), or "all" (everything). Use package_search first to browse available packages.',
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
- const schemaCount = Object.keys(info.schemas).length;
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
- config,
759
- `Loaded flow from ${source}. Use flow_validate to check, or add-step prompt to modify.`,
760
- {
761
- next: [
762
- "Use flow_validate to check",
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
- skeleton,
778
- `Created empty ${platform} flow. Use the add-step prompt to add sources, destinations, and transformers.`,
779
- {
780
- next: [
781
- "Read walkeros://reference/flow-schema for config structure",
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.1.0" });
846
- return mcpResult8({ ok: true }, "Feedback sent. Thanks!");
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
- const msg = `Deploy failed: ${deployData.errorMessage ?? "unknown error"}`;
1102
- return mcpResult9({ action, ok: false, data }, msg, {
1103
- next: ["Run flow_validate to check your configuration"]
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({ action, ok: true, data }, summary, {
1120
- next: nextHints
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 }, summary);
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
- "7. Use flow_validate to verify the result.",
1508
- "8. 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.",
1509
- "9. When adding a transformer that uses ingest fields, verify the source has `ingest` configured \u2014 otherwise ingest fields resolve to empty values.",
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.1.0"
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
- - Read \`walkeros://reference/flow-schema\` for the full annotated structure
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);