@walkeros/mcp 4.6.0 → 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/stdio.js CHANGED
@@ -8,7 +8,7 @@ import { setClientContext } from "@walkeros/cli";
8
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
9
 
10
10
  // src/tools/validate.ts
11
- import { validate, loadJsonConfig } from "@walkeros/cli";
11
+ import { validate } from "@walkeros/cli";
12
12
  import { schemas } from "@walkeros/cli/dev";
13
13
  import { mcpResult, mcpError } from "@walkeros/core";
14
14
 
@@ -102,7 +102,54 @@ var ExamplesListOutputShape = {
102
102
  ).describe("Step examples")
103
103
  };
104
104
 
105
+ // src/runtime/types.ts
106
+ var RuntimeRefusal = class extends Error {
107
+ hint;
108
+ constructor(message, hint) {
109
+ super(message);
110
+ this.name = "RuntimeRefusal";
111
+ this.hint = hint;
112
+ }
113
+ };
114
+ function refusalHint(error, fallback) {
115
+ return error instanceof RuntimeRefusal ? error.hint : fallback;
116
+ }
117
+ 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.";
118
+ function unavailableOperation(operation) {
119
+ const verb = operation === "bundle" ? "Bundling" : operation === "simulate" ? "Simulating" : "Running";
120
+ return new RuntimeRefusal(
121
+ `${verb} a flow is not available on the hosted walkerOS MCP server.`,
122
+ HINT_OUT_OF_PROCESS
123
+ );
124
+ }
125
+
126
+ // src/cloud-flow.ts
127
+ import { isObject } from "@walkeros/core";
128
+ var CLOUD_ID_PATTERN = /^(flow|cfg)_[A-Za-z0-9_-]+$/;
129
+ function isCloudId(input) {
130
+ return CLOUD_ID_PATTERN.test(input);
131
+ }
132
+ function flowConfigOf(flow) {
133
+ return isObject(flow) && isObject(flow.config) ? flow.config : {};
134
+ }
135
+
105
136
  // src/tools/validate.ts
137
+ async function loadValidateInput(runtime, input, type) {
138
+ if (!input || input.trim() === "") throw new Error(`${type} is required`);
139
+ const trimmed = input.trim();
140
+ const looksLikeJson = trimmed.startsWith("{") || trimmed.startsWith("[");
141
+ try {
142
+ return await runtime.load(input);
143
+ } catch (error) {
144
+ if (error instanceof RuntimeRefusal || isCloudId(trimmed)) throw error;
145
+ if (looksLikeJson) {
146
+ const message = error instanceof Error ? error.message : String(error);
147
+ throw new Error(`Failed to parse ${type}. ${message}`);
148
+ }
149
+ if (type === "event") return { name: trimmed };
150
+ throw error;
151
+ }
152
+ }
106
153
  var DEPRECATED_STORE_PACKAGE = "@walkeros/store-memory";
