@walkeros/mcp 2.0.1 → 3.0.0

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
@@ -4,23 +4,17 @@
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
 
7
- // src/tools/bundle.ts
8
- import { z as z2 } from "zod";
9
- import { bundle, bundleRemote } from "@walkeros/cli";
7
+ // src/tools/validate.ts
8
+ import { validate } from "@walkeros/cli";
10
9
  import { schemas } from "@walkeros/cli/dev";
10
+ import { mcpResult, mcpError } from "@walkeros/core";
11
11
 
12
12
  // src/schemas/output.ts
13
13
  import { z } from "zod";
14
- var timestamp = z.string().describe("ISO 8601 timestamp");
15
- var projectId = z.string().describe("Project ID (proj_...)");
16
- var flowId = z.string().describe("Flow ID (cfg_...)");
17
- var ErrorOutputShape = {
18
- error: z.string().describe("Error message")
19
- };
20
14
  var ValidateOutputShape = {
21
15
  valid: z.boolean().describe("Whether validation passed"),
22
16
  type: z.union([
23
- z.enum(["event", "flow", "mapping"]),
17
+ z.enum(["contract", "event", "flow", "mapping"]),
24
18
  z.string().regex(/^(destinations|sources|transformers)\.\w+$/)
25
19
  ]).describe("What was validated"),
26
20
  errors: z.array(
@@ -55,12 +49,23 @@ var BundleOutputShape = {
55
49
  };
56
50
  var SimulateOutputShape = {
57
51
  success: z.boolean().describe("Whether simulation succeeded"),
58
- error: z.string().optional().describe("Error message if simulation failed"),
59
- collector: z.unknown().optional().describe("Collector state after simulation"),
60
- elbResult: z.unknown().optional().describe("Push result from the collector"),
61
- logs: z.array(z.unknown()).optional().describe("Log entries from simulation"),
62
- usage: z.record(z.string(), z.array(z.unknown())).optional().describe("API call usage per destination"),
63
- duration: z.number().optional().describe("Simulation duration in milliseconds")
52
+ error: z.string().optional().describe("Error message if failed"),
53
+ summary: z.string().describe("One-line result summary"),
54
+ destinations: z.record(
55
+ z.string(),
56
+ z.object({
57
+ received: z.boolean().describe("Whether destination received the event"),
58
+ calls: z.number().describe("Number of API calls made"),
59
+ payload: z.unknown().optional().describe("Transformed payload sent")
60
+ })
61
+ ).optional().describe("Per-destination results"),
62
+ exampleMatch: z.object({
63
+ name: z.string(),
64
+ step: z.string(),
65
+ match: z.boolean(),
66
+ diff: z.string().optional()
67
+ }).optional().describe("Example comparison result when using example parameter"),
68
+ duration: z.number().optional().describe("Simulation duration in ms")
64
69
  };
65
70
  var PushOutputShape = {
66
71
  success: z.boolean().describe("Whether push succeeded"),
@@ -68,102 +73,142 @@ var PushOutputShape = {
68
73
  duration: z.number().describe("Push duration in milliseconds"),
69
74
  error: z.string().optional().describe("Error message if push failed")
70
75
  };
71
- var WhoamiOutputShape = {
72
- userId: z.string().describe("User ID"),
73
- email: z.string().describe("User email address"),
74
- projectId: z.string().nullable().describe("Project ID if token is project-scoped")
75
- };
76
- var projectFields = {
77
- id: projectId,
78
- name: z.string().describe("Project name"),
79
- role: z.enum(["owner", "member"]).describe("Your role in this project"),
80
- createdAt: timestamp,
81
- updatedAt: timestamp
82
- };
83
- var ProjectOutputShape = { ...projectFields };
84
- var ListProjectsOutputShape = {
85
- projects: z.array(z.object(projectFields)).describe("List of projects"),
86
- total: z.number().describe("Total number of projects")
87
- };
88
- var flowFields = {
89
- id: flowId,
90
- name: z.string().describe("Flow name"),
91
- content: z.record(z.string(), z.unknown()).describe("Flow.Setup JSON content"),
92
- createdAt: timestamp,
93
- updatedAt: timestamp,
94
- deletedAt: z.string().nullable().optional().describe("Deletion timestamp if soft-deleted")
95
- };
96
- var FlowOutputShape = { ...flowFields };
97
- var flowSummaryFields = {
98
- id: flowId,
99
- name: z.string().describe("Flow name"),
100
- createdAt: timestamp,
101
- updatedAt: timestamp,
102
- deletedAt: z.string().nullable().describe("Deletion timestamp if soft-deleted")
103
- };
104
- var ListFlowsOutputShape = {
105
- flows: z.array(z.object(flowSummaryFields)).describe("List of flow summaries"),
106
- total: z.number().describe("Total number of flows")
76
+ var ExamplesListOutputShape = {
77
+ flow: z.string().describe("Flow name"),
78
+ count: z.number().describe("Number of examples found"),
79
+ examples: z.array(
80
+ z.object({
81
+ step: z.string().describe('Step location (e.g., "destination.gtag")'),
82
+ stepType: z.enum(["source", "transformer", "destination"]).describe("Step type"),
83
+ stepName: z.string().describe("Step name"),
84
+ exampleName: z.string().describe("Example name"),
85
+ hasIn: z.boolean().describe("Whether the example has an input value"),
86
+ hasOut: z.boolean().describe("Whether the example has an output value"),
87
+ hasMapping: z.boolean().describe("Whether the example has a mapping configuration"),
88
+ in: z.unknown().optional().describe("Input event data"),
89
+ out: z.unknown().optional().describe("Expected output data"),
90
+ mapping: z.unknown().optional().describe("Mapping configuration for destinations")
91
+ })
92
+ ).describe("Step examples")
107
93
  };
108
- var DeleteOutputShape = {
109
- success: z.literal(true).describe("Deletion succeeded")
94
+ var PackageSearchOutputShape = {
95
+ package: z.string().describe("Package name"),
96
+ version: z.string().describe("Package version"),
97
+ description: z.string().optional().describe("Package description"),
98
+ type: z.string().optional().describe("Package type (destination, source, transformer)"),
99
+ platform: z.string().optional().describe("Target platform (web, server)"),
100
+ hintKeys: z.array(z.string()).describe("Available hint keys (use package_get section=hints to read)"),
101
+ exampleSummaries: z.array(
102
+ z.object({
103
+ name: z.string().describe("Example name"),
104
+ description: z.string().optional().describe("What this example shows")
105
+ })
106
+ ).describe(
107
+ "Step example names and descriptions (use package_get section=examples to read full content)"
108
+ )
110
109
  };
111
110
  var PackageSchemaOutputShape = {
112
111
  package: z.string().describe("Package name"),
113
112
  version: z.string().describe("Package version"),
114
113
  type: z.string().describe("Package type (destination, source, transformer)"),
115
114
  platform: z.string().describe("Target platform (web, server)"),
116
- schemas: z.record(z.string(), z.unknown()).describe("JSON Schemas for settings and mapping"),
117
- examples: z.record(z.string(), z.unknown()).optional().describe("Configuration examples")
118
- };
119
- var BundleRemoteOutputShape = {
120
- success: z.boolean().describe("Whether bundling succeeded"),
121
- bundle: z.string().describe("Compiled JavaScript bundle"),
122
- size: z.number().describe("Bundle size in bytes"),
123
- stats: z.record(z.string(), z.unknown()).optional().describe("Bundle statistics from server")
115
+ schemas: z.record(z.string(), z.unknown()).optional().describe("JSON Schemas for settings and mapping"),
116
+ examples: z.record(z.string(), z.unknown()).optional().describe(
117
+ "Full configuration examples (included when section=examples or section=all)"
118
+ ),
119
+ exampleSummaries: z.array(
120
+ z.object({
121
+ name: z.string().describe("Example name"),
122
+ description: z.string().optional().describe("What this example shows")
123
+ })
124
+ ).optional().describe(
125
+ "Example names and descriptions (included in default/summary mode)"
126
+ ),
127
+ hints: z.record(
128
+ z.string(),
129
+ z.object({
130
+ text: z.string(),
131
+ code: z.array(
132
+ z.object({
133
+ lang: z.string().optional(),
134
+ code: z.string()
135
+ })
136
+ ).optional()
137
+ })
138
+ ).optional().describe(
139
+ "Hints \u2014 text only in summary mode, with code blocks when section=hints or section=all"
140
+ )
124
141
  };
125
142
 
126
- // src/tools/helpers.ts
127
- function apiResult(result) {
128
- return {
129
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
130
- structuredContent: result
131
- };
132
- }
133
- function apiError(error) {
134
- return {
135
- content: [
136
- {
137
- type: "text",
138
- text: JSON.stringify({
139
- error: error instanceof Error ? error.message : "Unknown error"
140
- })
143
+ // src/tools/validate.ts
144
+ function registerFlowValidateTool(server2) {
145
+ server2.registerTool(
146
+ "flow_validate",
147
+ {
148
+ title: "Validate Flow",
149
+ 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.",
150
+ inputSchema: schemas.ValidateInputShape,
151
+ outputSchema: ValidateOutputShape,
152
+ annotations: {
153
+ readOnlyHint: true,
154
+ destructiveHint: false,
155
+ idempotentHint: true,
156
+ openWorldHint: false
157
+ }
158
+ },
159
+ async ({ type, input, flow, path }) => {
160
+ try {
161
+ const result = await validate(type, input, {
162
+ flow,
163
+ path
164
+ });
165
+ const summary = result.valid ? "Valid" : `Invalid: ${result.errors.length} errors, ${result.warnings.length} warnings`;
166
+ const hints = result.valid ? {
167
+ next: [
168
+ "Use flow_simulate to test event flow",
169
+ "Use flow_bundle to build"
170
+ ]
171
+ } : {
172
+ next: [
173
+ "Fix errors above, then run flow_validate again",
174
+ "Read walkeros://reference/flow-schema for correct structure"
175
+ ]
176
+ };
177
+ return mcpResult(result, summary, hints);
178
+ } catch (error) {
179
+ return mcpError(
180
+ error,
181
+ "Check the input parameter \u2014 expected a JSON string, file path, or URL"
182
+ );
141
183
  }
142
- ],
143
- isError: true
144
- };
184
+ }
185
+ );
145
186
  }
146
187
 
147
188
  // src/tools/bundle.ts
148
- function registerBundleTool(server2) {
189
+ import { z as z2 } from "zod";
190
+ import { bundle, bundleRemote } from "@walkeros/cli";
191
+ import { schemas as schemas2 } from "@walkeros/cli/dev";
192
+ import { mcpResult as mcpResult2, mcpError as mcpError2 } from "@walkeros/core";
193
+ function registerFlowBundleTool(server2) {
149
194
  server2.registerTool(
150
- "bundle",
195
+ "flow_bundle",
151
196
  {
152
- title: "Bundle",
197
+ title: "Bundle Flow",
153
198
  description: "Bundle a walkerOS flow configuration into deployable JavaScript. Resolves all destinations, sources, and transformers, then outputs a tree-shaken production bundle. Returns bundle statistics. Set remote: true to use the walkerOS cloud service instead of local build tools.",
154
199
  inputSchema: {
155
- ...schemas.BundleInputShape,
200
+ ...schemas2.BundleInputShape,
156
201
  remote: z2.boolean().optional().describe(
157
202
  "Use remote cloud bundling (requires WALKEROS_TOKEN). Default: false (local)"
158
203
  ),
159
- content: z2.record(z2.string(), z2.unknown()).optional().describe("Flow.Setup JSON content (required when remote: true)")
204
+ content: z2.record(z2.string(), z2.unknown()).optional().describe("Flow.Config JSON content (required when remote: true)")
160
205
  },
161
206
  outputSchema: BundleOutputShape,
162
207
  annotations: {
163
208
  readOnlyHint: false,
164
209
  destructiveHint: false,
165
- idempotentHint: true,
166
- openWorldHint: false
210
+ idempotentHint: false,
211
+ openWorldHint: true
167
212
  }
168
213
  },
169
214
  async ({ configPath, flow, stats, output, remote, content }) => {
@@ -175,54 +220,66 @@ function registerBundleTool(server2) {
175
220
  content,
176
221
  flowName: flow
177
222
  });
178
- return apiResult({ success: true, ...result2 });
223
+ const r = result2;
224
+ const size2 = r.totalSize ?? r.size;
225
+ const time2 = r.buildTime;
226
+ const summary2 = `Bundled${size2 ? ` (${formatBytes(size2)}` : ""}${time2 ? `, ${time2}ms)` : size2 ? ")" : ""}`;
227
+ return mcpResult2({ success: true, ...result2 }, summary2, {
228
+ next: [
229
+ "Use flow_simulate to test",
230
+ "Use api({ action: 'deploy' }) to publish"
231
+ ]
232
+ });
179
233
  }
180
234
  const result = await bundle(configPath, {
181
235
  flowName: flow,
182
236
  stats: stats ?? true,
183
237
  buildOverrides: output ? { output } : void 0
184
238
  });
185
- const output_ = result ?? {
186
- success: true,
187
- message: "Bundle created"
188
- };
189
- return {
190
- content: [
239
+ if (!result) {
240
+ return mcpResult2(
241
+ { success: false, message: "Bundle produced no output" },
242
+ "Bundle produced no output",
191
243
  {
192
- type: "text",
193
- text: JSON.stringify(output_, null, 2)
244
+ warnings: [
245
+ "The build returned no result. The flow may be empty or misconfigured."
246
+ ],
247
+ next: ["Run flow_validate to check your configuration"]
194
248
  }
195
- ],
196
- structuredContent: output_
197
- };
249
+ );
250
+ }
251
+ const output_ = result;
252
+ const size = output_.totalSize;
253
+ const time = output_.buildTime;
254
+ const summary = `Bundled${size ? ` (${formatBytes(size)}` : ""}${time ? `, ${time}ms)` : size ? ")" : ""}`;
255
+ return mcpResult2({ success: true, ...output_ }, summary, {
256
+ next: [
257
+ "Use flow_simulate to test",
258
+ "Use api({ action: 'deploy' }) to publish"
259
+ ]
260
+ });
198
261
  } catch (error) {
199
- return {
200
- content: [
201
- {
202
- type: "text",
203
- text: JSON.stringify({
204
- success: false,
205
- error: error instanceof Error ? error.message : "Unknown error"
206
- })
207
- }
208
- ],
209
- isError: true
210
- };
262
+ return mcpError2(error, "Run flow_validate for detailed error messages");
211
263
  }
212
264
  }
213
265
  );
214
266
  }
267
+ function formatBytes(bytes) {
268
+ if (bytes < 1024) return `${bytes} B`;
269
+ return `${(bytes / 1024).toFixed(1)} KB`;
270
+ }
215
271
 
216
272
  // src/tools/simulate.ts
217
273
  import { simulate } from "@walkeros/cli";
218
- import { schemas as schemas2 } from "@walkeros/cli/dev";
219
- function registerSimulateTool(server2) {
274
+ import { schemas as schemas3 } from "@walkeros/cli/dev";
275
+ import { mcpResult as mcpResult3, mcpError as mcpError3 } from "@walkeros/core";
276
+ function registerFlowSimulateTool(server2) {
220
277
  server2.registerTool(
221
- "simulate",
278
+ "flow_simulate",
222
279
  {
223
- title: "Simulate",
224
- description: "Simulate events through a walkerOS flow without making real API calls. Processes events through the full pipeline including transformers and destinations, returning detailed results with logs and usage statistics.",
225
- inputSchema: schemas2.SimulateInputShape,
280
+ title: "Simulate Flow",
281
+ description: 'Simulate events through a walkerOS flow without making real API calls. Events must be in walkerOS format (post-source): { name: "entity action", data: {...} }. Raw source input (dataLayer pushes, HTTP requests) must first be converted to walkerOS events. Check source package examples to see what events a source outputs. Use the example parameter to load event input from a step example and compare output.',
282
+ inputSchema: schemas3.SimulateInputShape,
226
283
  outputSchema: SimulateOutputShape,
227
284
  annotations: {
228
285
  readOnlyHint: true,
@@ -231,42 +288,58 @@ function registerSimulateTool(server2) {
231
288
  openWorldHint: false
232
289
  }
233
290
  },
234
- async ({ configPath, event, flow, platform }) => {
291
+ async ({ configPath, event, flow, platform, example, step }) => {
235
292
  try {
236
- let parsedEvent = event;
237
- if (event.startsWith("{") || event.startsWith("[")) {
238
- try {
239
- parsedEvent = JSON.parse(event);
240
- } catch {
241
- }
293
+ if (!event && !example) {
294
+ throw new Error("Either event or example must be provided");
242
295
  }
243
- const result = await simulate(configPath, parsedEvent, {
296
+ const raw = await simulate(configPath, event, {
244
297
  json: true,
245
298
  flow,
246
- platform
299
+ platform,
300
+ example,
301
+ step
247
302
  });
248
- return {
249
- content: [
250
- {
251
- type: "text",
252
- text: JSON.stringify(result, null, 2)
253
- }
254
- ],
255
- structuredContent: result
303
+ const destinations = {};
304
+ if (raw.usage) {
305
+ for (const [name, calls] of Object.entries(raw.usage)) {
306
+ destinations[name] = {
307
+ received: calls.length > 0,
308
+ calls: calls.length,
309
+ payload: calls.length > 0 ? calls[calls.length - 1] : void 0
310
+ };
311
+ }
312
+ }
313
+ const destCount = Object.keys(destinations).length;
314
+ const receivedCount = Object.values(destinations).filter(
315
+ (d) => d.received
316
+ ).length;
317
+ const warnings = [];
318
+ if (destCount === 0) {
319
+ warnings.push(
320
+ "No destinations found in flow configuration. Check that your flow defines at least one destination."
321
+ );
322
+ }
323
+ if (destCount > 0 && receivedCount === 0) {
324
+ warnings.push(
325
+ 'No destinations received the event. Most common cause: mapping keys must be NESTED entity \u2192 action objects \u2014 event "product add" needs { "product": { "add": Rule } }, not "product.add". Also check event name match and consent settings.'
326
+ );
327
+ }
328
+ const summary = `${receivedCount}/${destCount} destinations received the event`;
329
+ const result = {
330
+ success: raw.success,
331
+ error: raw.error,
332
+ summary,
333
+ destinations: destCount > 0 ? destinations : void 0,
334
+ exampleMatch: raw.exampleMatch,
335
+ duration: raw.duration
256
336
  };
337
+ return mcpResult3(result, summary, {
338
+ next: ["Use flow_bundle to build for production"],
339
+ ...warnings.length > 0 ? { warnings } : {}
340
+ });
257
341
  } catch (error) {
258
- return {
259
- content: [
260
- {
261
- type: "text",
262
- text: JSON.stringify({
263
- success: false,
264
- error: error instanceof Error ? error.message : "Unknown error"
265
- })
266
- }
267
- ],
268
- isError: true
269
- };
342
+ return mcpError3(error, "Run flow_validate for detailed error messages");
270
343
  }
271
344
  }
272
345
  );
@@ -274,14 +347,15 @@ function registerSimulateTool(server2) {
274
347
 
275
348
  // src/tools/push.ts
276
349
  import { push } from "@walkeros/cli";
277
- import { schemas as schemas3 } from "@walkeros/cli/dev";
278
- function registerPushTool(server2) {
350
+ import { schemas as schemas4 } from "@walkeros/cli/dev";
351
+ import { mcpResult as mcpResult4, mcpError as mcpError4 } from "@walkeros/core";
352
+ function registerFlowPushTool(server2) {
279
353
  server2.registerTool(
280
- "push",
354
+ "flow_push",
281
355
  {
282
- title: "Push",
283
- description: "Push a real event through a walkerOS flow to actual destinations. WARNING: This makes real API calls to real endpoints. Events will be sent to configured destinations (analytics, CRM, etc.).",
284
- inputSchema: schemas3.PushInputShape,
356
+ title: "Push Events",
357
+ description: "Push a real event through a walkerOS flow to actual destinations. WARNING: This makes real API calls to real endpoints. Note: Web destinations (gtag, meta, etc.) require browser globals that are not available in Node.js. For web flows, use flow_simulate to test. flow_push works best for server-side flows.",
358
+ inputSchema: schemas4.PushInputShape,
285
359
  outputSchema: PushOutputShape,
286
360
  annotations: {
287
361
  readOnlyHint: false,
@@ -292,56 +366,48 @@ function registerPushTool(server2) {
292
366
  },
293
367
  async ({ configPath, event, flow, platform }) => {
294
368
  try {
295
- let parsedEvent = event;
296
- if (event.startsWith("{") || event.startsWith("[")) {
297
- try {
298
- parsedEvent = JSON.parse(event);
299
- } catch {
300
- }
301
- }
302
- const result = await push(configPath, parsedEvent, {
369
+ const result = await push(configPath, event, {
303
370
  json: true,
304
371
  flow,
305
372
  platform
306
373
  });
307
- return {
308
- content: [
309
- {
310
- type: "text",
311
- text: JSON.stringify(result, null, 2)
312
- }
313
- ],
314
- structuredContent: result
315
- };
374
+ if (!result.success) {
375
+ return mcpError4(
376
+ new Error(result.error || "Push failed"),
377
+ "Check destination configuration and network connectivity. For web destinations, use flow_simulate instead."
378
+ );
379
+ }
380
+ const summary = `Pushed event${result.duration ? ` (${result.duration}ms)` : ""}`;
381
+ return mcpResult4(result, summary);
316
382
  } catch (error) {
317
- return {
318
- content: [
319
- {
320
- type: "text",
321
- text: JSON.stringify({
322
- success: false,
323
- error: error instanceof Error ? error.message : "Unknown error"
324
- })
325
- }
326
- ],
327
- isError: true
328
- };
383
+ return mcpError4(
384
+ error,
385
+ "Check configPath and event format. For web destinations, use flow_simulate instead."
386
+ );
329
387
  }
330
388
  }
331
389
  );
332
390
  }
333
391
 
334
- // src/tools/validate.ts
335
- import { validate } from "@walkeros/cli";
336
- import { schemas as schemas4 } from "@walkeros/cli/dev";
337
- function registerValidateTool(server2) {
392
+ // src/tools/examples.ts
393
+ import { z as z3 } from "zod";
394
+ import { loadJsonConfig } from "@walkeros/cli";
395
+ import { mcpResult as mcpResult5, mcpError as mcpError5 } from "@walkeros/core";
396
+ function registerFlowExamplesTool(server2) {
338
397
  server2.registerTool(
339
- "validate",
398
+ "flow_examples",
340
399
  {
341
- title: "Validate",
342
- description: "Validate walkerOS events, flow configurations, or mapping rules. Accepts JSON strings, file paths, or URLs as input. Returns validation results with errors, warnings, and details.",
343
- inputSchema: schemas4.ValidateInputShape,
344
- outputSchema: ValidateOutputShape,
400
+ title: "Flow Examples",
401
+ 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.",
402
+ inputSchema: {
403
+ configPath: z3.string().min(1).describe("Path to flow configuration file"),
404
+ flow: z3.string().optional().describe("Flow name for multi-flow configs"),
405
+ step: z3.string().optional().describe('Filter to a specific step (e.g., "destination.gtag")'),
406
+ full: z3.boolean().optional().describe(
407
+ "Return full in/out/mapping data for each example (default: false, returns metadata only)"
408
+ )
409
+ },
410
+ outputSchema: ExamplesListOutputShape,
345
411
  annotations: {
346
412
  readOnlyHint: true,
347
413
  destructiveHint: false,
@@ -349,111 +415,352 @@ function registerValidateTool(server2) {
349
415
  openWorldHint: false
350
416
  }
351
417
  },
352
- async ({ type, input, flow }) => {
418
+ async ({ configPath, flow, step, full }) => {
353
419
  try {
354
- let parsedInput = input;
355
- if (input.startsWith("{") || input.startsWith("[")) {
356
- try {
357
- parsedInput = JSON.parse(input);
358
- } catch {
359
- }
420
+ const rawConfig = await loadJsonConfig(configPath);
421
+ const flowNames = Object.keys(rawConfig.flows || {});
422
+ const flowName = flow || (flowNames.length === 1 ? flowNames[0] : void 0);
423
+ if (!flowName) {
424
+ throw new Error(
425
+ `Multiple flows found. Specify flow parameter. Available: ${flowNames.join(", ")}`
426
+ );
360
427
  }
361
- const result = await validate(type, parsedInput, { flow });
362
- return {
363
- content: [
364
- {
365
- type: "text",
366
- text: JSON.stringify(result, null, 2)
428
+ const flowSettings = rawConfig.flows[flowName];
429
+ if (!flowSettings) {
430
+ throw new Error(`Flow "${flowName}" not found`);
431
+ }
432
+ const examples = [];
433
+ const stepTypes = [
434
+ { key: "sources", type: "source" },
435
+ { key: "transformers", type: "transformer" },
436
+ { key: "destinations", type: "destination" }
437
+ ];
438
+ for (const { key, type } of stepTypes) {
439
+ const refs = flowSettings[key] || {};
440
+ for (const [name, ref] of Object.entries(refs)) {
441
+ if (!ref.examples) continue;
442
+ if (step && `${type}.${name}` !== step) continue;
443
+ for (const [exName, ex] of Object.entries(
444
+ ref.examples
445
+ )) {
446
+ examples.push({
447
+ step: `${type}.${name}`,
448
+ stepType: type,
449
+ stepName: name,
450
+ exampleName: exName,
451
+ hasIn: ex.in !== void 0,
452
+ hasOut: ex.out !== void 0,
453
+ hasMapping: ex.mapping !== void 0,
454
+ ...full ? { in: ex.in, out: ex.out, mapping: ex.mapping } : {}
455
+ });
367
456
  }
368
- ],
369
- structuredContent: result
457
+ }
458
+ }
459
+ const stepSet = new Set(examples.map((e) => e.step));
460
+ const result = {
461
+ flow: flowName,
462
+ count: examples.length,
463
+ examples
370
464
  };
371
- } catch (error) {
372
- return {
373
- content: [
374
- {
375
- type: "text",
376
- text: JSON.stringify({
377
- valid: false,
378
- error: error instanceof Error ? error.message : "Unknown error"
379
- })
380
- }
381
- ],
382
- isError: true
465
+ const totalExamples = examples.length;
466
+ const summary = `${totalExamples} examples across ${stepSet.size} steps`;
467
+ const hints = {
468
+ next: ["Use flow_simulate with example parameter to test"]
383
469
  };
470
+ if (totalExamples === 0) {
471
+ hints.warnings = [
472
+ "No examples found. Add examples to step definitions in your flow config for testing."
473
+ ];
474
+ }
475
+ return mcpResult5(result, summary, hints);
476
+ } catch (error) {
477
+ return mcpError5(error, "Check configPath \u2014 expected a flow.json file");
384
478
  }
385
479
  }
386
480
  );
387
481
  }
388
482
 
389
- // src/tools/auth.ts
390
- import { whoami } from "@walkeros/cli";
391
- function registerAuthTools(server2) {
392
- server2.registerTool(
393
- "whoami",
394
- {
395
- title: "Who Am I",
396
- description: "Verify your API token and see your identity. Returns user ID, email, and project ID (if token is project-scoped). Use this to confirm your token works and discover your project ID.",
397
- inputSchema: {},
398
- outputSchema: WhoamiOutputShape,
399
- annotations: {
400
- readOnlyHint: true,
401
- destructiveHint: false,
402
- idempotentHint: true,
403
- openWorldHint: true
404
- }
405
- },
406
- async () => {
407
- try {
408
- return apiResult(await whoami());
409
- } catch (error) {
410
- return apiError(error);
411
- }
412
- }
413
- );
483
+ // src/tools/package.ts
484
+ import { z as z4 } from "zod";
485
+ import { fetchPackage, mcpResult as mcpResult6, mcpError as mcpError6 } from "@walkeros/core";
486
+
487
+ // src/registry.ts
488
+ var PACKAGE_REGISTRY = [
489
+ // Web Destinations
490
+ {
491
+ name: "@walkeros/web-destination-gtag",
492
+ type: "destination",
493
+ platform: "web",
494
+ description: "Google destination (GA4, Ads, GTM via gtag.js)"
495
+ },
496
+ {
497
+ name: "@walkeros/web-destination-meta",
498
+ type: "destination",
499
+ platform: "web",
500
+ description: "Meta (Facebook) Pixel"
501
+ },
502
+ {
503
+ name: "@walkeros/web-destination-plausible",
504
+ type: "destination",
505
+ platform: "web",
506
+ description: "Plausible Analytics"
507
+ },
508
+ {
509
+ name: "@walkeros/web-destination-snowplow",
510
+ type: "destination",
511
+ platform: "web",
512
+ description: "Snowplow Analytics"
513
+ },
514
+ {
515
+ name: "@walkeros/web-destination-piwikpro",
516
+ type: "destination",
517
+ platform: "web",
518
+ description: "Piwik PRO Analytics"
519
+ },
520
+ {
521
+ name: "@walkeros/web-destination-api",
522
+ type: "destination",
523
+ platform: "web",
524
+ description: "Generic HTTP API destination"
525
+ },
526
+ // Server Destinations
527
+ {
528
+ name: "@walkeros/server-destination-gcp",
529
+ type: "destination",
530
+ platform: "server",
531
+ description: "Google Cloud Platform (BigQuery)"
532
+ },
533
+ {
534
+ name: "@walkeros/server-destination-aws",
535
+ type: "destination",
536
+ platform: "server",
537
+ description: "AWS (Firehose)"
538
+ },
539
+ {
540
+ name: "@walkeros/server-destination-meta",
541
+ type: "destination",
542
+ platform: "server",
543
+ description: "Meta Conversions API (server-side)"
544
+ },
545
+ {
546
+ name: "@walkeros/server-destination-api",
547
+ type: "destination",
548
+ platform: "server",
549
+ description: "Generic HTTP API destination (server)"
550
+ },
551
+ {
552
+ name: "@walkeros/server-destination-datamanager",
553
+ type: "destination",
554
+ platform: "server",
555
+ description: "Google Data Manager"
556
+ },
557
+ // Web Sources
558
+ {
559
+ name: "@walkeros/web-source-browser",
560
+ type: "source",
561
+ platform: "web",
562
+ description: "Browser DOM event capture (clicks, page views, forms)"
563
+ },
564
+ {
565
+ name: "@walkeros/web-source-datalayer",
566
+ type: "source",
567
+ platform: "web",
568
+ description: "Google Tag Manager dataLayer bridge"
569
+ },
570
+ {
571
+ name: "@walkeros/web-source-session",
572
+ type: "source",
573
+ platform: "web",
574
+ description: "Session tracking source"
575
+ },
576
+ // CMP Sources
577
+ {
578
+ name: "@walkeros/web-source-cmp-cookiefirst",
579
+ type: "source",
580
+ platform: "web",
581
+ description: "CookieFirst consent management"
582
+ },
583
+ {
584
+ name: "@walkeros/web-source-cmp-cookiepro",
585
+ type: "source",
586
+ platform: "web",
587
+ description: "CookiePro/OneTrust consent management"
588
+ },
589
+ {
590
+ name: "@walkeros/web-source-cmp-usercentrics",
591
+ type: "source",
592
+ platform: "web",
593
+ description: "Usercentrics consent management"
594
+ },
595
+ // Server Sources
596
+ {
597
+ name: "@walkeros/server-source-express",
598
+ type: "source",
599
+ platform: "server",
600
+ description: "Express.js HTTP event endpoint"
601
+ },
602
+ {
603
+ name: "@walkeros/server-source-fetch",
604
+ type: "source",
605
+ platform: "server",
606
+ description: "Web Fetch API source (Cloudflare, Vercel Edge, Deno, Bun)"
607
+ },
608
+ {
609
+ name: "@walkeros/server-source-aws",
610
+ type: "source",
611
+ platform: "server",
612
+ description: "AWS sources (Lambda, API Gateway, Function URLs)"
613
+ },
614
+ {
615
+ name: "@walkeros/server-source-gcp",
616
+ type: "source",
617
+ platform: "server",
618
+ description: "GCP sources (Cloud Functions)"
619
+ },
620
+ // Transformers
621
+ {
622
+ name: "@walkeros/transformer-router",
623
+ type: "transformer",
624
+ platform: "universal",
625
+ description: "Route events to different destination subsets"
626
+ },
627
+ {
628
+ name: "@walkeros/transformer-validator",
629
+ type: "transformer",
630
+ platform: "universal",
631
+ description: "Event validation using JSON Schema"
632
+ },
633
+ {
634
+ name: "@walkeros/server-transformer-fingerprint",
635
+ type: "transformer",
636
+ platform: "server",
637
+ description: "Device fingerprinting for anonymous user identification"
638
+ },
639
+ {
640
+ name: "@walkeros/server-transformer-cache",
641
+ type: "transformer",
642
+ platform: "server",
643
+ description: "HTTP response caching with LRU eviction"
644
+ },
645
+ {
646
+ name: "@walkeros/server-transformer-file",
647
+ type: "transformer",
648
+ platform: "server",
649
+ description: "File serving transformer for static files"
650
+ },
651
+ // Stores
652
+ {
653
+ name: "@walkeros/store-memory",
654
+ type: "store",
655
+ platform: "universal",
656
+ description: "In-memory key-value store with LRU eviction and TTL"
657
+ },
658
+ {
659
+ name: "@walkeros/server-store-fs",
660
+ type: "store",
661
+ platform: "server",
662
+ description: "File system key-value store"
663
+ },
664
+ {
665
+ name: "@walkeros/server-store-s3",
666
+ type: "store",
667
+ platform: "server",
668
+ description: "AWS S3 key-value store"
669
+ },
670
+ {
671
+ name: "@walkeros/server-store-gcs",
672
+ type: "store",
673
+ platform: "server",
674
+ description: "Google Cloud Storage key-value store"
675
+ }
676
+ ];
677
+ function filterRegistry(filters) {
678
+ let results = PACKAGE_REGISTRY;
679
+ if (filters?.type) {
680
+ results = results.filter((p) => p.type === filters.type);
681
+ }
682
+ if (filters?.platform) {
683
+ results = results.filter(
684
+ (p) => p.platform === filters.platform || p.platform === "universal"
685
+ );
686
+ }
687
+ return results;
414
688
  }
415
689
 
416
- // src/tools/projects.ts
417
- import { z as z3 } from "zod";
418
- import {
419
- listProjects,
420
- getProject,
421
- createProject,
422
- updateProject,
423
- deleteProject
424
- } from "@walkeros/cli";
425
- function registerProjectTools(server2) {
690
+ // src/tools/package.ts
691
+ function registerPackageSearchTool(server2) {
426
692
  server2.registerTool(
427
- "list-projects",
693
+ "package_search",
428
694
  {
429
- title: "List Projects",
430
- description: "List all projects you have access to. Returns project IDs, names, and your role.",
431
- inputSchema: {},
432
- outputSchema: ListProjectsOutputShape,
695
+ title: "Search Package",
696
+ description: "Browse walkerOS packages or look up a specific one. Without package name: returns catalog filtered by type/platform. With package name: returns metadata, hint keys, and example summaries.",
697
+ inputSchema: {
698
+ package: z4.string().min(1).optional().describe(
699
+ "Exact npm package name for detailed lookup (e.g., @walkeros/web-destination-snowplow)"
700
+ ),
701
+ type: z4.enum(["source", "destination", "transformer", "store"]).optional().describe("Filter by package type (browse mode)"),
702
+ platform: z4.enum(["web", "server"]).optional().describe(
703
+ "Filter by platform (browse mode, includes universal packages)"
704
+ ),
705
+ version: z4.string().optional().describe("Package version for detailed lookup (default: latest)")
706
+ },
707
+ // No outputSchema: browse mode returns {catalog, count}, lookup returns metadata — incompatible shapes
433
708
  annotations: {
434
709
  readOnlyHint: true,
435
710
  destructiveHint: false,
436
711
  idempotentHint: true,
437
- openWorldHint: true
712
+ openWorldHint: false
438
713
  }
439
714
  },
440
- async () => {
715
+ async ({ package: packageName, type, platform, version }) => {
716
+ if (!packageName) {
717
+ const catalog = filterRegistry({ type, platform });
718
+ const result = { catalog, count: catalog.length };
719
+ const summary = `${catalog.length} packages found`;
720
+ return mcpResult6(result, summary, {
721
+ next: ["Use package_get for schemas and examples"]
722
+ });
723
+ }
441
724
  try {
442
- return apiResult(await listProjects());
725
+ const info = await fetchPackage(packageName, { version });
726
+ const result = {
727
+ package: info.packageName,
728
+ version: info.version,
729
+ description: info.description,
730
+ type: info.type,
731
+ platform: info.platform,
732
+ hintKeys: info.hintKeys,
733
+ exampleSummaries: info.exampleSummaries
734
+ };
735
+ const summary = `${info.packageName} v${info.version}`;
736
+ return mcpResult6(result, summary, {
737
+ next: ["Use package_get for schemas and examples"]
738
+ });
443
739
  } catch (error) {
444
- return apiError(error);
740
+ return mcpError6(
741
+ error,
742
+ "Package not found. Use package_search without parameters to browse available packages."
743
+ );
445
744
  }
446
745
  }
447
746
  );
747
+ }
748
+ function registerGetPackageSchemaTool(server2) {
448
749
  server2.registerTool(
449
- "get-project",
750
+ "package_get",
450
751
  {
451
- title: "Get Project",
452
- description: "Get details for a project. Uses WALKEROS_PROJECT_ID if projectId is omitted.",
752
+ title: "Get Package",
753
+ description: 'Fetch walkerOS package details from npm. By default returns schemas + hint texts + example summaries (lightweight). Use section parameter to get full content: "hints" (with code blocks), "examples" (full in/out data), or "all" (everything). Use package_search first to browse available packages.',
453
754
  inputSchema: {
454
- projectId: z3.string().optional().describe("Project ID (defaults to WALKEROS_PROJECT_ID)")
755
+ package: z4.string().min(1).describe(
756
+ "Exact npm package name (e.g., @walkeros/web-destination-snowplow)"
757
+ ),
758
+ version: z4.string().optional().describe("Package version (default: latest)"),
759
+ section: z4.enum(["hints", "examples", "all"]).optional().describe(
760
+ "Section to expand with full content. Default: summary view with schemas + hint texts + example descriptions"
761
+ )
455
762
  },
456
- outputSchema: ProjectOutputShape,
763
+ outputSchema: PackageSchemaOutputShape,
457
764
  annotations: {
458
765
  readOnlyHint: true,
459
766
  destructiveHint: false,
@@ -461,377 +768,441 @@ function registerProjectTools(server2) {
461
768
  openWorldHint: true
462
769
  }
463
770
  },
464
- async ({ projectId: projectId2 }) => {
771
+ async ({ package: packageName, version, section }) => {
465
772
  try {
466
- return apiResult(await getProject({ projectId: projectId2 }));
773
+ const info = await fetchPackage(packageName, { version });
774
+ const result = {
775
+ package: info.packageName,
776
+ version: info.version,
777
+ type: info.type,
778
+ platform: info.platform,
779
+ schemas: info.schemas
780
+ };
781
+ if (info.hints) {
782
+ if (section === "hints" || section === "all") {
783
+ result.hints = info.hints;
784
+ } else {
785
+ const hintSummary = {};
786
+ for (const [key, hint] of Object.entries(info.hints)) {
787
+ const h = hint;
788
+ hintSummary[key] = { text: h.text };
789
+ }
790
+ result.hints = hintSummary;
791
+ }
792
+ }
793
+ if (section === "examples" || section === "all") {
794
+ result.examples = info.examples;
795
+ } else {
796
+ result.exampleSummaries = info.exampleSummaries;
797
+ }
798
+ const schemaCount = Object.keys(info.schemas).length;
799
+ const exampleCount = info.exampleSummaries.length;
800
+ const summary = `${info.packageName} \u2014 ${schemaCount} schemas, ${exampleCount} examples`;
801
+ return mcpResult6(result, summary);
467
802
  } catch (error) {
468
- return apiError(error);
803
+ return mcpError6(
804
+ error,
805
+ "Use package_search to browse available package names."
806
+ );
469
807
  }
470
808
  }
471
809
  );
472
- server2.registerTool(
473
- "create-project",
474
- {
475
- title: "Create Project",
476
- description: "Create a new project.",
477
- inputSchema: {
478
- name: z3.string().min(1).max(255).describe("Project name")
479
- },
480
- outputSchema: ProjectOutputShape,
481
- annotations: {
482
- readOnlyHint: false,
483
- destructiveHint: false,
484
- idempotentHint: false,
485
- openWorldHint: true
486
- }
487
- },
488
- async ({ name }) => {
489
- try {
490
- return apiResult(await createProject({ name }));
491
- } catch (error) {
492
- return apiError(error);
493
- }
810
+ }
811
+
812
+ // src/tools/flow-load.ts
813
+ import { z as z5 } from "zod";
814
+ import { loadJsonConfig as loadJsonConfig2 } from "@walkeros/cli";
815
+ import { mcpResult as mcpResult7, mcpError as mcpError7 } from "@walkeros/core";
816
+ var WEB_SKELETON = {
817
+ version: 3,
818
+ flows: {
819
+ default: {
820
+ web: {},
821
+ packages: {},
822
+ sources: {},
823
+ destinations: {}
494
824
  }
495
- );
496
- server2.registerTool(
497
- "update-project",
498
- {
499
- title: "Update Project",
500
- description: "Update a project name. Uses WALKEROS_PROJECT_ID if projectId is omitted.",
501
- inputSchema: {
502
- projectId: z3.string().optional().describe("Project ID (defaults to WALKEROS_PROJECT_ID)"),
503
- name: z3.string().min(1).max(255).describe("New project name")
504
- },
505
- outputSchema: ProjectOutputShape,
506
- annotations: {
507
- readOnlyHint: false,
508
- destructiveHint: false,
509
- idempotentHint: false,
510
- openWorldHint: true
511
- }
512
- },
513
- async ({ projectId: projectId2, name }) => {
514
- try {
515
- return apiResult(await updateProject({ projectId: projectId2, name }));
516
- } catch (error) {
517
- return apiError(error);
518
- }
825
+ }
826
+ };
827
+ var SERVER_SKELETON = {
828
+ version: 3,
829
+ flows: {
830
+ default: {
831
+ server: {},
832
+ packages: {},
833
+ sources: {},
834
+ destinations: {}
519
835
  }
520
- );
836
+ }
837
+ };
838
+ function registerFlowLoadTool(server2) {
521
839
  server2.registerTool(
522
- "delete-project",
840
+ "flow_load",
523
841
  {
524
- title: "Delete Project",
525
- description: "Soft-delete a project and all its flows. WARNING: This deletes the project and ALL associated flows and data. Uses WALKEROS_PROJECT_ID if projectId is omitted.",
842
+ title: "Load or Create Flow",
843
+ 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.",
526
844
  inputSchema: {
527
- projectId: z3.string().optional().describe("Project ID (defaults to WALKEROS_PROJECT_ID)")
845
+ source: z5.string().optional().describe(
846
+ "Flow source: local file path (./flow.json), URL (https://...), or API flow ID (cfg_...). Omit to create a new flow."
847
+ ),
848
+ platform: z5.enum(["web", "server"]).optional().describe(
849
+ "Platform for new flows. Required when source is omitted. web = browser tracking, server = Node.js HTTP."
850
+ )
851
+ },
852
+ outputSchema: {
853
+ version: z5.number().describe("Flow config version"),
854
+ flows: z5.record(z5.string(), z5.unknown()).describe("Flow definitions")
528
855
  },
529
- outputSchema: DeleteOutputShape,
530
856
  annotations: {
531
- readOnlyHint: false,
532
- destructiveHint: true,
533
- idempotentHint: false,
857
+ readOnlyHint: true,
858
+ destructiveHint: false,
859
+ idempotentHint: true,
534
860
  openWorldHint: true
535
861
  }
536
862
  },
537
- async ({ projectId: projectId2 }) => {
863
+ async ({ source, platform }) => {
538
864
  try {
539
- return apiResult(await deleteProject({ projectId: projectId2 }));
865
+ if (source) {
866
+ const config = await loadJsonConfig2(source);
867
+ return mcpResult7(
868
+ config,
869
+ `Loaded flow from ${source}. Use flow_validate to check, or add-step prompt to modify.`,
870
+ {
871
+ next: [
872
+ "Use flow_validate to check",
873
+ "Use add-step prompt to modify"
874
+ ]
875
+ }
876
+ );
877
+ }
878
+ if (!platform) {
879
+ return mcpError7(
880
+ new Error(
881
+ "Provide source (file path, URL, or flow ID) to load existing flow, or platform (web/server) to create a new one."
882
+ )
883
+ );
884
+ }
885
+ const skeleton = platform === "web" ? WEB_SKELETON : SERVER_SKELETON;
886
+ return mcpResult7(
887
+ skeleton,
888
+ `Created empty ${platform} flow. Use the add-step prompt to add sources, destinations, and transformers.`,
889
+ {
890
+ next: [
891
+ "Read walkeros://reference/flow-schema for config structure",
892
+ "Use add-step prompt to add sources and destinations"
893
+ ]
894
+ }
895
+ );
540
896
  } catch (error) {
541
- return apiError(error);
897
+ const msg = error instanceof Error ? error.message : "";
898
+ if (msg.includes("not found") || msg.includes("ENOENT"))
899
+ return mcpError7(
900
+ error,
901
+ "Check configPath \u2014 expected a flow.json file"
902
+ );
903
+ return mcpError7(error);
542
904
  }
543
905
  }
544
906
  );
545
907
  }
546
908
 
547
- // src/tools/flows.ts
548
- import { z as z4 } from "zod";
909
+ // src/tools/api.ts
910
+ import { z as z7 } from "zod";
549
911
  import {
912
+ whoami,
913
+ listProjects,
914
+ getProject,
915
+ createProject,
916
+ updateProject,
917
+ deleteProject,
550
918
  listFlows,
551
919
  getFlow,
552
920
  createFlow,
553
921
  updateFlow,
554
922
  deleteFlow,
555
- duplicateFlow
923
+ duplicateFlow,
924
+ deploy,
925
+ getDeployment,
926
+ listDeployments,
927
+ getDeploymentBySlug,
928
+ createDeployment as createDep,
929
+ deleteDeployment as deleteDep
556
930
  } from "@walkeros/cli";
557
- function registerFlowTools(server2) {
558
- server2.registerTool(
559
- "list-flows",
560
- {
561
- title: "List Flows",
562
- description: "List all flow configurations in a project.",
563
- inputSchema: {
564
- projectId: z4.string().optional().describe("Project ID (defaults to WALKEROS_PROJECT_ID)"),
565
- sort: z4.enum(["name", "updated_at", "created_at"]).optional().describe("Sort field (default: updated_at)"),
566
- order: z4.enum(["asc", "desc"]).optional().describe("Sort order (default: desc)"),
567
- includeDeleted: z4.boolean().optional().describe("Include soft-deleted flows (default: false)")
568
- },
569
- outputSchema: ListFlowsOutputShape,
570
- annotations: {
571
- readOnlyHint: true,
572
- destructiveHint: false,
573
- idempotentHint: true,
574
- openWorldHint: true
575
- }
576
- },
577
- async ({ projectId: projectId2, sort, order, includeDeleted }) => {
578
- try {
579
- return apiResult(
580
- await listFlows({ projectId: projectId2, sort, order, includeDeleted })
581
- );
582
- } catch (error) {
583
- return apiError(error);
584
- }
585
- }
586
- );
931
+ import { mcpResult as mcpResult8, mcpError as mcpError8 } from "@walkeros/core";
932
+
933
+ // src/schemas/api-output.ts
934
+ import { z as z6 } from "zod";
935
+ var ApiOutputShape = {
936
+ action: z6.string().describe("Action that was executed"),
937
+ ok: z6.boolean().describe("Whether the action succeeded"),
938
+ data: z6.unknown().describe("Action-specific result data")
939
+ };
940
+
941
+ // src/tools/api.ts
942
+ var ACTIONS = [
943
+ "whoami",
944
+ "project.list",
945
+ "project.get",
946
+ "project.create",
947
+ "project.update",
948
+ "project.delete",
949
+ "flow.list",
950
+ "flow.get",
951
+ "flow.create",
952
+ "flow.update",
953
+ "flow.delete",
954
+ "flow.duplicate",
955
+ "deploy",
956
+ "deployment.get",
957
+ "deployment.list",
958
+ "deployment.create",
959
+ "deployment.delete"
960
+ ];
961
+ function registerApiTool(server2) {
587
962
  server2.registerTool(
588
- "get-flow",
963
+ "api",
589
964
  {
590
- title: "Get Flow",
591
- description: "Get a flow configuration with its full content (Flow.Setup JSON).",
965
+ title: "walkerOS Cloud API",
966
+ description: "Manage walkerOS cloud projects, flows, and deployments. Requires WALKEROS_TOKEN env var.\n\nActions:\n- whoami \u2014 verify token, get user info\n- project.list/get/create/update/delete \u2014 manage projects\n- flow.list/get/create/update/delete/duplicate \u2014 manage flow configs\n- deploy \u2014 deploy a flow (auto-detects web/server)\n- deployment.get/list/create/delete \u2014 manage deployments\n\nParameters vary by action. id = flowId/projectId/slug depending on context. content = Flow.Config JSON for flow.create/update.",
592
967
  inputSchema: {
593
- flowId: z4.string().describe("Flow ID (cfg_...)"),
594
- projectId: z4.string().optional().describe("Project ID (defaults to WALKEROS_PROJECT_ID)")
968
+ action: z7.enum(ACTIONS).describe("API action to perform"),
969
+ id: z7.string().optional().describe("Resource ID (flowId, projectId, or deployment slug)"),
970
+ name: z7.string().optional().describe("Name for create/update operations"),
971
+ content: z7.record(z7.string(), z7.unknown()).optional().describe("Flow.Config JSON for flow operations"),
972
+ patch: z7.boolean().optional().describe("Use merge-patch for flow.update (default: true)"),
973
+ wait: z7.boolean().optional().describe("Wait for deploy to complete (default: true)"),
974
+ flowName: z7.string().optional().describe("Flow name for multi-settings flows"),
975
+ fields: z7.array(z7.string()).optional().describe("Dot-path field selectors for flow.get"),
976
+ type: z7.enum(["web", "server"]).optional().describe("Deployment type for deployment.create"),
977
+ sort: z7.string().optional().describe("Sort field for list operations"),
978
+ order: z7.enum(["asc", "desc"]).optional().describe("Sort order"),
979
+ status: z7.string().optional().describe("Status filter for deployment.list"),
980
+ includeDeleted: z7.boolean().optional().describe("Include deleted items in lists")
595
981
  },
596
- outputSchema: FlowOutputShape,
982
+ outputSchema: ApiOutputShape,
597
983
  annotations: {
598
- readOnlyHint: true,
599
- destructiveHint: false,
600
- idempotentHint: true,
984
+ readOnlyHint: false,
985
+ destructiveHint: true,
986
+ idempotentHint: false,
601
987
  openWorldHint: true
602
988
  }
603
989
  },
604
- async ({ flowId: flowId2, projectId: projectId2 }) => {
990
+ async (params, extra) => {
991
+ const {
992
+ action,
993
+ id,
994
+ name,
995
+ content,
996
+ patch,
997
+ wait,
998
+ flowName,
999
+ fields,
1000
+ type,
1001
+ sort,
1002
+ order,
1003
+ status,
1004
+ includeDeleted
1005
+ } = params;
605
1006
  try {
606
- return apiResult(await getFlow({ flowId: flowId2, projectId: projectId2 }));
607
- } catch (error) {
608
- return apiError(error);
609
- }
610
- }
611
- );
612
- server2.registerTool(
613
- "create-flow",
614
- {
615
- title: "Create Flow",
616
- description: "Create a new flow configuration in a project.",
617
- inputSchema: {
618
- name: z4.string().min(1).max(255).describe("Flow name"),
619
- content: z4.record(z4.string(), z4.unknown()).describe("Flow.Setup JSON content (must have version: 1)"),
620
- projectId: z4.string().optional().describe("Project ID (defaults to WALKEROS_PROJECT_ID)")
621
- },
622
- outputSchema: FlowOutputShape,
623
- annotations: {
624
- readOnlyHint: false,
625
- destructiveHint: false,
626
- idempotentHint: false,
627
- openWorldHint: true
628
- }
629
- },
630
- async ({ name, content, projectId: projectId2 }) => {
631
- try {
632
- return apiResult(await createFlow({ name, content, projectId: projectId2 }));
633
- } catch (error) {
634
- return apiError(error);
635
- }
636
- }
637
- );
638
- server2.registerTool(
639
- "update-flow",
640
- {
641
- title: "Update Flow",
642
- description: "Update a flow configuration name and/or content.",
643
- inputSchema: {
644
- flowId: z4.string().describe("Flow ID (cfg_...)"),
645
- name: z4.string().min(1).max(255).optional().describe("New flow name"),
646
- content: z4.record(z4.string(), z4.unknown()).optional().describe("New Flow.Setup JSON content"),
647
- projectId: z4.string().optional().describe("Project ID (defaults to WALKEROS_PROJECT_ID)")
648
- },
649
- outputSchema: FlowOutputShape,
650
- annotations: {
651
- readOnlyHint: false,
652
- destructiveHint: false,
653
- idempotentHint: false,
654
- openWorldHint: true
655
- }
656
- },
657
- async ({ flowId: flowId2, name, content, projectId: projectId2 }) => {
658
- try {
659
- return apiResult(
660
- await updateFlow({ flowId: flowId2, name, content, projectId: projectId2 })
661
- );
662
- } catch (error) {
663
- return apiError(error);
664
- }
665
- }
666
- );
667
- server2.registerTool(
668
- "delete-flow",
669
- {
670
- title: "Delete Flow",
671
- description: "Soft-delete a flow configuration. WARNING: This removes the flow configuration. Can be restored later. Requires flowId.",
672
- inputSchema: {
673
- flowId: z4.string().describe("Flow ID (cfg_...)"),
674
- projectId: z4.string().optional().describe("Project ID (defaults to WALKEROS_PROJECT_ID)")
675
- },
676
- outputSchema: DeleteOutputShape,
677
- annotations: {
678
- readOnlyHint: false,
679
- destructiveHint: true,
680
- idempotentHint: false,
681
- openWorldHint: true
682
- }
683
- },
684
- async ({ flowId: flowId2, projectId: projectId2 }) => {
685
- try {
686
- return apiResult(await deleteFlow({ flowId: flowId2, projectId: projectId2 }));
687
- } catch (error) {
688
- return apiError(error);
689
- }
690
- }
691
- );
692
- server2.registerTool(
693
- "duplicate-flow",
694
- {
695
- title: "Duplicate Flow",
696
- description: "Create a copy of an existing flow configuration.",
697
- inputSchema: {
698
- flowId: z4.string().describe("Flow ID to duplicate (cfg_...)"),
699
- name: z4.string().optional().describe('Name for the copy (defaults to "Copy of ...")'),
700
- projectId: z4.string().optional().describe("Project ID (defaults to WALKEROS_PROJECT_ID)")
701
- },
702
- outputSchema: FlowOutputShape,
703
- annotations: {
704
- readOnlyHint: false,
705
- destructiveHint: false,
706
- idempotentHint: false,
707
- openWorldHint: true
708
- }
709
- },
710
- async ({ flowId: flowId2, name, projectId: projectId2 }) => {
711
- try {
712
- return apiResult(await duplicateFlow({ flowId: flowId2, name, projectId: projectId2 }));
713
- } catch (error) {
714
- return apiError(error);
715
- }
716
- }
717
- );
718
- }
719
-
720
- // src/tools/deploy.ts
721
- import { z as z5 } from "zod";
722
- import { deploy, getDeployment } from "@walkeros/cli";
723
- function registerDeployTools(server2) {
724
- server2.registerTool(
725
- "deploy-flow",
726
- {
727
- title: "Deploy Flow",
728
- description: "Deploy a flow to walkerOS cloud. Auto-detects web (script hosting) or server (container) from the flow content. Returns deployment status, and for web deploys the public URL and script tag.",
729
- inputSchema: {
730
- flowId: z5.string().describe("Flow ID to deploy"),
731
- projectId: z5.string().optional().describe("Project ID (defaults to WALKEROS_PROJECT_ID)"),
732
- wait: z5.boolean().optional().default(true).describe(
733
- "Wait for deployment to complete (default: true). Set to false to return immediately after triggering."
734
- ),
735
- flowName: z5.string().optional().describe(
736
- "Flow name for multi-config flows. Required when a flow has multiple configs."
737
- )
738
- },
739
- annotations: {
740
- readOnlyHint: false,
741
- destructiveHint: false,
742
- idempotentHint: false,
743
- openWorldHint: true
744
- }
745
- },
746
- async ({ flowId: flowId2, projectId: projectId2, wait, flowName }) => {
747
- try {
748
- return apiResult(await deploy({ flowId: flowId2, projectId: projectId2, wait, flowName }));
749
- } catch (error) {
750
- return apiError(error);
751
- }
752
- }
753
- );
754
- server2.registerTool(
755
- "get-deployment",
756
- {
757
- title: "Get Deployment",
758
- description: "Get the latest deployment status for a flow. Returns deployment type, status, URLs, and error details.",
759
- inputSchema: {
760
- flowId: z5.string().describe("Flow ID to check"),
761
- projectId: z5.string().optional().describe("Project ID (defaults to WALKEROS_PROJECT_ID)"),
762
- flowName: z5.string().optional().describe(
763
- "Flow name for multi-config flows. Required when a flow has multiple configs."
764
- )
765
- },
766
- annotations: {
767
- readOnlyHint: true,
768
- destructiveHint: false,
769
- idempotentHint: true,
770
- openWorldHint: true
771
- }
772
- },
773
- async ({ flowId: flowId2, projectId: projectId2, flowName }) => {
774
- try {
775
- return apiResult(await getDeployment({ flowId: flowId2, projectId: projectId2, flowName }));
776
- } catch (error) {
777
- return apiError(error);
778
- }
779
- }
780
- );
781
- }
782
-
783
- // src/tools/get-package-schema.ts
784
- import { z as z6 } from "zod";
785
- import { fetchPackageSchema } from "@walkeros/core";
786
- function registerGetPackageSchemaTool(server2) {
787
- server2.registerTool(
788
- "get-package-schema",
789
- {
790
- title: "Get Package Schema",
791
- description: "Fetch walkerOS package schemas and examples from npm via jsdelivr CDN. Returns JSON Schemas for settings and mapping configuration, plus examples. Use this to understand how to configure a destination, source, or transformer when building a flow.json.",
792
- inputSchema: {
793
- package: z6.string().min(1).describe(
794
- "Exact npm package name (e.g., @walkeros/web-destination-snowplow)"
795
- ),
796
- version: z6.string().optional().describe("Package version (default: latest)")
797
- },
798
- outputSchema: PackageSchemaOutputShape,
799
- annotations: {
800
- readOnlyHint: true,
801
- destructiveHint: false,
802
- idempotentHint: true,
803
- openWorldHint: true
804
- }
805
- },
806
- async ({ package: packageName, version }) => {
807
- try {
808
- const info = await fetchPackageSchema(packageName, { version });
809
- const result = {
810
- package: info.packageName,
811
- version: info.version,
812
- type: info.type,
813
- platform: info.platform,
814
- schemas: info.schemas,
815
- examples: info.examples
816
- };
817
- return {
818
- content: [
819
- { type: "text", text: JSON.stringify(result, null, 2) }
820
- ],
821
- structuredContent: result
822
- };
823
- } catch (error) {
824
- return {
825
- content: [
826
- {
827
- type: "text",
828
- text: JSON.stringify({
829
- error: error instanceof Error ? error.message : "Unknown error"
830
- })
1007
+ let data;
1008
+ let summary;
1009
+ switch (action) {
1010
+ // Auth
1011
+ case "whoami": {
1012
+ data = await whoami();
1013
+ summary = `Authenticated as ${data.email}`;
1014
+ break;
1015
+ }
1016
+ // Projects
1017
+ case "project.list": {
1018
+ data = await listProjects();
1019
+ summary = `${(data.projects ?? []).length} projects`;
1020
+ break;
1021
+ }
1022
+ case "project.get": {
1023
+ data = await getProject({ projectId: id });
1024
+ summary = `Project "${data.name}"`;
1025
+ break;
1026
+ }
1027
+ case "project.create": {
1028
+ if (!name) throw new Error("name required for project.create");
1029
+ data = await createProject({ name });
1030
+ summary = `Created project "${name}"`;
1031
+ break;
1032
+ }
1033
+ case "project.update": {
1034
+ if (!name) throw new Error("name required for project.update");
1035
+ data = await updateProject({ projectId: id, name });
1036
+ summary = `Updated project "${name}"`;
1037
+ break;
1038
+ }
1039
+ case "project.delete": {
1040
+ data = await deleteProject({ projectId: id });
1041
+ summary = `Deleted project ${id ?? "default"}`;
1042
+ break;
1043
+ }
1044
+ // Flows
1045
+ case "flow.list": {
1046
+ data = await listFlows({
1047
+ projectId: id,
1048
+ sort,
1049
+ order,
1050
+ includeDeleted
1051
+ });
1052
+ summary = `${(data.flows ?? []).length} flows`;
1053
+ break;
1054
+ }
1055
+ case "flow.get": {
1056
+ if (!id) throw new Error("id required for flow.get");
1057
+ data = await getFlow({ flowId: id, fields });
1058
+ summary = `Flow "${data.name}" (${id})`;
1059
+ break;
1060
+ }
1061
+ case "flow.create": {
1062
+ if (!name) throw new Error("name required for flow.create");
1063
+ if (!content) throw new Error("content required for flow.create");
1064
+ data = await createFlow({ name, content });
1065
+ summary = `Created flow "${name}" (${data.id})`;
1066
+ break;
1067
+ }
1068
+ case "flow.update": {
1069
+ if (!id) throw new Error("id required for flow.update");
1070
+ data = await updateFlow({
1071
+ flowId: id,
1072
+ name,
1073
+ content,
1074
+ mergePatch: patch ?? true
1075
+ });
1076
+ summary = `Updated flow ${id}`;
1077
+ break;
1078
+ }
1079
+ case "flow.delete": {
1080
+ if (!id) throw new Error("id required for flow.delete");
1081
+ data = await deleteFlow({ flowId: id });
1082
+ summary = `Deleted flow ${id}`;
1083
+ break;
1084
+ }
1085
+ case "flow.duplicate": {
1086
+ if (!id) throw new Error("id required for flow.duplicate");
1087
+ data = await duplicateFlow({ flowId: id, name });
1088
+ summary = `Duplicated flow ${id}`;
1089
+ break;
1090
+ }
1091
+ // Deploy
1092
+ case "deploy": {
1093
+ if (!id) throw new Error("id (flowId) required for deploy");
1094
+ const progressToken = extra._meta?.progressToken;
1095
+ data = await deploy({
1096
+ flowId: id,
1097
+ wait: wait ?? true,
1098
+ flowName,
1099
+ onStatus: (s, sub) => {
1100
+ if (!progressToken) return;
1101
+ const stages = {
1102
+ bundling: 15,
1103
+ deploying: 55,
1104
+ published: 100,
1105
+ active: 100,
1106
+ failed: 100
1107
+ };
1108
+ extra.sendNotification({
1109
+ method: "notifications/progress",
1110
+ params: {
1111
+ progressToken,
1112
+ progress: stages[s] ?? 0,
1113
+ total: 100,
1114
+ message: sub ? `${s}:${sub}` : s
1115
+ }
1116
+ });
1117
+ },
1118
+ signal: extra.signal
1119
+ });
1120
+ const st = data.status;
1121
+ const deployData = data;
1122
+ if (st === "failed") {
1123
+ const msg = `Deploy failed: ${deployData.errorMessage ?? "unknown error"}`;
1124
+ return mcpResult8({ action, ok: false, data }, msg, {
1125
+ next: ["Run flow_validate to check your configuration"]
1126
+ });
1127
+ } else {
1128
+ summary = `Deployed flow ${id} \u2014 status: ${st}`;
1129
+ const publicUrl = deployData.publicUrl;
1130
+ const containerUrl = deployData.containerUrl;
1131
+ const deployType = deployData.type;
1132
+ const nextHints = [];
1133
+ if (deployType === "web" && publicUrl) {
1134
+ nextHints.push(`Bundle at ${publicUrl}`);
1135
+ nextHints.push(`Add <script src='${publicUrl}'></script>`);
1136
+ } else if (deployType === "server" && containerUrl) {
1137
+ nextHints.push(`Container at ${containerUrl}`);
1138
+ nextHints.push(`Test: curl ${containerUrl}/health`);
1139
+ }
1140
+ if (nextHints.length > 0) {
1141
+ return mcpResult8({ action, ok: true, data }, summary, {
1142
+ next: nextHints
1143
+ });
1144
+ }
831
1145
  }
832
- ],
833
- isError: true
834
- };
1146
+ break;
1147
+ }
1148
+ // Deployments
1149
+ case "deployment.get": {
1150
+ if (!id)
1151
+ throw new Error(
1152
+ "id (flowId or slug) required for deployment.get"
1153
+ );
1154
+ try {
1155
+ data = await getDeployment({ flowId: id, flowName });
1156
+ } catch {
1157
+ data = await getDeploymentBySlug({ slug: id });
1158
+ }
1159
+ summary = `Deployment ${data.slug ?? id} \u2014 ${data.status}`;
1160
+ break;
1161
+ }
1162
+ case "deployment.list": {
1163
+ data = await listDeployments({
1164
+ projectId: id,
1165
+ type,
1166
+ status
1167
+ });
1168
+ summary = `${(data.deployments ?? []).length} deployments`;
1169
+ break;
1170
+ }
1171
+ case "deployment.create": {
1172
+ if (!type)
1173
+ throw new Error(
1174
+ "type (web/server) required for deployment.create"
1175
+ );
1176
+ data = await createDep({ type, label: name, projectId: id });
1177
+ summary = `Created ${type} deployment ${data.slug}`;
1178
+ break;
1179
+ }
1180
+ case "deployment.delete": {
1181
+ if (!id)
1182
+ throw new Error("id (slug) required for deployment.delete");
1183
+ data = await deleteDep({ slug: id });
1184
+ summary = `Deleted deployment ${id}`;
1185
+ break;
1186
+ }
1187
+ default:
1188
+ throw new Error(
1189
+ `Unknown action: ${action}. Use one of: ${ACTIONS.join(", ")}`
1190
+ );
1191
+ }
1192
+ return mcpResult8({ action, ok: true, data }, summary);
1193
+ } catch (error) {
1194
+ const msg = error instanceof Error ? error.message : "";
1195
+ if (msg.includes("401") || msg.includes("403") || msg.includes("Unauthorized"))
1196
+ return mcpError8(
1197
+ error,
1198
+ "Set WALKEROS_TOKEN env var or check token expiry"
1199
+ );
1200
+ if (msg.includes("required"))
1201
+ return mcpError8(
1202
+ error,
1203
+ `See api tool description for ${action} parameters.`
1204
+ );
1205
+ return mcpError8(error);
835
1206
  }
836
1207
  }
837
1208
  );
@@ -839,25 +1210,14 @@ function registerGetPackageSchemaTool(server2) {
839
1210
 
840
1211
  // src/resources/package-schemas.ts
841
1212
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
842
- import { fetchPackageSchema as fetchPackageSchema2 } from "@walkeros/core";
843
- var KNOWN_PACKAGES = [
844
- "@walkeros/web-destination-google-ga4",
845
- "@walkeros/web-destination-meta-pixel",
846
- "@walkeros/web-destination-plausible",
847
- "@walkeros/web-destination-snowplow",
848
- "@walkeros/web-destination-piwikpro",
849
- "@walkeros/web-destination-etag",
850
- "@walkeros/web-destination-bigquery",
851
- "@walkeros/web-source-walker",
852
- "@walkeros/web-source-datalayer"
853
- ];
1213
+ import { fetchPackageSchema } from "@walkeros/core";
854
1214
  function registerPackageSchemaResources(server2) {
855
1215
  const template = new ResourceTemplate("walkeros://schema/{packageName}", {
856
1216
  list: async () => ({
857
- resources: KNOWN_PACKAGES.map((pkg) => ({
858
- uri: `walkeros://schema/${encodeURIComponent(pkg)}`,
859
- name: pkg,
860
- description: `Schema and examples for ${pkg}`,
1217
+ resources: PACKAGE_REGISTRY.map((pkg) => ({
1218
+ uri: `walkeros://schema/${encodeURIComponent(pkg.name)}`,
1219
+ name: pkg.name,
1220
+ description: `Schema and examples for ${pkg.name}`,
861
1221
  mimeType: "application/json"
862
1222
  }))
863
1223
  })
@@ -871,7 +1231,7 @@ function registerPackageSchemaResources(server2) {
871
1231
  mimeType: "application/json"
872
1232
  },
873
1233
  async (uri, { packageName }) => {
874
- const info = await fetchPackageSchema2(
1234
+ const info = await fetchPackageSchema(
875
1235
  decodeURIComponent(packageName)
876
1236
  );
877
1237
  return {
@@ -887,72 +1247,508 @@ function registerPackageSchemaResources(server2) {
887
1247
  );
888
1248
  }
889
1249
 
890
- // src/resources/flows.ts
891
- import { listFlows as listFlows2, getFlow as getFlow2 } from "@walkeros/cli";
892
- import { ResourceTemplate as ResourceTemplate2 } from "@modelcontextprotocol/sdk/server/mcp.js";
893
- function registerFlowResources(server2) {
894
- const template = new ResourceTemplate2("walkeros://flow/{flowId}", {
895
- list: async () => {
1250
+ // src/resources/references.ts
1251
+ import { schemas as schemas5 } from "@walkeros/core/dev";
1252
+ function registerReferenceResources(server2) {
1253
+ server2.resource(
1254
+ "flow-schema",
1255
+ "walkeros://reference/flow-schema",
1256
+ {
1257
+ description: "JSON Schema for Flow.Config \u2014 the complete flow configuration structure",
1258
+ mimeType: "application/json"
1259
+ },
1260
+ async () => ({
1261
+ contents: [
1262
+ {
1263
+ uri: "walkeros://reference/flow-schema",
1264
+ text: JSON.stringify(schemas5.configJsonSchema, null, 2),
1265
+ mimeType: "application/json"
1266
+ }
1267
+ ]
1268
+ })
1269
+ );
1270
+ server2.resource(
1271
+ "event-model",
1272
+ "walkeros://reference/event-model",
1273
+ {
1274
+ description: "JSON Schema for walkerOS events: entity-action naming, data, context, globals, user, consent",
1275
+ mimeType: "application/json"
1276
+ },
1277
+ async () => ({
1278
+ contents: [
1279
+ {
1280
+ uri: "walkeros://reference/event-model",
1281
+ text: JSON.stringify(schemas5.eventJsonSchema, null, 2),
1282
+ mimeType: "application/json"
1283
+ }
1284
+ ]
1285
+ })
1286
+ );
1287
+ server2.resource(
1288
+ "mapping",
1289
+ "walkeros://reference/mapping",
1290
+ {
1291
+ description: "JSON Schemas for walkerOS mapping: rules, valueConfig, rule, policy",
1292
+ mimeType: "application/json"
1293
+ },
1294
+ async () => ({
1295
+ contents: [
1296
+ {
1297
+ uri: "walkeros://reference/mapping",
1298
+ text: JSON.stringify(
1299
+ {
1300
+ rules: schemas5.rulesJsonSchema,
1301
+ valueConfig: schemas5.valueConfigJsonSchema,
1302
+ rule: schemas5.ruleJsonSchema,
1303
+ policy: schemas5.policyJsonSchema
1304
+ },
1305
+ null,
1306
+ 2
1307
+ ),
1308
+ mimeType: "application/json"
1309
+ }
1310
+ ]
1311
+ })
1312
+ );
1313
+ server2.resource(
1314
+ "consent",
1315
+ "walkeros://reference/consent",
1316
+ {
1317
+ description: "JSON Schema for walkerOS consent: destination-level, rule-level, and field-level consent gating",
1318
+ mimeType: "application/json"
1319
+ },
1320
+ async () => ({
1321
+ contents: [
1322
+ {
1323
+ uri: "walkeros://reference/consent",
1324
+ text: JSON.stringify(schemas5.consentJsonSchema, null, 2),
1325
+ mimeType: "application/json"
1326
+ }
1327
+ ]
1328
+ })
1329
+ );
1330
+ server2.resource(
1331
+ "variables",
1332
+ "walkeros://reference/variables",
1333
+ {
1334
+ description: "walkerOS variable patterns: $var, $env, $def, $contract, $code, $store substitution",
1335
+ mimeType: "application/json"
1336
+ },
1337
+ async () => ({
1338
+ contents: [
1339
+ {
1340
+ uri: "walkeros://reference/variables",
1341
+ text: JSON.stringify(
1342
+ {
1343
+ patterns: {
1344
+ "$var.name": "Variable substitution \u2014 cascade: step settings > flow settings > config variables",
1345
+ "$env.NAME": "Environment variable \u2014 $env.GA_ID reads process.env.GA_ID",
1346
+ "$env.NAME:default": "Environment variable with fallback \u2014 $env.GA_ID:G-DEFAULT",
1347
+ "$def.name": "Definition reference \u2014 reusable config blocks from definitions section",
1348
+ "$def.name.path.deep": "Nested definition access \u2014 $def.ga4Events.purchase",
1349
+ "$contract.name": "Contract reference \u2014 links to named contract for validation",
1350
+ "$contract.name.path": "Nested contract access \u2014 $contract.ecommerce.product",
1351
+ "$code:(expr)": "Inline JavaScript \u2014 $code:(event) => event.data.price * 100",
1352
+ "$store:storeId": "Store injection in env values \u2014 wires runtime store access"
1353
+ },
1354
+ cascade: {
1355
+ priority: [
1356
+ "1. Step-level settings (highest)",
1357
+ "2. Flow-level settings",
1358
+ "3. Config-level variables (lowest)"
1359
+ ],
1360
+ example: {
1361
+ variables: { apiKey: "default-key" },
1362
+ flows: {
1363
+ production: {
1364
+ destinations: {
1365
+ api: {
1366
+ config: { key: "$var.apiKey" },
1367
+ settings: { apiKey: "prod-key" }
1368
+ }
1369
+ }
1370
+ }
1371
+ }
1372
+ }
1373
+ }
1374
+ },
1375
+ null,
1376
+ 2
1377
+ ),
1378
+ mimeType: "application/json"
1379
+ }
1380
+ ]
1381
+ })
1382
+ );
1383
+ server2.resource(
1384
+ "contract",
1385
+ "walkeros://reference/contract",
1386
+ {
1387
+ description: "JSON Schema for walkerOS contracts: event schema validation with entity-action keying",
1388
+ mimeType: "application/json"
1389
+ },
1390
+ async () => ({
1391
+ contents: [
1392
+ {
1393
+ uri: "walkeros://reference/contract",
1394
+ text: JSON.stringify(schemas5.contractJsonSchema, null, 2),
1395
+ mimeType: "application/json"
1396
+ }
1397
+ ]
1398
+ })
1399
+ );
1400
+ server2.resource(
1401
+ "examples",
1402
+ "walkeros://reference/examples",
1403
+ {
1404
+ description: "Complete flow config example: web + server flows, mapping, contracts, step examples",
1405
+ mimeType: "application/json"
1406
+ },
1407
+ async () => {
1408
+ let example;
896
1409
  try {
897
- const { flows } = await listFlows2({});
898
- return {
899
- resources: flows.map((f) => ({
900
- uri: `walkeros://flow/${f.id}`,
901
- name: f.name,
902
- mimeType: "application/json"
903
- }))
904
- };
1410
+ const { readFileSync } = await import("fs");
1411
+ const { createRequire } = await import("module");
1412
+ const require2 = createRequire(import.meta.url);
1413
+ const examplePath = require2.resolve("@walkeros/cli/examples/flow-complete.json");
1414
+ example = readFileSync(examplePath, "utf-8");
905
1415
  } catch {
906
- return { resources: [] };
1416
+ example = JSON.stringify({ error: "Example not found" });
907
1417
  }
1418
+ return {
1419
+ contents: [
1420
+ {
1421
+ uri: "walkeros://reference/examples",
1422
+ text: example,
1423
+ mimeType: "application/json"
1424
+ }
1425
+ ]
1426
+ };
908
1427
  }
909
- });
910
- server2.registerResource(
911
- "flow-config",
912
- template,
1428
+ );
1429
+ server2.resource(
1430
+ "api",
1431
+ "walkeros://reference/api",
913
1432
  {
914
- title: "walkerOS Flow Configuration",
915
- description: "Flow configurations from your walkerOS project",
1433
+ description: "walkerOS cloud API \u2014 OpenAPI 3.1 specification",
916
1434
  mimeType: "application/json"
917
1435
  },
918
- async (uri, { flowId: flowId2 }) => {
919
- const flow = await getFlow2({ flowId: flowId2 });
1436
+ async () => {
1437
+ let openApiSpec;
1438
+ try {
1439
+ const { readFileSync } = await import("fs");
1440
+ const { createRequire } = await import("module");
1441
+ const require2 = createRequire(import.meta.url);
1442
+ const specPath = require2.resolve("@walkeros/cli/openapi/spec.json");
1443
+ openApiSpec = readFileSync(specPath, "utf-8");
1444
+ } catch {
1445
+ openApiSpec = JSON.stringify({ error: "OpenAPI spec not found" });
1446
+ }
920
1447
  return {
921
1448
  contents: [
922
1449
  {
923
- uri: uri.toString(),
924
- mimeType: "application/json",
925
- text: JSON.stringify(flow, null, 2)
1450
+ uri: "walkeros://reference/api",
1451
+ text: openApiSpec,
1452
+ mimeType: "application/json"
926
1453
  }
927
1454
  ]
928
1455
  };
929
1456
  }
930
1457
  );
1458
+ server2.resource(
1459
+ "packages",
1460
+ "walkeros://reference/packages",
1461
+ {
1462
+ description: "Complete walkerOS package catalog \u2014 all sources, destinations, transformers, and stores",
1463
+ mimeType: "application/json"
1464
+ },
1465
+ async () => ({
1466
+ contents: [
1467
+ {
1468
+ uri: "walkeros://reference/packages",
1469
+ text: JSON.stringify(PACKAGE_REGISTRY, null, 2),
1470
+ mimeType: "application/json"
1471
+ }
1472
+ ]
1473
+ })
1474
+ );
1475
+ }
1476
+
1477
+ // src/prompts/add-step.ts
1478
+ import { z as z8 } from "zod";
1479
+ function registerAddStepPrompt(server2) {
1480
+ server2.registerPrompt(
1481
+ "add-step",
1482
+ {
1483
+ description: "Add a source, destination, transformer, or store step to a flow configuration. Guides through package selection, config scaffolding, and wiring.",
1484
+ argsSchema: {
1485
+ stepType: z8.string().optional().describe(
1486
+ "Type of step to add: source, destination, transformer, or store"
1487
+ ),
1488
+ flowPath: z8.string().optional().describe("Path to the flow.json file to modify")
1489
+ }
1490
+ },
1491
+ async ({ stepType, flowPath }) => ({
1492
+ messages: [
1493
+ {
1494
+ role: "user",
1495
+ content: {
1496
+ type: "text",
1497
+ text: [
1498
+ `Help me add a ${stepType || "new"} step to my flow${flowPath ? ` at ${flowPath}` : ""}.`,
1499
+ "",
1500
+ "Follow these steps:",
1501
+ `1. ${stepType ? "" : "Ask what type of step (source, destination, transformer, store). Then "}Use package_search to browse available packages for the selected type and platform.`,
1502
+ `2. Use package_get with section="hints" to read the selected package's configuration guidance.`,
1503
+ '3. Use package_get with section="examples" to see working configuration examples.',
1504
+ "4. Scaffold the step config using the package schemas \u2014 include required settings with placeholder values.",
1505
+ "5. Wire the step into the flow: add to packages section (with version if needed), connect via next/before chains if needed.",
1506
+ '6. For destinations: configure mapping using nested entity \u2192 action keys. Event "product add" maps to `{ "product": { "add": { name: "AddToCart" } } }`. Use the setup-mapping prompt for guidance.',
1507
+ "7. Use flow_validate to verify the result.",
1508
+ "",
1509
+ "Important:",
1510
+ "- Read the walkeros://reference/flow-schema resource to understand connection rules.",
1511
+ "- Sources connect to pre-collector transformers via `next`.",
1512
+ "- Destinations connect to post-collector transformers via `before`.",
1513
+ "- Stores are passive \u2014 referenced via `$store:storeName` in env values.",
1514
+ "- Use variables ($var) for values that change between environments.",
1515
+ "- For required settings without defaults in the package schema, ask the user which value to use. Do not guess credentials, IDs, or environment-specific values."
1516
+ ].join("\n")
1517
+ }
1518
+ }
1519
+ ]
1520
+ })
1521
+ );
1522
+ }
1523
+
1524
+ // src/prompts/setup-mapping.ts
1525
+ import { z as z9 } from "zod";
1526
+ function registerSetupMappingPrompt(server2) {
1527
+ server2.registerPrompt(
1528
+ "setup-mapping",
1529
+ {
1530
+ description: "Set up event mapping for any step in a flow. Teaches mapping syntax and uses package examples as templates.",
1531
+ argsSchema: {
1532
+ stepName: z9.string().optional().describe('Step name in the flow (e.g., "gtag", "meta", "express")')
1533
+ }
1534
+ },
1535
+ async ({ stepName }) => ({
1536
+ messages: [
1537
+ {
1538
+ role: "user",
1539
+ content: {
1540
+ type: "text",
1541
+ text: [
1542
+ `Help me set up mapping${stepName ? ` for the "${stepName}" step` : ""}.`,
1543
+ "",
1544
+ "IMPORTANT \u2014 Mapping key structure:",
1545
+ "Mapping uses NESTED entity \u2192 action keys, NOT dot-separated strings.",
1546
+ 'Event name "product add" splits into entity "product" and action "add".',
1547
+ 'Config structure: `{ "mapping": { "product": { "add": { name: "AddToCart", data: { ... } } } } }`',
1548
+ 'Wildcards: `{ "*": { "view": Rule } }` matches any entity with action "view".',
1549
+ "",
1550
+ "Follow these steps:",
1551
+ "1. Read the walkeros://reference/mapping resource for full syntax reference.",
1552
+ `2. ${stepName ? `Identify the package for "${stepName}" in the flow, then u` : "U"}se package_get with section="examples" to see how events are mapped for this package.`,
1553
+ '3. Ask which events I want to map (e.g., "product view", "order complete").',
1554
+ "4. Generate mapping rules using the package examples as templates.",
1555
+ "5. Use flow_validate to verify the mapping.",
1556
+ "",
1557
+ "Mapping operates at two levels:",
1558
+ "- **Source mapping**: normalizes raw input \u2192 walkerOS events",
1559
+ "- **Destination mapping**: transforms walkerOS events \u2192 vendor format",
1560
+ "",
1561
+ "Key mapping operators: data (extract), map (object transform), loop (array processing), ",
1562
+ "set (create array), fn ($code function), condition (conditional), consent (consent-gated).",
1563
+ "",
1564
+ "Use $def references for shared mapping patterns across destinations."
1565
+ ].join("\n")
1566
+ }
1567
+ }
1568
+ ]
1569
+ })
1570
+ );
1571
+ }
1572
+
1573
+ // src/prompts/manage-contract.ts
1574
+ import { z as z10 } from "zod";
1575
+ function registerManageContractPrompt(server2) {
1576
+ server2.registerPrompt(
1577
+ "manage-contract",
1578
+ {
1579
+ description: "Create or update event contracts for a flow. Can generate contracts from existing mappings or scaffold mappings from contracts.",
1580
+ argsSchema: {
1581
+ direction: z10.string().optional().describe(
1582
+ 'Direction: "from-mappings" (extract contract from existing mappings), "from-scratch" (create new contract), or "to-mappings" (scaffold mappings from contract)'
1583
+ )
1584
+ }
1585
+ },
1586
+ async ({ direction }) => ({
1587
+ messages: [
1588
+ {
1589
+ role: "user",
1590
+ content: {
1591
+ type: "text",
1592
+ text: [
1593
+ `Help me ${direction === "to-mappings" ? "scaffold mappings from a contract" : direction === "from-mappings" ? "generate a contract from existing mappings" : "manage event contracts"}.`,
1594
+ "",
1595
+ "Follow these steps:",
1596
+ "1. Read the walkeros://reference/contract resource to understand contract structure.",
1597
+ direction === "from-mappings" ? "2. Read all destination mappings in the flow, extract referenced event fields, and generate a contract with those as required properties." : direction === "to-mappings" ? "2. Read the contract from the flow, then scaffold mapping stubs for each destination based on the contract fields." : "2. Ask for entity-action names and required properties, or ask whether to generate from existing mappings.",
1598
+ "3. Use entity-action keying with wildcards (*.*, *.action, entity.*) for broad rules.",
1599
+ "4. Define JSON Schema for events, globals, context, custom, user, and consent.",
1600
+ "5. Use flow_validate to verify the contract.",
1601
+ "",
1602
+ "Contracts and mappings are bidirectional:",
1603
+ "- **Contract \u2192 Mappings**: contract defines what events look like, mappings are scaffolded to match.",
1604
+ "- **Mappings \u2192 Contract**: existing mappings reveal which fields are used, contract formalizes them.",
1605
+ "",
1606
+ "Use $contract.name references to link contracts in the flow.",
1607
+ "Contracts support extends for inheritance between event types."
1608
+ ].join("\n")
1609
+ }
1610
+ }
1611
+ ]
1612
+ })
1613
+ );
1614
+ }
1615
+
1616
+ // src/prompts/use-definitions.ts
1617
+ import { z as z11 } from "zod";
1618
+ function registerUseDefinitionsPrompt(server2) {
1619
+ server2.registerPrompt(
1620
+ "use-definitions",
1621
+ {
1622
+ description: "Extract shared patterns into definitions and variables for DRY, environment-aware flow configurations.",
1623
+ argsSchema: {
1624
+ flowPath: z11.string().optional().describe("Path to the flow.json file to analyze")
1625
+ }
1626
+ },
1627
+ async ({ flowPath }) => ({
1628
+ messages: [
1629
+ {
1630
+ role: "user",
1631
+ content: {
1632
+ type: "text",
1633
+ text: [
1634
+ `Help me extract shared patterns into definitions and variables${flowPath ? ` in ${flowPath}` : ""}.`,
1635
+ "",
1636
+ "Follow these steps:",
1637
+ "1. Read the walkeros://reference/variables resource to understand variable syntax.",
1638
+ "2. Analyze the flow config for repeated patterns (same mapping blocks, same config values).",
1639
+ "3. Extract repeated mapping patterns into the `definitions` section with `$def.name` references.",
1640
+ "4. Extract environment-specific values into `variables` with `$var.name` references.",
1641
+ "5. Show the cascade priority: step > settings > config.",
1642
+ "6. Use flow_validate to verify the result.",
1643
+ "",
1644
+ "Variable types:",
1645
+ "- `$var.name` \u2014 variable substitution (cascade: step > settings > config)",
1646
+ "- `$env.NAME` and `$env.NAME:default` \u2014 environment variables",
1647
+ "- `$def.name` and `$def.name.path.deep` \u2014 definition references",
1648
+ "- `$contract.name` \u2014 contract references",
1649
+ "- `$code:(expr)` \u2014 inline JavaScript functions",
1650
+ "- `$store:storeId` \u2014 store injection in env values",
1651
+ "",
1652
+ "Look for:",
1653
+ "- Same API keys or URLs across multiple destinations \u2192 $var or $env",
1654
+ "- Identical mapping rules in multiple destinations \u2192 $def",
1655
+ "- Environment-specific values (dev/staging/prod) \u2192 $var with overrides"
1656
+ ].join("\n")
1657
+ }
1658
+ }
1659
+ ]
1660
+ })
1661
+ );
931
1662
  }
932
1663
 
933
1664
  // src/index.ts
934
- var server = new McpServer({
935
- name: "walkeros",
936
- version: "2.0.1"
937
- });
938
- registerBundleTool(server);
939
- registerSimulateTool(server);
940
- registerPushTool(server);
941
- registerValidateTool(server);
942
- registerAuthTools(server);
943
- registerProjectTools(server);
944
- registerFlowTools(server);
945
- registerDeployTools(server);
1665
+ var server = new McpServer(
1666
+ {
1667
+ name: "walkeros-flow",
1668
+ version: "3.0.0"
1669
+ },
1670
+ {
1671
+ instructions: `walkerOS is an open-source, privacy-first event data collection platform. Define event pipelines as code using JSON flow configurations.
1672
+
1673
+ ## Architecture: Source \u2192 Collector \u2192 Destination(s)
1674
+
1675
+ 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.
1676
+
1677
+ ## Flow Config Structure
1678
+
1679
+ Every flow config follows this shape:
1680
+
1681
+ \`\`\`json
1682
+ {
1683
+ "version": 3,
1684
+ "flows": {
1685
+ "default": {
1686
+ "web": {},
1687
+ "sources": { "<name>": { "package": "<npm-package>", "config": {} } },
1688
+ "destinations": { "<name>": { "package": "<npm-package>", "config": { "settings": {} } } }
1689
+ }
1690
+ }
1691
+ }
1692
+ \`\`\`
1693
+
1694
+ Event format: \`{ name: "entity action", data: {...}, entity: "...", action: "..." }\`. Sources convert raw input into this format.
1695
+
1696
+ Key rules:
1697
+ - \`version: 3\` is required
1698
+ - Each flow must have exactly one of \`web: {}\` or \`server: {}\`
1699
+ - Destination settings go inside \`config.settings\`, not directly on the destination
1700
+ - Read \`walkeros://reference/flow-schema\` for the full annotated structure
1701
+
1702
+ ## Getting Started
1703
+
1704
+ 1. \`flow_load({ platform: "web" })\` or \`flow_load({ source: "./flow.json" })\` \u2014 create or load a flow
1705
+ 2. \`package_search({ type: "destination", platform: "web" })\` \u2014 discover available packages
1706
+ 3. Use the \`add-step\` prompt to add sources, destinations, transformers, or stores
1707
+ 4. Use the \`setup-mapping\` prompt to configure event transformations
1708
+ 5. \`flow_validate({ type: "flow", input: "flow.json" })\` \u2014 verify configuration
1709
+ 6. \`flow_simulate({ configPath: "flow.json", event: "..." })\` \u2014 test with mocked API calls
1710
+ 7. \`flow_bundle({ configPath: "flow.json" })\` \u2014 build deployable JavaScript
1711
+ 8. \`api({ action: "deploy", id: "cfg_..." })\` \u2014 deploy to walkerOS cloud (requires WALKEROS_TOKEN env var; unavailable without it)
1712
+
1713
+ If validation fails, fix the reported errors and re-validate. Do not skip validation.
1714
+
1715
+ ## Reference Resources
1716
+
1717
+ Read these before constructing configs manually: \`walkeros://reference/flow-schema\`, \`walkeros://reference/mapping\`, \`walkeros://reference/event-model\`, \`walkeros://reference/consent\`, \`walkeros://reference/variables\`, \`walkeros://reference/contract\`, \`walkeros://reference/examples\`.
1718
+
1719
+ ## Key Concepts
1720
+
1721
+ - **Steps** are sources, destinations, transformers, or stores \u2014 each backed by an npm package. Use \`package_search\` to browse, \`package_get\` for schemas and examples.
1722
+ - **Mapping** transforms events using data/map/loop/set/condition rules. Same syntax on sources and destinations. Mapping rules use NESTED entity \u2192 action keying: event name "product add" maps to \`{ "product": { "add": Rule } }\`. Wildcards: \`{ "*": { "view": Rule } }\`.
1723
+ - **Contracts** define event schemas using entity-action keying. Can generate FROM mappings or scaffold mappings FROM contracts.
1724
+ - **Variables** ($var, $env, $def, $code, $store) enable DRY, environment-aware config. Use the \`use-definitions\` prompt to extract shared patterns.
1725
+ - **Consent** gates destinations, mapping rules, and individual fields. Privacy-first by design.`
1726
+ }
1727
+ );
1728
+ registerFlowValidateTool(server);
1729
+ registerFlowBundleTool(server);
1730
+ registerFlowSimulateTool(server);
1731
+ registerFlowPushTool(server);
1732
+ registerFlowExamplesTool(server);
1733
+ registerPackageSearchTool(server);
946
1734
  registerGetPackageSchemaTool(server);
1735
+ registerFlowLoadTool(server);
947
1736
  registerPackageSchemaResources(server);
948
- registerFlowResources(server);
1737
+ registerReferenceResources(server);
1738
+ registerAddStepPrompt(server);
1739
+ registerSetupMappingPrompt(server);
1740
+ registerManageContractPrompt(server);
1741
+ registerUseDefinitionsPrompt(server);
1742
+ if (process.env.WALKEROS_TOKEN) {
1743
+ registerApiTool(server);
1744
+ }
949
1745
  async function main() {
950
1746
  const transport = new StdioServerTransport();
951
1747
  await server.connect(transport);
952
- console.error("walkerOS MCP server running on stdio");
1748
+ console.error("walkerOS Flow MCP server running on stdio");
953
1749
  }
954
1750
  main().catch((error) => {
955
- console.error("Failed to start MCP server:", error);
1751
+ console.error("Failed to start Flow MCP server:", error);
956
1752
  process.exit(1);
957
1753
  });
958
1754
  //# sourceMappingURL=index.js.map