@walkeros/mcp 4.6.0 → 4.7.0-next-1790187973605

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
@@ -1,3 +1,98 @@
1
+ // src/cloud-flow.ts
2
+ import { isObject } from "@walkeros/core";
3
+ var CLOUD_ID_PATTERN = /^(flow|cfg)_[A-Za-z0-9_-]+$/;
4
+ function isCloudId(input) {
5
+ return CLOUD_ID_PATTERN.test(input);
6
+ }
7
+ function flowConfigOf(flow) {
8
+ return isObject(flow) && isObject(flow.config) ? flow.config : {};
9
+ }
10
+
11
+ // src/runtime/types.ts
12
+ var RuntimeRefusal = class extends Error {
13
+ hint;
14
+ constructor(message, hint) {
15
+ super(message);
16
+ this.name = "RuntimeRefusal";
17
+ this.hint = hint;
18
+ }
19
+ };
20
+ function refusalHint(error, fallback) {
21
+ return error instanceof RuntimeRefusal ? error.hint : fallback;
22
+ }
23
+ var HINT_OUT_OF_PROCESS = "Build and deploy through the app with deploy_manage, or simulate the flow in the app. On your own machine, use the walkerOS CLI.";
24
+ function unavailableOperation(operation) {
25
+ const verb = operation === "bundle" ? "Bundling" : operation === "simulate" ? "Simulating" : "Running";
26
+ return new RuntimeRefusal(
27
+ `${verb} a flow is not available on the hosted walkerOS MCP server.`,
28
+ HINT_OUT_OF_PROCESS
29
+ );
30
+ }
31
+
32
+ // src/runtime/hosted.ts
33
+ function isHttpUrl(value) {
34
+ try {
35
+ const url = new URL(value);
36
+ return url.protocol === "http:" || url.protocol === "https:";
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+ function looksLikePath(value) {
42
+ return /[\\/]/.test(value) || value.startsWith(".") || /\.[A-Za-z0-9]+$/.test(value);
43
+ }
44
+ function classifyConfigInput(input) {
45
+ const trimmed = input.trim();
46
+ if (isHttpUrl(trimmed)) return "url";
47
+ if (isCloudId(trimmed)) return "cloud-id";
48
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "inline-json";
49
+ if (looksLikePath(trimmed)) return "local-path";
50
+ return "bare-string";
51
+ }
52
+ var HINT_INLINE_OR_ID = "Pass the flow inline as JSON, or reference a saved flow by its flow_ or cfg_ id.";
53
+ function refuseLocalPath() {
54
+ return new RuntimeRefusal(
55
+ "Local file paths are not available on the hosted walkerOS MCP server.",
56
+ HINT_INLINE_OR_ID
57
+ );
58
+ }
59
+ function refuseUrl() {
60
+ return new RuntimeRefusal(
61
+ "Fetching URLs is not available on the hosted walkerOS MCP server.",
62
+ HINT_INLINE_OR_ID
63
+ );
64
+ }
65
+ function parseInline(trimmed) {
66
+ try {
67
+ return JSON.parse(trimmed);
68
+ } catch (error) {
69
+ const message = error instanceof Error ? error.message : String(error);
70
+ throw new Error(`Input appears to be JSON but contains errors: ${message}`);
71
+ }
72
+ }
73
+ function createHostedRuntime(client) {
74
+ return {
75
+ async load(input) {
76
+ const trimmed = input.trim();
77
+ if (trimmed === "") throw new Error("Input is required");
78
+ switch (classifyConfigInput(trimmed)) {
79
+ case "url":
80
+ throw refuseUrl();
81
+ case "cloud-id":
82
+ return flowConfigOf(await client.getFlow({ flowId: trimmed }));
83
+ case "inline-json":
84
+ return parseInline(trimmed);
85
+ case "local-path":
86
+ throw refuseLocalPath();
87
+ case "bare-string":
88
+ throw new Error(
89
+ `Cannot resolve "${trimmed}" on the hosted walkerOS MCP server. ${HINT_INLINE_OR_ID}`
90
+ );
91
+ }
92
+ }
93
+ };
94
+ }
95
+
1
96
  // src/tools/auth.ts
2
97
  import { z } from "zod";
3
98
  import { mcpResult, mcpError } from "@walkeros/core";
@@ -1929,7 +2024,7 @@ var HUB_MANAGE_INPUT_SCHEMA = {
1929
2024
  'One frame (frm_...), the named rectangle a note hangs on. Action "knowledge" only. Narrower than pageKey, since a page holds several frames.'
1930
2025
  ),
1931
2026
  markId: z8.string().min(1).max(MAX_MARK_ID_LENGTH).optional().describe(
1932
- 'One mark within "frameId". Action "knowledge" only, and refused without frameId, since a mark id alone addresses nothing. Naming a mark is also what attaches the message bodies.'
2027
+ 'One tag within "frameId", by its tag id (the id of an entry in frame_manage marks.tags). Action "knowledge" only, and refused without frameId, since a mark id alone addresses nothing. Naming a mark is also what attaches the message bodies.'
1933
2028
  )
1934
2029
  };
1935
2030
  var annotations8 = {
@@ -2465,7 +2560,7 @@ import { mcpResult as mcpResult9, mcpError as mcpError9 } from "@walkeros/core";
2465
2560
  var FRAME_ID_PATTERN2 = /^frm_[A-Za-z0-9_-]{21}$/;
2466
2561
  var MAX_PAGE_KEY_CHARS = 1024;
2467
2562
  var TITLE9 = "Frames";
2468
- var FRAME_MANAGE_DESCRIPTION = 'Read the frames of a measurement plan: named rectangles with marks inside them, drawn in Tag Mode or in the app. Actions: list (every frame of the project, without marks), page (the frames of one page at any depth, with marks), get (one frame with its marks). Read-only: frames are drawn and edited in Tag Mode or the app, never here. A frame name is documentation; the marks inside it carry the meaning. A frame that extends another stores only what it adds. Use hub_manage action "knowledge" with a frameId or markId to read what people wrote on a frame. A markId is an id read from the marks of a frame here: mark ids come back literal so they can be passed straight back, while the text around them is wrapped as data. An entity action is an object carrying its id beside the raw attribute text, because the id is the address and the raw text is not.';
2563
+ var FRAME_MANAGE_DESCRIPTION = 'Read the frames of a measurement plan: named rectangles with marks inside them, drawn in Tag Mode or in the app. Actions: list (every frame of the project, without marks), page (the frames of one page at any depth, with marks), get (one frame with its marks). Read-only: frames are drawn and edited in Tag Mode or the app, never here. A frame name is documentation; the marks inside it carry the meaning. A frame that extends another stores only what it adds, so its tags may carry only the fields they change. Marks are { tags, note }: tags is one flat list of tags, each with an id, a kind such as entity, property or action, a name, and a parentId naming the tag it sits under; note is the frame\u2019s own description and thread. Use hub_manage action "knowledge" with a frameId or markId to read what people wrote on a frame. A markId is a tag id: a tag\u2019s id, parentId and threadRef come back literal when they have the shape the app mints, so they can be passed straight back; every other string value in the marks is wrapped as data, and so is an object key that is not a plain identifier.';
2469
2564
  var FRAME_MANAGE_INPUT_SCHEMA = {
2470
2565
  action: z9.enum(["list", "page", "get"]).describe(
2471
2566
  "list the project\u2019s frames, read one page with marks, or read one frame"
@@ -2489,8 +2584,8 @@ var annotations9 = {
2489
2584
  var FRAME_HINT_OPEN_PAGE_OR_GET = 'Use action "page" with a pageKey (a frame\u2019s source.key) to read a page with marks, or action "get" with a frameId.';
2490
2585
  var FRAME_HINT_NAMES_ARE_DOCUMENTATION = "Frame names are documentation; the marks inside a frame carry the meaning.";
2491
2586
  var FRAME_HINT_NONE_YET = "This project has no frames yet. Frames are drawn in Tag Mode or the app, not through this tool.";
2492
- var FRAME_HINT_MARK_SPACE = "Marks are in their frame\u2019s own 0..1 space; a child frame sits inside its parent through placements[].rect.";
2493
- var FRAME_HINT_READ_KNOWLEDGE = 'Use hub_manage action "knowledge" with frameId (and markId) to read what people wrote here. A markId is an id from these marks, and an entity action is addressed by its own id, never by its raw text.';
2587
+ var FRAME_HINT_MARK_SPACE = "Tag geometry is fractional: a rect is 0..1 of its frame, and an at is 0..1 of its parent tag\u2019s box, or of the frame for a root tag; a child frame sits inside its parent through placements[].rect.";
2588
+ var FRAME_HINT_READ_KNOWLEDGE = 'Use hub_manage action "knowledge" with frameId (and markId) to read what people wrote here. A markId is a tag id from these marks, and a tag\u2019s threadRef is the id of its thread entry there.';
2494
2589
  var FRAME_HINT_NONE_ON_PAGE = 'No frames on this page. Check the pageKey against the source.key values from action "list".';
2495
2590
  var FRAME_HINT_EXTENDS_BASE = "This frame extends another and stores only what it adds; read the base frame (extends) for the rest.";
2496
2591
  var FRAME_NOT_FOUND_HINT = 'Use action "list" or "page" to find frame ids.';
@@ -2526,67 +2621,59 @@ function serializeLean(frame) {
2526
2621
  function isRecord2(value) {
2527
2622
  return typeof value === "object" && value !== null && !Array.isArray(value);
2528
2623
  }
2529
- function actionMarkId(entityId, raw) {
2530
- return `${entityId}#action.${raw}`;
2531
- }
2532
- var MARK_ADDRESS_KEYS = /* @__PURE__ */ new Set([
2533
- // The mark id itself, and a component of the data and action addresses.
2534
- "id",
2535
- // The first half of an ambient address, `ambient.<kind>.<key>`, and the
2536
- // discriminator a reader branches on.
2537
- "kind",
2538
- // An entity's pointer at another entity, which is that entity's mark id.
2539
- "link",
2540
- // The thread a note became, which action "note_add" takes as threadId.
2541
- "threadRef"
2624
+ var TAG_ID_PATTERN = /^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*(?:#(?:data\.v?[0-9]+|action\.[0-9]+|context\.[0-9]+|[0-9]+))?$/;
2625
+ var MAX_TAG_ID_CHARS = 200;
2626
+ var THREAD_ID_PATTERN = /^thr_[a-z0-9]{21}$/;
2627
+ var isTagId = (value) => value.length <= MAX_TAG_ID_CHARS && TAG_ID_PATTERN.test(value);
2628
+ var isThreadId = (value) => THREAD_ID_PATTERN.test(value);
2629
+ var TAG_ADDRESSES = /* @__PURE__ */ new Map([
2630
+ // The tag id: the markId `hub_manage` action "knowledge" takes, which the
2631
+ // app joins to the frame id as the anchor key `<frameId>:<markId>`.
2632
+ ["id", isTagId],
2633
+ // The tag this one sits under, which is that tag's id: the only relation
2634
+ // between tags, so a reader joins it to `id` to rebuild the tree, and it is
2635
+ // a markId in its own right.
2636
+ ["parentId", isTagId],
2637
+ // The hub thread this tag's note thread became: the `id` of the thread
2638
+ // entry `hub_manage` action "knowledge" returns for this tag.
2639
+ ["threadRef", isThreadId]
2542
2640
  ]);
2543
- var MARK_ADDRESS_LIST_KEYS = /* @__PURE__ */ new Set([
2544
- // The entities a context band covers, by their mark ids.
2545
- "covers"
2546
- ]);
2547
- var MARK_PAGE_DATA_KEYS = /* @__PURE__ */ new Set(["anchor", "actionAnchors"]);
2548
- function isProseKeyedRecord(key, owner) {
2549
- return key === "data" && typeof owner.kind !== "string";
2550
- }
2551
- function walkMarks(value, inPageData = false) {
2641
+ var NOTE_ADDRESSES = /* @__PURE__ */ new Map(
2642
+ [["threadRef", isThreadId]]
2643
+ );
2644
+ var MARK_KEY_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$-]{0,63}$/;
2645
+ var markKey = (key) => MARK_KEY_PATTERN.test(key) ? key : wrapUserData(key);
2646
+ function walkText(value) {
2552
2647
  if (typeof value === "string") return wrapUserData(value);
2553
- if (Array.isArray(value)) {
2554
- return value.map((item) => walkMarks(item, inPageData));
2555
- }
2648
+ if (Array.isArray(value)) return value.map(walkText);
2556
2649
  if (!isRecord2(value)) return value;
2557
- const entityId = typeof value.id === "string" ? value.id : void 0;
2558
2650
  const out = {};
2559
2651
  for (const [key, child] of Object.entries(value)) {
2560
- if (MARK_PAGE_DATA_KEYS.has(key)) {
2561
- out[key] = walkMarks(child, true);
2562
- } else if (!inPageData && isRecord2(child) && isProseKeyedRecord(key, value)) {
2563
- out[key] = proseKeyedPairs(child);
2652
+ out[markKey(key)] = walkText(child);
2653
+ }
2654
+ return out;
2655
+ }
2656
+ function walkMarks(marks) {
2657
+ const out = {};
2658
+ for (const [key, value] of Object.entries(marks)) {
2659
+ if (key === "tags" && Array.isArray(value)) {
2660
+ out[key] = value.map((tag) => walkAddressed(tag, TAG_ADDRESSES));
2661
+ } else if (key === "note") {
2662
+ out[key] = walkAddressed(value, NOTE_ADDRESSES);
2564
2663
  } else {
2565
- out[key] = walkMarkEntry(key, child, entityId, inPageData);
2664
+ out[markKey(key)] = walkText(value);
2566
2665
  }
2567
2666
  }
2568
2667
  return out;
2569
2668
  }
2570
- function proseKeyedPairs(record) {
2571
- return Object.entries(record).map(([key, value]) => ({
2572
- key: wrapUserData(key),
2573
- value: walkMarks(value)
2574
- }));
2575
- }
2576
- function walkMarkEntry(key, child, entityId, inPageData) {
2577
- if (inPageData) return walkMarks(child, true);
2578
- if (MARK_ADDRESS_KEYS.has(key) && typeof child === "string") return child;
2579
- if (MARK_ADDRESS_LIST_KEYS.has(key) && Array.isArray(child)) {
2580
- return child.map(
2581
- (item) => typeof item === "string" ? item : walkMarks(item)
2582
- );
2583
- }
2584
- if (key === "actions" && entityId !== void 0 && Array.isArray(child)) {
2585
- return child.map(
2586
- (raw) => typeof raw === "string" ? { id: actionMarkId(entityId, raw), raw: wrapUserData(raw) } : walkMarks(raw)
2587
- );
2669
+ function walkAddressed(value, addresses) {
2670
+ if (!isRecord2(value)) return walkText(value);
2671
+ const out = {};
2672
+ for (const [key, field] of Object.entries(value)) {
2673
+ const isAddress = addresses.get(key);
2674
+ out[markKey(key)] = isAddress !== void 0 && typeof field === "string" && isAddress(field) ? field : walkText(field);
2588
2675
  }
2589
- return walkMarks(child);
2676
+ return out;
2590
2677
  }
2591
2678
  function serializeFrame(frame) {
2592
2679
  return {
@@ -2738,7 +2825,7 @@ async function feedbackHandlerBody(client, input) {
2738
2825
  const isAnonymous = explicitAnonymous ?? anonymous ?? true;
2739
2826
  await client.submitFeedback(text, {
2740
2827
  anonymous: isAnonymous,
2741
- version: "4.6.0"
2828
+ version: "4.7.0-next-1790187973605"
2742
2829
  });
2743
2830
  return mcpResult10({ ok: true });
2744
2831
  } catch (error) {
@@ -2762,7 +2849,7 @@ function registerFeedbackTool(server, client) {
2762
2849
  }
2763
2850
 
2764
2851
  // src/tools/validate.ts
2765
- import { validate, loadJsonConfig } from "@walkeros/cli";
2852
+ import { validate } from "@walkeros/cli";
2766
2853
  import { schemas } from "@walkeros/cli/dev";
2767
2854
  import { mcpResult as mcpResult11, mcpError as mcpError11 } from "@walkeros/core";
2768
2855
 
@@ -2857,6 +2944,22 @@ var ExamplesListOutputShape = {
2857
2944
  };
2858
2945
 
2859
2946
  // src/tools/validate.ts
2947
+ async function loadValidateInput(runtime, input, type) {
2948
+ if (!input || input.trim() === "") throw new Error(`${type} is required`);
2949
+ const trimmed = input.trim();
2950
+ const looksLikeJson = trimmed.startsWith("{") || trimmed.startsWith("[");
2951
+ try {
2952
+ return await runtime.load(input);
2953
+ } catch (error) {
2954
+ if (error instanceof RuntimeRefusal || isCloudId(trimmed)) throw error;
2955
+ if (looksLikeJson) {
2956
+ const message = error instanceof Error ? error.message : String(error);
2957
+ throw new Error(`Failed to parse ${type}. ${message}`);
2958
+ }
2959
+ if (type === "event") return { name: trimmed };
2960
+ throw error;
2961
+ }
2962
+ }
2860
2963
  var DEPRECATED_STORE_PACKAGE = "@walkeros/store-memory";
2861
2964
  function detectDeprecatedStorePackages(config) {
2862
2965
  const errors = [];
@@ -2886,7 +2989,7 @@ function detectDeprecatedStorePackages(config) {
2886
2989
  return errors;
2887
2990
  }
2888
2991
  var TITLE11 = "Validate Flow";
2889
- var DESCRIPTION9 = "Validate walkerOS events, flow configurations, mapping rules, or data contracts. Accepts JSON strings, file paths, or URLs as input. Returns validation results with errors, warnings, and details.";
2992
+ var DESCRIPTION9 = "Validate walkerOS events, flow configurations, mapping rules, or data contracts. Accepts JSON strings, file paths, or URLs as input; on the hosted server only inline JSON or a saved flow id (flow_ or cfg_), no file paths or URLs. Returns validation results with errors, warnings, and details.";
2890
2993
  var inputSchema9 = schemas.ValidateInputShape;
2891
2994
  var annotations11 = {
2892
2995
  readOnlyHint: true,
@@ -2894,17 +2997,17 @@ var annotations11 = {
2894
2997
  idempotentHint: true,
2895
2998
  openWorldHint: false
2896
2999
  };
2897
- function createFlowValidateToolSpec() {
3000
+ function createFlowValidateToolSpec(runtime) {
2898
3001
  return {
2899
3002
  name: "flow_validate",
2900
3003
  title: TITLE11,
2901
3004
  description: DESCRIPTION9,
2902
3005
  inputSchema: inputSchema9,
2903
3006
  annotations: annotations11,
2904
- handler: (input) => flowValidateHandlerBody(input)
3007
+ handler: (input) => flowValidateHandlerBody(runtime, input)
2905
3008
  };
2906
3009
  }
2907
- async function flowValidateHandlerBody(input) {
3010
+ async function flowValidateHandlerBody(runtime, input) {
2908
3011
  const {
2909
3012
  type,
2910
3013
  input: validateInput,
@@ -2912,30 +3015,27 @@ async function flowValidateHandlerBody(input) {
2912
3015
  path: path2
2913
3016
  } = input ?? {};
2914
3017
  try {
2915
- const result = await validate(type, validateInput, {
3018
+ const resolved = await loadValidateInput(runtime, validateInput, type);
3019
+ const result = await validate(type, resolved, {
2916
3020
  flow,
2917
3021
  path: path2
2918
3022
  });
2919
3023
  let augmented = result;
2920
- if (type === "flow" && typeof validateInput === "string") {
2921
- try {
2922
- const config = await loadJsonConfig(validateInput);
2923
- const deprecatedErrors = detectDeprecatedStorePackages(config);
2924
- if (deprecatedErrors.length > 0) {
2925
- augmented = {
2926
- ...result,
2927
- valid: false,
2928
- errors: [...result.errors, ...deprecatedErrors]
2929
- };
2930
- }
2931
- } catch {
3024
+ if (type === "flow") {
3025
+ const deprecatedErrors = detectDeprecatedStorePackages(resolved);
3026
+ if (deprecatedErrors.length > 0) {
3027
+ augmented = {
3028
+ ...result,
3029
+ valid: false,
3030
+ errors: [...result.errors, ...deprecatedErrors]
3031
+ };
2932
3032
  }
2933
3033
  }
2934
3034
  const hints = augmented.valid ? {
2935
- next: [
3035
+ next: runtime.simulate ? [
2936
3036
  "Use flow_simulate to test event flow",
2937
3037
  "Use flow_bundle to build"
2938
- ]
3038
+ ] : [HINT_OUT_OF_PROCESS]
2939
3039
  } : {
2940
3040
  next: [
2941
3041
  "Fix errors above, then run flow_validate again",
@@ -2946,12 +3046,15 @@ async function flowValidateHandlerBody(input) {
2946
3046
  } catch (error) {
2947
3047
  return mcpError11(
2948
3048
  error,
2949
- "Check the input parameter \u2014 expected a JSON string, file path, or URL"
3049
+ refusalHint(
3050
+ error,
3051
+ "Check the input parameter \u2014 expected a JSON string, file path, or URL"
3052
+ )
2950
3053
  );
2951
3054
  }
2952
3055
  }
2953
- function registerFlowValidateTool(server) {
2954
- const spec = createFlowValidateToolSpec();
3056
+ function registerFlowValidateTool(server, runtime) {
3057
+ const spec = createFlowValidateToolSpec(runtime);
2955
3058
  server.registerTool(
2956
3059
  spec.name,
2957
3060
  {
@@ -2969,17 +3072,14 @@ function registerFlowValidateTool(server) {
2969
3072
  }
2970
3073
 
2971
3074
  // src/tools/bundle.ts
2972
- import { bundle } from "@walkeros/cli";
2973
3075
  import { schemas as schemas2 } from "@walkeros/cli/dev";
2974
3076
  import { mcpResult as mcpResult12, mcpError as mcpError12 } from "@walkeros/core";
2975
3077
 
2976
3078
  // src/tools/resolve-config-path.ts
2977
- var API_ID_PREFIX = /^(flow|cfg)_/;
2978
3079
  async function resolveConfigPath(client, configPath) {
2979
- if (!API_ID_PREFIX.test(configPath)) return configPath;
3080
+ if (!isCloudId(configPath)) return configPath;
2980
3081
  const flow = await client.getFlow({ flowId: configPath });
2981
- const config = flow.config;
2982
- return JSON.stringify(config ?? {});
3082
+ return JSON.stringify(flowConfigOf(flow));
2983
3083
  }
2984
3084
 
2985
3085
  // src/tools/bundle.ts
@@ -2994,24 +3094,28 @@ var annotations12 = {
2994
3094
  idempotentHint: false,
2995
3095
  openWorldHint: true
2996
3096
  };
2997
- function createFlowBundleToolSpec(client) {
3097
+ function createFlowBundleToolSpec(client, runtime) {
2998
3098
  return {
2999
3099
  name: "flow_bundle",
3000
3100
  title: TITLE12,
3001
3101
  description: DESCRIPTION10,
3002
3102
  inputSchema: inputSchema10,
3003
3103
  annotations: annotations12,
3004
- handler: (input) => flowBundleHandlerBody(client, input)
3104
+ handler: (input) => flowBundleHandlerBody(client, runtime, input)
3005
3105
  };
3006
3106
  }
3007
- async function flowBundleHandlerBody(client, input) {
3107
+ async function flowBundleHandlerBody(client, runtime, input) {
3008
3108
  const { configPath, flow, stats, output } = input ?? {};
3109
+ if (!runtime.bundle) {
3110
+ const refusal = unavailableOperation("bundle");
3111
+ return mcpError12(refusal, refusal.hint);
3112
+ }
3009
3113
  try {
3010
3114
  const resolvedConfigPath = await resolveConfigPath(client, configPath);
3011
- const result = await bundle(resolvedConfigPath, {
3115
+ const result = await runtime.bundle(resolvedConfigPath, {
3012
3116
  flowName: flow,
3013
3117
  stats: stats ?? true,
3014
- buildOverrides: output ? { output } : void 0
3118
+ output
3015
3119
  });
3016
3120
  if (!result) {
3017
3121
  return mcpResult12(
@@ -3035,11 +3139,14 @@ async function flowBundleHandlerBody(client, input) {
3035
3139
  }
3036
3140
  );
3037
3141
  } catch (error) {
3038
- return mcpError12(error, "Run flow_validate for detailed error messages");
3142
+ return mcpError12(
3143
+ error,
3144
+ refusalHint(error, "Run flow_validate for detailed error messages")
3145
+ );
3039
3146
  }
3040
3147
  }
3041
- function registerFlowBundleTool(server, client) {
3042
- const spec = createFlowBundleToolSpec(client);
3148
+ function registerFlowBundleTool(server, client, runtime) {
3149
+ const spec = createFlowBundleToolSpec(client, runtime);
3043
3150
  server.registerTool(
3044
3151
  spec.name,
3045
3152
  {
@@ -3058,101 +3165,17 @@ function registerFlowBundleTool(server, client) {
3058
3165
 
3059
3166
  // src/tools/simulate.ts
3060
3167
  import { z as z12 } from "zod";
3061
- import {
3062
- simulateSource,
3063
- simulateTransformer,
3064
- simulateDestination,
3065
- simulateCollector
3066
- } from "@walkeros/cli";
3067
3168
  import { schemas as schemas3 } from "@walkeros/cli/dev";
3068
3169
  import { mcpResult as mcpResult13, mcpError as mcpError13 } from "@walkeros/core";
3069
-
3070
- // src/tools/bundle-cache.ts
3071
- import { createHash } from "crypto";
3072
- import os from "os";
3073
- import path from "path";
3074
- import { mkdir, rm, writeFile } from "fs/promises";
3075
- import { rmSync } from "fs";
3076
- import { bundle as bundle2 } from "@walkeros/cli";
3077
- var MAX_ENTRIES = 8;
3078
- var cache = /* @__PURE__ */ new Map();
3079
- var inFlight = /* @__PURE__ */ new Map();
3080
- var cleanupRegistered = false;
3081
- function hashConfig(resolvedConfig) {
3082
- return createHash("sha256").update(resolvedConfig).digest("hex");
3083
- }
3084
- function isInlineJsonConfig(resolvedConfig) {
3085
- const trimmed = resolvedConfig.trimStart();
3086
- if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false;
3087
- try {
3088
- JSON.parse(resolvedConfig);
3089
- return true;
3090
- } catch {
3091
- return false;
3092
- }
3093
- }
3094
- function registerProcessCleanup() {
3095
- if (cleanupRegistered) return;
3096
- cleanupRegistered = true;
3097
- const cleanup = () => {
3098
- for (const entry of cache.values()) {
3099
- try {
3100
- rmSync(entry.dir, { recursive: true, force: true });
3101
- } catch {
3102
- }
3103
- }
3104
- cache.clear();
3105
- };
3106
- process.once("exit", cleanup);
3107
- }
3108
- async function evictIfNeeded() {
3109
- while (cache.size > MAX_ENTRIES) {
3110
- const oldestKey = cache.keys().next().value;
3111
- if (oldestKey === void 0) break;
3112
- const evicted = cache.get(oldestKey);
3113
- cache.delete(oldestKey);
3114
- if (evicted) await rm(evicted.dir, { recursive: true, force: true });
3115
- }
3116
- }
3117
- async function getOrBuildBundle(resolvedConfig) {
3118
- if (!isInlineJsonConfig(resolvedConfig)) return void 0;
3119
- registerProcessCleanup();
3120
- const key = hashConfig(resolvedConfig);
3121
- const cached = cache.get(key);
3122
- if (cached) {
3123
- cache.delete(key);
3124
- cache.set(key, cached);
3125
- return cached.bundlePath;
3126
- }
3127
- const pending = inFlight.get(key);
3128
- if (pending) return pending;
3129
- const build = (async () => {
3130
- const dir = path.join(
3131
- os.tmpdir(),
3132
- `walkeros-mcp-bundle-${key.slice(0, 16)}`
3133
- );
3134
- await rm(dir, { recursive: true, force: true });
3135
- await mkdir(dir, { recursive: true });
3136
- const bundlePath = path.join(dir, "flow.mjs");
3137
- await writeFile(bundlePath, "", "utf-8");
3138
- await bundle2(resolvedConfig, {
3139
- target: "simulate",
3140
- silent: true,
3141
- buildOverrides: { output: bundlePath, format: "esm", minify: false }
3142
- });
3143
- cache.set(key, { bundlePath, dir });
3144
- await evictIfNeeded();
3145
- return bundlePath;
3146
- })();
3147
- inFlight.set(key, build);
3148
- try {
3149
- return await build;
3150
- } finally {
3151
- inFlight.delete(key);
3152
- }
3170
+ var STEP_TYPES = [
3171
+ "source",
3172
+ "transformer",
3173
+ "collector",
3174
+ "destination"
3175
+ ];
3176
+ function isStepType(value) {
3177
+ return STEP_TYPES.some((t) => t === value);
3153
3178
  }
3154
-
3155
- // src/tools/simulate.ts
3156
3179
  var TITLE13 = "Simulate Flow";
3157
3180
  var DESCRIPTION11 = '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? } }, where content is the walkerOS event { name: "entity action", data: {...} }. step (required) targets the step to simulate, e.g. "destination.gtag". 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.';
3158
3181
  var inputSchema11 = {
@@ -3165,7 +3188,7 @@ var inputSchema11 = {
3165
3188
  // Override the (optional) CLI `step` shape: the simulate handler hard-requires
3166
3189
  // a target step (no all-steps mode), so the registered schema must be honest.
3167
3190
  step: z12.string().describe(
3168
- 'Required. Target step as "type.name" \u2014 e.g. "source.demo", "destination.gtag", "transformer.router".'
3191
+ 'Required. Target step as "type.name", e.g. "source.demo", "collector.default", "destination.gtag", "transformer.router". A collector step runs enrichment and then collector.next, returning every event the destinations would receive (none after a stop).'
3169
3192
  ),
3170
3193
  verbose: z12.boolean().optional().describe("Include full payload per destination (default: false)"),
3171
3194
  ingest: z12.record(z12.string(), z12.unknown()).optional().describe(
@@ -3181,23 +3204,31 @@ var inputSchema11 = {
3181
3204
  )
3182
3205
  };
3183
3206
  var annotations13 = {
3184
- readOnlyHint: true,
3185
- destructiveHint: false,
3186
- idempotentHint: true,
3187
- openWorldHint: false
3207
+ // Simulation downloads the packages a config names and runs caller-controlled
3208
+ // flow code in process. Destinations are mocked, but that code is not, so the
3209
+ // hints stay conservative: side effects possible, repeat calls not assumed
3210
+ // safe, external systems reachable.
3211
+ readOnlyHint: false,
3212
+ destructiveHint: true,
3213
+ idempotentHint: false,
3214
+ openWorldHint: true
3188
3215
  };
3189
- function createFlowSimulateToolSpec(client) {
3216
+ function createFlowSimulateToolSpec(client, runtime) {
3190
3217
  return {
3191
3218
  name: "flow_simulate",
3192
3219
  title: TITLE13,
3193
3220
  description: DESCRIPTION11,
3194
3221
  inputSchema: inputSchema11,
3195
3222
  annotations: annotations13,
3196
- handler: (input) => flowSimulateHandlerBody(client, input)
3223
+ handler: (input) => flowSimulateHandlerBody(client, runtime, input)
3197
3224
  };
3198
3225
  }
3199
- async function flowSimulateHandlerBody(client, input) {
3226
+ async function flowSimulateHandlerBody(client, runtime, input) {
3200
3227
  const { configPath, event, flow, platform, step, verbose, ingest, state } = input ?? {};
3228
+ if (!runtime.simulate) {
3229
+ const refusal = unavailableOperation("simulate");
3230
+ return mcpError13(refusal, refusal.hint);
3231
+ }
3201
3232
  try {
3202
3233
  if (!event) {
3203
3234
  throw new Error(
@@ -3227,66 +3258,16 @@ async function flowSimulateHandlerBody(client, input) {
3227
3258
  }
3228
3259
  const stepType = step.substring(0, dotIndex);
3229
3260
  const stepId = step.substring(dotIndex + 1);
3230
- const resolvedConfigPath = await resolveConfigPath(client, configPath);
3231
- let bundlePath;
3232
- try {
3233
- bundlePath = await getOrBuildBundle(resolvedConfigPath);
3234
- } catch {
3235
- bundlePath = void 0;
3236
- }
3237
- let result;
3238
- switch (stepType) {
3239
- case "source":
3240
- result = await simulateSource(resolvedConfigPath, resolvedEvent, {
3241
- sourceId: stepId,
3242
- bundlePath,
3243
- flow,
3244
- silent: true
3245
- });
3246
- break;
3247
- case "transformer":
3248
- result = await simulateTransformer(
3249
- resolvedConfigPath,
3250
- resolvedEvent,
3251
- {
3252
- transformerId: stepId,
3253
- bundlePath,
3254
- flow,
3255
- silent: true,
3256
- ingest
3257
- }
3258
- );
3259
- break;
3260
- case "collector":
3261
- result = await simulateCollector(
3262
- resolvedConfigPath,
3263
- resolvedEvent,
3264
- {
3265
- collectorName: stepId,
3266
- bundlePath,
3267
- flow,
3268
- silent: true,
3269
- state
3270
- }
3271
- );
3272
- break;
3273
- case "destination":
3274
- result = await simulateDestination(
3275
- resolvedConfigPath,
3276
- resolvedEvent,
3277
- {
3278
- destinationId: stepId,
3279
- bundlePath,
3280
- flow,
3281
- silent: true
3282
- }
3283
- );
3284
- break;
3285
- default:
3286
- throw new Error(
3287
- `Unknown step type "${stepType}". Use "source", "collector", "transformer", or "destination".`
3288
- );
3261
+ if (!isStepType(stepType)) {
3262
+ throw new Error(
3263
+ `Unknown step type "${stepType}". Use "source", "collector", "transformer", or "destination".`
3264
+ );
3289
3265
  }
3266
+ const resolvedConfigPath = await resolveConfigPath(client, configPath);
3267
+ const result = await runtime.simulate(
3268
+ resolvedConfigPath,
3269
+ { stepType, stepId, event: resolvedEvent, flow, ingest, state }
3270
+ );
3290
3271
  const success = !result.error;
3291
3272
  const errorMessage = result.error?.message;
3292
3273
  if (result.step === "source") {
@@ -3374,11 +3355,11 @@ async function flowSimulateHandlerBody(client, input) {
3374
3355
  if (msg.includes("not found in collector")) {
3375
3356
  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.';
3376
3357
  }
3377
- return mcpError13(error, hint);
3358
+ return mcpError13(error, refusalHint(error, hint));
3378
3359
  }
3379
3360
  }
3380
- function registerFlowSimulateTool(server, client) {
3381
- const spec = createFlowSimulateToolSpec(client);
3361
+ function registerFlowSimulateTool(server, client, runtime) {
3362
+ const spec = createFlowSimulateToolSpec(client, runtime);
3382
3363
  server.registerTool(
3383
3364
  spec.name,
3384
3365
  {
@@ -3397,7 +3378,6 @@ function registerFlowSimulateTool(server, client) {
3397
3378
 
3398
3379
  // src/tools/push.ts
3399
3380
  import { z as z13 } from "zod";
3400
- import { push } from "@walkeros/cli";
3401
3381
  import { schemas as schemas4 } from "@walkeros/cli/dev";
3402
3382
  import { mcpResult as mcpResult14, mcpError as mcpError14 } from "@walkeros/core";
3403
3383
  var TITLE14 = "Push Events";
@@ -3416,21 +3396,24 @@ var annotations14 = {
3416
3396
  idempotentHint: false,
3417
3397
  openWorldHint: true
3418
3398
  };
3419
- function createFlowPushToolSpec() {
3399
+ function createFlowPushToolSpec(runtime) {
3420
3400
  return {
3421
3401
  name: "flow_push",
3422
3402
  title: TITLE14,
3423
3403
  description: DESCRIPTION12,
3424
3404
  inputSchema: inputSchema12,
3425
3405
  annotations: annotations14,
3426
- handler: (input) => flowPushHandlerBody(input)
3406
+ handler: (input) => flowPushHandlerBody(runtime, input)
3427
3407
  };
3428
3408
  }
3429
- async function flowPushHandlerBody(input) {
3409
+ async function flowPushHandlerBody(runtime, input) {
3430
3410
  const { configPath, event, flow, platform } = input ?? {};
3411
+ if (!runtime.push) {
3412
+ const refusal = unavailableOperation("push");
3413
+ return mcpError14(refusal, refusal.hint);
3414
+ }
3431
3415
  try {
3432
- const result = await push(configPath, event, {
3433
- json: true,
3416
+ const result = await runtime.push(configPath, event, {
3434
3417
  flow,
3435
3418
  platform
3436
3419
  });
@@ -3444,12 +3427,15 @@ async function flowPushHandlerBody(input) {
3444
3427
  } catch (error) {
3445
3428
  return mcpError14(
3446
3429
  error,
3447
- "Check configPath and event format. For web flows, use flow_simulate."
3430
+ refusalHint(
3431
+ error,
3432
+ "Check configPath and event format. For web flows, use flow_simulate."
3433
+ )
3448
3434
  );
3449
3435
  }
3450
3436
  }
3451
- function registerFlowPushTool(server) {
3452
- const spec = createFlowPushToolSpec();
3437
+ function registerFlowPushTool(server, runtime) {
3438
+ const spec = createFlowPushToolSpec(runtime);
3453
3439
  server.registerTool(
3454
3440
  spec.name,
3455
3441
  {
@@ -3468,7 +3454,6 @@ function registerFlowPushTool(server) {
3468
3454
 
3469
3455
  // src/tools/examples.ts
3470
3456
  import { z as z14 } from "zod";
3471
- import { loadJsonConfig as loadJsonConfig2 } from "@walkeros/cli";
3472
3457
  import { fetchPackage, mcpResult as mcpResult15, mcpError as mcpError15 } from "@walkeros/core";
3473
3458
 
3474
3459
  // src/catalog.ts
@@ -3476,7 +3461,7 @@ var NPM_SEARCH_URL = "https://registry.npmjs.org/-/v1/search";
3476
3461
  var JSDELIVR_BASE = "https://cdn.jsdelivr.net/npm";
3477
3462
  var WALKEROS_JSON_PATH = "dist/walkerOS.json";
3478
3463
  var CACHE_TTL = 5 * 60 * 1e3;
3479
- var CLIENT_HEADER = "walkeros-mcp/4.6.0";
3464
+ var CLIENT_HEADER = "walkeros-mcp/4.7.0-next-1790187973605";
3480
3465
  function getPackageBaseUrl() {
3481
3466
  return process.env.WALKEROS_APP_URL || void 0;
3482
3467
  }
@@ -3484,7 +3469,7 @@ var lastFetchInfo;
3484
3469
  function getLastCatalogSource() {
3485
3470
  return lastFetchInfo;
3486
3471
  }
3487
- var cache2 = /* @__PURE__ */ new Map();
3472
+ var cache = /* @__PURE__ */ new Map();
3488
3473
  function normalizePlatform(platform) {
3489
3474
  if (platform == null) return [];
3490
3475
  if (typeof platform === "string") {
@@ -3498,7 +3483,7 @@ function normalizePlatform(platform) {
3498
3483
  async function fetchCatalog(filters) {
3499
3484
  const sourceKey = filters?.baseUrl ?? "npm";
3500
3485
  const warnings = [];
3501
- const cached = cache2.get(sourceKey);
3486
+ const cached = cache.get(sourceKey);
3502
3487
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
3503
3488
  lastFetchInfo = {
3504
3489
  source: filters?.baseUrl ? "app" : "npm",
@@ -3556,7 +3541,7 @@ async function fetchCatalog(filters) {
3556
3541
  timestamp: Date.now()
3557
3542
  };
3558
3543
  if (result.entries.length > 0 && result.complete) {
3559
- cache2.set(sourceKey, { entries: result.entries, timestamp: Date.now() });
3544
+ cache.set(sourceKey, { entries: result.entries, timestamp: Date.now() });
3560
3545
  }
3561
3546
  return { entries: applyFilters(result.entries, filters), warnings };
3562
3547
  }
@@ -3629,10 +3614,13 @@ function applyFilters(entries, filters) {
3629
3614
  }
3630
3615
 
3631
3616
  // src/tools/examples.ts
3617
+ var MAX_PACKAGE_LOOKUPS = 25;
3632
3618
  var TITLE15 = "Flow Examples";
3633
- var DESCRIPTION13 = 'List all step examples in a walkerOS flow configuration. Shows example names, step locations, and in/out shapes. Inline examples on a step take precedence; steps without inline examples fall back to the examples shipped by their referenced package. Each result is tagged with its source ("inline" or "package"). Use this to discover available test fixtures and simulation data.';
3619
+ var DESCRIPTION13 = 'List all step examples in a walkerOS flow configuration. Shows example names, step locations, and in/out shapes. Inline examples on a step take precedence; steps without inline examples fall back to the examples shipped by their referenced package. Each result is tagged with its source ("inline" or "package"). On the hosted server configPath accepts only inline JSON or a saved flow id (flow_ or cfg_), no file paths or URLs. Use this to discover available test fixtures and simulation data.';
3634
3620
  var inputSchema13 = {
3635
- configPath: z14.string().min(1).describe("Path to flow configuration file, URL, or inline JSON string"),
3621
+ configPath: z14.string().min(1).describe(
3622
+ "Inline JSON string, file path, or URL of a flow configuration (hosted server: inline JSON or a saved flow id only)"
3623
+ ),
3636
3624
  flow: z14.string().optional().describe("Flow name for multi-flow configs"),
3637
3625
  step: z14.string().optional().describe('Filter to a specific step (e.g., "destination.gtag")'),
3638
3626
  full: z14.boolean().optional().describe(
@@ -3648,20 +3636,20 @@ var annotations15 = {
3648
3636
  idempotentHint: true,
3649
3637
  openWorldHint: false
3650
3638
  };
3651
- function createFlowExamplesToolSpec() {
3639
+ function createFlowExamplesToolSpec(runtime) {
3652
3640
  return {
3653
3641
  name: "flow_examples",
3654
3642
  title: TITLE15,
3655
3643
  description: DESCRIPTION13,
3656
3644
  inputSchema: inputSchema13,
3657
3645
  annotations: annotations15,
3658
- handler: (input) => flowExamplesHandlerBody(input)
3646
+ handler: (input) => flowExamplesHandlerBody(runtime, input)
3659
3647
  };
3660
3648
  }
3661
- async function flowExamplesHandlerBody(input) {
3649
+ async function flowExamplesHandlerBody(runtime, input) {
3662
3650
  const { configPath, flow, step, full, includeHidden } = input ?? {};
3663
3651
  try {
3664
- const rawConfig = await loadJsonConfig2(configPath);
3652
+ const rawConfig = await runtime.load(configPath);
3665
3653
  const flowNames = Object.keys(rawConfig.flows || {});
3666
3654
  const flowName = flow || (flowNames.length === 1 ? flowNames[0] : void 0);
3667
3655
  if (!flowName) {
@@ -3715,6 +3703,19 @@ async function flowExamplesHandlerBody(input) {
3715
3703
  }
3716
3704
  return void 0;
3717
3705
  };
3706
+ const packageLookups = /* @__PURE__ */ new Map();
3707
+ let skippedPackages = false;
3708
+ const examplesForPackage = (packageName) => {
3709
+ const existing = packageLookups.get(packageName);
3710
+ if (existing) return existing;
3711
+ if (packageLookups.size >= MAX_PACKAGE_LOOKUPS) {
3712
+ skippedPackages = true;
3713
+ return Promise.resolve(void 0);
3714
+ }
3715
+ const lookup = loadPackageExamples(packageName);
3716
+ packageLookups.set(packageName, lookup);
3717
+ return lookup;
3718
+ };
3718
3719
  const stepTypes = [
3719
3720
  { key: "sources", type: "source" },
3720
3721
  { key: "transformers", type: "transformer" },
@@ -3731,7 +3732,7 @@ async function flowExamplesHandlerBody(input) {
3731
3732
  continue;
3732
3733
  }
3733
3734
  if (!ref.package) continue;
3734
- const packageExamples = await loadPackageExamples(ref.package);
3735
+ const packageExamples = await examplesForPackage(ref.package);
3735
3736
  if (packageExamples)
3736
3737
  examples.push(...toItems(packageExamples, type, name, "package"));
3737
3738
  }
@@ -3741,21 +3742,33 @@ async function flowExamplesHandlerBody(input) {
3741
3742
  count: examples.length,
3742
3743
  examples
3743
3744
  };
3744
- const hints = {
3745
- next: ["Use flow_simulate with step and event to simulate"]
3746
- };
3745
+ const warnings = [];
3747
3746
  if (examples.length === 0) {
3748
- hints.warnings = [
3747
+ warnings.push(
3749
3748
  "No examples found. Add examples to step entries, or reference a package that ships examples (see package_get)."
3750
- ];
3749
+ );
3751
3750
  }
3751
+ if (skippedPackages) {
3752
+ warnings.push(
3753
+ `Package examples were looked up for the first ${MAX_PACKAGE_LOOKUPS} packages only. Use step to narrow the result.`
3754
+ );
3755
+ }
3756
+ const hints = {
3757
+ next: [
3758
+ runtime.simulate ? "Use flow_simulate with step and event to simulate" : HINT_OUT_OF_PROCESS
3759
+ ],
3760
+ ...warnings.length > 0 ? { warnings } : {}
3761
+ };
3752
3762
  return mcpResult15(result, hints);
3753
3763
  } catch (error) {
3754
- return mcpError15(error, "Check configPath \u2014 expected a flow.json file");
3764
+ return mcpError15(
3765
+ error,
3766
+ refusalHint(error, "Check configPath \u2014 expected a flow.json file")
3767
+ );
3755
3768
  }
3756
3769
  }
3757
- function registerFlowExamplesTool(server) {
3758
- const spec = createFlowExamplesToolSpec();
3770
+ function registerFlowExamplesTool(server, runtime) {
3771
+ const spec = createFlowExamplesToolSpec(runtime);
3759
3772
  server.registerTool(
3760
3773
  spec.name,
3761
3774
  {
@@ -3774,9 +3787,7 @@ function registerFlowExamplesTool(server) {
3774
3787
 
3775
3788
  // src/tools/flow-load.ts
3776
3789
  import { z as z15 } from "zod";
3777
- import { loadJsonConfig as loadJsonConfig3 } from "@walkeros/cli";
3778
3790
  import { mcpResult as mcpResult16, mcpError as mcpError16 } from "@walkeros/core";
3779
- var API_ID_PREFIX2 = /^(flow|cfg)_/;
3780
3791
  var WEB_SKELETON = {
3781
3792
  version: 4,
3782
3793
  flows: {
@@ -3798,7 +3809,7 @@ var SERVER_SKELETON = {
3798
3809
  }
3799
3810
  };
3800
3811
  var TITLE16 = "Load or Create Flow";
3801
- var DESCRIPTION14 = "Load an existing flow configuration from a local file path, URL, or walkerOS API (by flow ID). Or create a new empty flow by specifying a platform (web or server). Use the add-step prompt to add sources, destinations, transformers, or stores to the flow.";
3812
+ var DESCRIPTION14 = "Load an existing flow configuration from a local file path, URL, or walkerOS API (by flow ID). On the hosted server only inline JSON or a saved flow id is accepted, no file paths or URLs. Or create a new empty flow by specifying a platform (web or server). Use the add-step prompt to add sources, destinations, transformers, or stores to the flow.";
3802
3813
  var inputSchema14 = {
3803
3814
  source: z15.string().optional().describe(
3804
3815
  "Flow source: local file path (./flow.json), URL (https://...), inline JSON string, or API flow ID (cfg_...). Omit to create a new flow."
@@ -3817,19 +3828,19 @@ var annotations16 = {
3817
3828
  idempotentHint: true,
3818
3829
  openWorldHint: true
3819
3830
  };
3820
- function createFlowLoadToolSpec(client) {
3831
+ function createFlowLoadToolSpec(client, runtime) {
3821
3832
  return {
3822
3833
  name: "flow_load",
3823
3834
  title: TITLE16,
3824
3835
  description: DESCRIPTION14,
3825
3836
  inputSchema: inputSchema14,
3826
3837
  annotations: annotations16,
3827
- handler: (input) => flowLoadHandlerBody(client, input)
3838
+ handler: (input) => flowLoadHandlerBody(client, runtime, input)
3828
3839
  };
3829
3840
  }
3830
- async function flowLoadHandlerBody(client, input) {
3841
+ async function flowLoadHandlerBody(client, runtime, input) {
3831
3842
  const { source, platform } = input ?? {};
3832
- if (source && API_ID_PREFIX2.test(source)) {
3843
+ if (source && isCloudId(source)) {
3833
3844
  const resolvedProjectId = resolveDefaultProject(client, void 0);
3834
3845
  if (!resolvedProjectId) {
3835
3846
  return mcpError16(new Error(NO_DEFAULT_PROJECT_ERROR));
@@ -3839,9 +3850,8 @@ async function flowLoadHandlerBody(client, input) {
3839
3850
  flowId: source,
3840
3851
  projectId: resolvedProjectId
3841
3852
  });
3842
- const config = flow.config;
3843
3853
  return mcpResult16(
3844
- redactNestedStrings(config ?? {}, { skip: keepStructural }),
3854
+ redactNestedStrings(flowConfigOf(flow), { skip: keepStructural }),
3845
3855
  {
3846
3856
  next: ["Use flow_validate to check", "Use add-step prompt to modify"]
3847
3857
  }
@@ -3852,7 +3862,7 @@ async function flowLoadHandlerBody(client, input) {
3852
3862
  }
3853
3863
  try {
3854
3864
  if (source) {
3855
- const config = await loadJsonConfig3(source);
3865
+ const config = await runtime.load(source);
3856
3866
  return mcpResult16(redactNestedStrings(config, { skip: keepStructural }), {
3857
3867
  next: ["Use flow_validate to check", "Use add-step prompt to modify"]
3858
3868
  });
@@ -3872,14 +3882,15 @@ async function flowLoadHandlerBody(client, input) {
3872
3882
  ]
3873
3883
  });
3874
3884
  } catch (error) {
3885
+ if (error instanceof RuntimeRefusal) return mcpError16(error, error.hint);
3875
3886
  const msg = error instanceof Error ? error.message : "";
3876
3887
  if (msg.includes("not found") || msg.includes("ENOENT"))
3877
3888
  return mcpError16(error, "Check configPath \u2014 expected a flow.json file");
3878
3889
  return mcpError16(error);
3879
3890
  }
3880
3891
  }
3881
- function registerFlowLoadTool(server, client) {
3882
- const spec = createFlowLoadToolSpec(client);
3892
+ function registerFlowLoadTool(server, client, runtime) {
3893
+ const spec = createFlowLoadToolSpec(client, runtime);
3883
3894
  server.registerTool(
3884
3895
  spec.name,
3885
3896
  {
@@ -24264,9 +24275,10 @@ function registerAddStepPrompt(server) {
24264
24275
  "",
24265
24276
  "Important:",
24266
24277
  "- Read the walkeros://reference/flow-schema resource to understand connection rules.",
24267
- "- Sources connect to pre-collector transformers via `next`.",
24268
- "- Destinations connect to post-collector transformers via `before`.",
24269
- '- Routing supports string IDs, sequences (`["a", "b"]`), or RouteConfig (`{ match?, next }` for gated single-target or `{ match?, one: [...] }` for first-match dispatch). Use `many` (instead of `one`) when every matching entry should run as an independent parallel flow \u2014 restricted to pre-collector positions. Omit `match` for always-match.',
24278
+ "- Sources connect to transformers via `next` (runs before the collector).",
24279
+ "- The collector connects to transformers via `collector.next` (runs once per event before the destination fan-out, for every destination).",
24280
+ "- Destinations connect to transformers via `before` (runs for that destination only; put per-destination filtering here).",
24281
+ '- Routing uses one grammar in every chain field: string IDs, sequences (`["a", "b"]`, where a member\'s own `next` runs right after it), or RouteConfig (`{ match?, next }` for a gated link, `{ match?, one: [...] }` for first-match dispatch, `{ match?, many: [...] }` to fork each match into its own copy that finishes the rest of the path, `{ match?, stop: true }` to drop). A `stop` in `collector.next` drops the event for all destinations. Omit `match` for always-match.',
24270
24282
  "- A transformer entry can be code-bearing (`code`), package-bearing (`package`), or a **pass-through step** (no `code`, no `package`). Pass-through synthesis is automatic at runtime. Three flavors of pass-through: `before`/`next`-only (named hop reusable across destinations), `cache`-only (dedup, rate-limit), or `mapping`-only (declarative event-to-event transform). A pass-through must declare at least one of `before`, `next`, `cache`, or `mapping`.",
24271
24283
  "- The `mapping` field on a transformer step uses the same `Mapping.Config` shape as destinations, but the semantic is event-to-event (mutates the event in place via `policy`, per-rule `policy`, `mapping[].name` for renames, `mapping[].ignore` to drop). Vendor-payload fields (`data`, `silent`) are no-ops at this position.",
24272
24284
  "- Closed schema: unknown top-level keys on a transformer step are errors (catches typos like `dedup` instead of `cache`). `code` + `package` together is a conflict.",
@@ -24405,7 +24417,7 @@ var SERVER_INSTRUCTIONS = `walkerOS is an open-source, privacy-first event data
24405
24417
 
24406
24418
  ## Architecture: Source \u2192 Collector \u2192 Destination(s)
24407
24419
 
24408
- 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.
24420
+ Every component in a flow is a **step**: sources capture events, transformers process them, destinations deliver them, stores provide shared state. Steps connect via chains: \`source.next\` runs before the collector, \`collector.next\` runs once per event before the destination fan-out (all destinations), and \`destination.before\` runs for one destination.
24409
24421
 
24410
24422
  ## Flow Config Structure
24411
24423
 
@@ -24433,11 +24445,11 @@ Every component in a flow is a **step**: sources capture events, transformers pr
24433
24445
  - **Contracts** define event schemas using entity-action keying. Can generate FROM mappings or scaffold mappings FROM contracts.
24434
24446
  - **Variables** ($var, $env, $secret, $code, $store) enable DRY, environment-aware config. \`$var.name\` is a reusable flow variable from the top-level \`variables\` block; whole-string refs preserve native type (object/array/scalar), inline interpolation requires a scalar, deep paths via \`$var.name.deep.path\`. \`$env.NAME[:default]\` is an environment variable resolved at bundle/deploy time (non-secret config only). \`$secret.NAME\` is a managed secret the deploy pipeline injects into the server runner at runtime; credentials, tokens, and private keys must use \`$secret\`, not \`$env\`.
24435
24447
  - **Consent** gates destinations, mapping rules, and individual fields. Privacy-first by design.
24436
- - **Routing** wires steps via \`next\` (pre-collector) and \`before\` (post-collector). A route is a string ID, a sequence (\`["a", "b"]\`), or a RouteConfig (\`{ match?, next }\` or \`{ match?, one: [...] }\`). The \`one\` operator dispatches first-match against an ordered list of branches. Use \`many\` (instead of \`one\`) when every matching entry should run as an independent parallel flow \u2014 restricted to pre-collector positions. The optional \`match\` field is omitted to mean always-match (no wildcard literal).
24448
+ - **Routing** uses one grammar in every chain field (\`source.before\`, \`source.next\`, \`transformer.before\`, \`transformer.next\`, \`collector.next\`, \`destination.before\`, \`destination.next\`). A route is a string ID, a sequence (\`["a", "b"]\`), or a RouteConfig: \`{ match?, next }\`, \`{ match?, one: [...] }\` (first match wins), \`{ match?, many: [...] }\` (every match becomes its own copy of the event with its own id and finishes the rest of the path on its own, never merged), or \`{ match?, stop: true }\` (ends that copy). In a sequence, a member's own \`next\` runs right after it, then the sequence continues. \`match\` reads \`{ ingest, event }\` and each route is evaluated when the event reaches it. A \`stop\` in \`collector.next\` drops the event for every destination; per-destination filtering belongs in \`destination.before\`. An array of only route configs is an implicit \`one\`. The optional \`match\` field is omitted to mean always-match (no wildcard literal).
24437
24449
  - **Pass-through steps** are transformer entries with no \`code\` and no \`package\`. The runtime synthesizes the push for them automatically. Three flavors share this shape: a \`before\` / \`next\`-only step (named hop reusable across destinations), a \`cache\`-only step (dedup, rate-limit), and a \`mapping\`-only step (declarative event-to-event transform). A pass-through must declare at least one of \`before\`, \`next\`, \`cache\`, or \`mapping\`.
24438
24450
  - **Mapping at the transformer position** uses the same \`Mapping.Config\` shape as destinations, but the semantic is event-to-event: \`policy\` and per-rule \`policy\`, \`mapping[].name\` (rename), \`mapping[].ignore\` (drop from chain), \`consent\`, and \`include\` apply. Vendor-payload fields (\`data\`, \`silent\`, \`mapping[].data\`) are no-ops at this position.
24439
24451
  - **Closed-schema rule on transformer entries:** known keys only. Operative keys are \`code\`, \`package\`, \`before\`, \`next\`, \`cache\`, \`mapping\`. Combined with structural keys (\`config\`, \`env\`, \`validate\`, \`disabled\`, \`id\`, etc.). Unknown top-level keys on a transformer step are errors (catches typos like \`dedup: {}\` instead of nested under \`cache\`). \`code\` + \`package\` together is a conflict.
24440
- - **Cache** memoizes step output on a configured store. Set \`cache.stop: true\` to short-circuit the chain on HIT (sources default to stop-on-HIT). Set \`cache.namespace\` to prefix keys; omit it to write keys directly. \`CacheRule.match\` is optional, omit for always-match.
24452
+ - **Cache** memoizes step output on a configured store. Set \`cache.stop: true\` to short-circuit the chain on HIT (sources default to stop-on-HIT); in a source chain or \`collector.next\` that halts the event for all destinations. Set \`cache.namespace\` to prefix keys; omit it to write keys directly. \`CacheRule.match\` is optional, omit for always-match.
24441
24453
 
24442
24454
  ## Simulation Tips
24443
24455
 
@@ -24517,6 +24529,7 @@ function wrapRegisteredToolsWithTelemetry(server) {
24517
24529
  }
24518
24530
  function createWalkerOSMcpServer(opts) {
24519
24531
  const packageVersion = opts.version ?? "0.0.0";
24532
+ const runtime = opts.runtime ?? createHostedRuntime(opts.client);
24520
24533
  const server = new McpServer(
24521
24534
  {
24522
24535
  name: "walkeros-flow",
@@ -24535,12 +24548,12 @@ function createWalkerOSMcpServer(opts) {
24535
24548
  registerFrameManageTool(server, opts.client);
24536
24549
  registerFeedbackTool(server, opts.client);
24537
24550
  registerDiagnosticsTool(server, opts.client, packageVersion);
24538
- registerFlowValidateTool(server);
24539
- registerFlowBundleTool(server, opts.client);
24540
- registerFlowSimulateTool(server, opts.client);
24541
- registerFlowPushTool(server);
24542
- registerFlowExamplesTool(server);
24543
- registerFlowLoadTool(server, opts.client);
24551
+ registerFlowValidateTool(server, runtime);
24552
+ registerFlowBundleTool(server, opts.client, runtime);
24553
+ registerFlowSimulateTool(server, opts.client, runtime);
24554
+ registerFlowPushTool(server, runtime);
24555
+ registerFlowExamplesTool(server, runtime);
24556
+ registerFlowLoadTool(server, opts.client, runtime);
24544
24557
  registerPackageSearchTool(server);
24545
24558
  registerGetPackageSchemaTool(server);
24546
24559
  registerPackageSchemaResources(server);
@@ -24835,6 +24848,153 @@ function createStreamableHttpHandler(server, opts = {}) {
24835
24848
  };
24836
24849
  }
24837
24850
 
24851
+ // src/runtime/local.ts
24852
+ import {
24853
+ loadJsonConfig,
24854
+ bundle as bundle2,
24855
+ push,
24856
+ simulateSource,
24857
+ simulateTransformer,
24858
+ simulateCollector,
24859
+ simulateDestination
24860
+ } from "@walkeros/cli";
24861
+
24862
+ // src/runtime/bundle-cache.ts
24863
+ import { createHash } from "crypto";
24864
+ import os from "os";
24865
+ import path from "path";
24866
+ import { mkdir, rm, writeFile } from "fs/promises";
24867
+ import { rmSync } from "fs";
24868
+ import { bundle } from "@walkeros/cli";
24869
+ var MAX_ENTRIES = 8;
24870
+ var cache2 = /* @__PURE__ */ new Map();
24871
+ var inFlight = /* @__PURE__ */ new Map();
24872
+ var cleanupRegistered = false;
24873
+ function hashConfig(resolvedConfig) {
24874
+ return createHash("sha256").update(resolvedConfig).digest("hex");
24875
+ }
24876
+ function isInlineJsonConfig(resolvedConfig) {
24877
+ const trimmed = resolvedConfig.trimStart();
24878
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false;
24879
+ try {
24880
+ JSON.parse(resolvedConfig);
24881
+ return true;
24882
+ } catch {
24883
+ return false;
24884
+ }
24885
+ }
24886
+ function registerProcessCleanup() {
24887
+ if (cleanupRegistered) return;
24888
+ cleanupRegistered = true;
24889
+ const cleanup = () => {
24890
+ for (const entry of cache2.values()) {
24891
+ try {
24892
+ rmSync(entry.dir, { recursive: true, force: true });
24893
+ } catch {
24894
+ }
24895
+ }
24896
+ cache2.clear();
24897
+ };
24898
+ process.once("exit", cleanup);
24899
+ }
24900
+ async function evictIfNeeded() {
24901
+ while (cache2.size > MAX_ENTRIES) {
24902
+ const oldestKey = cache2.keys().next().value;
24903
+ if (oldestKey === void 0) break;
24904
+ const evicted = cache2.get(oldestKey);
24905
+ cache2.delete(oldestKey);
24906
+ if (evicted) await rm(evicted.dir, { recursive: true, force: true });
24907
+ }
24908
+ }
24909
+ async function getOrBuildBundle(resolvedConfig) {
24910
+ if (!isInlineJsonConfig(resolvedConfig)) return void 0;
24911
+ registerProcessCleanup();
24912
+ const key = hashConfig(resolvedConfig);
24913
+ const cached = cache2.get(key);
24914
+ if (cached) {
24915
+ cache2.delete(key);
24916
+ cache2.set(key, cached);
24917
+ return cached.bundlePath;
24918
+ }
24919
+ const pending = inFlight.get(key);
24920
+ if (pending) return pending;
24921
+ const build = (async () => {
24922
+ const dir = path.join(
24923
+ os.tmpdir(),
24924
+ `walkeros-mcp-bundle-${key.slice(0, 16)}`
24925
+ );
24926
+ await rm(dir, { recursive: true, force: true });
24927
+ await mkdir(dir, { recursive: true });
24928
+ const bundlePath = path.join(dir, "flow.mjs");
24929
+ await writeFile(bundlePath, "", "utf-8");
24930
+ await bundle(resolvedConfig, {
24931
+ target: "simulate",
24932
+ silent: true,
24933
+ buildOverrides: { output: bundlePath, format: "esm", minify: false }
24934
+ });
24935
+ cache2.set(key, { bundlePath, dir });
24936
+ await evictIfNeeded();
24937
+ return bundlePath;
24938
+ })();
24939
+ inFlight.set(key, build);
24940
+ try {
24941
+ return await build;
24942
+ } finally {
24943
+ inFlight.delete(key);
24944
+ }
24945
+ }
24946
+
24947
+ // src/runtime/local.ts
24948
+ function createLocalRuntime() {
24949
+ return {
24950
+ load: (input) => loadJsonConfig(input),
24951
+ bundle: (input, opts) => bundle2(input, {
24952
+ flowName: opts.flowName,
24953
+ stats: opts.stats,
24954
+ buildOverrides: opts.output ? { output: opts.output } : void 0
24955
+ }),
24956
+ async simulate(input, opts) {
24957
+ let bundlePath;
24958
+ try {
24959
+ bundlePath = await getOrBuildBundle(input);
24960
+ } catch {
24961
+ bundlePath = void 0;
24962
+ }
24963
+ const common = { bundlePath, flow: opts.flow, silent: true };
24964
+ switch (opts.stepType) {
24965
+ case "source":
24966
+ return simulateSource(input, opts.event, {
24967
+ sourceId: opts.stepId,
24968
+ ...common
24969
+ });
24970
+ case "transformer":
24971
+ return simulateTransformer(
24972
+ input,
24973
+ opts.event,
24974
+ { transformerId: opts.stepId, ...common, ingest: opts.ingest }
24975
+ );
24976
+ case "collector":
24977
+ return simulateCollector(
24978
+ input,
24979
+ opts.event,
24980
+ { collectorName: opts.stepId, ...common, state: opts.state }
24981
+ );
24982
+ case "destination":
24983
+ return simulateDestination(
24984
+ input,
24985
+ opts.event,
24986
+ { destinationId: opts.stepId, ...common }
24987
+ );
24988
+ }
24989
+ },
24990
+ push: (input, event, opts) => push(input, event, {
24991
+ json: true,
24992
+ flow: opts.flow,
24993
+ platform: opts.platform
24994
+ })
24995
+ };
24996
+ }
24997
+
24838
24998
  // src/tool-definitions.ts
24839
24999
  import { z as z20 } from "zod";
24840
25000
  import { schemas as schemas6 } from "@walkeros/cli/dev";
@@ -24999,7 +25159,7 @@ var TOOL_DEFINITIONS = [
24999
25159
  {
25000
25160
  name: "flow_validate",
25001
25161
  title: "Validate Flow",
25002
- description: "Validate walkerOS events, flow configurations, mapping rules, or data contracts. Accepts JSON strings, file paths, or URLs as input. Returns validation results with errors, warnings, and details.",
25162
+ description: "Validate walkerOS events, flow configurations, mapping rules, or data contracts. Accepts JSON strings, file paths, or URLs as input; on the hosted server only inline JSON or a saved flow id (flow_ or cfg_), no file paths or URLs. Returns validation results with errors, warnings, and details.",
25003
25163
  inputSchema: schemas6.ValidateInputShape,
25004
25164
  annotations: {
25005
25165
  readOnlyHint: true,
@@ -25037,10 +25197,11 @@ var TOOL_DEFINITIONS = [
25037
25197
  verbose: z20.boolean().optional()
25038
25198
  },
25039
25199
  annotations: {
25040
- readOnlyHint: true,
25041
- destructiveHint: false,
25042
- idempotentHint: true,
25043
- openWorldHint: false
25200
+ // Not read-only: simulation compiles and runs the flow in process.
25201
+ readOnlyHint: false,
25202
+ destructiveHint: true,
25203
+ idempotentHint: false,
25204
+ openWorldHint: true
25044
25205
  }
25045
25206
  },
25046
25207
  {
@@ -25063,7 +25224,7 @@ var TOOL_DEFINITIONS = [
25063
25224
  {
25064
25225
  name: "flow_examples",
25065
25226
  title: "Flow Examples",
25066
- description: "List all step examples in a walkerOS flow configuration. Shows example names, step locations, and in/out shapes. Use this to discover available test fixtures and simulation data.",
25227
+ description: "List all step examples in a walkerOS flow configuration. Shows example names, step locations, and in/out shapes. On the hosted server configPath accepts only inline JSON or a saved flow id (flow_ or cfg_), no file paths or URLs. Use this to discover available test fixtures and simulation data.",
25067
25228
  inputSchema: {
25068
25229
  configPath: z20.string().min(1),
25069
25230
  flow: z20.string().optional(),
@@ -25081,7 +25242,7 @@ var TOOL_DEFINITIONS = [
25081
25242
  {
25082
25243
  name: "flow_load",
25083
25244
  title: "Load or Create Flow",
25084
- description: "Load an existing flow configuration from a local file path, URL, or walkerOS API (by flow ID). Or create a new empty flow by specifying a platform (web or server). Use the add-step prompt to add sources, destinations, transformers, or stores to the flow.",
25245
+ description: "Load an existing flow configuration from a local file path, URL, or walkerOS API (by flow ID). On the hosted server only inline JSON or a saved flow id is accepted, no file paths or URLs. Or create a new empty flow by specifying a platform (web or server). Use the add-step prompt to add sources, destinations, transformers, or stores to the flow.",
25085
25246
  inputSchema: {
25086
25247
  source: z20.string().optional(),
25087
25248
  platform: z20.enum(["web", "server"]).optional()
@@ -25172,7 +25333,7 @@ var TOOL_DEFINITIONS = [
25172
25333
  ];
25173
25334
 
25174
25335
  // src/index.ts
25175
- function createToolHandlers(client, packageVersion = "0.0.0") {
25336
+ function createToolHandlers(client, packageVersion = "0.0.0", runtime = createHostedRuntime(client)) {
25176
25337
  const specs = [
25177
25338
  createAuthToolSpec(client),
25178
25339
  createProjectManageToolSpec(client),
@@ -25184,12 +25345,12 @@ function createToolHandlers(client, packageVersion = "0.0.0") {
25184
25345
  createHubManageToolSpec(client),
25185
25346
  createFrameManageToolSpec(client),
25186
25347
  createFeedbackToolSpec(client),
25187
- createFlowValidateToolSpec(),
25188
- createFlowBundleToolSpec(client),
25189
- createFlowSimulateToolSpec(client),
25190
- createFlowPushToolSpec(),
25191
- createFlowExamplesToolSpec(),
25192
- createFlowLoadToolSpec(client),
25348
+ createFlowValidateToolSpec(runtime),
25349
+ createFlowBundleToolSpec(client, runtime),
25350
+ createFlowSimulateToolSpec(client, runtime),
25351
+ createFlowPushToolSpec(runtime),
25352
+ createFlowExamplesToolSpec(runtime),
25353
+ createFlowLoadToolSpec(client, runtime),
25193
25354
  createPackageSearchToolSpec(),
25194
25355
  createPackageGetToolSpec(),
25195
25356
  createDiagnosticsToolSpec(client, packageVersion)
@@ -25249,7 +25410,10 @@ export {
25249
25410
  HUB_NOT_FOUND_HINT,
25250
25411
  HttpToolClient,
25251
25412
  DESCRIPTION7 as OBSERVE_SESSION_DESCRIPTION,
25413
+ RuntimeRefusal,
25252
25414
  TOOL_DEFINITIONS,
25415
+ createHostedRuntime,
25416
+ createLocalRuntime,
25253
25417
  createStreamableHttpHandler,
25254
25418
  createToolHandlers,
25255
25419
  createWalkerOSMcpServer,