107
154
  function detectDeprecatedStorePackages(config) {
108
155
  const errors = [];
@@ -132,7 +179,7 @@ function detectDeprecatedStorePackages(config) {
132
179
  return errors;
133
180
  }
134
181
  var TITLE = "Validate Flow";
135
- var 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.";
182
+ var 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.";
136
183
  var inputSchema = schemas.ValidateInputShape;
137
184
  var annotations = {
138
185
  readOnlyHint: true,
@@ -140,17 +187,17 @@ var annotations = {
140
187
  idempotentHint: true,
141
188
  openWorldHint: false
142
189
  };
143
- function createFlowValidateToolSpec() {
190
+ function createFlowValidateToolSpec(runtime) {
144
191
  return {
145
192
  name: "flow_validate",
146
193
  title: TITLE,
147
194
  description: DESCRIPTION,
148
195
  inputSchema,
149
196
  annotations,
150
- handler: (input) => flowValidateHandlerBody(input)
197
+ handler: (input) => flowValidateHandlerBody(runtime, input)
151
198
  };
152
199
  }
153
- async function flowValidateHandlerBody(input) {
200
+ async function flowValidateHandlerBody(runtime, input) {
154
201
  const {
155
202
  type,
156
203
  input: validateInput,
@@ -158,30 +205,27 @@ async function flowValidateHandlerBody(input) {
158
205
  path: path2
159
206
  } = input ?? {};
160
207
  try {
161
- const result = await validate(type, validateInput, {
208
+ const resolved = await loadValidateInput(runtime, validateInput, type);
209
+ const result = await validate(type, resolved, {
162
210
  flow,
163
211
  path: path2
164
212
  });
165
213
  let augmented = result;
166
- if (type === "flow" && typeof validateInput === "string") {
167
- try {
168
- const config = await loadJsonConfig(validateInput);
169
- const deprecatedErrors = detectDeprecatedStorePackages(config);
170
- if (deprecatedErrors.length > 0) {
171
- augmented = {
172
- ...result,
173
- valid: false,
174
- errors: [...result.errors, ...deprecatedErrors]
175
- };
176
- }
177
- } catch {
214
+ if (type === "flow") {
215
+ const deprecatedErrors = detectDeprecatedStorePackages(resolved);
216
+ if (deprecatedErrors.length > 0) {
217
+ augmented = {
218
+ ...result,
219
+ valid: false,
220
+ errors: [...result.errors, ...deprecatedErrors]
221
+ };
178
222
  }
179
223
  }
180
224
  const hints = augmented.valid ? {
181
- next: [
225
+ next: runtime.simulate ? [
182
226
  "Use flow_simulate to test event flow",
183
227
  "Use flow_bundle to build"
184
- ]
228
+ ] : [HINT_OUT_OF_PROCESS]
185
229
  } : {
186
230
  next: [
187
231
  "Fix errors above, then run flow_validate again",
@@ -192,12 +236,15 @@ async function flowValidateHandlerBody(input) {
192
236
  } catch (error) {
193
237
  return mcpError(
194
238
  error,
195
- "Check the input parameter \u2014 expected a JSON string, file path, or URL"
239
+ refusalHint(
240
+ error,
241
+ "Check the input parameter \u2014 expected a JSON string, file path, or URL"
242
+ )
196
243
  );
197
244
  }
198
245
  }
199
- function registerFlowValidateTool(server) {
200
- const spec = createFlowValidateToolSpec();
246
+ function registerFlowValidateTool(server, runtime) {
247
+ const spec = createFlowValidateToolSpec(runtime);
201
248
  server.registerTool(
202
249
  spec.name,
203
250
  {
@@ -215,17 +262,14 @@ function registerFlowValidateTool(server) {
215
262
  }
216
263
 
217
264
  // src/tools/bundle.ts
218
- import { bundle } from "@walkeros/cli";
219
265
  import { schemas as schemas2 } from "@walkeros/cli/dev";
220
266
  import { mcpResult as mcpResult2, mcpError as mcpError2 } from "@walkeros/core";
221
267
 
222
268
  // src/tools/resolve-config-path.ts
223
- var API_ID_PREFIX = /^(flow|cfg)_/;
224
269
  async function resolveConfigPath(client, configPath) {
225
- if (!API_ID_PREFIX.test(configPath)) return configPath;
270
+ if (!isCloudId(configPath)) return configPath;
226
271
  const flow = await client.getFlow({ flowId: configPath });
227
- const config = flow.config;
228
- return JSON.stringify(config ?? {});
272
+ return JSON.stringify(flowConfigOf(flow));
229
273
  }
230
274
 
231
275
  // src/tools/bundle.ts
@@ -240,24 +284,28 @@ var annotations2 = {
240
284
  idempotentHint: false,
241
285
  openWorldHint: true
242
286
  };
243
- function createFlowBundleToolSpec(client) {
287
+ function createFlowBundleToolSpec(client, runtime) {
244
288
  return {
245
289
  name: "flow_bundle",
246
290
  title: TITLE2,
247
291
  description: DESCRIPTION2,
248
292
  inputSchema: inputSchema2,
249
293
  annotations: annotations2,
250
- handler: (input) => flowBundleHandlerBody(client, input)
294
+ handler: (input) => flowBundleHandlerBody(client, runtime, input)
251
295
  };
252
296
  }
253
- async function flowBundleHandlerBody(client, input) {
297
+ async function flowBundleHandlerBody(client, runtime, input) {
254
298
  const { configPath, flow, stats, output } = input ?? {};
299
+ if (!runtime.bundle) {
300
+ const refusal = unavailableOperation("bundle");
301
+ return mcpError2(refusal, refusal.hint);
302
+ }
255
303
  try {
256
304
  const resolvedConfigPath = await resolveConfigPath(client, configPath);
257
- const result = await bundle(resolvedConfigPath, {
305
+ const result = await runtime.bundle(resolvedConfigPath, {
258
306
  flowName: flow,
259
307
  stats: stats ?? true,
260
- buildOverrides: output ? { output } : void 0
308
+ output
261
309
  });
262
310
  if (!result) {
263
311
  return mcpResult2(
@@ -281,11 +329,14 @@ async function flowBundleHandlerBody(client, input) {
281
329
  }
282
330
  );
283
331
  } catch (error) {
284
- return mcpError2(error, "Run flow_validate for detailed error messages");
332
+ return mcpError2(
333
+ error,
334
+ refusalHint(error, "Run flow_validate for detailed error messages")
335
+ );
285
336
  }
286
337
  }
287
- function registerFlowBundleTool(server, client) {
288
- const spec = createFlowBundleToolSpec(client);
338
+ function registerFlowBundleTool(server, client, runtime) {
339
+ const spec = createFlowBundleToolSpec(client, runtime);
289
340
  server.registerTool(
290
341
  spec.name,
291
342
  {
@@ -304,101 +355,17 @@ function registerFlowBundleTool(server, client) {
304
355
 
305
356
  // src/tools/simulate.ts
306
357
  import { z as z2 } from "zod";
307
- import {
308
- simulateSource,
309
- simulateTransformer,
310
- simulateDestination,
311
- simulateCollector
312
- } from "@walkeros/cli";
313
358
  import { schemas as schemas3 } from "@walkeros/cli/dev";
314
359
  import { mcpResult as mcpResult3, mcpError as mcpError3 } from "@walkeros/core";
315
-
316
- // src/tools/bundle-cache.ts
317
- import { createHash } from "crypto";
318
- import os from "os";
319
- import path from "path";
320
- import { mkdir, rm, writeFile } from "fs/promises";
321
- import { rmSync } from "fs";
322
- import { bundle as bundle2 } from "@walkeros/cli";
323
- var MAX_ENTRIES = 8;
324
- var cache = /* @__PURE__ */ new Map();
325
- var inFlight = /* @__PURE__ */ new Map();
326
- var cleanupRegistered = false;
327
- function hashConfig(resolvedConfig) {
328
- return createHash("sha256").update(resolvedConfig).digest("hex");
329
- }
330
- function isInlineJsonConfig(resolvedConfig) {
331
- const trimmed = resolvedConfig.trimStart();
332
- if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false;
333
- try {
334
- JSON.parse(resolvedConfig);
335
- return true;
336
- } catch {
337
- return false;
338
- }
339
- }
340
- function registerProcessCleanup() {
341
- if (cleanupRegistered) return;
342
- cleanupRegistered = true;
343
- const cleanup = () => {
344
- for (const entry of cache.values()) {
345
- try {
346
- rmSync(entry.dir, { recursive: true, force: true });
347
- } catch {
348
- }
349
- }
350
- cache.clear();
351
- };
352
- process.once("exit", cleanup);
353
- }
354
- async function evictIfNeeded() {
355
- while (cache.size > MAX_ENTRIES) {
356
- const oldestKey = cache.keys().next().value;
357
- if (oldestKey === void 0) break;
358
- const evicted = cache.get(oldestKey);
359
- cache.delete(oldestKey);
360
- if (evicted) await rm(evicted.dir, { recursive: true, force: true });
361
- }
362
- }
363
- async function getOrBuildBundle(resolvedConfig) {
364
- if (!isInlineJsonConfig(resolvedConfig)) return void 0;
365
- registerProcessCleanup();
366
- const key = hashConfig(resolvedConfig);
367
- const cached = cache.get(key);
368
- if (cached) {
369
- cache.delete(key);
370
- cache.set(key, cached);
371
- return cached.bundlePath;
372
- }
373
- const pending = inFlight.get(key);
374
- if (pending) return pending;
375
- const build = (async () => {
376
- const dir = path.join(
377
- os.tmpdir(),
378
- `walkeros-mcp-bundle-${key.slice(0, 16)}`
379
- );
380
- await rm(dir, { recursive: true, force: true });
381
- await mkdir(dir, { recursive: true });
382
- const bundlePath = path.join(dir, "flow.mjs");
383
- await writeFile(bundlePath, "", "utf-8");
384
- await bundle2(resolvedConfig, {
385
- target: "simulate",
386
- silent: true,
387
- buildOverrides: { output: bundlePath, format: "esm", minify: false }
388
- });
389
- cache.set(key, { bundlePath, dir });
390
- await evictIfNeeded();
391
- return bundlePath;
392
- })();
393
- inFlight.set(key, build);
394
- try {
395
- return await build;
396
- } finally {
397
- inFlight.delete(key);
398
- }
360
+ var STEP_TYPES = [
361
+ "source",
362
+ "transformer",
363
+ "collector",
364
+ "destination"
365
+ ];
366
+ function isStepType(value) {
367
+ return STEP_TYPES.some((t) => t === value);
399
368
  }
400
-
401
- // src/tools/simulate.ts
402
369
  var TITLE3 = "Simulate Flow";
403
370
  var DESCRIPTION3 = '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.';
404
371
  var inputSchema3 = {
@@ -427,23 +394,31 @@ var inputSchema3 = {
427
394
  )
428
395
  };
429
396
  var annotations3 = {
430
- readOnlyHint: true,
431
- destructiveHint: false,
432
- idempotentHint: true,
433
- openWorldHint: false
397
+ // Simulation downloads the packages a config names and runs caller-controlled
398
+ // flow code in process. Destinations are mocked, but that code is not, so the
399
+ // hints stay conservative: side effects possible, repeat calls not assumed
400
+ // safe, external systems reachable.
401
+ readOnlyHint: false,
402
+ destructiveHint: true,
403
+ idempotentHint: false,
404
+ openWorldHint: true
434
405
  };
435
- function createFlowSimulateToolSpec(client) {
406
+ function createFlowSimulateToolSpec(client, runtime) {
436
407
  return {
437
408
  name: "flow_simulate",
438
409
  title: TITLE3,
439
410
  description: DESCRIPTION3,
440
411
  inputSchema: inputSchema3,
441
412
  annotations: annotations3,
442
- handler: (input) => flowSimulateHandlerBody(client, input)
413
+ handler: (input) => flowSimulateHandlerBody(client, runtime, input)
443
414
  };
444
415
  }
445
- async function flowSimulateHandlerBody(client, input) {
416
+ async function flowSimulateHandlerBody(client, runtime, input) {
446
417
  const { configPath, event, flow, platform, step, verbose, ingest, state } = input ?? {};
418
+ if (!runtime.simulate) {
419
+ const refusal = unavailableOperation("simulate");
420
+ return mcpError3(refusal, refusal.hint);
421
+ }
447
422
  try {
448
423
  if (!event) {
449
424
  throw new Error(
@@ -473,66 +448,16 @@ async function flowSimulateHandlerBody(client, input) {
473
448
  }
474
449
  const stepType = step.substring(0, dotIndex);
475
450
  const stepId = step.substring(dotIndex + 1);
476
- const resolvedConfigPath = await resolveConfigPath(client, configPath);
477
- let bundlePath;
478
- try {
479
- bundlePath = await getOrBuildBundle(resolvedConfigPath);
480
- } catch {
481
- bundlePath = void 0;
482
- }
483
- let result;
484
- switch (stepType) {
485
- case "source":
486
- result = await simulateSource(resolvedConfigPath, resolvedEvent, {
487
- sourceId: stepId,
488
- bundlePath,
489
- flow,
490
- silent: true
491
- });
492
- break;
493
- case "transformer":
494
- result = await simulateTransformer(
495
- resolvedConfigPath,
496
- resolvedEvent,
497
- {
498
- transformerId: stepId,
499
- bundlePath,
500
- flow,
501
- silent: true,
502
- ingest
503
- }
504
- );
505
- break;
506
- case "collector":
507
- result = await simulateCollector(
508
- resolvedConfigPath,
509
- resolvedEvent,
510
- {
511
- collectorName: stepId,
512
- bundlePath,
513
- flow,
514
- silent: true,
515
- state
516
- }
517
- );
518
- break;
519
- case "destination":
520
- result = await simulateDestination(
521
- resolvedConfigPath,
522
- resolvedEvent,
523
- {
524
- destinationId: stepId,
525
- bundlePath,
526
- flow,
527
- silent: true
528
- }
529
- );
530
- break;
531
- default:
532
- throw new Error(
533
- `Unknown step type "${stepType}". Use "source", "collector", "transformer", or "destination".`
534
- );
451
+ if (!isStepType(stepType)) {
452
+ throw new Error(
453
+ `Unknown step type "${stepType}". Use "source", "collector", "transformer", or "destination".`
454
+ );
535
455
  }
456
+ const resolvedConfigPath = await resolveConfigPath(client, configPath);
457
+ const result = await runtime.simulate(
458
+ resolvedConfigPath,
459
+ { stepType, stepId, event: resolvedEvent, flow, ingest, state }
460
+ );
536
461
  const success = !result.error;
537
462
  const errorMessage = result.error?.message;
538
463
  if (result.step === "source") {
@@ -620,11 +545,11 @@ async function flowSimulateHandlerBody(client, input) {
620
545
  if (msg.includes("not found in collector")) {
621
546
  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.';
622
547
  }
623
- return mcpError3(error, hint);
548
+ return mcpError3(error, refusalHint(error, hint));
624
549
  }
625
550
  }
626
- function registerFlowSimulateTool(server, client) {
627
- const spec = createFlowSimulateToolSpec(client);
551
+ function registerFlowSimulateTool(server, client, runtime) {
552
+ const spec = createFlowSimulateToolSpec(client, runtime);
628
553
  server.registerTool(
629
554
  spec.name,
630
555
  {
@@ -643,7 +568,6 @@ function registerFlowSimulateTool(server, client) {
643
568
 
644
569
  // src/tools/push.ts
645
570
  import { z as z3 } from "zod";
646
- import { push } from "@walkeros/cli";
647
571
  import { schemas as schemas4 } from "@walkeros/cli/dev";
648
572
  import { mcpResult as mcpResult4, mcpError as mcpError4 } from "@walkeros/core";
649
573
  var TITLE4 = "Push Events";
@@ -662,21 +586,24 @@ var annotations4 = {
662
586
  idempotentHint: false,
663
587
  openWorldHint: true
664
588
  };
665
- function createFlowPushToolSpec() {
589
+ function createFlowPushToolSpec(runtime) {
666
590
  return {
667
591
  name: "flow_push",
668
592
  title: TITLE4,
669
593
  description: DESCRIPTION4,
670
594
  inputSchema: inputSchema4,
671
595
  annotations: annotations4,
672
- handler: (input) => flowPushHandlerBody(input)
596
+ handler: (input) => flowPushHandlerBody(runtime, input)
673
597
  };
674
598
  }
675
- async function flowPushHandlerBody(input) {
599
+ async function flowPushHandlerBody(runtime, input) {
676
600
  const { configPath, event, flow, platform } = input ?? {};
601
+ if (!runtime.push) {
602
+ const refusal = unavailableOperation("push");
603
+ return mcpError4(refusal, refusal.hint);
604
+ }
677
605
  try {
678
- const result = await push(configPath, event, {
679
- json: true,
606
+ const result = await runtime.push(configPath, event, {
680
607
  flow,
681
608
  platform
682
609
  });
@@ -690,12 +617,15 @@ async function flowPushHandlerBody(input) {
690
617
  } catch (error) {
691
618
  return mcpError4(
692
619
  error,
693
- "Check configPath and event format. For web flows, use flow_simulate."
620
+ refusalHint(
621
+ error,
622
+ "Check configPath and event format. For web flows, use flow_simulate."
623
+ )
694
624
  );
695
625
  }
696
626
  }
697
- function registerFlowPushTool(server) {
698
- const spec = createFlowPushToolSpec();
627
+ function registerFlowPushTool(server, runtime) {
628
+ const spec = createFlowPushToolSpec(runtime);
699
629
  server.registerTool(
700
630
  spec.name,
701
631
  {
@@ -714,7 +644,6 @@ function registerFlowPushTool(server) {
714
644
 
715
645
  // src/tools/examples.ts
716
646
  import { z as z4 } from "zod";
717
- import { loadJsonConfig as loadJsonConfig2 } from "@walkeros/cli";
718
647
  import { fetchPackage, mcpResult as mcpResult5, mcpError as mcpError5 } from "@walkeros/core";
719
648
 
720
649
  // src/catalog.ts
@@ -722,7 +651,7 @@ var NPM_SEARCH_URL = "https://registry.npmjs.org/-/v1/search";
722
651
  var JSDELIVR_BASE = "https://cdn.jsdelivr.net/npm";
723
652
  var WALKEROS_JSON_PATH = "dist/walkerOS.json";
724
653
  var CACHE_TTL = 5 * 60 * 1e3;
725
- var CLIENT_HEADER = "walkeros-mcp/4.6.0";
654
+ var CLIENT_HEADER = "walkeros-mcp/4.6.1";
726
655
  function getPackageBaseUrl() {
727
656
  return process.env.WALKEROS_APP_URL || void 0;
728
657
  }
@@ -730,7 +659,7 @@ var lastFetchInfo;
730
659
  function getLastCatalogSource() {
731
660
  return lastFetchInfo;
732
661
  }
733
- var cache2 = /* @__PURE__ */ new Map();
662
+ var cache = /* @__PURE__ */ new Map();
734
663
  function normalizePlatform(platform) {
735
664
  if (platform == null) return [];
736
665
  if (typeof platform === "string") {
@@ -744,7 +673,7 @@ function normalizePlatform(platform) {
744
673
  async function fetchCatalog(filters) {
745
674
  const sourceKey = filters?.baseUrl ?? "npm";
746
675
  const warnings = [];
747
- const cached = cache2.get(sourceKey);
676
+ const cached = cache.get(sourceKey);
748
677
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
749
678
  lastFetchInfo = {
750
679
  source: filters?.baseUrl ? "app" : "npm",
@@ -802,7 +731,7 @@ async function fetchCatalog(filters) {
802
731
  timestamp: Date.now()
803
732
  };
804
733
  if (result.entries.length > 0 && result.complete) {
805
- cache2.set(sourceKey, { entries: result.entries, timestamp: Date.now() });
734
+ cache.set(sourceKey, { entries: result.entries, timestamp: Date.now() });
806
735
  }
807
736
  return { entries: applyFilters(result.entries, filters), warnings };
808
737
  }
@@ -875,10 +804,13 @@ function applyFilters(entries, filters) {
875
804
  }
876
805
 
877
806
  // src/tools/examples.ts
807
+ var MAX_PACKAGE_LOOKUPS = 25;
878
808
  var TITLE5 = "Flow Examples";
879
- var DESCRIPTION5 = '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.';
809
+ var DESCRIPTION5 = '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.';
880
810
  var inputSchema5 = {
881
- configPath: z4.string().min(1).describe("Path to flow configuration file, URL, or inline JSON string"),
811
+ configPath: z4.string().min(1).describe(
812
+ "Inline JSON string, file path, or URL of a flow configuration (hosted server: inline JSON or a saved flow id only)"
813
+ ),
882
814
  flow: z4.string().optional().describe("Flow name for multi-flow configs"),
883
815
  step: z4.string().optional().describe('Filter to a specific step (e.g., "destination.gtag")'),
884
816
  full: z4.boolean().optional().describe(
@@ -894,20 +826,20 @@ var annotations5 = {
894
826
  idempotentHint: true,
895
827
  openWorldHint: false
896
828
  };
897
- function createFlowExamplesToolSpec() {
829
+ function createFlowExamplesToolSpec(runtime) {
898
830
  return {
899
831
  name: "flow_examples",
900
832
  title: TITLE5,
901
833
  description: DESCRIPTION5,
902
834
  inputSchema: inputSchema5,
903
835
  annotations: annotations5,
904
- handler: (input) => flowExamplesHandlerBody(input)
836
+ handler: (input) => flowExamplesHandlerBody(runtime, input)
905
837
  };
906
838
  }
907
- async function flowExamplesHandlerBody(input) {
839
+ async function flowExamplesHandlerBody(runtime, input) {
908
840
  const { configPath, flow, step, full, includeHidden } = input ?? {};
909
841
  try {
910
- const rawConfig = await loadJsonConfig2(configPath);
842
+ const rawConfig = await runtime.load(configPath);
911
843
  const flowNames = Object.keys(rawConfig.flows || {});
912
844
  const flowName = flow || (flowNames.length === 1 ? flowNames[0] : void 0);
913
845
  if (!flowName) {
@@ -961,6 +893,19 @@ async function flowExamplesHandlerBody(input) {
961
893
  }
962
894
  return void 0;
963
895
  };
896
+ const packageLookups = /* @__PURE__ */ new Map();
897
+ let skippedPackages = false;
898
+ const examplesForPackage = (packageName) => {
899
+ const existing = packageLookups.get(packageName);
900
+ if (existing) return existing;
901
+ if (packageLookups.size >= MAX_PACKAGE_LOOKUPS) {
902
+ skippedPackages = true;
903
+ return Promise.resolve(void 0);
904
+ }
905
+ const lookup = loadPackageExamples(packageName);
906
+ packageLookups.set(packageName, lookup);
907
+ return lookup;
908
+ };
964
909
  const stepTypes = [
965
910
  { key: "sources", type: "source" },
966
911
  { key: "transformers", type: "transformer" },
@@ -977,7 +922,7 @@ async function flowExamplesHandlerBody(input) {
977
922
  continue;
978
923
  }
979
924
  if (!ref.package) continue;
980
- const packageExamples = await loadPackageExamples(ref.package);
925
+ const packageExamples = await examplesForPackage(ref.package);
981
926
  if (packageExamples)
982
927
  examples.push(...toItems(packageExamples, type, name, "package"));
983
928
  }
@@ -987,21 +932,33 @@ async function flowExamplesHandlerBody(input) {
987
932
  count: examples.length,
988
933
  examples
989
934
  };
990
- const hints = {
991
- next: ["Use flow_simulate with step and event to simulate"]
992
- };
935
+ const warnings = [];
993
936
  if (examples.length === 0) {
994
- hints.warnings = [
937
+ warnings.push(
995
938
  "No examples found. Add examples to step entries, or reference a package that ships examples (see package_get)."
996
- ];
939
+ );
940
+ }
941
+ if (skippedPackages) {
942
+ warnings.push(
943
+ `Package examples were looked up for the first ${MAX_PACKAGE_LOOKUPS} packages only. Use step to narrow the result.`
944
+ );
997
945
  }
946
+ const hints = {
947
+ next: [
948
+ runtime.simulate ? "Use flow_simulate with step and event to simulate" : HINT_OUT_OF_PROCESS
949
+ ],
950
+ ...warnings.length > 0 ? { warnings } : {}
951
+ };
998
952
  return mcpResult5(result, hints);
999
953
  } catch (error) {
1000
- return mcpError5(error, "Check configPath \u2014 expected a flow.json file");
954
+ return mcpError5(
955
+ error,
956
+ refusalHint(error, "Check configPath \u2014 expected a flow.json file")
957
+ );
1001
958
  }
1002
959
  }
1003
- function registerFlowExamplesTool(server) {
1004
- const spec = createFlowExamplesToolSpec();
960
+ function registerFlowExamplesTool(server, runtime) {
961
+ const spec = createFlowExamplesToolSpec(runtime);
1005
962
  server.registerTool(
1006
963
  spec.name,
1007
964
  {
@@ -1212,7 +1169,6 @@ function registerGetPackageSchemaTool(server) {
1212
1169
 
1213
1170
  // src/tools/flow-load.ts
1214
1171
  import { z as z6 } from "zod";
1215
- import { loadJsonConfig as loadJsonConfig3 } from "@walkeros/cli";
1216
1172
  import { mcpResult as mcpResult7, mcpError as mcpError7 } from "@walkeros/core";
1217
1173
 
1218
1174
  // src/types.ts
@@ -1297,7 +1253,6 @@ function resolveDefaultProject(client, projectId) {
1297
1253
  }
1298
1254
 
1299
1255
  // src/tools/flow-load.ts
1300
- var API_ID_PREFIX2 = /^(flow|cfg)_/;
1301
1256
  var WEB_SKELETON = {
1302
1257
  version: 4,
1303
1258
  flows: {
@@ -1319,7 +1274,7 @@ var SERVER_SKELETON = {
1319
1274
  }
1320
1275
  };
1321
1276
  var TITLE6 = "Load or Create Flow";
1322
- var DESCRIPTION6 = "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.";
1277
+ var DESCRIPTION6 = "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.";
1323
1278
  var inputSchema6 = {
1324
1279
  source: z6.string().optional().describe(
1325
1280
  "Flow source: local file path (./flow.json), URL (https://...), inline JSON string, or API flow ID (cfg_...). Omit to create a new flow."
@@ -1338,19 +1293,19 @@ var annotations6 = {
1338
1293
  idempotentHint: true,
1339
1294
  openWorldHint: true
1340
1295
  };
1341
- function createFlowLoadToolSpec(client) {
1296
+ function createFlowLoadToolSpec(client, runtime) {
1342
1297
  return {
1343
1298
  name: "flow_load",
1344
1299
  title: TITLE6,
1345
1300
  description: DESCRIPTION6,
1346
1301
  inputSchema: inputSchema6,
1347
1302
  annotations: annotations6,
1348
- handler: (input) => flowLoadHandlerBody(client, input)
1303
+ handler: (input) => flowLoadHandlerBody(client, runtime, input)
1349
1304
  };
1350
1305
  }
1351
- async function flowLoadHandlerBody(client, input) {
1306
+ async function flowLoadHandlerBody(client, runtime, input) {
1352
1307
  const { source, platform } = input ?? {};
1353
- if (source && API_ID_PREFIX2.test(source)) {
1308
+ if (source && isCloudId(source)) {
1354
1309
  const resolvedProjectId = resolveDefaultProject(client, void 0);
1355
1310
  if (!resolvedProjectId) {
1356
1311
  return mcpError7(new Error(NO_DEFAULT_PROJECT_ERROR));
@@ -1360,9 +1315,8 @@ async function flowLoadHandlerBody(client, input) {
1360
1315
  flowId: source,
1361
1316
  projectId: resolvedProjectId
1362
1317
  });
1363
- const config = flow.config;
1364
1318
  return mcpResult7(
1365
- redactNestedStrings(config ?? {}, { skip: keepStructural }),
1319
+ redactNestedStrings(flowConfigOf(flow), { skip: keepStructural }),
1366
1320
  {
1367
1321
  next: ["Use flow_validate to check", "Use add-step prompt to modify"]
1368
1322
  }
@@ -1373,7 +1327,7 @@ async function flowLoadHandlerBody(client, input) {
1373
1327
  }
1374
1328
  try {
1375
1329
  if (source) {
1376
- const config = await loadJsonConfig3(source);
1330
+ const config = await runtime.load(source);
1377
1331
  return mcpResult7(redactNestedStrings(config, { skip: keepStructural }), {
1378
1332
  next: ["Use flow_validate to check", "Use add-step prompt to modify"]
1379
1333
  });
@@ -1393,14 +1347,15 @@ async function flowLoadHandlerBody(client, input) {
1393
1347
  ]
1394
1348
  });
1395
1349
  } catch (error) {
1350
+ if (error instanceof RuntimeRefusal) return mcpError7(error, error.hint);
1396
1351
  const msg = error instanceof Error ? error.message : "";
1397
1352
  if (msg.includes("not found") || msg.includes("ENOENT"))
1398
1353
  return mcpError7(error, "Check configPath \u2014 expected a flow.json file");
1399
1354
  return mcpError7(error);
1400
1355
  }
1401
1356
  }
1402
- function registerFlowLoadTool(server, client) {
1403
- const spec = createFlowLoadToolSpec(client);
1357
+ function registerFlowLoadTool(server, client, runtime) {
1358
+ const spec = createFlowLoadToolSpec(client, runtime);
1404
1359
  server.registerTool(
1405
1360
  spec.name,
1406
1361
  {
@@ -1466,7 +1421,7 @@ async function feedbackHandlerBody(client, input) {
1466
1421
  const isAnonymous = explicitAnonymous ?? anonymous ?? true;
1467
1422
  await client.submitFeedback(text, {
1468
1423
  anonymous: isAnonymous,
1469
- version: "4.6.0"
1424
+ version: "4.6.1"
1470
1425
  });
1471
1426
  return mcpResult8({ ok: true });
1472
1427
  } catch (error) {
@@ -24491,6 +24446,70 @@ async function createMcpEmitter(opts) {
24491
24446
  };
24492
24447
  }
24493
24448
 
24449
+ // src/runtime/hosted.ts
24450
+ function isHttpUrl(value) {
24451
+ try {
24452
+ const url = new URL(value);
24453
+ return url.protocol === "http:" || url.protocol === "https:";
24454
+ } catch {
24455
+ return false;
24456
+ }
24457
+ }
24458
+ function looksLikePath(value) {
24459
+ return /[\\/]/.test(value) || value.startsWith(".") || /\.[A-Za-z0-9]+$/.test(value);
24460
+ }
24461
+ function classifyConfigInput(input) {
24462
+ const trimmed = input.trim();
24463
+ if (isHttpUrl(trimmed)) return "url";
24464
+ if (isCloudId(trimmed)) return "cloud-id";
24465
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "inline-json";
24466
+ if (looksLikePath(trimmed)) return "local-path";
24467
+ return "bare-string";
24468
+ }
24469
+ var HINT_INLINE_OR_ID = "Pass the flow inline as JSON, or reference a saved flow by its flow_ or cfg_ id.";
24470
+ function refuseLocalPath() {
24471
+ return new RuntimeRefusal(
24472
+ "Local file paths are not available on the hosted walkerOS MCP server.",
24473
+ HINT_INLINE_OR_ID
24474
+ );
24475
+ }
24476
+ function refuseUrl() {
24477
+ return new RuntimeRefusal(
24478
+ "Fetching URLs is not available on the hosted walkerOS MCP server.",
24479
+ HINT_INLINE_OR_ID
24480
+ );
24481
+ }
24482
+ function parseInline(trimmed) {
24483
+ try {
24484
+ return JSON.parse(trimmed);
24485
+ } catch (error) {
24486
+ const message = error instanceof Error ? error.message : String(error);
24487
+ throw new Error(`Input appears to be JSON but contains errors: ${message}`);
24488
+ }
24489
+ }
24490
+ function createHostedRuntime(client) {
24491
+ return {
24492
+ async load(input) {
24493
+ const trimmed = input.trim();
24494
+ if (trimmed === "") throw new Error("Input is required");
24495
+ switch (classifyConfigInput(trimmed)) {
24496
+ case "url":
24497
+ throw refuseUrl();
24498
+ case "cloud-id":
24499
+ return flowConfigOf(await client.getFlow({ flowId: trimmed }));
24500
+ case "inline-json":
24501
+ return parseInline(trimmed);
24502
+ case "local-path":
24503
+ throw refuseLocalPath();
24504
+ case "bare-string":
24505
+ throw new Error(
24506
+ `Cannot resolve "${trimmed}" on the hosted walkerOS MCP server. ${HINT_INLINE_OR_ID}`
24507
+ );
24508
+ }
24509
+ }
24510
+ };
24511
+ }
24512
+
24494
24513
  // src/server.ts
24495
24514
  var currentEmitter;
24496
24515
  function getMcpEmitterSingleton() {
@@ -24525,6 +24544,7 @@ function wrapRegisteredToolsWithTelemetry(server) {
24525
24544
  }
24526
24545
  function createWalkerOSMcpServer(opts) {
24527
24546
  const packageVersion = opts.version ?? "0.0.0";
24547
+ const runtime = opts.runtime ?? createHostedRuntime(opts.client);
24528
24548
  const server = new McpServer(
24529
24549
  {
24530
24550
  name: "walkeros-flow",
@@ -24543,12 +24563,12 @@ function createWalkerOSMcpServer(opts) {
24543
24563
  registerFrameManageTool(server, opts.client);
24544
24564
  registerFeedbackTool(server, opts.client);
24545
24565
  registerDiagnosticsTool(server, opts.client, packageVersion);
24546
- registerFlowValidateTool(server);
24547
- registerFlowBundleTool(server, opts.client);
24548
- registerFlowSimulateTool(server, opts.client);
24549
- registerFlowPushTool(server);
24550
- registerFlowExamplesTool(server);
24551
- registerFlowLoadTool(server, opts.client);
24566
+ registerFlowValidateTool(server, runtime);
24567
+ registerFlowBundleTool(server, opts.client, runtime);
24568
+ registerFlowSimulateTool(server, opts.client, runtime);
24569
+ registerFlowPushTool(server, runtime);
24570
+ registerFlowExamplesTool(server, runtime);
24571
+ registerFlowLoadTool(server, opts.client, runtime);
24552
24572
  registerPackageSearchTool(server);
24553
24573
  registerGetPackageSchemaTool(server);
24554
24574
  registerPackageSchemaResources(server);
@@ -24822,8 +24842,155 @@ var HttpToolClient = class {
24822
24842
  }
24823
24843
  };
24824
24844
 
24845
+ // src/runtime/local.ts
24846
+ import {
24847
+ loadJsonConfig,
24848
+ bundle as bundle2,
24849
+ push,
24850
+ simulateSource,
24851
+ simulateTransformer,
24852
+ simulateCollector,
24853
+ simulateDestination
24854
+ } from "@walkeros/cli";
24855
+
24856
+ // src/runtime/bundle-cache.ts
24857
+ import { createHash } from "crypto";
24858
+ import os from "os";
24859
+ import path from "path";
24860
+ import { mkdir, rm, writeFile } from "fs/promises";
24861
+ import { rmSync } from "fs";
24862
+ import { bundle } from "@walkeros/cli";
24863
+ var MAX_ENTRIES = 8;
24864
+ var cache2 = /* @__PURE__ */ new Map();
24865
+ var inFlight = /* @__PURE__ */ new Map();
24866
+ var cleanupRegistered = false;
24867
+ function hashConfig(resolvedConfig) {
24868
+ return createHash("sha256").update(resolvedConfig).digest("hex");
24869
+ }
24870
+ function isInlineJsonConfig(resolvedConfig) {
24871
+ const trimmed = resolvedConfig.trimStart();
24872
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false;
24873
+ try {
24874
+ JSON.parse(resolvedConfig);
24875
+ return true;
24876
+ } catch {
24877
+ return false;
24878
+ }
24879
+ }
24880
+ function registerProcessCleanup() {
24881
+ if (cleanupRegistered) return;
24882
+ cleanupRegistered = true;
24883
+ const cleanup = () => {
24884
+ for (const entry of cache2.values()) {
24885
+ try {
24886
+ rmSync(entry.dir, { recursive: true, force: true });
24887
+ } catch {
24888
+ }
24889
+ }
24890
+ cache2.clear();
24891
+ };
24892
+ process.once("exit", cleanup);
24893
+ }
24894
+ async function evictIfNeeded() {
24895
+ while (cache2.size > MAX_ENTRIES) {
24896
+ const oldestKey = cache2.keys().next().value;
24897
+ if (oldestKey === void 0) break;
24898
+ const evicted = cache2.get(oldestKey);
24899
+ cache2.delete(oldestKey);
24900
+ if (evicted) await rm(evicted.dir, { recursive: true, force: true });
24901
+ }
24902
+ }
24903
+ async function getOrBuildBundle(resolvedConfig) {
24904
+ if (!isInlineJsonConfig(resolvedConfig)) return void 0;
24905
+ registerProcessCleanup();
24906
+ const key = hashConfig(resolvedConfig);
24907
+ const cached = cache2.get(key);
24908
+ if (cached) {
24909
+ cache2.delete(key);
24910
+ cache2.set(key, cached);
24911
+ return cached.bundlePath;
24912
+ }
24913
+ const pending = inFlight.get(key);
24914
+ if (pending) return pending;
24915
+ const build = (async () => {
24916
+ const dir = path.join(
24917
+ os.tmpdir(),
24918
+ `walkeros-mcp-bundle-${key.slice(0, 16)}`
24919
+ );
24920
+ await rm(dir, { recursive: true, force: true });
24921
+ await mkdir(dir, { recursive: true });
24922
+ const bundlePath = path.join(dir, "flow.mjs");
24923
+ await writeFile(bundlePath, "", "utf-8");
24924
+ await bundle(resolvedConfig, {
24925
+ target: "simulate",
24926
+ silent: true,
24927
+ buildOverrides: { output: bundlePath, format: "esm", minify: false }
24928
+ });
24929
+ cache2.set(key, { bundlePath, dir });
24930
+ await evictIfNeeded();
24931
+ return bundlePath;
24932
+ })();
24933
+ inFlight.set(key, build);
24934
+ try {
24935
+ return await build;
24936
+ } finally {
24937
+ inFlight.delete(key);
24938
+ }
24939
+ }
24940
+
24941
+ // src/runtime/local.ts
24942
+ function createLocalRuntime() {
24943
+ return {
24944
+ load: (input) => loadJsonConfig(input),
24945
+ bundle: (input, opts) => bundle2(input, {
24946
+ flowName: opts.flowName,
24947
+ stats: opts.stats,
24948
+ buildOverrides: opts.output ? { output: opts.output } : void 0
24949
+ }),
24950
+ async simulate(input, opts) {
24951
+ let bundlePath;
24952
+ try {
24953
+ bundlePath = await getOrBuildBundle(input);
24954
+ } catch {
24955
+ bundlePath = void 0;
24956
+ }
24957
+ const common = { bundlePath, flow: opts.flow, silent: true };
24958
+ switch (opts.stepType) {
24959
+ case "source":
24960
+ return simulateSource(input, opts.event, {
24961
+ sourceId: opts.stepId,
24962
+ ...common
24963
+ });
24964
+ case "transformer":
24965
+ return simulateTransformer(
24966
+ input,
24967
+ opts.event,
24968
+ { transformerId: opts.stepId, ...common, ingest: opts.ingest }
24969
+ );
24970
+ case "collector":
24971
+ return simulateCollector(
24972
+ input,
24973
+ opts.event,
24974
+ { collectorName: opts.stepId, ...common, state: opts.state }
24975
+ );
24976
+ case "destination":
24977
+ return simulateDestination(
24978
+ input,
24979
+ opts.event,
24980
+ { destinationId: opts.stepId, ...common }
24981
+ );
24982
+ }
24983
+ },
24984
+ push: (input, event, opts) => push(input, event, {
24985
+ json: true,
24986
+ flow: opts.flow,
24987
+ platform: opts.platform
24988
+ })
24989
+ };
24990
+ }
24991
+
24825
24992
  // src/stdio.ts
24826
- setClientContext({ type: "mcp", version: "4.6.0" });
24993
+ setClientContext({ type: "mcp", version: "4.6.1" });
24827
24994
  process.on("uncaughtException", (err) => {
24828
24995
  const emitter = getMcpEmitterSingleton();
24829
24996
  if (emitter) {
@@ -24843,7 +25010,8 @@ process.on("unhandledRejection", (reason) => {
24843
25010
  async function main() {
24844
25011
  const server = createWalkerOSMcpServer({
24845
25012
  client: new HttpToolClient(),
24846
- version: "4.6.0"
25013
+ version: "4.6.1",
25014
+ runtime: createLocalRuntime()
24847
25015
  });
24848
25016
  const transport = new StdioServerTransport();
24849
25017
  await server.connect(transport);
@@ -24853,7 +25021,7 @@ main().catch(async (error) => {
24853
25021
  try {
24854
25022
  const emitter = await createMcpEmitter({
24855
25023
  clientInfo: void 0,
24856
- packageVersion: "4.6.0"
25024
+ packageVersion: "4.6.1"
24857
25025
  });
24858
25026
  await emitter.emitError("startup");
24859
25027
  } catch {