@walkeros/mcp 3.4.2 → 4.0.0-next-1777463920154

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,1579 +1,2030 @@
1
- #!/usr/bin/env node
2
-
3
- // src/index.ts
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { setClientContext } from "@walkeros/cli";
7
-
8
- // src/tools/validate.ts
9
- import { validate } from "@walkeros/cli";
10
- import { schemas } from "@walkeros/cli/dev";
11
- import { mcpResult, mcpError } from "@walkeros/core";
12
-
13
- // src/schemas/output.ts
1
+ // src/tools/auth.ts
14
2
  import { z } from "zod";
15
- var ValidateOutputShape = {
16
- valid: z.boolean().describe("Whether validation passed"),
17
- type: z.union([
18
- z.enum(["contract", "entry", "event", "flow", "mapping"]),
19
- z.string().regex(/^(destinations|sources|transformers)\.\w+$/)
20
- ]).describe("What was validated"),
21
- errors: z.array(
22
- z.object({
23
- path: z.string(),
24
- message: z.string(),
25
- value: z.unknown().optional(),
26
- code: z.string().optional()
27
- })
28
- ).describe("Validation errors"),
29
- warnings: z.array(
30
- z.object({
31
- path: z.string(),
32
- message: z.string(),
33
- suggestion: z.string().optional()
34
- })
35
- ).describe("Validation warnings"),
36
- details: z.record(z.string(), z.unknown()).describe("Additional validation details")
37
- };
38
- var BundleOutputShape = {
39
- success: z.boolean().describe("Whether bundling succeeded"),
40
- totalSize: z.number().optional().describe("Total bundle size in bytes"),
41
- buildTime: z.number().optional().describe("Build time in milliseconds"),
42
- packages: z.array(
43
- z.object({
44
- name: z.string(),
45
- size: z.number()
46
- })
47
- ).optional().describe("Per-package size breakdown"),
48
- treeshakingEffective: z.boolean().optional().describe("Whether tree-shaking was effective"),
49
- message: z.string().optional().describe("Status message")
50
- };
51
- var SimulateOutputShape = {
52
- success: z.boolean().describe("Whether simulation succeeded"),
53
- error: z.string().optional().describe("Error message if failed"),
54
- summary: z.string().describe("One-line result summary"),
55
- destinations: z.record(
56
- z.string(),
57
- z.object({
58
- received: z.boolean().describe("Whether destination received the event"),
59
- calls: z.number().describe("Number of API calls made"),
60
- payload: z.unknown().optional().describe("All intercepted API calls (only when verbose: true)")
61
- })
62
- ).optional().describe("Per-destination results"),
63
- capturedEvents: z.array(z.record(z.string(), z.unknown())).optional().describe("Events captured by source simulation"),
64
- duration: z.number().optional().describe("Simulation duration in ms")
65
- };
66
- var PushOutputShape = {
67
- success: z.boolean().describe("Whether push succeeded"),
68
- elbResult: z.unknown().optional().describe("Push result from the collector"),
69
- duration: z.number().describe("Push duration in milliseconds"),
70
- error: z.string().optional().describe("Error message if push failed")
3
+ import { mcpResult, mcpError } from "@walkeros/core";
4
+ var TITLE = "Authentication";
5
+ var DESCRIPTION = "Manage walkerOS authentication. Check login status, log in via device code flow, or log out. No terminal or browser required, the MCP client handles the authorization URL.";
6
+ var inputSchema = {
7
+ action: z.enum(["status", "login", "logout"]).describe("Authentication action to perform"),
8
+ deviceCode: z.string().optional().describe(
9
+ "Device code from a previous pending login attempt. Provide to resume polling without requesting a new code."
10
+ )
71
11
  };
72
- var ExamplesListOutputShape = {
73
- flow: z.string().describe("Flow name"),
74
- count: z.number().describe("Number of examples found"),
75
- examples: z.array(
76
- z.object({
77
- step: z.string().describe('Step location (e.g., "destination.gtag")'),
78
- stepType: z.enum(["source", "transformer", "destination"]).describe("Step type"),
79
- stepName: z.string().describe("Step name"),
80
- exampleName: z.string().describe("Example name"),
81
- title: z.string().optional().describe("Human-readable title if set"),
82
- description: z.string().optional().describe("Short human-readable description"),
83
- public: z.boolean().optional().describe(
84
- "Whether the example is public (defaults to true if omitted)"
85
- ),
86
- hasIn: z.boolean().describe("Whether the example has an input value"),
87
- hasOut: z.boolean().describe("Whether the example has an output value"),
88
- hasMapping: z.boolean().describe("Whether the example has a mapping configuration"),
89
- hasTrigger: z.boolean().describe("Whether the example has trigger metadata"),
90
- in: z.unknown().optional().describe("Input event data"),
91
- out: z.unknown().optional().describe("Expected output data"),
92
- mapping: z.unknown().optional().describe("Mapping configuration for destinations"),
93
- trigger: z.object({
94
- type: z.string().optional(),
95
- options: z.unknown().optional()
96
- }).optional().describe("Trigger metadata for source simulation")
97
- })
98
- ).describe("Step examples")
12
+ var annotations = {
13
+ readOnlyHint: false,
14
+ destructiveHint: true,
15
+ idempotentHint: false,
16
+ openWorldHint: true
99
17
  };
100
-
101
- // src/tools/validate.ts
102
- function registerFlowValidateTool(server2) {
103
- server2.registerTool(
104
- "flow_validate",
105
- {
106
- title: "Validate Flow",
107
- 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.",
108
- inputSchema: schemas.ValidateInputShape,
109
- outputSchema: ValidateOutputShape,
110
- annotations: {
111
- readOnlyHint: true,
112
- destructiveHint: false,
113
- idempotentHint: true,
114
- openWorldHint: false
18
+ function createAuthToolSpec(client) {
19
+ return {
20
+ name: "auth",
21
+ title: TITLE,
22
+ description: DESCRIPTION,
23
+ inputSchema,
24
+ annotations,
25
+ handler: (input) => authHandlerBody(client, input)
26
+ };
27
+ }
28
+ async function authHandlerBody(client, input) {
29
+ const { action, deviceCode } = input ?? {};
30
+ try {
31
+ switch (action) {
32
+ case "status": {
33
+ const resolved = client.resolveToken();
34
+ if (!resolved) {
35
+ return mcpResult(
36
+ { authenticated: false },
37
+ { next: ['Use auth with action "login" to authenticate'] }
38
+ );
39
+ }
40
+ const user = await client.whoami();
41
+ return mcpResult({
42
+ authenticated: true,
43
+ ...user
44
+ });
115
45
  }
116
- },
117
- async ({ type, input, flow, path }) => {
118
- try {
119
- const result = await validate(type, input, {
120
- flow,
121
- path
46
+ case "login": {
47
+ if (deviceCode) {
48
+ const pollResult = await client.pollForToken(deviceCode, {
49
+ timeoutMs: 6e4
50
+ });
51
+ if (pollResult.success) {
52
+ return mcpResult(
53
+ { authenticated: true, email: pollResult.email },
54
+ {
55
+ next: [
56
+ 'Use project_manage with action "list" to see your projects'
57
+ ]
58
+ }
59
+ );
60
+ }
61
+ if (pollResult.status === "pending") {
62
+ return mcpResult({
63
+ authenticated: false,
64
+ status: "pending",
65
+ message: "Still waiting for authorization. Try again shortly.",
66
+ deviceCode
67
+ });
68
+ }
69
+ return mcpError(
70
+ new Error(pollResult.error || "Authorization failed")
71
+ );
72
+ }
73
+ const code = await client.requestDeviceCode();
74
+ const loginUrl = code.verificationUriComplete || code.verificationUri;
75
+ return mcpResult({
76
+ authenticated: false,
77
+ status: "awaiting_authorization",
78
+ loginUrl,
79
+ message: `Open this link to authorize: ${loginUrl}`,
80
+ deviceCode: code.deviceCode
122
81
  });
123
- const hints = result.valid ? {
124
- next: [
125
- "Use flow_simulate to test event flow",
126
- "Use flow_bundle to build"
127
- ]
128
- } : {
129
- next: [
130
- "Fix errors above, then run flow_validate again",
131
- "Read walkeros://reference/flow-schema for correct structure"
132
- ]
133
- };
134
- return mcpResult(result, hints);
135
- } catch (error) {
136
- return mcpError(
137
- error,
138
- "Check the input parameter \u2014 expected a JSON string, file path, or URL"
139
- );
140
82
  }
83
+ case "logout": {
84
+ const deleted = client.deleteConfig();
85
+ const hadEnvToken = typeof process.env.WALKEROS_TOKEN === "string" && process.env.WALKEROS_TOKEN.length > 0;
86
+ delete process.env.WALKEROS_TOKEN;
87
+ let message;
88
+ if (deleted && hadEnvToken) {
89
+ message = "Logged out. Config removed and WALKEROS_TOKEN cleared from process environment.";
90
+ } else if (deleted) {
91
+ message = "Logged out and config removed.";
92
+ } else if (hadEnvToken) {
93
+ message = "No config found. WALKEROS_TOKEN cleared from process environment.";
94
+ } else {
95
+ message = "No config found, already logged out.";
96
+ }
97
+ return mcpResult({
98
+ loggedOut: true,
99
+ message
100
+ });
101
+ }
102
+ default:
103
+ throw new Error(
104
+ `Unknown action: ${action}. Use one of: status, login, logout`
105
+ );
141
106
  }
107
+ } catch (error) {
108
+ return mcpError(error);
109
+ }
110
+ }
111
+ function registerAuthTool(server, client) {
112
+ const spec = createAuthToolSpec(client);
113
+ server.registerTool(
114
+ spec.name,
115
+ {
116
+ title: spec.title,
117
+ description: spec.description,
118
+ inputSchema: spec.inputSchema,
119
+ annotations: spec.annotations
120
+ },
121
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
122
+ // type-erased (input: unknown) => Promise<unknown> form by design.
123
+ spec.handler
142
124
  );
143
125
  }
144
126
 
145
- // src/tools/bundle.ts
127
+ // src/tools/project-manage.ts
146
128
  import { z as z2 } from "zod";
147
- import { bundle, bundleRemote } from "@walkeros/cli";
148
- import { schemas as schemas2 } from "@walkeros/cli/dev";
149
129
  import { mcpResult as mcpResult2, mcpError as mcpError2 } from "@walkeros/core";
150
- function registerFlowBundleTool(server2) {
151
- server2.registerTool(
152
- "flow_bundle",
153
- {
154
- title: "Bundle Flow",
155
- 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.",
156
- inputSchema: {
157
- ...schemas2.BundleInputShape,
158
- remote: z2.boolean().optional().describe(
159
- "Use remote cloud bundling (requires authentication). Default: false (local)"
160
- ),
161
- content: z2.record(z2.string(), z2.unknown()).optional().describe("Flow.Config JSON content (required when remote: true)")
162
- },
163
- outputSchema: BundleOutputShape,
164
- annotations: {
165
- readOnlyHint: false,
166
- destructiveHint: false,
167
- idempotentHint: false,
168
- openWorldHint: true
130
+
131
+ // src/types.ts
132
+ function isAuthError(error) {
133
+ if (!(error instanceof Error)) return false;
134
+ const msg = error.message.toLowerCase();
135
+ if (msg.includes("unauthorized") || msg.includes("forbidden") || msg.includes("invalid token") || msg.includes("token expired") || msg.includes("not authenticated")) {
136
+ return true;
137
+ }
138
+ const code = error.code;
139
+ if (!code) return false;
140
+ const upperCode = code.toUpperCase();
141
+ return upperCode === "UNAUTHORIZED" || upperCode === "FORBIDDEN" || upperCode === "401" || upperCode === "403" || upperCode.startsWith("AUTH_");
142
+ }
143
+ var AUTH_HINT = 'Are you logged in? Use auth(action: "status") to check.';
144
+
145
+ // src/user-data.ts
146
+ function wrapUserData(s) {
147
+ const alreadyWrapped = s.startsWith("<user_data>") && s.endsWith("</user_data>") && s.slice(11, -12).indexOf("</user_data>") === -1;
148
+ if (alreadyWrapped) return s;
149
+ const neutralised = s.replace(/<\/user_data>/g, "</user_data_>");
150
+ return `<user_data>${neutralised}</user_data>`;
151
+ }
152
+ function redactNestedStrings(value, opts) {
153
+ return walk(value, opts);
154
+ }
155
+ function walk(value, opts) {
156
+ if (typeof value === "string") return wrapUserData(value);
157
+ if (Array.isArray(value)) return value.map((v) => walk(v, opts));
158
+ if (value && typeof value === "object") {
159
+ const out = {};
160
+ for (const [k, v] of Object.entries(value)) {
161
+ if (opts?.skip?.(k) && typeof v === "string") {
162
+ out[k] = v;
163
+ } else {
164
+ out[k] = walk(v, opts);
169
165
  }
170
- },
171
- async ({ configPath, flow, stats, output, remote, content }) => {
172
- try {
173
- if (remote) {
174
- if (!content)
175
- throw new Error("content is required when remote: true");
176
- const result2 = await bundleRemote({
177
- content,
178
- flowName: flow
179
- });
166
+ }
167
+ return out;
168
+ }
169
+ return value;
170
+ }
171
+
172
+ // src/tools/project-manage.ts
173
+ function wrapProjectName(p) {
174
+ return p.name !== void 0 ? { ...p, name: wrapUserData(p.name) } : p;
175
+ }
176
+ var TITLE2 = "Project Management";
177
+ var DESCRIPTION2 = "Manage walkerOS projects. List, create, update, delete projects, or set a default project for CLI operations.";
178
+ var inputSchema2 = {
179
+ action: z2.enum(["list", "get", "create", "update", "delete", "set_default"]).describe("Project management action to perform"),
180
+ projectId: z2.string().optional().describe(
181
+ "Project ID. Required for get, update, delete, and set_default actions."
182
+ ),
183
+ name: z2.string().optional().describe(
184
+ "Project name. Required for create. Optional for update (to rename)."
185
+ )
186
+ };
187
+ var annotations2 = {
188
+ readOnlyHint: false,
189
+ destructiveHint: true,
190
+ idempotentHint: false,
191
+ openWorldHint: true
192
+ };
193
+ function createProjectManageToolSpec(client) {
194
+ return {
195
+ name: "project_manage",
196
+ title: TITLE2,
197
+ description: DESCRIPTION2,
198
+ inputSchema: inputSchema2,
199
+ annotations: annotations2,
200
+ handler: (input) => projectManageHandlerBody(client, input)
201
+ };
202
+ }
203
+ async function projectManageHandlerBody(client, input) {
204
+ const { action, projectId, name } = input ?? {};
205
+ try {
206
+ switch (action) {
207
+ case "list": {
208
+ const projects = await client.listProjects();
209
+ const items = Array.isArray(projects) ? projects : projects?.projects || [];
210
+ if (items.length === 0) {
180
211
  return mcpResult2(
181
- { success: true, ...result2 },
212
+ { projects: [] },
182
213
  {
183
214
  next: [
184
- "Use flow_simulate to test",
185
- 'Use deploy_manage with action "deploy" to publish'
215
+ 'Use project_manage with action "create" to create your first project',
216
+ 'Use project_manage with action "set_default" after creating to set it as default'
186
217
  ]
187
218
  }
188
219
  );
189
220
  }
190
- const result = await bundle(configPath, {
191
- flowName: flow,
192
- stats: stats ?? true,
193
- buildOverrides: output ? { output } : void 0
221
+ const typedItems = items;
222
+ const safe = Array.isArray(projects) ? typedItems.map(wrapProjectName) : { ...projects, projects: typedItems.map(wrapProjectName) };
223
+ return mcpResult2(safe);
224
+ }
225
+ case "get": {
226
+ if (!projectId) {
227
+ return mcpError2(
228
+ new Error(
229
+ 'projectId is required for get action. Use action "list" to see available projects.'
230
+ )
231
+ );
232
+ }
233
+ const project = await client.getProject({ projectId });
234
+ return mcpResult2(wrapProjectName(project));
235
+ }
236
+ case "create": {
237
+ if (!name) {
238
+ return mcpError2(new Error("name is required for create action."));
239
+ }
240
+ const created = await client.createProject({ name });
241
+ return mcpResult2(wrapProjectName(created), {
242
+ next: [
243
+ 'Use project_manage with action "set_default" to make this your active project'
244
+ ]
194
245
  });
195
- if (!result) {
196
- return mcpResult2(
197
- { success: false, message: "Bundle produced no output" },
198
- {
199
- warnings: [
200
- "The build returned no result. The flow may be empty or misconfigured."
201
- ],
202
- next: ["Run flow_validate to check your configuration"]
203
- }
246
+ }
247
+ case "update": {
248
+ if (!projectId) {
249
+ return mcpError2(
250
+ new Error(
251
+ 'projectId is required for update action. Use action "list" to see available projects.'
252
+ )
253
+ );
254
+ }
255
+ if (!name) {
256
+ return mcpError2(new Error("name is required for update action."));
257
+ }
258
+ const updated = await client.updateProject({ projectId, name });
259
+ return mcpResult2(wrapProjectName(updated));
260
+ }
261
+ case "delete": {
262
+ if (!projectId) {
263
+ return mcpError2(
264
+ new Error(
265
+ 'projectId is required for delete action. Use action "list" to see available projects.'
266
+ )
267
+ );
268
+ }
269
+ const deleted = await client.deleteProject({ projectId });
270
+ return mcpResult2(deleted);
271
+ }
272
+ case "set_default": {
273
+ if (!projectId) {
274
+ return mcpError2(
275
+ new Error(
276
+ 'projectId is required for set_default action. Use action "list" to see available projects.'
277
+ )
204
278
  );
205
279
  }
206
- const output_ = result;
280
+ client.setDefaultProject(projectId);
207
281
  return mcpResult2(
208
- { success: true, ...output_ },
282
+ { defaultProjectId: projectId },
209
283
  {
210
284
  next: [
211
- "Use flow_simulate to test",
212
- 'Use deploy_manage with action "deploy" to publish'
285
+ 'Use flow_manage with action "list" to see flows in this project'
213
286
  ]
214
287
  }
215
288
  );
216
- } catch (error) {
217
- return mcpError2(error, "Run flow_validate for detailed error messages");
218
289
  }
290
+ default:
291
+ throw new Error(
292
+ `Unknown action: ${action}. Use one of: list, get, create, update, delete, set_default`
293
+ );
219
294
  }
295
+ } catch (error) {
296
+ return mcpError2(error, isAuthError(error) ? AUTH_HINT : void 0);
297
+ }
298
+ }
299
+ function registerProjectManageTool(server, client) {
300
+ const spec = createProjectManageToolSpec(client);
301
+ server.registerTool(
302
+ spec.name,
303
+ {
304
+ title: spec.title,
305
+ description: spec.description,
306
+ inputSchema: spec.inputSchema,
307
+ annotations: spec.annotations
308
+ },
309
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
310
+ // type-erased (input: unknown) => Promise<unknown> form by design.
311
+ spec.handler
220
312
  );
221
313
  }
222
314
 
223
- // src/tools/simulate.ts
315
+ // src/tools/flow-manage.ts
224
316
  import { z as z3 } from "zod";
225
- import {
226
- simulateSource,
227
- simulateTransformer,
228
- simulateDestination
229
- } from "@walkeros/cli";
230
- import { schemas as schemas3 } from "@walkeros/cli/dev";
231
317
  import { mcpResult as mcpResult3, mcpError as mcpError3 } from "@walkeros/core";
232
- function registerFlowSimulateTool(server2) {
233
- server2.registerTool(
234
- "flow_simulate",
235
- {
236
- title: "Simulate Flow",
237
- description: 'Simulate events through a walkerOS flow without making real API calls. For destinations: event is a walkerOS event { name: "entity action", data: {...} }. For sources: event is { content: ..., trigger?: { type?, options? }, env?: {...} }. Use step to target a specific step. Use flow_examples to discover available test data. IMPORTANT: Destinations with require (e.g. require: ["consent"]) stay pending until that collector event fires \u2014 simulation will error "not found" if require is not satisfied. Remove require from config or provide consent/user events before simulating. Separately, destinations with consent (e.g. consent: { marketing: true }) only receive events where the event includes matching consent. Mapping transforms event names and data at the destination level. Policy redacts or injects fields before mapping runs.',
238
- inputSchema: {
239
- configPath: schemas3.SimulateInputShape.configPath,
240
- event: z3.union([z3.record(z3.string(), z3.unknown()), z3.string()]).optional().describe(
241
- "For destinations: { name, data, consent? }. Include consent (e.g. { marketing: true }) to satisfy destination consent requirements. For sources: { content, trigger?, env? }. Can also be a JSON string or file path."
242
- ),
243
- flow: schemas3.SimulateInputShape.flow,
244
- platform: schemas3.SimulateInputShape.platform,
245
- step: schemas3.SimulateInputShape.step,
246
- verbose: z3.boolean().optional().describe("Include full payload per destination (default: false)")
247
- },
248
- outputSchema: SimulateOutputShape,
249
- annotations: {
250
- readOnlyHint: true,
251
- destructiveHint: false,
252
- idempotentHint: true,
253
- openWorldHint: false
318
+
319
+ // src/ui-parts.ts
320
+ function flowCanvasResult(payload) {
321
+ const structured = { kind: "flow-canvas", ...payload };
322
+ return {
323
+ content: [
324
+ {
325
+ type: "text",
326
+ text: JSON.stringify(structured, null, 2)
254
327
  }
255
- },
256
- async ({ configPath, event, flow, platform, step, verbose }) => {
257
- try {
258
- if (!event) {
259
- throw new Error(
260
- "event is required. For sources provide { content, trigger? }, for destinations provide { name, data }."
261
- );
328
+ ],
329
+ structuredContent: structured
330
+ };
331
+ }
332
+ function isFlowCanvasResult(v) {
333
+ return typeof v === "object" && v !== null && v.kind === "flow-canvas";
334
+ }
335
+
336
+ // src/tools/flow-manage.ts
337
+ function pickPlatform(content) {
338
+ if (content && typeof content === "object") {
339
+ const flows = content.flows;
340
+ if (flows && typeof flows === "object") {
341
+ for (const flow of Object.values(flows)) {
342
+ if (flow && typeof flow === "object") {
343
+ const config = flow.config;
344
+ if (config && typeof config === "object") {
345
+ const platform = config.platform;
346
+ if (platform === "web" || platform === "server") return platform;
347
+ }
262
348
  }
263
- if (!step) {
264
- throw new Error(
265
- 'step is required. Specify a target like "source.browser", "destination.gtag", or "transformer.demo".'
266
- );
349
+ }
350
+ }
351
+ }
352
+ return "server";
353
+ }
354
+ var KEEP_LITERAL = /* @__PURE__ */ new Set([
355
+ "id",
356
+ "flowId",
357
+ "projectId",
358
+ "previewId",
359
+ "version",
360
+ "slug",
361
+ "createdAt",
362
+ "updatedAt",
363
+ "deletedAt"
364
+ ]);
365
+ var keepLiteral = (key) => KEEP_LITERAL.has(key);
366
+ function safeSummary(flow) {
367
+ return flow.name !== void 0 ? { ...flow, name: wrapUserData(flow.name) } : flow;
368
+ }
369
+ function safeDetail(flow) {
370
+ const withName = flow.name !== void 0 ? { ...flow, name: wrapUserData(flow.name) } : { ...flow };
371
+ if (flow.config !== void 0) {
372
+ withName.config = redactNestedStrings(
373
+ flow.config,
374
+ { skip: keepLiteral }
375
+ );
376
+ }
377
+ return withName;
378
+ }
379
+ var TITLE3 = "Flow Management";
380
+ var DESCRIPTION3 = "Manage walkerOS flows and their previews. List/get/create/update/delete/duplicate flows, or create/inspect/delete preview bundles for testing flow changes on live sites.";
381
+ var inputSchema3 = {
382
+ action: z3.enum([
383
+ "list",
384
+ "get",
385
+ "create",
386
+ "update",
387
+ "delete",
388
+ "duplicate",
389
+ "preview_list",
390
+ "preview_get",
391
+ "preview_create",
392
+ "preview_delete"
393
+ ]).describe("Flow management action to perform"),
394
+ flowId: z3.string().optional().describe(
395
+ "Flow ID (flow_...) or config ID (cfg_...). Required for get, update, delete, duplicate."
396
+ ),
397
+ projectId: z3.string().optional().describe(
398
+ "Project ID. Optional filter for list (omit to list all projects). Required for create if no default project set."
399
+ ),
400
+ name: z3.string().optional().describe(
401
+ "Flow name. Required for create. Optional for update (to rename) and duplicate."
402
+ ),
403
+ content: z3.record(z3.string(), z3.unknown()).optional().describe("Flow.Json content. Used for create and update."),
404
+ patch: z3.boolean().optional().describe(
405
+ "Merge-patch for update (default true). When true, only provided fields are updated."
406
+ ),
407
+ fields: z3.array(z3.string()).optional().describe("Dot-path selectors for get to return only specific fields."),
408
+ sort: z3.enum(["name", "updated_at", "created_at"]).optional().describe("Sort field for list."),
409
+ order: z3.enum(["asc", "desc"]).optional().describe("Sort order for list."),
410
+ includeDeleted: z3.boolean().optional().describe("Include soft-deleted flows in list results."),
411
+ previewId: z3.string().optional().describe(
412
+ "Preview ID (prv_...). Required for preview_get and preview_delete."
413
+ ),
414
+ flowName: z3.string().optional().describe(
415
+ "Flow settings name. Used by preview_create as an alternative to flowSettingsId."
416
+ ),
417
+ flowSettingsId: z3.string().optional().describe(
418
+ "Flow settings ID. Used by preview_create as an alternative to flowName."
419
+ ),
420
+ siteUrl: z3.string().optional().describe(
421
+ "Optional site URL for preview_create. When provided, the response includes full activationUrl and deactivationUrl the user can click."
422
+ )
423
+ };
424
+ var annotations3 = {
425
+ readOnlyHint: false,
426
+ destructiveHint: true,
427
+ idempotentHint: false,
428
+ openWorldHint: true
429
+ };
430
+ function createFlowManageToolSpec(client) {
431
+ return {
432
+ name: "flow_manage",
433
+ title: TITLE3,
434
+ description: DESCRIPTION3,
435
+ inputSchema: inputSchema3,
436
+ annotations: annotations3,
437
+ handler: (input) => flowManageHandlerBody(client, input)
438
+ };
439
+ }
440
+ async function flowManageHandlerBody(client, input) {
441
+ const {
442
+ action,
443
+ flowId,
444
+ projectId,
445
+ name,
446
+ content,
447
+ patch,
448
+ fields,
449
+ sort,
450
+ order,
451
+ includeDeleted,
452
+ previewId,
453
+ flowName,
454
+ flowSettingsId,
455
+ siteUrl
456
+ } = input ?? {};
457
+ try {
458
+ switch (action) {
459
+ case "list": {
460
+ if (projectId) {
461
+ const data2 = await client.listFlows({
462
+ projectId,
463
+ sort,
464
+ order,
465
+ includeDeleted
466
+ });
467
+ const dataObj = data2;
468
+ const flows = dataObj.flows;
469
+ const safe2 = Array.isArray(flows) ? { ...dataObj, flows: flows.map(safeSummary) } : data2;
470
+ return mcpResult3(safe2);
267
471
  }
268
- let resolvedEvent = event;
269
- if (typeof event === "string") {
270
- try {
271
- resolvedEvent = JSON.parse(event);
272
- } catch {
273
- throw new Error(
274
- "Event string must be valid JSON. Got: " + event.substring(0, 50)
275
- );
472
+ const data = await client.listAllFlows({
473
+ sort,
474
+ order,
475
+ includeDeleted
476
+ });
477
+ const safe = Array.isArray(data) ? data.map(safeSummary) : data;
478
+ return mcpResult3(
479
+ { projects: safe },
480
+ {
481
+ next: [
482
+ 'Use flow_manage with action "get" and a flowId to inspect a specific flow',
483
+ "Use flow_load to open a flow for editing"
484
+ ]
276
485
  }
277
- }
278
- const dotIndex = step.indexOf(".");
279
- if (dotIndex === -1) {
280
- throw new Error(
281
- `Invalid step format "${step}". Use "type.name" (e.g. "source.browser", "destination.gtag").`
486
+ );
487
+ }
488
+ case "get": {
489
+ if (!flowId) {
490
+ return mcpError3(
491
+ new Error(
492
+ 'flowId is required for get action. Use action "list" to see available flows.'
493
+ )
282
494
  );
283
495
  }
284
- const stepType = step.substring(0, dotIndex);
285
- const stepId = step.substring(dotIndex + 1);
286
- let result;
287
- switch (stepType) {
288
- case "source":
289
- result = await simulateSource(configPath, resolvedEvent, {
290
- sourceId: stepId,
291
- flow,
292
- silent: true
293
- });
294
- break;
295
- case "transformer":
296
- result = await simulateTransformer(
297
- configPath,
298
- resolvedEvent,
299
- {
300
- transformerId: stepId,
301
- flow,
302
- silent: true
303
- }
304
- );
305
- break;
306
- case "destination":
307
- result = await simulateDestination(
308
- configPath,
309
- resolvedEvent,
310
- {
311
- destinationId: stepId,
312
- flow,
313
- silent: true
314
- }
315
- );
316
- break;
317
- default:
318
- throw new Error(
319
- `Unknown step type "${stepType}". Use "source", "transformer", or "destination".`
320
- );
496
+ const flow = await client.getFlow({ flowId, projectId, fields });
497
+ const safe = safeDetail(
498
+ flow
499
+ );
500
+ return flowCanvasResult({
501
+ flowId: safe.id,
502
+ configName: safe.name ?? "default",
503
+ platform: pickPlatform(safe.config),
504
+ flowConfig: safe.config ?? {},
505
+ suggestions: [
506
+ {
507
+ label: "Validate this flow",
508
+ prompt: `Validate flow ${safe.id}`,
509
+ autoSend: true
510
+ },
511
+ {
512
+ label: "Add a destination",
513
+ prompt: `Help me add a destination to flow ${safe.id}`
514
+ }
515
+ ]
516
+ });
517
+ }
518
+ case "create": {
519
+ if (!name) {
520
+ return mcpError3(new Error("name is required for create action."));
321
521
  }
322
- if (result.captured && result.captured.length > 0) {
323
- const eventCount = result.captured.length;
324
- const summary2 = `Source captured ${eventCount} event${eventCount !== 1 ? "s" : ""}`;
325
- return mcpResult3(
522
+ const created = await client.createFlow({
523
+ name,
524
+ content: content ?? {},
525
+ projectId
526
+ });
527
+ const safeCreated = safeDetail(
528
+ created
529
+ );
530
+ return flowCanvasResult({
531
+ flowId: safeCreated.id,
532
+ configName: safeCreated.name ?? "default",
533
+ platform: pickPlatform(safeCreated.config),
534
+ flowConfig: safeCreated.config ?? {},
535
+ suggestions: [
326
536
  {
327
- success: result.success,
328
- error: result.error,
329
- summary: summary2,
330
- capturedEvents: result.captured,
331
- duration: result.duration
537
+ label: "Validate this flow",
538
+ prompt: `Validate flow ${safeCreated.id}`,
539
+ autoSend: true
332
540
  },
333
541
  {
334
- next: eventCount > 0 ? [
335
- "Use flow_simulate with a destination step to test downstream processing"
336
- ] : [
337
- "Check source package examples with package_get, verify trigger type matches"
338
- ]
542
+ label: "Deploy it",
543
+ prompt: `Deploy flow ${safeCreated.id}`
339
544
  }
545
+ ]
546
+ });
547
+ }
548
+ case "update": {
549
+ if (!flowId) {
550
+ return mcpError3(
551
+ new Error(
552
+ 'flowId is required for update action. Use action "list" to see available flows.'
553
+ )
340
554
  );
341
555
  }
342
- const destinations = {};
343
- if (result.elbResult && typeof result.elbResult === "object" && "done" in result.elbResult && result.elbResult.done) {
344
- const done = result.elbResult.done;
345
- for (const name of Object.keys(done)) {
346
- destinations[name] = { received: true, calls: 0 };
347
- }
348
- }
349
- if (result.usage) {
350
- for (const [name, calls] of Object.entries(result.usage)) {
351
- const summary2 = {
352
- received: calls.length > 0,
353
- calls: calls.length
354
- };
355
- if (verbose && calls.length > 0) {
356
- summary2.payload = calls;
556
+ const updated = await client.updateFlow({
557
+ flowId,
558
+ projectId,
559
+ name,
560
+ content,
561
+ mergePatch: patch ?? true
562
+ });
563
+ const safeUpdated = safeDetail(
564
+ updated
565
+ );
566
+ return flowCanvasResult({
567
+ flowId: safeUpdated.id,
568
+ configName: safeUpdated.name ?? "default",
569
+ platform: pickPlatform(safeUpdated.config),
570
+ flowConfig: safeUpdated.config ?? {},
571
+ suggestions: [
572
+ {
573
+ label: "Validate this flow",
574
+ prompt: `Validate flow ${safeUpdated.id}`,
575
+ autoSend: true
576
+ },
577
+ {
578
+ label: "Deploy it",
579
+ prompt: `Deploy flow ${safeUpdated.id}`
357
580
  }
358
- destinations[name] = summary2;
359
- }
581
+ ]
582
+ });
583
+ }
584
+ case "delete": {
585
+ if (!flowId) {
586
+ return mcpError3(
587
+ new Error(
588
+ 'flowId is required for delete action. Use action "list" to see available flows.'
589
+ )
590
+ );
360
591
  }
361
- const destCount = Object.keys(destinations).length;
362
- const receivedCount = Object.values(destinations).filter(
363
- (d) => d.received
364
- ).length;
365
- const warnings = [];
366
- if (stepType === "destination" && destCount === 0) {
367
- warnings.push(
368
- 'Destination did not receive the event. Common causes: (1) destination config has consent: { marketing: true } but event lacks matching consent, (2) mapping rules do not match the event name, (3) policy redacted required fields. Add consent to the event: { name: "...", data: {...}, consent: { marketing: true } }.'
592
+ const deleted = await client.deleteFlow({ flowId, projectId });
593
+ return mcpResult3(deleted);
594
+ }
595
+ case "duplicate": {
596
+ if (!flowId) {
597
+ return mcpError3(
598
+ new Error(
599
+ 'flowId is required for duplicate action. Use action "list" to see available flows.'
600
+ )
369
601
  );
370
602
  }
371
- const summary = stepType === "transformer" ? `Transformer processed event` : `${receivedCount}/${destCount} destinations received the event`;
372
- const resultObj = {
373
- success: result.success,
374
- error: result.error,
375
- summary,
376
- destinations: destCount > 0 ? destinations : void 0,
377
- duration: result.duration
378
- };
379
- return mcpResult3(resultObj, {
380
- next: ["Use flow_bundle to build for production"],
381
- ...warnings.length > 0 ? { warnings } : {}
603
+ const duplicated = await client.duplicateFlow({
604
+ flowId,
605
+ name,
606
+ projectId
382
607
  });
383
- } catch (error) {
384
- const msg = error instanceof Error ? error.message : "";
385
- let hint = "Run flow_validate for detailed error messages";
386
- if (msg.includes("not found in collector")) {
387
- 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.';
388
- }
389
- return mcpError3(error, hint);
608
+ return mcpResult3(
609
+ safeDetail(duplicated)
610
+ );
390
611
  }
391
- }
392
- );
393
- }
394
-
395
- // src/tools/push.ts
396
- import { z as z4 } from "zod";
397
- import { push } from "@walkeros/cli";
398
- import { schemas as schemas4 } from "@walkeros/cli/dev";
399
- import { mcpResult as mcpResult4, mcpError as mcpError4 } from "@walkeros/core";
400
- function registerFlowPushTool(server2) {
401
- server2.registerTool(
402
- "flow_push",
403
- {
404
- title: "Push Events",
405
- description: "Push a real event through a walkerOS flow to actual destinations. Makes real API calls to real endpoints. Best suited for server-side flows \u2014 web flows should use flow_simulate for testing.",
406
- inputSchema: {
407
- configPath: schemas4.PushInputShape.configPath,
408
- event: z4.record(z4.string(), z4.unknown()).describe(
409
- 'Event object, e.g. { name: "page view", data: { title: "Home" } }'
410
- ),
411
- flow: schemas4.PushInputShape.flow,
412
- platform: schemas4.PushInputShape.platform
413
- },
414
- outputSchema: PushOutputShape,
415
- annotations: {
416
- readOnlyHint: false,
417
- destructiveHint: true,
418
- idempotentHint: false,
419
- openWorldHint: true
612
+ case "preview_list": {
613
+ if (!flowId) {
614
+ return mcpError3(
615
+ new Error(
616
+ 'flowId is required for preview_list. Use action "list" to see available flows.'
617
+ )
618
+ );
619
+ }
620
+ const data = await client.listPreviews({ projectId, flowId });
621
+ return mcpResult3(data);
420
622
  }
421
- },
422
- async ({ configPath, event, flow, platform }) => {
423
- try {
424
- const result = await push(configPath, event, {
425
- json: true,
426
- flow,
427
- platform
428
- });
429
- if (!result.success) {
430
- return mcpError4(
431
- new Error(result.error || "Push failed"),
432
- "Check destination configuration and connectivity."
623
+ case "preview_get": {
624
+ if (!flowId || !previewId) {
625
+ return mcpError3(
626
+ new Error("flowId and previewId are required for preview_get.")
433
627
  );
434
628
  }
435
- return mcpResult4(result);
436
- } catch (error) {
437
- return mcpError4(
438
- error,
439
- "Check configPath and event format. For web flows, use flow_simulate."
440
- );
629
+ const data = await client.getPreview({
630
+ projectId,
631
+ flowId,
632
+ previewId
633
+ });
634
+ return mcpResult3(data);
441
635
  }
442
- }
443
- );
444
- }
445
-
446
- // src/tools/examples.ts
447
- import { z as z5 } from "zod";
448
- import { loadJsonConfig } from "@walkeros/cli";
449
- import { mcpResult as mcpResult5, mcpError as mcpError5 } from "@walkeros/core";
450
- function registerFlowExamplesTool(server2) {
451
- server2.registerTool(
452
- "flow_examples",
453
- {
454
- title: "Flow Examples",
455
- 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.",
456
- inputSchema: {
457
- configPath: z5.string().min(1).describe(
458
- "Path to flow configuration file, URL, or inline JSON string"
459
- ),
460
- flow: z5.string().optional().describe("Flow name for multi-flow configs"),
461
- step: z5.string().optional().describe('Filter to a specific step (e.g., "destination.gtag")'),
462
- full: z5.boolean().optional().describe(
463
- "Return full in/out/mapping data for each example (default: false, returns metadata only)"
464
- ),
465
- includeHidden: z5.boolean().optional().describe(
466
- "Include examples marked public: false (default: false). Set true for test/debug discovery."
467
- )
468
- },
469
- outputSchema: ExamplesListOutputShape,
470
- annotations: {
471
- readOnlyHint: true,
472
- destructiveHint: false,
473
- idempotentHint: true,
474
- openWorldHint: false
475
- }
476
- },
477
- async ({ configPath, flow, step, full, includeHidden }) => {
478
- try {
479
- const rawConfig = await loadJsonConfig(configPath);
480
- const flowNames = Object.keys(rawConfig.flows || {});
481
- const flowName = flow || (flowNames.length === 1 ? flowNames[0] : void 0);
482
- if (!flowName) {
483
- throw new Error(
484
- `Multiple flows found. Specify flow parameter. Available: ${flowNames.join(", ")}`
485
- );
486
- }
487
- const flowSettings = rawConfig.flows[flowName];
488
- if (!flowSettings) {
489
- throw new Error(`Flow "${flowName}" not found`);
636
+ case "preview_create": {
637
+ if (!flowId) {
638
+ return mcpError3(new Error("flowId is required for preview_create."));
490
639
  }
491
- const examples = [];
492
- const stepTypes = [
493
- { key: "sources", type: "source" },
494
- { key: "transformers", type: "transformer" },
495
- { key: "destinations", type: "destination" }
496
- ];
497
- for (const { key, type } of stepTypes) {
498
- const refs = flowSettings[key] || {};
499
- for (const [name, ref] of Object.entries(refs)) {
500
- if (!ref.examples) continue;
501
- if (step && `${type}.${name}` !== step) continue;
502
- for (const [exName, ex] of Object.entries(
503
- ref.examples
504
- )) {
505
- if (!includeHidden && ex.public === false) continue;
506
- examples.push({
507
- step: `${type}.${name}`,
508
- stepType: type,
509
- stepName: name,
510
- exampleName: exName,
511
- title: ex.title,
512
- description: ex.description,
513
- public: ex.public,
514
- hasIn: ex.in !== void 0,
515
- hasOut: ex.out !== void 0,
516
- hasMapping: ex.mapping !== void 0,
517
- hasTrigger: ex.trigger !== void 0,
518
- ...full ? {
519
- in: ex.in,
520
- out: ex.out,
521
- mapping: ex.mapping,
522
- trigger: ex.trigger
523
- } : {}
524
- });
525
- }
526
- }
640
+ if (!flowName && !flowSettingsId) {
641
+ return mcpError3(
642
+ new Error(
643
+ "flowName or flowSettingsId is required for preview_create."
644
+ )
645
+ );
527
646
  }
528
- const result = {
529
- flow: flowName,
530
- count: examples.length,
531
- examples
532
- };
533
- const hints = {
534
- next: ["Use flow_simulate with step and event to simulate"]
647
+ const preview = await client.createPreview({
648
+ projectId,
649
+ flowId,
650
+ flowName,
651
+ flowSettingsId
652
+ });
653
+ const typedPreview = preview;
654
+ const enriched = {
655
+ ...typedPreview,
656
+ activationParam: typedPreview.activationUrl
535
657
  };
536
- if (examples.length === 0) {
537
- hints.warnings = [
538
- "No examples found. Add examples to step definitions in your flow config for testing."
539
- ];
658
+ if (siteUrl) {
659
+ const on = new URL(siteUrl);
660
+ on.searchParams.set("elbPreview", typedPreview.token);
661
+ enriched.activationUrl = on.toString();
662
+ const off = new URL(siteUrl);
663
+ off.searchParams.set("elbPreview", "off");
664
+ enriched.deactivationUrl = off.toString();
665
+ } else {
666
+ delete enriched.activationUrl;
540
667
  }
541
- return mcpResult5(result, hints);
542
- } catch (error) {
543
- return mcpError5(error, "Check configPath \u2014 expected a flow.json file");
668
+ return mcpResult3(enriched, {
669
+ next: siteUrl ? [
670
+ "Open activationUrl to activate preview mode; open deactivationUrl to exit."
671
+ ] : [
672
+ "Append activationParam to any URL on your site to activate preview mode."
673
+ ]
674
+ });
675
+ }
676
+ case "preview_delete": {
677
+ if (!flowId || !previewId) {
678
+ return mcpError3(
679
+ new Error("flowId and previewId are required for preview_delete.")
680
+ );
681
+ }
682
+ const data = await client.deletePreview({
683
+ projectId,
684
+ flowId,
685
+ previewId
686
+ });
687
+ return mcpResult3(data);
544
688
  }
689
+ default:
690
+ throw new Error(
691
+ `Unknown action: ${action}. Use one of: list, get, create, update, delete, duplicate, preview_list, preview_get, preview_create, preview_delete`
692
+ );
545
693
  }
694
+ } catch (error) {
695
+ return mcpError3(error, isAuthError(error) ? AUTH_HINT : void 0);
696
+ }
697
+ }
698
+ function registerFlowManageTool(server, client) {
699
+ const spec = createFlowManageToolSpec(client);
700
+ server.registerTool(
701
+ spec.name,
702
+ {
703
+ title: spec.title,
704
+ description: spec.description,
705
+ inputSchema: spec.inputSchema,
706
+ annotations: spec.annotations
707
+ },
708
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
709
+ // type-erased (input: unknown) => Promise<unknown> form by design.
710
+ spec.handler
546
711
  );
547
712
  }
548
713
 
549
- // src/tools/package.ts
550
- import { z as z6 } from "zod";
551
- import { fetchPackage, mcpResult as mcpResult6, mcpError as mcpError6 } from "@walkeros/core";
552
- import { mergeConfigSchema } from "@walkeros/core/dev";
714
+ // src/tools/deploy-manage.ts
715
+ import { z as z4 } from "zod";
716
+ import { mcpResult as mcpResult4, mcpError as mcpError4 } from "@walkeros/core";
553
717
 
554
- // src/catalog.ts
555
- var NPM_SEARCH_URL = "https://registry.npmjs.org/-/v1/search";
556
- var JSDELIVR_BASE = "https://cdn.jsdelivr.net/npm";
557
- var WALKEROS_JSON_PATH = "dist/walkerOS.json";
558
- var CACHE_TTL = 5 * 60 * 1e3;
559
- var cache;
560
- function normalizePlatform(platform) {
561
- if (platform == null) return [];
562
- if (typeof platform === "string") {
563
- return platform === "universal" ? ["web", "server"] : [platform];
564
- }
565
- if (Array.isArray(platform)) {
566
- return platform.filter((v) => typeof v === "string");
567
- }
568
- return [];
569
- }
570
- async function fetchCatalog(filters) {
571
- if (cache && Date.now() - cache.timestamp < CACHE_TTL) {
572
- return applyFilters(cache.entries, filters);
718
+ // src/tools/_resolvers.ts
719
+ var DeploymentNotFoundError = class extends Error {
720
+ code = "NOT_FOUND";
721
+ constructor(message) {
722
+ super(message);
723
+ this.name = "DeploymentNotFoundError";
573
724
  }
574
- let entries;
575
- try {
576
- entries = filters?.baseUrl ? await fetchCatalogFrom(filters.baseUrl, filters) : await fetchFromNpm();
577
- } catch {
578
- try {
579
- entries = await fetchFromNpm();
580
- } catch {
581
- return [];
582
- }
725
+ };
726
+ var MultipleDeploymentsError = class extends Error {
727
+ code = "MULTIPLE_DEPLOYMENTS";
728
+ details;
729
+ constructor(message, details) {
730
+ super(message);
731
+ this.name = "MultipleDeploymentsError";
732
+ this.details = details;
583
733
  }
584
- cache = { entries, timestamp: Date.now() };
585
- return applyFilters(entries, filters);
586
- }
587
- async function fetchCatalogFrom(baseUrl, filters) {
588
- const params = new URLSearchParams();
589
- if (filters?.type) params.set("type", filters.type);
590
- if (filters?.platform) params.set("platform", filters.platform);
591
- const url = `${baseUrl}/api/packages${params.toString() ? `?${params}` : ""}`;
592
- const res = await fetch(url, { signal: AbortSignal.timeout(15e3) });
593
- if (!res.ok) throw new Error(`Catalog fetch failed: ${res.status}`);
594
- const data = await res.json();
595
- return data.catalog;
596
- }
597
- async function fetchFromNpm() {
598
- const res = await fetch(`${NPM_SEARCH_URL}?text=@walkeros/&size=250`, {
599
- signal: AbortSignal.timeout(1e4)
734
+ };
735
+ async function resolveDeploymentSlug(args) {
736
+ const matches = await args.list({
737
+ projectId: args.projectId,
738
+ flowId: args.flowId
600
739
  });
601
- if (!res.ok) throw new Error(`npm search failed: ${res.status}`);
602
- const data = await res.json();
603
- const metaResults = await Promise.allSettled(
604
- data.objects.map((obj) => enrichWithMeta(obj.package))
605
- );
606
- return metaResults.filter(
607
- (r) => r.status === "fulfilled"
608
- ).map((r) => r.value).filter((entry) => entry !== void 0);
609
- }
610
- async function enrichWithMeta(pkg) {
611
- try {
612
- const res = await fetch(
613
- `${JSDELIVR_BASE}/${pkg.name}@${pkg.version}/${WALKEROS_JSON_PATH}`,
614
- { signal: AbortSignal.timeout(5e3) }
740
+ if (matches.length === 0) {
741
+ throw new DeploymentNotFoundError(
742
+ `No deployments found for flow ${args.flowId}`
615
743
  );
616
- if (!res.ok) return void 0;
617
- const json = await res.json();
618
- const meta = json.$meta;
619
- if (!meta || typeof meta.type !== "string") return void 0;
620
- return {
621
- name: pkg.name,
622
- version: pkg.version,
623
- description: pkg.description,
624
- type: meta.type,
625
- platform: normalizePlatform(meta.platform)
626
- };
627
- } catch {
628
- return void 0;
629
744
  }
630
- }
631
- function applyFilters(entries, filters) {
632
- let results = entries;
633
- if (filters?.type) {
634
- results = results.filter((e) => e.type === filters.type);
745
+ if (args.slug !== void 0) {
746
+ const hit = matches.find((m) => m.slug === args.slug);
747
+ if (!hit) {
748
+ throw new DeploymentNotFoundError(
749
+ `No deployment with slug ${args.slug} in flow ${args.flowId}`
750
+ );
751
+ }
752
+ return hit.slug;
635
753
  }
636
- if (filters?.platform) {
637
- results = results.filter(
638
- (e) => e.platform.length === 0 || e.platform.includes(filters.platform)
639
- );
754
+ if (matches.length === 1) {
755
+ return matches[0].slug;
640
756
  }
641
- return results;
757
+ throw new MultipleDeploymentsError(
758
+ `Flow ${args.flowId} has ${matches.length} active deployments; pass slug to disambiguate`,
759
+ matches.map((m) => ({
760
+ slug: m.slug,
761
+ type: m.type,
762
+ status: m.status,
763
+ updatedAt: m.updatedAt
764
+ }))
765
+ );
642
766
  }
643
767
 
644
- // src/tools/package.ts
645
- function registerPackageSearchTool(server2) {
646
- server2.registerTool(
647
- "package_search",
648
- {
649
- title: "Search Package",
650
- description: "Start here for package discovery. Never guess package names \u2014 use this tool first to find exact names. Without package name: returns catalog filtered by type/platform. With package name: returns metadata, hint keys, and example summaries.",
651
- inputSchema: {
652
- package: z6.string().min(1).optional().describe(
653
- "Exact npm package name for detailed lookup (e.g., @walkeros/web-destination-snowplow)"
654
- ),
655
- type: z6.enum(["source", "destination", "transformer", "store"]).optional().describe("Filter by package type (browse mode)"),
656
- platform: z6.enum(["web", "server"]).optional().describe(
657
- "Filter by platform (browse mode, includes universal packages)"
658
- ),
659
- version: z6.string().optional().describe("Package version for detailed lookup (default: latest)")
660
- },
661
- // No outputSchema: browse mode returns {catalog, count}, lookup returns metadata — incompatible shapes
662
- annotations: {
663
- readOnlyHint: true,
664
- destructiveHint: false,
665
- idempotentHint: true,
666
- openWorldHint: false
667
- }
668
- },
669
- async ({ package: packageName, type, platform, version }) => {
670
- const baseUrl = process.env.APP_URL || void 0;
671
- if (!packageName) {
672
- const catalog = await fetchCatalog({ type, platform, baseUrl });
673
- const result = { catalog, count: catalog.length };
674
- return mcpResult6(result, {
675
- next: ["Use package_get for schemas and examples"]
768
+ // src/tools/deploy-manage.ts
769
+ var TITLE4 = "Deploy Management";
770
+ var DESCRIPTION4 = "Deploy walkerOS flows and manage deployments. For get/delete actions pass flowId (required) plus optional slug to disambiguate when a flow has multiple active deployments. If a flow has >=2 active deployments and no slug is supplied, the tool returns a MULTIPLE_DEPLOYMENTS error with a details[] list showing each deployment's slug, type, status, and updatedAt.";
771
+ var inputSchema4 = {
772
+ action: z4.enum(["deploy", "list", "get", "delete"]).describe("Deployment action to perform"),
773
+ projectId: z4.string().optional().describe("Project ID. Optional; falls back to the default project."),
774
+ flowId: z4.string().optional().describe(
775
+ "Flow ID. Required for: deploy, get, delete. Optional filter for list."
776
+ ),
777
+ slug: z4.string().optional().describe(
778
+ "Deployment slug. Optional disambiguator for get/delete when the flow has multiple active deployments."
779
+ ),
780
+ type: z4.enum(["web", "server"]).optional().describe("Deployment type filter for list."),
781
+ status: z4.string().optional().describe("Status filter for list."),
782
+ wait: z4.boolean().optional().describe(
783
+ "Wait for deploy to complete (default true). Only used with deploy action."
784
+ ),
785
+ flowName: z4.string().optional().describe(
786
+ "Flow name for multi-settings flows. Only used with deploy action."
787
+ )
788
+ };
789
+ var annotations4 = {
790
+ readOnlyHint: false,
791
+ destructiveHint: true,
792
+ idempotentHint: false,
793
+ openWorldHint: true
794
+ };
795
+ function listForResolver(client, projectId) {
796
+ return async (q) => {
797
+ const resp = await client.listDeployments({
798
+ projectId: projectId || q.projectId || void 0,
799
+ flowId: q.flowId
800
+ });
801
+ return resp.deployments ?? [];
802
+ };
803
+ }
804
+ function createDeployManageToolSpec(client) {
805
+ return {
806
+ name: "deploy_manage",
807
+ title: TITLE4,
808
+ description: DESCRIPTION4,
809
+ inputSchema: inputSchema4,
810
+ annotations: annotations4,
811
+ handler: (input) => deployManageHandlerBody(client, input)
812
+ };
813
+ }
814
+ async function deployManageHandlerBody(client, input) {
815
+ const { action, projectId, flowId, slug, type, status, wait, flowName } = input ?? {};
816
+ try {
817
+ switch (action) {
818
+ case "deploy": {
819
+ if (!flowId) {
820
+ return mcpError4(
821
+ new Error(
822
+ 'flowId is required for deploy action. Use flow_manage with action "list" to see available flows.'
823
+ )
824
+ );
825
+ }
826
+ const result = await client.deploy({
827
+ flowId,
828
+ wait: wait ?? true,
829
+ flowName
676
830
  });
677
- }
678
- try {
679
- const info = await fetchPackage(packageName, { version, baseUrl });
680
- const result = {
681
- package: info.packageName,
682
- version: info.version,
683
- description: info.description,
684
- type: info.type,
685
- platform: normalizePlatform(info.platform),
686
- hintKeys: info.hintKeys,
687
- exampleSummaries: info.exampleSummaries
688
- };
689
- return mcpResult6(result, {
690
- next: ["Use package_get for schemas and examples"]
831
+ return mcpResult4(result, {
832
+ next: [
833
+ 'Use deploy_manage with action "get" to check deployment status'
834
+ ]
691
835
  });
692
- } catch (error) {
693
- return mcpError6(
694
- error,
695
- "Package not found. Use package_search without parameters to browse available packages."
696
- );
697
836
  }
698
- }
699
- );
700
- }
701
- function registerGetPackageSchemaTool(server2) {
702
- server2.registerTool(
703
- "package_get",
704
- {
705
- title: "Get Package",
706
- description: 'Requires exact package name \u2014 do not guess names, use package_search first to find them. Returns schemas + hint texts + example summaries by default (lightweight). Use section parameter for full content: "hints" (with code blocks), "examples" (full in/out data), or "all".',
707
- inputSchema: {
708
- package: z6.string().min(1).describe(
709
- "Exact npm package name (e.g., @walkeros/web-destination-snowplow)"
710
- ),
711
- version: z6.string().optional().describe("Package version (default: latest)"),
712
- section: z6.enum(["hints", "examples", "all"]).optional().describe(
713
- "Section to expand with full content. Default: summary view with schemas + hint texts + example descriptions"
714
- )
715
- },
716
- // No outputSchema — removed to avoid SDK -32602 crashes on unexpected field values
717
- annotations: {
718
- readOnlyHint: true,
719
- destructiveHint: false,
720
- idempotentHint: true,
721
- openWorldHint: true
837
+ case "list": {
838
+ const data = await client.listDeployments({
839
+ projectId,
840
+ flowId,
841
+ type,
842
+ status
843
+ });
844
+ return mcpResult4(data);
722
845
  }
723
- },
724
- async ({ package: packageName, version, section }) => {
725
- const baseUrl = process.env.APP_URL || void 0;
726
- try {
727
- const info = await fetchPackage(packageName, { version, baseUrl });
728
- const mergedSchemas = {};
729
- if (info.type) {
730
- mergedSchemas.config = mergeConfigSchema(
731
- info.type,
732
- info.schemas
846
+ case "get": {
847
+ if (!flowId) {
848
+ return mcpError4(
849
+ new Error(
850
+ 'flowId is required for get action. Use flow_manage with action "list" to see available flows.'
851
+ )
733
852
  );
734
853
  }
735
- for (const [key, value] of Object.entries(info.schemas)) {
736
- if (key !== "settings") {
737
- mergedSchemas[key] = value;
738
- }
739
- }
740
- const result = {
741
- package: info.packageName,
742
- version: info.version,
743
- type: info.type,
744
- platform: normalizePlatform(info.platform),
745
- schemas: mergedSchemas
746
- };
747
- if (info.hints) {
748
- if (section === "hints" || section === "all") {
749
- result.hints = info.hints;
750
- } else {
751
- const hintSummary = {};
752
- for (const [key, hint] of Object.entries(info.hints)) {
753
- const h = hint;
754
- hintSummary[key] = { text: h.text };
755
- }
756
- result.hints = hintSummary;
757
- }
758
- }
759
- if (section === "examples" || section === "all") {
760
- result.examples = info.examples;
761
- } else {
762
- result.exampleSummaries = info.exampleSummaries;
763
- }
764
- return mcpResult6(result);
765
- } catch (error) {
766
- return mcpError6(
767
- error,
768
- "Use package_search to browse available package names."
769
- );
770
- }
771
- }
772
- );
773
- }
774
-
775
- // src/tools/flow-load.ts
776
- import { z as z7 } from "zod";
777
- import { loadJsonConfig as loadJsonConfig2 } from "@walkeros/cli";
778
- import { mcpResult as mcpResult7, mcpError as mcpError7 } from "@walkeros/core";
779
- var WEB_SKELETON = {
780
- version: 3,
781
- flows: {
782
- default: {
783
- web: {},
784
- packages: {},
785
- sources: {},
786
- destinations: {}
787
- }
788
- }
789
- };
790
- var SERVER_SKELETON = {
791
- version: 3,
792
- flows: {
793
- default: {
794
- server: {},
795
- packages: {},
796
- sources: {},
797
- destinations: {}
798
- }
799
- }
800
- };
801
- function registerFlowLoadTool(server2) {
802
- server2.registerTool(
803
- "flow_load",
804
- {
805
- title: "Load or Create Flow",
806
- 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.",
807
- inputSchema: {
808
- source: z7.string().optional().describe(
809
- "Flow source: local file path (./flow.json), URL (https://...), inline JSON string, or API flow ID (cfg_...). Omit to create a new flow."
810
- ),
811
- platform: z7.enum(["web", "server"]).optional().describe(
812
- "Platform for new flows. Required when source is omitted. web = browser tracking, server = Node.js HTTP."
813
- )
814
- },
815
- outputSchema: {
816
- version: z7.number().describe("Flow config version"),
817
- flows: z7.record(z7.string(), z7.unknown()).describe("Flow definitions")
818
- },
819
- annotations: {
820
- readOnlyHint: true,
821
- destructiveHint: false,
822
- idempotentHint: true,
823
- openWorldHint: true
854
+ const resolvedSlug = await resolveDeploymentSlug({
855
+ projectId: projectId ?? "",
856
+ flowId,
857
+ slug,
858
+ list: listForResolver(client, projectId)
859
+ });
860
+ const data = await client.getDeploymentBySlug({
861
+ slug: resolvedSlug,
862
+ projectId
863
+ });
864
+ return mcpResult4(data);
824
865
  }
825
- },
826
- async ({ source, platform }) => {
827
- try {
828
- if (source) {
829
- const config = await loadJsonConfig2(source);
830
- return mcpResult7(config, {
831
- next: [
832
- "Use flow_validate to check",
833
- "Use add-step prompt to modify"
834
- ]
835
- });
836
- }
837
- if (!platform) {
838
- return mcpError7(
866
+ case "delete": {
867
+ if (!flowId) {
868
+ return mcpError4(
839
869
  new Error(
840
- "Provide source (file path, URL, or flow ID) to load existing flow, or platform (web/server) to create a new one."
870
+ 'flowId is required for delete action. Use flow_manage with action "list" to see available flows.'
841
871
  )
842
872
  );
843
873
  }
844
- const skeleton = platform === "web" ? WEB_SKELETON : SERVER_SKELETON;
845
- return mcpResult7(skeleton, {
846
- next: [
847
- "Read walkeros://reference/flow-schema for config structure",
848
- "Use add-step prompt to add sources and destinations"
849
- ]
874
+ const resolvedSlug = await resolveDeploymentSlug({
875
+ projectId: projectId ?? "",
876
+ flowId,
877
+ slug,
878
+ list: listForResolver(client, projectId)
879
+ });
880
+ const data = await client.deleteDeployment({
881
+ slug: resolvedSlug,
882
+ projectId
883
+ });
884
+ return mcpResult4({
885
+ deleted: true,
886
+ ...data
850
887
  });
851
- } catch (error) {
852
- const msg = error instanceof Error ? error.message : "";
853
- if (msg.includes("not found") || msg.includes("ENOENT"))
854
- return mcpError7(
855
- error,
856
- "Check configPath \u2014 expected a flow.json file"
857
- );
858
- return mcpError7(error);
859
888
  }
889
+ default:
890
+ throw new Error(
891
+ `Unknown action: ${action}. Use one of: deploy, list, get, delete`
892
+ );
860
893
  }
894
+ } catch (error) {
895
+ return mcpError4(error, isAuthError(error) ? AUTH_HINT : void 0);
896
+ }
897
+ }
898
+ function registerDeployTool(server, client) {
899
+ const spec = createDeployManageToolSpec(client);
900
+ server.registerTool(
901
+ spec.name,
902
+ {
903
+ title: spec.title,
904
+ description: spec.description,
905
+ inputSchema: spec.inputSchema,
906
+ annotations: spec.annotations
907
+ },
908
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
909
+ // type-erased (input: unknown) => Promise<unknown> form by design.
910
+ spec.handler
861
911
  );
862
912
  }
863
913
 
864
914
  // src/tools/feedback.ts
865
- import { z as z8 } from "zod";
866
- import {
867
- feedback,
868
- getFeedbackPreference,
869
- setFeedbackPreference
870
- } from "@walkeros/cli";
871
- import { mcpResult as mcpResult8, mcpError as mcpError8 } from "@walkeros/core";
872
- function registerFeedbackTool(server2) {
873
- server2.registerTool(
874
- "feedback",
875
- {
876
- title: "Send Feedback",
877
- description: "Send feedback about walkerOS",
878
- inputSchema: {
879
- text: z8.string().describe("Your feedback text"),
880
- anonymous: z8.boolean().optional().describe(
881
- "Include user/project info? false = include, true = anonymous. Only needed on first call if not yet configured."
882
- )
883
- },
884
- annotations: {
885
- readOnlyHint: false,
886
- destructiveHint: false,
887
- idempotentHint: false,
888
- openWorldHint: true
889
- }
890
- },
891
- async (params) => {
892
- try {
893
- const { text, anonymous: explicitAnonymous } = params;
894
- let anonymous = getFeedbackPreference();
895
- if (anonymous === void 0 && explicitAnonymous === void 0) {
896
- return mcpResult8(
897
- { needsConsent: true },
898
- {
899
- next: [
900
- "Ask the user if they want to include their info",
901
- "Call feedback again with anonymous: true or false"
902
- ]
903
- }
904
- );
905
- }
906
- if (anonymous === void 0 && explicitAnonymous !== void 0) {
907
- anonymous = explicitAnonymous;
908
- setFeedbackPreference(anonymous);
915
+ import { z as z5 } from "zod";
916
+ import { mcpResult as mcpResult5, mcpError as mcpError5 } from "@walkeros/core";
917
+ var TITLE5 = "Send Feedback";
918
+ var DESCRIPTION5 = "Send feedback about walkerOS";
919
+ var inputSchema5 = {
920
+ text: z5.string().describe("Your feedback text"),
921
+ anonymous: z5.boolean().optional().describe(
922
+ "Include user/project info? false = include, true = anonymous. Only needed on first call if not yet configured."
923
+ )
924
+ };
925
+ var annotations5 = {
926
+ readOnlyHint: false,
927
+ destructiveHint: false,
928
+ idempotentHint: false,
929
+ openWorldHint: true
930
+ };
931
+ function createFeedbackToolSpec(client) {
932
+ return {
933
+ name: "feedback",
934
+ title: TITLE5,
935
+ description: DESCRIPTION5,
936
+ inputSchema: inputSchema5,
937
+ annotations: annotations5,
938
+ handler: (input) => feedbackHandlerBody(client, input)
939
+ };
940
+ }
941
+ async function feedbackHandlerBody(client, input) {
942
+ const { text, anonymous: explicitAnonymous } = input ?? {};
943
+ try {
944
+ let anonymous = client.getFeedbackPreference();
945
+ if (anonymous === void 0 && explicitAnonymous === void 0) {
946
+ return mcpResult5(
947
+ { needsConsent: true },
948
+ {
949
+ next: [
950
+ "Ask the user if they want to include their info",
951
+ "Call feedback again with anonymous: true or false"
952
+ ]
909
953
  }
910
- const isAnonymous = explicitAnonymous ?? anonymous ?? true;
911
- await feedback(text, { anonymous: isAnonymous, version: "3.4.2" });
912
- return mcpResult8({ ok: true });
913
- } catch (error) {
914
- return mcpError8(error);
915
- }
954
+ );
955
+ }
956
+ if (anonymous === void 0 && explicitAnonymous !== void 0) {
957
+ anonymous = explicitAnonymous;
958
+ client.setFeedbackPreference(anonymous);
916
959
  }
960
+ const isAnonymous = explicitAnonymous ?? anonymous ?? true;
961
+ await client.submitFeedback(text, {
962
+ anonymous: isAnonymous,
963
+ version: "4.0.0-next-1777463920154"
964
+ });
965
+ return mcpResult5({ ok: true });
966
+ } catch (error) {
967
+ return mcpError5(error);
968
+ }
969
+ }
970
+ function registerFeedbackTool(server, client) {
971
+ const spec = createFeedbackToolSpec(client);
972
+ server.registerTool(
973
+ spec.name,
974
+ {
975
+ title: spec.title,
976
+ description: spec.description,
977
+ inputSchema: spec.inputSchema,
978
+ annotations: spec.annotations
979
+ },
980
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
981
+ // type-erased (input: unknown) => Promise<unknown> form by design.
982
+ spec.handler
917
983
  );
918
984
  }
919
985
 
920
- // src/tools/auth.ts
921
- import { z as z9 } from "zod";
922
- import {
923
- resolveToken,
924
- deleteConfig,
925
- requestDeviceCode,
926
- pollForToken,
927
- whoami
928
- } from "@walkeros/cli";
929
- import { mcpResult as mcpResult9, mcpError as mcpError9 } from "@walkeros/core";
930
- function registerAuthTool(server2) {
931
- server2.registerTool(
932
- "auth",
986
+ // src/tools/validate.ts
987
+ import { validate } from "@walkeros/cli";
988
+ import { schemas } from "@walkeros/cli/dev";
989
+ import { mcpResult as mcpResult6, mcpError as mcpError6 } from "@walkeros/core";
990
+
991
+ // src/schemas/output.ts
992
+ import { z as z6 } from "zod";
993
+ var ValidateOutputShape = {
994
+ valid: z6.boolean().describe("Whether validation passed"),
995
+ type: z6.union([
996
+ z6.enum(["contract", "entry", "event", "flow", "mapping"]),
997
+ z6.string().regex(/^(destinations|sources|transformers)\.\w+$/)
998
+ ]).describe("What was validated"),
999
+ errors: z6.array(
1000
+ z6.object({
1001
+ path: z6.string(),
1002
+ message: z6.string(),
1003
+ value: z6.unknown().optional(),
1004
+ code: z6.string().optional()
1005
+ })
1006
+ ).describe("Validation errors"),
1007
+ warnings: z6.array(
1008
+ z6.object({
1009
+ path: z6.string(),
1010
+ message: z6.string(),
1011
+ suggestion: z6.string().optional()
1012
+ })
1013
+ ).describe("Validation warnings"),
1014
+ details: z6.record(z6.string(), z6.unknown()).describe("Additional validation details")
1015
+ };
1016
+ var BundleOutputShape = {
1017
+ success: z6.boolean().describe("Whether bundling succeeded"),
1018
+ totalSize: z6.number().optional().describe("Total bundle size in bytes"),
1019
+ buildTime: z6.number().optional().describe("Build time in milliseconds"),
1020
+ packages: z6.array(
1021
+ z6.object({
1022
+ name: z6.string(),
1023
+ size: z6.number()
1024
+ })
1025
+ ).optional().describe("Per-package size breakdown"),
1026
+ treeshakingEffective: z6.boolean().optional().describe("Whether tree-shaking was effective"),
1027
+ message: z6.string().optional().describe("Status message")
1028
+ };
1029
+ var SimulateOutputShape = {
1030
+ success: z6.boolean().describe("Whether simulation succeeded"),
1031
+ error: z6.string().optional().describe("Error message if failed"),
1032
+ summary: z6.string().describe("One-line result summary"),
1033
+ destinations: z6.record(
1034
+ z6.string(),
1035
+ z6.object({
1036
+ received: z6.boolean().describe("Whether destination received the event"),
1037
+ calls: z6.number().describe("Number of API calls made"),
1038
+ payload: z6.unknown().optional().describe("All intercepted API calls (only when verbose: true)")
1039
+ })
1040
+ ).optional().describe("Per-destination results"),
1041
+ capturedEvents: z6.array(z6.record(z6.string(), z6.unknown())).optional().describe("Events captured by source simulation"),
1042
+ duration: z6.number().optional().describe("Simulation duration in ms")
1043
+ };
1044
+ var PushOutputShape = {
1045
+ success: z6.boolean().describe("Whether push succeeded"),
1046
+ elbResult: z6.unknown().optional().describe("Push result from the collector"),
1047
+ duration: z6.number().describe("Push duration in milliseconds"),
1048
+ error: z6.string().optional().describe("Error message if push failed")
1049
+ };
1050
+ var ExamplesListOutputShape = {
1051
+ flow: z6.string().describe("Flow name"),
1052
+ count: z6.number().describe("Number of examples found"),
1053
+ examples: z6.array(
1054
+ z6.object({
1055
+ step: z6.string().describe('Step location (e.g., "destination.gtag")'),
1056
+ stepType: z6.enum(["source", "transformer", "destination"]).describe("Step type"),
1057
+ stepName: z6.string().describe("Step name"),
1058
+ exampleName: z6.string().describe("Example name"),
1059
+ title: z6.string().optional().describe("Human-readable title if set"),
1060
+ description: z6.string().optional().describe("Short human-readable description"),
1061
+ public: z6.boolean().optional().describe(
1062
+ "Whether the example is public (defaults to true if omitted)"
1063
+ ),
1064
+ hasIn: z6.boolean().describe("Whether the example has an input value"),
1065
+ hasOut: z6.boolean().describe("Whether the example has an output value"),
1066
+ hasMapping: z6.boolean().describe("Whether the example has a mapping configuration"),
1067
+ hasTrigger: z6.boolean().describe("Whether the example has trigger metadata"),
1068
+ in: z6.unknown().optional().describe("Input event data"),
1069
+ out: z6.unknown().optional().describe("Expected output data"),
1070
+ mapping: z6.unknown().optional().describe("Mapping configuration for destinations"),
1071
+ trigger: z6.object({
1072
+ type: z6.string().optional(),
1073
+ options: z6.unknown().optional()
1074
+ }).optional().describe("Trigger metadata for source simulation")
1075
+ })
1076
+ ).describe("Step examples")
1077
+ };
1078
+
1079
+ // src/tools/validate.ts
1080
+ function wrapIssueMessages(result) {
1081
+ return {
1082
+ ...result,
1083
+ errors: result.errors.map((e) => ({
1084
+ ...e,
1085
+ message: wrapUserData(e.message)
1086
+ })),
1087
+ warnings: result.warnings.map((w) => ({
1088
+ ...w,
1089
+ message: wrapUserData(w.message)
1090
+ }))
1091
+ };
1092
+ }
1093
+ var TITLE6 = "Validate Flow";
1094
+ var DESCRIPTION6 = "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.";
1095
+ var inputSchema6 = schemas.ValidateInputShape;
1096
+ var annotations6 = {
1097
+ readOnlyHint: true,
1098
+ destructiveHint: false,
1099
+ idempotentHint: true,
1100
+ openWorldHint: false
1101
+ };
1102
+ function createFlowValidateToolSpec() {
1103
+ return {
1104
+ name: "flow_validate",
1105
+ title: TITLE6,
1106
+ description: DESCRIPTION6,
1107
+ inputSchema: inputSchema6,
1108
+ annotations: annotations6,
1109
+ handler: (input) => flowValidateHandlerBody(input)
1110
+ };
1111
+ }
1112
+ async function flowValidateHandlerBody(input) {
1113
+ const {
1114
+ type,
1115
+ input: validateInput,
1116
+ flow,
1117
+ path
1118
+ } = input ?? {};
1119
+ try {
1120
+ const result = await validate(type, validateInput, {
1121
+ flow,
1122
+ path
1123
+ });
1124
+ const hints = result.valid ? {
1125
+ next: [
1126
+ "Use flow_simulate to test event flow",
1127
+ "Use flow_bundle to build"
1128
+ ]
1129
+ } : {
1130
+ next: [
1131
+ "Fix errors above, then run flow_validate again",
1132
+ "Read walkeros://reference/flow-schema for correct structure"
1133
+ ]
1134
+ };
1135
+ return mcpResult6(wrapIssueMessages(result), hints);
1136
+ } catch (error) {
1137
+ return mcpError6(
1138
+ error,
1139
+ "Check the input parameter \u2014 expected a JSON string, file path, or URL"
1140
+ );
1141
+ }
1142
+ }
1143
+ function registerFlowValidateTool(server) {
1144
+ const spec = createFlowValidateToolSpec();
1145
+ server.registerTool(
1146
+ spec.name,
933
1147
  {
934
- title: "Authentication",
935
- description: "Manage walkerOS authentication. Check login status, log in via device code flow, or log out. No terminal or browser required \u2014 the MCP client handles the authorization URL.",
936
- inputSchema: {
937
- action: z9.enum(["status", "login", "logout"]).describe("Authentication action to perform"),
938
- deviceCode: z9.string().optional().describe(
939
- "Device code from a previous pending login attempt. Provide to resume polling without requesting a new code."
940
- )
941
- },
942
- // No outputSchema: action-dispatched tool each action returns a different shape
943
- annotations: {
944
- readOnlyHint: false,
945
- destructiveHint: true,
946
- idempotentHint: false,
947
- openWorldHint: true
1148
+ title: spec.title,
1149
+ description: spec.description,
1150
+ inputSchema: spec.inputSchema,
1151
+ // outputSchema is wire-only; not part of ToolSpec
1152
+ outputSchema: ValidateOutputShape,
1153
+ annotations: spec.annotations
1154
+ },
1155
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
1156
+ // type-erased (input: unknown) => Promise<unknown> form by design.
1157
+ spec.handler
1158
+ );
1159
+ }
1160
+
1161
+ // src/tools/bundle.ts
1162
+ import { bundle } from "@walkeros/cli";
1163
+ import { schemas as schemas2 } from "@walkeros/cli/dev";
1164
+ import { mcpResult as mcpResult7, mcpError as mcpError7 } from "@walkeros/core";
1165
+ var TITLE7 = "Bundle Flow";
1166
+ var DESCRIPTION7 = "Bundle a walkerOS flow configuration into deployable JavaScript. Resolves all destinations, sources, and transformers, then outputs a tree-shaken production bundle. Returns bundle statistics.";
1167
+ var inputSchema7 = {
1168
+ ...schemas2.BundleInputShape
1169
+ };
1170
+ var annotations7 = {
1171
+ readOnlyHint: false,
1172
+ destructiveHint: false,
1173
+ idempotentHint: false,
1174
+ openWorldHint: true
1175
+ };
1176
+ function createFlowBundleToolSpec() {
1177
+ return {
1178
+ name: "flow_bundle",
1179
+ title: TITLE7,
1180
+ description: DESCRIPTION7,
1181
+ inputSchema: inputSchema7,
1182
+ annotations: annotations7,
1183
+ handler: (input) => flowBundleHandlerBody(input)
1184
+ };
1185
+ }
1186
+ async function flowBundleHandlerBody(input) {
1187
+ const { configPath, flow, stats, output } = input ?? {};
1188
+ try {
1189
+ const result = await bundle(configPath, {
1190
+ flowName: flow,
1191
+ stats: stats ?? true,
1192
+ buildOverrides: output ? { output } : void 0
1193
+ });
1194
+ if (!result) {
1195
+ return mcpResult7(
1196
+ { success: false, message: "Bundle produced no output" },
1197
+ {
1198
+ warnings: [
1199
+ "The build returned no result. The flow may be empty or misconfigured."
1200
+ ],
1201
+ next: ["Run flow_validate to check your configuration"]
1202
+ }
1203
+ );
1204
+ }
1205
+ const output_ = result;
1206
+ return mcpResult7(
1207
+ { success: true, ...output_ },
1208
+ {
1209
+ next: [
1210
+ "Use flow_simulate to test",
1211
+ 'Use deploy_manage with action "deploy" to publish'
1212
+ ]
948
1213
  }
1214
+ );
1215
+ } catch (error) {
1216
+ return mcpError7(error, "Run flow_validate for detailed error messages");
1217
+ }
1218
+ }
1219
+ function registerFlowBundleTool(server) {
1220
+ const spec = createFlowBundleToolSpec();
1221
+ server.registerTool(
1222
+ spec.name,
1223
+ {
1224
+ title: spec.title,
1225
+ description: spec.description,
1226
+ inputSchema: spec.inputSchema,
1227
+ // outputSchema is wire-only; not part of ToolSpec
1228
+ outputSchema: BundleOutputShape,
1229
+ annotations: spec.annotations
949
1230
  },
950
- async ({ action, deviceCode }) => {
1231
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
1232
+ // type-erased (input: unknown) => Promise<unknown> form by design.
1233
+ spec.handler
1234
+ );
1235
+ }
1236
+
1237
+ // src/tools/simulate.ts
1238
+ import { z as z7 } from "zod";
1239
+ import {
1240
+ simulateSource,
1241
+ simulateTransformer,
1242
+ simulateDestination
1243
+ } from "@walkeros/cli";
1244
+ import { schemas as schemas3 } from "@walkeros/cli/dev";
1245
+ import { mcpResult as mcpResult8, mcpError as mcpError8 } from "@walkeros/core";
1246
+ var TITLE8 = "Simulate Flow";
1247
+ var DESCRIPTION8 = 'Simulate events through a walkerOS flow without making real API calls. For destinations: event is a walkerOS event { name: "entity action", data: {...} }. For sources: event is { content: ..., trigger?: { type?, options? }, env?: {...} }. Use step to target a specific step. Use flow_examples to discover available test data. IMPORTANT: Destinations with require (e.g. require: ["consent"]) stay pending until that collector event fires \u2014 simulation will error "not found" if require is not satisfied. Remove require from config or provide consent/user events before simulating. Separately, destinations with consent (e.g. consent: { marketing: true }) only receive events where the event includes matching consent. Mapping transforms event names and data at the destination level. Policy redacts or injects fields before mapping runs.';
1248
+ var inputSchema8 = {
1249
+ configPath: schemas3.SimulateInputShape.configPath,
1250
+ event: z7.union([z7.record(z7.string(), z7.unknown()), z7.string()]).optional().describe(
1251
+ "For destinations: { name, data, consent? }. Include consent (e.g. { marketing: true }) to satisfy destination consent requirements. For sources: { content, trigger?, env? }. Can also be a JSON string or file path."
1252
+ ),
1253
+ flow: schemas3.SimulateInputShape.flow,
1254
+ platform: schemas3.SimulateInputShape.platform,
1255
+ step: schemas3.SimulateInputShape.step,
1256
+ verbose: z7.boolean().optional().describe("Include full payload per destination (default: false)")
1257
+ };
1258
+ var annotations8 = {
1259
+ readOnlyHint: true,
1260
+ destructiveHint: false,
1261
+ idempotentHint: true,
1262
+ openWorldHint: false
1263
+ };
1264
+ function createFlowSimulateToolSpec() {
1265
+ return {
1266
+ name: "flow_simulate",
1267
+ title: TITLE8,
1268
+ description: DESCRIPTION8,
1269
+ inputSchema: inputSchema8,
1270
+ annotations: annotations8,
1271
+ handler: (input) => flowSimulateHandlerBody(input)
1272
+ };
1273
+ }
1274
+ async function flowSimulateHandlerBody(input) {
1275
+ const { configPath, event, flow, platform, step, verbose } = input ?? {};
1276
+ try {
1277
+ if (!event) {
1278
+ throw new Error(
1279
+ "event is required. For sources provide { content, trigger? }, for destinations provide { name, data }."
1280
+ );
1281
+ }
1282
+ if (!step) {
1283
+ throw new Error(
1284
+ 'step is required. Specify a target like "source.browser", "destination.gtag", or "transformer.demo".'
1285
+ );
1286
+ }
1287
+ let resolvedEvent = event;
1288
+ if (typeof event === "string") {
951
1289
  try {
952
- switch (action) {
953
- case "status": {
954
- const resolved = resolveToken();
955
- if (!resolved) {
956
- return mcpResult9(
957
- { authenticated: false },
958
- { next: ['Use auth with action "login" to authenticate'] }
959
- );
960
- }
961
- const user = await whoami();
962
- return mcpResult9({ authenticated: true, ...user });
963
- }
964
- case "login": {
965
- if (deviceCode) {
966
- const pollResult = await pollForToken(deviceCode, {
967
- timeoutMs: 6e4
968
- });
969
- if (pollResult.success) {
970
- return mcpResult9(
971
- { authenticated: true, email: pollResult.email },
972
- {
973
- next: [
974
- 'Use project_manage with action "list" to see your projects'
975
- ]
976
- }
977
- );
978
- }
979
- if (pollResult.status === "pending") {
980
- return mcpResult9({
981
- authenticated: false,
982
- status: "pending",
983
- message: "Still waiting for authorization. Try again shortly.",
984
- deviceCode
985
- });
986
- }
987
- return mcpError9(
988
- new Error(pollResult.error || "Authorization failed")
989
- );
990
- }
991
- const code = await requestDeviceCode();
992
- const loginUrl = code.verificationUriComplete || code.verificationUri;
993
- return mcpResult9({
994
- authenticated: false,
995
- status: "awaiting_authorization",
996
- loginUrl,
997
- message: `Open this link to authorize: ${loginUrl}`,
998
- deviceCode: code.deviceCode
999
- });
1290
+ resolvedEvent = JSON.parse(event);
1291
+ } catch {
1292
+ throw new Error(
1293
+ "Event string must be valid JSON. Got: " + event.substring(0, 50)
1294
+ );
1295
+ }
1296
+ }
1297
+ const dotIndex = step.indexOf(".");
1298
+ if (dotIndex === -1) {
1299
+ throw new Error(
1300
+ `Invalid step format "${step}". Use "type.name" (e.g. "source.browser", "destination.gtag").`
1301
+ );
1302
+ }
1303
+ const stepType = step.substring(0, dotIndex);
1304
+ const stepId = step.substring(dotIndex + 1);
1305
+ let result;
1306
+ switch (stepType) {
1307
+ case "source":
1308
+ result = await simulateSource(configPath, resolvedEvent, {
1309
+ sourceId: stepId,
1310
+ flow,
1311
+ silent: true
1312
+ });
1313
+ break;
1314
+ case "transformer":
1315
+ result = await simulateTransformer(
1316
+ configPath,
1317
+ resolvedEvent,
1318
+ {
1319
+ transformerId: stepId,
1320
+ flow,
1321
+ silent: true
1000
1322
  }
1001
- case "logout": {
1002
- const deleted = deleteConfig();
1003
- const hadEnvToken = typeof process.env.WALKEROS_TOKEN === "string" && process.env.WALKEROS_TOKEN.length > 0;
1004
- delete process.env.WALKEROS_TOKEN;
1005
- let message;
1006
- if (deleted && hadEnvToken) {
1007
- message = "Logged out. Config removed and WALKEROS_TOKEN cleared from process environment.";
1008
- } else if (deleted) {
1009
- message = "Logged out and config removed.";
1010
- } else if (hadEnvToken) {
1011
- message = "No config found. WALKEROS_TOKEN cleared from process environment.";
1012
- } else {
1013
- message = "No config found \u2014 already logged out.";
1014
- }
1015
- return mcpResult9({
1016
- loggedOut: true,
1017
- message
1018
- });
1323
+ );
1324
+ break;
1325
+ case "destination":
1326
+ result = await simulateDestination(
1327
+ configPath,
1328
+ resolvedEvent,
1329
+ {
1330
+ destinationId: stepId,
1331
+ flow,
1332
+ silent: true
1019
1333
  }
1020
- default:
1021
- throw new Error(
1022
- `Unknown action: ${action}. Use one of: status, login, logout`
1023
- );
1334
+ );
1335
+ break;
1336
+ default:
1337
+ throw new Error(
1338
+ `Unknown step type "${stepType}". Use "source", "transformer", or "destination".`
1339
+ );
1340
+ }
1341
+ if (result.captured && result.captured.length > 0) {
1342
+ const eventCount = result.captured.length;
1343
+ const summary2 = `Source captured ${eventCount} event${eventCount !== 1 ? "s" : ""}`;
1344
+ return mcpResult8(
1345
+ {
1346
+ success: result.success,
1347
+ error: result.error,
1348
+ summary: summary2,
1349
+ capturedEvents: result.captured,
1350
+ duration: result.duration
1351
+ },
1352
+ {
1353
+ next: eventCount > 0 ? [
1354
+ "Use flow_simulate with a destination step to test downstream processing"
1355
+ ] : [
1356
+ "Check source package examples with package_get, verify trigger type matches"
1357
+ ]
1358
+ }
1359
+ );
1360
+ }
1361
+ const destinations = {};
1362
+ if (result.elbResult && typeof result.elbResult === "object" && "done" in result.elbResult && result.elbResult.done) {
1363
+ const done = result.elbResult.done;
1364
+ for (const name of Object.keys(done)) {
1365
+ destinations[name] = { received: true, calls: 0 };
1366
+ }
1367
+ }
1368
+ if (result.usage) {
1369
+ for (const [name, calls] of Object.entries(result.usage)) {
1370
+ const summary2 = {
1371
+ received: calls.length > 0,
1372
+ calls: calls.length
1373
+ };
1374
+ if (verbose && calls.length > 0) {
1375
+ summary2.payload = calls;
1024
1376
  }
1025
- } catch (error) {
1026
- return mcpError9(error);
1377
+ destinations[name] = summary2;
1027
1378
  }
1028
1379
  }
1380
+ const destCount = Object.keys(destinations).length;
1381
+ const receivedCount = Object.values(destinations).filter(
1382
+ (d) => d.received
1383
+ ).length;
1384
+ const warnings = [];
1385
+ if (stepType === "destination" && destCount === 0) {
1386
+ warnings.push(
1387
+ 'Destination did not receive the event. Common causes: (1) destination config has consent: { marketing: true } but event lacks matching consent, (2) mapping rules do not match the event name, (3) policy redacted required fields. Add consent to the event: { name: "...", data: {...}, consent: { marketing: true } }.'
1388
+ );
1389
+ }
1390
+ const summary = stepType === "transformer" ? `Transformer processed event` : `${receivedCount}/${destCount} destinations received the event`;
1391
+ const resultObj = {
1392
+ success: result.success,
1393
+ error: result.error,
1394
+ summary,
1395
+ destinations: destCount > 0 ? destinations : void 0,
1396
+ duration: result.duration
1397
+ };
1398
+ return mcpResult8(resultObj, {
1399
+ next: ["Use flow_bundle to build for production"],
1400
+ ...warnings.length > 0 ? { warnings } : {}
1401
+ });
1402
+ } catch (error) {
1403
+ const msg = error instanceof Error ? error.message : "";
1404
+ let hint = "Run flow_validate for detailed error messages";
1405
+ if (msg.includes("not found in collector")) {
1406
+ 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.';
1407
+ }
1408
+ return mcpError8(error, hint);
1409
+ }
1410
+ }
1411
+ function registerFlowSimulateTool(server) {
1412
+ const spec = createFlowSimulateToolSpec();
1413
+ server.registerTool(
1414
+ spec.name,
1415
+ {
1416
+ title: spec.title,
1417
+ description: spec.description,
1418
+ inputSchema: spec.inputSchema,
1419
+ // outputSchema is wire-only; not part of ToolSpec
1420
+ outputSchema: SimulateOutputShape,
1421
+ annotations: spec.annotations
1422
+ },
1423
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
1424
+ // type-erased (input: unknown) => Promise<unknown> form by design.
1425
+ spec.handler
1029
1426
  );
1030
1427
  }
1031
1428
 
1032
- // src/tools/project-manage.ts
1033
- import { z as z10 } from "zod";
1034
- import {
1035
- listProjects,
1036
- getProject,
1037
- createProject,
1038
- updateProject,
1039
- deleteProject,
1040
- setDefaultProject
1041
- } from "@walkeros/cli";
1042
- import { mcpResult as mcpResult10, mcpError as mcpError10 } from "@walkeros/core";
1043
-
1044
- // src/types.ts
1045
- function isAuthError(error) {
1046
- if (!(error instanceof Error)) return false;
1047
- const msg = error.message.toLowerCase();
1048
- if (msg.includes("unauthorized") || msg.includes("forbidden") || msg.includes("invalid token") || msg.includes("token expired") || msg.includes("not authenticated")) {
1049
- return true;
1429
+ // src/tools/push.ts
1430
+ import { z as z8 } from "zod";
1431
+ import { push } from "@walkeros/cli";
1432
+ import { schemas as schemas4 } from "@walkeros/cli/dev";
1433
+ import { mcpResult as mcpResult9, mcpError as mcpError9 } from "@walkeros/core";
1434
+ var TITLE9 = "Push Events";
1435
+ var DESCRIPTION9 = "Push a real event through a walkerOS flow to actual destinations. Makes real API calls to real endpoints. Best suited for server-side flows \u2014 web flows should use flow_simulate for testing.";
1436
+ var inputSchema9 = {
1437
+ configPath: schemas4.PushInputShape.configPath,
1438
+ event: z8.record(z8.string(), z8.unknown()).describe(
1439
+ 'Event object, e.g. { name: "page view", data: { title: "Home" } }'
1440
+ ),
1441
+ flow: schemas4.PushInputShape.flow,
1442
+ platform: schemas4.PushInputShape.platform
1443
+ };
1444
+ var annotations9 = {
1445
+ readOnlyHint: false,
1446
+ destructiveHint: true,
1447
+ idempotentHint: false,
1448
+ openWorldHint: true
1449
+ };
1450
+ function createFlowPushToolSpec() {
1451
+ return {
1452
+ name: "flow_push",
1453
+ title: TITLE9,
1454
+ description: DESCRIPTION9,
1455
+ inputSchema: inputSchema9,
1456
+ annotations: annotations9,
1457
+ handler: (input) => flowPushHandlerBody(input)
1458
+ };
1459
+ }
1460
+ async function flowPushHandlerBody(input) {
1461
+ const { configPath, event, flow, platform } = input ?? {};
1462
+ try {
1463
+ const result = await push(configPath, event, {
1464
+ json: true,
1465
+ flow,
1466
+ platform
1467
+ });
1468
+ if (!result.success) {
1469
+ return mcpError9(
1470
+ new Error(result.error || "Push failed"),
1471
+ "Check destination configuration and connectivity."
1472
+ );
1473
+ }
1474
+ return mcpResult9(result);
1475
+ } catch (error) {
1476
+ return mcpError9(
1477
+ error,
1478
+ "Check configPath and event format. For web flows, use flow_simulate."
1479
+ );
1050
1480
  }
1051
- const code = error.code;
1052
- if (!code) return false;
1053
- const upperCode = code.toUpperCase();
1054
- return upperCode === "UNAUTHORIZED" || upperCode === "FORBIDDEN" || upperCode === "401" || upperCode === "403" || upperCode.startsWith("AUTH_");
1055
1481
  }
1056
- var AUTH_HINT = 'Are you logged in? Use auth(action: "status") to check.';
1057
-
1058
- // src/tools/project-manage.ts
1059
- function registerProjectManageTool(server2) {
1060
- server2.registerTool(
1061
- "project_manage",
1482
+ function registerFlowPushTool(server) {
1483
+ const spec = createFlowPushToolSpec();
1484
+ server.registerTool(
1485
+ spec.name,
1062
1486
  {
1063
- title: "Project Management",
1064
- description: "Manage walkerOS projects. List, create, update, delete projects, or set a default project for CLI operations.",
1065
- inputSchema: {
1066
- action: z10.enum(["list", "get", "create", "update", "delete", "set_default"]).describe("Project management action to perform"),
1067
- projectId: z10.string().optional().describe(
1068
- "Project ID. Required for get, update, delete, and set_default actions."
1069
- ),
1070
- name: z10.string().optional().describe(
1071
- "Project name. Required for create. Optional for update (to rename)."
1072
- )
1073
- },
1074
- // No outputSchema: action-dispatched tool — each action returns a different shape
1075
- annotations: {
1076
- readOnlyHint: false,
1077
- destructiveHint: true,
1078
- idempotentHint: false,
1079
- openWorldHint: true
1080
- }
1487
+ title: spec.title,
1488
+ description: spec.description,
1489
+ inputSchema: spec.inputSchema,
1490
+ // outputSchema is wire-only; not part of ToolSpec
1491
+ outputSchema: PushOutputShape,
1492
+ annotations: spec.annotations
1081
1493
  },
1082
- async ({ action, projectId, name }) => {
1083
- try {
1084
- switch (action) {
1085
- case "list": {
1086
- const projects = await listProjects();
1087
- const items = Array.isArray(projects) ? projects : projects?.projects || [];
1088
- if (items.length === 0) {
1089
- return mcpResult10(
1090
- { projects: [] },
1091
- {
1092
- next: [
1093
- 'Use project_manage with action "create" to create your first project',
1094
- 'Use project_manage with action "set_default" after creating to set it as default'
1095
- ]
1096
- }
1097
- );
1098
- }
1099
- return mcpResult10(projects);
1100
- }
1101
- case "get": {
1102
- if (!projectId) {
1103
- return mcpError10(
1104
- new Error(
1105
- 'projectId is required for get action. Use action "list" to see available projects.'
1106
- )
1107
- );
1108
- }
1109
- const project = await getProject({ projectId });
1110
- return mcpResult10(project);
1111
- }
1112
- case "create": {
1113
- if (!name) {
1114
- return mcpError10(new Error("name is required for create action."));
1115
- }
1116
- const created = await createProject({ name });
1117
- return mcpResult10(created, {
1118
- next: [
1119
- 'Use project_manage with action "set_default" to make this your active project'
1120
- ]
1121
- });
1122
- }
1123
- case "update": {
1124
- if (!projectId) {
1125
- return mcpError10(
1126
- new Error(
1127
- 'projectId is required for update action. Use action "list" to see available projects.'
1128
- )
1129
- );
1130
- }
1131
- if (!name) {
1132
- return mcpError10(new Error("name is required for update action."));
1133
- }
1134
- const updated = await updateProject({ projectId, name });
1135
- return mcpResult10(updated);
1136
- }
1137
- case "delete": {
1138
- if (!projectId) {
1139
- return mcpError10(
1140
- new Error(
1141
- 'projectId is required for delete action. Use action "list" to see available projects.'
1142
- )
1143
- );
1144
- }
1145
- const deleted = await deleteProject({ projectId });
1146
- return mcpResult10(deleted);
1147
- }
1148
- case "set_default": {
1149
- if (!projectId) {
1150
- return mcpError10(
1151
- new Error(
1152
- 'projectId is required for set_default action. Use action "list" to see available projects.'
1153
- )
1154
- );
1155
- }
1156
- setDefaultProject(projectId);
1157
- return mcpResult10(
1158
- { defaultProjectId: projectId },
1159
- {
1160
- next: [
1161
- 'Use flow_manage with action "list" to see flows in this project'
1162
- ]
1163
- }
1164
- );
1165
- }
1166
- default:
1167
- throw new Error(
1168
- `Unknown action: ${action}. Use one of: list, get, create, update, delete, set_default`
1169
- );
1494
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
1495
+ // type-erased (input: unknown) => Promise<unknown> form by design.
1496
+ spec.handler
1497
+ );
1498
+ }
1499
+
1500
+ // src/tools/examples.ts
1501
+ import { z as z9 } from "zod";
1502
+ import { loadJsonConfig } from "@walkeros/cli";
1503
+ import { mcpResult as mcpResult10, mcpError as mcpError10 } from "@walkeros/core";
1504
+ var TITLE10 = "Flow Examples";
1505
+ var DESCRIPTION10 = "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.";
1506
+ var inputSchema10 = {
1507
+ configPath: z9.string().min(1).describe("Path to flow configuration file, URL, or inline JSON string"),
1508
+ flow: z9.string().optional().describe("Flow name for multi-flow configs"),
1509
+ step: z9.string().optional().describe('Filter to a specific step (e.g., "destination.gtag")'),
1510
+ full: z9.boolean().optional().describe(
1511
+ "Return full in/out/mapping data for each example (default: false, returns metadata only)"
1512
+ ),
1513
+ includeHidden: z9.boolean().optional().describe(
1514
+ "Include examples marked public: false (default: false). Set true for test/debug discovery."
1515
+ )
1516
+ };
1517
+ var annotations10 = {
1518
+ readOnlyHint: true,
1519
+ destructiveHint: false,
1520
+ idempotentHint: true,
1521
+ openWorldHint: false
1522
+ };
1523
+ function createFlowExamplesToolSpec() {
1524
+ return {
1525
+ name: "flow_examples",
1526
+ title: TITLE10,
1527
+ description: DESCRIPTION10,
1528
+ inputSchema: inputSchema10,
1529
+ annotations: annotations10,
1530
+ handler: (input) => flowExamplesHandlerBody(input)
1531
+ };
1532
+ }
1533
+ async function flowExamplesHandlerBody(input) {
1534
+ const { configPath, flow, step, full, includeHidden } = input ?? {};
1535
+ try {
1536
+ const rawConfig = await loadJsonConfig(configPath);
1537
+ const flowNames = Object.keys(rawConfig.flows || {});
1538
+ const flowName = flow || (flowNames.length === 1 ? flowNames[0] : void 0);
1539
+ if (!flowName) {
1540
+ throw new Error(
1541
+ `Multiple flows found. Specify flow parameter. Available: ${flowNames.join(", ")}`
1542
+ );
1543
+ }
1544
+ const flowSettings = rawConfig.flows[flowName];
1545
+ if (!flowSettings) {
1546
+ throw new Error(`Flow "${flowName}" not found`);
1547
+ }
1548
+ const examples = [];
1549
+ const stepTypes = [
1550
+ { key: "sources", type: "source" },
1551
+ { key: "transformers", type: "transformer" },
1552
+ { key: "destinations", type: "destination" }
1553
+ ];
1554
+ for (const { key, type } of stepTypes) {
1555
+ const refs = flowSettings[key] || {};
1556
+ for (const [name, ref] of Object.entries(refs)) {
1557
+ if (!ref.examples) continue;
1558
+ if (step && `${type}.${name}` !== step) continue;
1559
+ for (const [exName, ex] of Object.entries(
1560
+ ref.examples
1561
+ )) {
1562
+ if (!includeHidden && ex.public === false) continue;
1563
+ examples.push({
1564
+ step: `${type}.${name}`,
1565
+ stepType: type,
1566
+ stepName: name,
1567
+ exampleName: exName,
1568
+ title: ex.title,
1569
+ description: ex.description,
1570
+ public: ex.public,
1571
+ hasIn: ex.in !== void 0,
1572
+ hasOut: ex.out !== void 0,
1573
+ hasMapping: ex.mapping !== void 0,
1574
+ hasTrigger: ex.trigger !== void 0,
1575
+ ...full ? {
1576
+ in: ex.in,
1577
+ out: ex.out,
1578
+ mapping: ex.mapping,
1579
+ trigger: ex.trigger
1580
+ } : {}
1581
+ });
1170
1582
  }
1171
- } catch (error) {
1172
- return mcpError10(error, isAuthError(error) ? AUTH_HINT : void 0);
1173
1583
  }
1174
1584
  }
1585
+ const result = {
1586
+ flow: flowName,
1587
+ count: examples.length,
1588
+ examples
1589
+ };
1590
+ const hints = {
1591
+ next: ["Use flow_simulate with step and event to simulate"]
1592
+ };
1593
+ if (examples.length === 0) {
1594
+ hints.warnings = [
1595
+ "No examples found. Add examples to step definitions in your flow config for testing."
1596
+ ];
1597
+ }
1598
+ return mcpResult10(result, hints);
1599
+ } catch (error) {
1600
+ return mcpError10(error, "Check configPath \u2014 expected a flow.json file");
1601
+ }
1602
+ }
1603
+ function registerFlowExamplesTool(server) {
1604
+ const spec = createFlowExamplesToolSpec();
1605
+ server.registerTool(
1606
+ spec.name,
1607
+ {
1608
+ title: spec.title,
1609
+ description: spec.description,
1610
+ inputSchema: spec.inputSchema,
1611
+ // outputSchema is wire-only; not part of ToolSpec
1612
+ outputSchema: ExamplesListOutputShape,
1613
+ annotations: spec.annotations
1614
+ },
1615
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
1616
+ // type-erased (input: unknown) => Promise<unknown> form by design.
1617
+ spec.handler
1175
1618
  );
1176
1619
  }
1177
1620
 
1178
- // src/tools/flow-manage.ts
1179
- import { z as z11 } from "zod";
1180
- import {
1181
- listAllFlows,
1182
- listFlows,
1183
- getFlow,
1184
- createFlow,
1185
- updateFlow,
1186
- deleteFlow,
1187
- duplicateFlow,
1188
- listPreviews,
1189
- getPreview,
1190
- createPreview,
1191
- deletePreview
1192
- } from "@walkeros/cli";
1621
+ // src/tools/flow-load.ts
1622
+ import { z as z10 } from "zod";
1623
+ import { loadJsonConfig as loadJsonConfig2 } from "@walkeros/cli";
1193
1624
  import { mcpResult as mcpResult11, mcpError as mcpError11 } from "@walkeros/core";
1194
- function registerFlowManageTool(server2) {
1195
- server2.registerTool(
1196
- "flow_manage",
1197
- {
1198
- title: "Flow Management",
1199
- description: "Manage walkerOS flows and their previews. List/get/create/update/delete/duplicate flows, or create/inspect/delete preview bundles for testing flow changes on live sites.",
1200
- inputSchema: {
1201
- action: z11.enum([
1202
- "list",
1203
- "get",
1204
- "create",
1205
- "update",
1206
- "delete",
1207
- "duplicate",
1208
- "preview_list",
1209
- "preview_get",
1210
- "preview_create",
1211
- "preview_delete"
1212
- ]).describe("Flow management action to perform"),
1213
- flowId: z11.string().optional().describe(
1214
- "Flow ID (flow_...) or config ID (cfg_...). Required for get, update, delete, duplicate."
1215
- ),
1216
- projectId: z11.string().optional().describe(
1217
- "Project ID. Optional filter for list (omit to list all projects). Required for create if no default project set."
1218
- ),
1219
- name: z11.string().optional().describe(
1220
- "Flow name. Required for create. Optional for update (to rename) and duplicate."
1221
- ),
1222
- content: z11.record(z11.string(), z11.unknown()).optional().describe("Flow.Config JSON content. Used for create and update."),
1223
- patch: z11.boolean().optional().describe(
1224
- "Merge-patch for update (default true). When true, only provided fields are updated."
1225
- ),
1226
- fields: z11.array(z11.string()).optional().describe(
1227
- "Dot-path selectors for get to return only specific fields."
1228
- ),
1229
- sort: z11.enum(["name", "updated_at", "created_at"]).optional().describe("Sort field for list."),
1230
- order: z11.enum(["asc", "desc"]).optional().describe("Sort order for list."),
1231
- includeDeleted: z11.boolean().optional().describe("Include soft-deleted flows in list results."),
1232
- previewId: z11.string().optional().describe(
1233
- "Preview ID (prv_...). Required for preview_get and preview_delete."
1234
- ),
1235
- flowName: z11.string().optional().describe(
1236
- "Flow settings name. Used by preview_create as an alternative to flowSettingsId."
1237
- ),
1238
- flowSettingsId: z11.string().optional().describe(
1239
- "Flow settings ID. Used by preview_create as an alternative to flowName."
1240
- ),
1241
- siteUrl: z11.string().optional().describe(
1242
- "Optional site URL for preview_create. When provided, the response includes full activationUrl and deactivationUrl the user can click."
1625
+ var KEEP_LITERAL2 = /* @__PURE__ */ new Set([
1626
+ "id",
1627
+ "flowId",
1628
+ "projectId",
1629
+ "version",
1630
+ "slug",
1631
+ "createdAt",
1632
+ "updatedAt",
1633
+ "deletedAt"
1634
+ ]);
1635
+ var keepLiteral2 = (key) => KEEP_LITERAL2.has(key);
1636
+ var WEB_SKELETON = {
1637
+ version: 4,
1638
+ flows: {
1639
+ default: {
1640
+ config: { platform: "web", bundle: { packages: {} } },
1641
+ sources: {},
1642
+ destinations: {}
1643
+ }
1644
+ }
1645
+ };
1646
+ var SERVER_SKELETON = {
1647
+ version: 4,
1648
+ flows: {
1649
+ default: {
1650
+ config: { platform: "server", bundle: { packages: {} } },
1651
+ sources: {},
1652
+ destinations: {}
1653
+ }
1654
+ }
1655
+ };
1656
+ var TITLE11 = "Load or Create Flow";
1657
+ var DESCRIPTION11 = "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.";
1658
+ var inputSchema11 = {
1659
+ source: z10.string().optional().describe(
1660
+ "Flow source: local file path (./flow.json), URL (https://...), inline JSON string, or API flow ID (cfg_...). Omit to create a new flow."
1661
+ ),
1662
+ platform: z10.enum(["web", "server"]).optional().describe(
1663
+ "Platform for new flows. Required when source is omitted. web = browser tracking, server = Node.js HTTP."
1664
+ )
1665
+ };
1666
+ var outputSchema = {
1667
+ version: z10.number().describe("Flow config version"),
1668
+ flows: z10.record(z10.string(), z10.unknown()).describe("Flow definitions")
1669
+ };
1670
+ var annotations11 = {
1671
+ readOnlyHint: true,
1672
+ destructiveHint: false,
1673
+ idempotentHint: true,
1674
+ openWorldHint: true
1675
+ };
1676
+ function createFlowLoadToolSpec() {
1677
+ return {
1678
+ name: "flow_load",
1679
+ title: TITLE11,
1680
+ description: DESCRIPTION11,
1681
+ inputSchema: inputSchema11,
1682
+ annotations: annotations11,
1683
+ handler: (input) => flowLoadHandlerBody(input)
1684
+ };
1685
+ }
1686
+ async function flowLoadHandlerBody(input) {
1687
+ const { source, platform } = input ?? {};
1688
+ try {
1689
+ if (source) {
1690
+ const config = await loadJsonConfig2(source);
1691
+ return mcpResult11(redactNestedStrings(config, { skip: keepLiteral2 }), {
1692
+ next: ["Use flow_validate to check", "Use add-step prompt to modify"]
1693
+ });
1694
+ }
1695
+ if (!platform) {
1696
+ return mcpError11(
1697
+ new Error(
1698
+ "Provide source (file path, URL, or flow ID) to load existing flow, or platform (web/server) to create a new one."
1243
1699
  )
1244
- },
1245
- // No outputSchema: action-dispatched tool — each action returns a different shape
1246
- annotations: {
1247
- readOnlyHint: false,
1248
- destructiveHint: true,
1249
- idempotentHint: false,
1250
- openWorldHint: true
1251
- }
1700
+ );
1701
+ }
1702
+ const skeleton = platform === "web" ? WEB_SKELETON : SERVER_SKELETON;
1703
+ return mcpResult11(skeleton, {
1704
+ next: [
1705
+ "Read walkeros://reference/flow-schema for config structure",
1706
+ "Use add-step prompt to add sources and destinations"
1707
+ ]
1708
+ });
1709
+ } catch (error) {
1710
+ const msg = error instanceof Error ? error.message : "";
1711
+ if (msg.includes("not found") || msg.includes("ENOENT"))
1712
+ return mcpError11(error, "Check configPath \u2014 expected a flow.json file");
1713
+ return mcpError11(error);
1714
+ }
1715
+ }
1716
+ function registerFlowLoadTool(server) {
1717
+ const spec = createFlowLoadToolSpec();
1718
+ server.registerTool(
1719
+ spec.name,
1720
+ {
1721
+ title: spec.title,
1722
+ description: spec.description,
1723
+ inputSchema: spec.inputSchema,
1724
+ // outputSchema is wire-only; not part of ToolSpec
1725
+ outputSchema,
1726
+ annotations: spec.annotations
1252
1727
  },
1253
- async ({
1254
- action,
1255
- flowId,
1256
- projectId,
1257
- name,
1258
- content,
1259
- patch,
1260
- fields,
1261
- sort,
1262
- order,
1263
- includeDeleted,
1264
- previewId,
1265
- flowName,
1266
- flowSettingsId,
1267
- siteUrl
1268
- }) => {
1269
- try {
1270
- switch (action) {
1271
- case "list": {
1272
- if (projectId) {
1273
- const data2 = await listFlows({
1274
- projectId,
1275
- sort,
1276
- order,
1277
- includeDeleted
1278
- });
1279
- return mcpResult11(data2);
1280
- }
1281
- const data = await listAllFlows({ sort, order, includeDeleted });
1282
- return mcpResult11(
1283
- { projects: data },
1284
- {
1285
- next: [
1286
- 'Use flow_manage with action "get" and a flowId to inspect a specific flow',
1287
- "Use flow_load to open a flow for editing"
1288
- ]
1289
- }
1290
- );
1291
- }
1292
- case "get": {
1293
- if (!flowId) {
1294
- return mcpError11(
1295
- new Error(
1296
- 'flowId is required for get action. Use action "list" to see available flows.'
1297
- )
1298
- );
1299
- }
1300
- const flow = await getFlow({ flowId, projectId, fields });
1301
- return mcpResult11(flow, {
1302
- next: [
1303
- "Use flow_load to open this flow for editing and validation"
1304
- ]
1305
- });
1306
- }
1307
- case "create": {
1308
- if (!name) {
1309
- return mcpError11(new Error("name is required for create action."));
1310
- }
1311
- const created = await createFlow({
1312
- name,
1313
- content: content ?? {},
1314
- projectId
1315
- });
1316
- return mcpResult11(created, {
1317
- next: [
1318
- "Use flow_load to open this flow for editing and validation"
1319
- ]
1320
- });
1321
- }
1322
- case "update": {
1323
- if (!flowId) {
1324
- return mcpError11(
1325
- new Error(
1326
- 'flowId is required for update action. Use action "list" to see available flows.'
1327
- )
1328
- );
1329
- }
1330
- const updated = await updateFlow({
1331
- flowId,
1332
- projectId,
1333
- name,
1334
- content,
1335
- mergePatch: patch ?? true
1336
- });
1337
- return mcpResult11(updated);
1338
- }
1339
- case "delete": {
1340
- if (!flowId) {
1341
- return mcpError11(
1342
- new Error(
1343
- 'flowId is required for delete action. Use action "list" to see available flows.'
1344
- )
1345
- );
1346
- }
1347
- const deleted = await deleteFlow({ flowId, projectId });
1348
- return mcpResult11(deleted);
1349
- }
1350
- case "duplicate": {
1351
- if (!flowId) {
1352
- return mcpError11(
1353
- new Error(
1354
- 'flowId is required for duplicate action. Use action "list" to see available flows.'
1355
- )
1356
- );
1357
- }
1358
- const duplicated = await duplicateFlow({
1359
- flowId,
1360
- name,
1361
- projectId
1362
- });
1363
- return mcpResult11(duplicated);
1364
- }
1365
- case "preview_list": {
1366
- if (!flowId) {
1367
- return mcpError11(
1368
- new Error(
1369
- 'flowId is required for preview_list. Use action "list" to see available flows.'
1370
- )
1371
- );
1372
- }
1373
- const data = await listPreviews({ projectId, flowId });
1374
- return mcpResult11(data);
1375
- }
1376
- case "preview_get": {
1377
- if (!flowId || !previewId) {
1378
- return mcpError11(
1379
- new Error("flowId and previewId are required for preview_get.")
1380
- );
1381
- }
1382
- const data = await getPreview({ projectId, flowId, previewId });
1383
- return mcpResult11(data);
1384
- }
1385
- case "preview_create": {
1386
- if (!flowId) {
1387
- return mcpError11(
1388
- new Error("flowId is required for preview_create.")
1389
- );
1390
- }
1391
- if (!flowName && !flowSettingsId) {
1392
- return mcpError11(
1393
- new Error(
1394
- "flowName or flowSettingsId is required for preview_create."
1395
- )
1396
- );
1397
- }
1398
- const preview = await createPreview({
1399
- projectId,
1400
- flowId,
1401
- flowName,
1402
- flowSettingsId
1403
- });
1404
- const typedPreview = preview;
1405
- const enriched = {
1406
- ...typedPreview,
1407
- activationParam: typedPreview.activationUrl
1408
- };
1409
- if (siteUrl) {
1410
- const on = new URL(siteUrl);
1411
- on.searchParams.set("elbPreview", typedPreview.token);
1412
- enriched.activationUrl = on.toString();
1413
- const off = new URL(siteUrl);
1414
- off.searchParams.set("elbPreview", "off");
1415
- enriched.deactivationUrl = off.toString();
1416
- } else {
1417
- delete enriched.activationUrl;
1418
- }
1419
- return mcpResult11(enriched, {
1420
- next: siteUrl ? [
1421
- "Open activationUrl to activate preview mode; open deactivationUrl to exit."
1422
- ] : [
1423
- "Append activationParam to any URL on your site to activate preview mode."
1424
- ]
1425
- });
1426
- }
1427
- case "preview_delete": {
1428
- if (!flowId || !previewId) {
1429
- return mcpError11(
1430
- new Error(
1431
- "flowId and previewId are required for preview_delete."
1432
- )
1433
- );
1434
- }
1435
- const data = await deletePreview({
1436
- projectId,
1437
- flowId,
1438
- previewId
1439
- });
1440
- return mcpResult11(data);
1441
- }
1442
- default:
1443
- throw new Error(
1444
- `Unknown action: ${action}. Use one of: list, get, create, update, delete, duplicate, preview_list, preview_get, preview_create, preview_delete`
1445
- );
1446
- }
1447
- } catch (error) {
1448
- return mcpError11(error, isAuthError(error) ? AUTH_HINT : void 0);
1449
- }
1728
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
1729
+ // type-erased (input: unknown) => Promise<unknown> form by design.
1730
+ spec.handler
1731
+ );
1732
+ }
1733
+
1734
+ // src/tools/package.ts
1735
+ import { z as z11 } from "zod";
1736
+ import { fetchPackage, mcpResult as mcpResult12, mcpError as mcpError12 } from "@walkeros/core";
1737
+ import { mergeConfigSchema } from "@walkeros/core/dev";
1738
+
1739
+ // src/catalog.ts
1740
+ var NPM_SEARCH_URL = "https://registry.npmjs.org/-/v1/search";
1741
+ var JSDELIVR_BASE = "https://cdn.jsdelivr.net/npm";
1742
+ var WALKEROS_JSON_PATH = "dist/walkerOS.json";
1743
+ var CACHE_TTL = 5 * 60 * 1e3;
1744
+ var CLIENT_HEADER = "walkeros-mcp/4.0.0-next-1777463920154";
1745
+ var cache;
1746
+ function normalizePlatform(platform) {
1747
+ if (platform == null) return [];
1748
+ if (typeof platform === "string") {
1749
+ return platform === "universal" ? ["web", "server"] : [platform];
1750
+ }
1751
+ if (Array.isArray(platform)) {
1752
+ return platform.filter((v) => typeof v === "string");
1753
+ }
1754
+ return [];
1755
+ }
1756
+ async function fetchCatalog(filters) {
1757
+ if (cache && Date.now() - cache.timestamp < CACHE_TTL) {
1758
+ return applyFilters(cache.entries, filters);
1759
+ }
1760
+ let entries;
1761
+ try {
1762
+ entries = filters?.baseUrl ? await fetchCatalogFrom(filters.baseUrl, filters) : await fetchFromNpm();
1763
+ } catch {
1764
+ try {
1765
+ entries = await fetchFromNpm();
1766
+ } catch {
1767
+ return [];
1450
1768
  }
1769
+ }
1770
+ cache = { entries, timestamp: Date.now() };
1771
+ return applyFilters(entries, filters);
1772
+ }
1773
+ async function fetchCatalogFrom(baseUrl, filters) {
1774
+ const params = new URLSearchParams();
1775
+ if (filters?.type) params.set("type", filters.type);
1776
+ if (filters?.platform) params.set("platform", filters.platform);
1777
+ const url = `${baseUrl}/api/packages${params.toString() ? `?${params}` : ""}`;
1778
+ const res = await fetch(url, {
1779
+ signal: AbortSignal.timeout(15e3),
1780
+ headers: { "X-Walkeros-Client": CLIENT_HEADER }
1781
+ });
1782
+ if (!res.ok) throw new Error(`Catalog fetch failed: ${res.status}`);
1783
+ const data = await res.json();
1784
+ return data.catalog;
1785
+ }
1786
+ async function fetchFromNpm() {
1787
+ const res = await fetch(`${NPM_SEARCH_URL}?text=@walkeros/&size=250`, {
1788
+ signal: AbortSignal.timeout(1e4),
1789
+ headers: { "X-Walkeros-Client": CLIENT_HEADER }
1790
+ });
1791
+ if (!res.ok) throw new Error(`npm search failed: ${res.status}`);
1792
+ const data = await res.json();
1793
+ const metaResults = await Promise.allSettled(
1794
+ data.objects.map((obj) => enrichWithMeta(obj.package))
1451
1795
  );
1796
+ return metaResults.filter(
1797
+ (r) => r.status === "fulfilled"
1798
+ ).map((r) => r.value).filter((entry) => entry !== void 0);
1799
+ }
1800
+ async function enrichWithMeta(pkg) {
1801
+ try {
1802
+ const res = await fetch(
1803
+ `${JSDELIVR_BASE}/${pkg.name}@${pkg.version}/${WALKEROS_JSON_PATH}`,
1804
+ {
1805
+ signal: AbortSignal.timeout(5e3),
1806
+ headers: { "X-Walkeros-Client": CLIENT_HEADER }
1807
+ }
1808
+ );
1809
+ if (!res.ok) return void 0;
1810
+ const json = await res.json();
1811
+ const meta = json.$meta;
1812
+ if (!meta || typeof meta.type !== "string") return void 0;
1813
+ return {
1814
+ name: pkg.name,
1815
+ version: pkg.version,
1816
+ description: pkg.description,
1817
+ type: meta.type,
1818
+ platform: normalizePlatform(meta.platform)
1819
+ };
1820
+ } catch {
1821
+ return void 0;
1822
+ }
1823
+ }
1824
+ function applyFilters(entries, filters) {
1825
+ let results = entries;
1826
+ if (filters?.type) {
1827
+ results = results.filter((e) => e.type === filters.type);
1828
+ }
1829
+ if (filters?.platform) {
1830
+ results = results.filter(
1831
+ (e) => e.platform.length === 0 || e.platform.includes(filters.platform)
1832
+ );
1833
+ }
1834
+ return results;
1452
1835
  }
1453
1836
 
1454
- // src/tools/deploy-manage.ts
1455
- import { z as z12 } from "zod";
1456
- import {
1457
- deploy as deployFlow,
1458
- listDeployments,
1459
- getDeployment,
1460
- getDeploymentBySlug,
1461
- deleteDeployment
1462
- } from "@walkeros/cli";
1463
- import { mcpResult as mcpResult12, mcpError as mcpError12 } from "@walkeros/core";
1464
- function registerDeployTool(server2) {
1465
- server2.registerTool(
1466
- "deploy_manage",
1837
+ // src/tools/package.ts
1838
+ var SEARCH_TITLE = "Search Package";
1839
+ var SEARCH_DESCRIPTION = "Start here for package discovery. Never guess package names: use this tool first to find exact names. Without package name: returns catalog filtered by type/platform. With package name: returns metadata, hint keys, and example summaries.";
1840
+ var searchInputSchema = {
1841
+ package: z11.string().min(1).optional().describe(
1842
+ "Exact npm package name for detailed lookup (e.g., @walkeros/web-destination-snowplow)"
1843
+ ),
1844
+ type: z11.enum(["source", "destination", "transformer", "store"]).optional().describe("Filter by package type (browse mode)"),
1845
+ platform: z11.enum(["web", "server"]).optional().describe("Filter by platform (browse mode, includes universal packages)"),
1846
+ version: z11.string().optional().describe("Package version for detailed lookup (default: latest)")
1847
+ };
1848
+ var searchAnnotations = {
1849
+ readOnlyHint: true,
1850
+ destructiveHint: false,
1851
+ idempotentHint: true,
1852
+ openWorldHint: false
1853
+ };
1854
+ function createPackageSearchToolSpec() {
1855
+ return {
1856
+ name: "package_search",
1857
+ title: SEARCH_TITLE,
1858
+ description: SEARCH_DESCRIPTION,
1859
+ inputSchema: searchInputSchema,
1860
+ annotations: searchAnnotations,
1861
+ handler: (input) => packageSearchHandlerBody(input)
1862
+ };
1863
+ }
1864
+ async function packageSearchHandlerBody(input) {
1865
+ const {
1866
+ package: packageName,
1867
+ type,
1868
+ platform,
1869
+ version
1870
+ } = input ?? {};
1871
+ const baseUrl = process.env.APP_URL || void 0;
1872
+ if (!packageName) {
1873
+ const catalog = await fetchCatalog({ type, platform, baseUrl });
1874
+ const result = { catalog, count: catalog.length };
1875
+ return mcpResult12(result, {
1876
+ next: ["Use package_get for schemas and examples"]
1877
+ });
1878
+ }
1879
+ try {
1880
+ const info = await fetchPackage(packageName, {
1881
+ version,
1882
+ baseUrl,
1883
+ client: CLIENT_HEADER
1884
+ });
1885
+ const result = {
1886
+ package: info.packageName,
1887
+ version: info.version,
1888
+ description: info.description,
1889
+ type: info.type,
1890
+ platform: normalizePlatform(info.platform),
1891
+ hintKeys: info.hintKeys,
1892
+ exampleSummaries: info.exampleSummaries
1893
+ };
1894
+ return mcpResult12(result, {
1895
+ next: ["Use package_get for schemas and examples"]
1896
+ });
1897
+ } catch (error) {
1898
+ return mcpError12(
1899
+ error,
1900
+ "Package not found. Use package_search without parameters to browse available packages."
1901
+ );
1902
+ }
1903
+ }
1904
+ function registerPackageSearchTool(server) {
1905
+ const spec = createPackageSearchToolSpec();
1906
+ server.registerTool(
1907
+ spec.name,
1467
1908
  {
1468
- title: "Deploy Management",
1469
- description: 'Deploy walkerOS flows and manage deployments. Deploy a flow, list deployments, get deployment details, or delete deployments. Note: the "get" and "delete" actions accept a deployment slug (e.g. "dep_..."), not a flow ID. If you only have a flowId, resolve the latest deployment slug first via flow_manage with action "get".',
1470
- inputSchema: {
1471
- action: z12.enum(["deploy", "list", "get", "delete"]).describe("Deployment action to perform"),
1472
- flowId: z12.string().optional().describe("Flow ID. Required for deploy action."),
1473
- id: z12.string().optional().describe(
1474
- 'Deployment slug (e.g. "dep_..."). Required for get and delete actions. This is the deployment slug, not a flow ID \u2014 if you only have a flowId, use flow_manage action "get" to resolve the latest deployment slug.'
1475
- ),
1476
- projectId: z12.string().optional().describe("Project ID. Optional filter for list."),
1477
- type: z12.enum(["web", "server"]).optional().describe("Deployment type filter for list."),
1478
- status: z12.string().optional().describe("Status filter for list."),
1479
- wait: z12.boolean().optional().describe(
1480
- "Wait for deploy to complete (default true). Only used with deploy action."
1481
- ),
1482
- flowName: z12.string().optional().describe(
1483
- "Flow name for multi-settings flows. Only used with deploy action."
1484
- )
1485
- },
1486
- // No outputSchema: action-dispatched tool — each action returns a different shape
1487
- annotations: {
1488
- readOnlyHint: false,
1489
- destructiveHint: true,
1490
- idempotentHint: false,
1491
- openWorldHint: true
1492
- }
1909
+ title: spec.title,
1910
+ description: spec.description,
1911
+ inputSchema: spec.inputSchema,
1912
+ // No outputSchema: browse mode returns {catalog, count}, lookup returns metadata — incompatible shapes
1913
+ annotations: spec.annotations
1493
1914
  },
1494
- async ({ action, flowId, id, projectId, type, status, wait, flowName }) => {
1495
- try {
1496
- switch (action) {
1497
- case "deploy": {
1498
- if (!flowId) {
1499
- return mcpError12(
1500
- new Error(
1501
- 'flowId is required for deploy action. Use flow_manage with action "list" to see available flows.'
1502
- )
1503
- );
1504
- }
1505
- const result = await deployFlow({
1506
- flowId,
1507
- wait: wait ?? true,
1508
- flowName
1509
- });
1510
- return mcpResult12(result, {
1511
- next: [
1512
- 'Use deploy_manage with action "get" to check deployment status'
1513
- ]
1514
- });
1515
- }
1516
- case "list": {
1517
- const data = await listDeployments({ projectId, type, status });
1518
- return mcpResult12(data);
1519
- }
1520
- case "get": {
1521
- if (!id) {
1522
- return mcpError12(
1523
- new Error(
1524
- 'id is required for get action. Use deploy_manage with action "list" to see available deployments.'
1525
- )
1526
- );
1527
- }
1528
- try {
1529
- const data = await getDeploymentBySlug({ slug: id, projectId });
1530
- return mcpResult12(data);
1531
- } catch {
1532
- const data = await getDeployment({ flowId: id, projectId });
1533
- return mcpResult12(data);
1534
- }
1535
- }
1536
- case "delete": {
1537
- if (!id) {
1538
- return mcpError12(
1539
- new Error(
1540
- 'id is required for delete action. Use deploy_manage with action "list" to see available deployments.'
1541
- )
1542
- );
1543
- }
1544
- try {
1545
- const data = await deleteDeployment({ slug: id, projectId });
1546
- return mcpResult12({ deleted: true, ...data });
1547
- } catch (error) {
1548
- if (isAuthError(error)) {
1549
- return mcpError12(error, AUTH_HINT);
1550
- }
1551
- const message = error instanceof Error ? error.message : String(error);
1552
- if (/not[\s_-]?found|404/i.test(message)) {
1553
- return mcpError12(
1554
- error,
1555
- 'Deployment not found. The "delete" action expects a deployment slug (e.g. "dep_..."), not a flow ID. If you only have a flowId, use flow_manage with action "get" to resolve the latest deployment slug first.'
1556
- );
1557
- }
1558
- return mcpError12(error);
1559
- }
1560
- }
1561
- default:
1562
- throw new Error(
1563
- `Unknown action: ${action}. Use one of: deploy, list, get, delete`
1564
- );
1915
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
1916
+ // type-erased (input: unknown) => Promise<unknown> form by design.
1917
+ spec.handler
1918
+ );
1919
+ }
1920
+ var GET_TITLE = "Get Package";
1921
+ var GET_DESCRIPTION = 'Requires exact package name: do not guess names, use package_search first to find them. Returns schemas + hint texts + example summaries by default (lightweight). Use section parameter for full content: "hints" (with code blocks), "examples" (full in/out data), or "all".';
1922
+ var getInputSchema = {
1923
+ package: z11.string().min(1).describe(
1924
+ "Exact npm package name (e.g., @walkeros/web-destination-snowplow)"
1925
+ ),
1926
+ version: z11.string().optional().describe("Package version (default: latest)"),
1927
+ section: z11.enum(["hints", "examples", "all"]).optional().describe(
1928
+ "Section to expand with full content. Default: summary view with schemas + hint texts + example descriptions"
1929
+ )
1930
+ };
1931
+ var getAnnotations = {
1932
+ readOnlyHint: true,
1933
+ destructiveHint: false,
1934
+ idempotentHint: true,
1935
+ openWorldHint: true
1936
+ };
1937
+ function createPackageGetToolSpec() {
1938
+ return {
1939
+ name: "package_get",
1940
+ title: GET_TITLE,
1941
+ description: GET_DESCRIPTION,
1942
+ inputSchema: getInputSchema,
1943
+ annotations: getAnnotations,
1944
+ handler: (input) => packageGetHandlerBody(input)
1945
+ };
1946
+ }
1947
+ async function packageGetHandlerBody(input) {
1948
+ const {
1949
+ package: packageName,
1950
+ version,
1951
+ section
1952
+ } = input ?? {};
1953
+ const baseUrl = process.env.APP_URL || void 0;
1954
+ try {
1955
+ const info = await fetchPackage(packageName, {
1956
+ version,
1957
+ baseUrl,
1958
+ client: CLIENT_HEADER
1959
+ });
1960
+ const mergedSchemas = {};
1961
+ if (info.type) {
1962
+ mergedSchemas.config = mergeConfigSchema(
1963
+ info.type,
1964
+ info.schemas
1965
+ );
1966
+ }
1967
+ for (const [key, value] of Object.entries(info.schemas)) {
1968
+ if (key !== "settings") {
1969
+ mergedSchemas[key] = value;
1970
+ }
1971
+ }
1972
+ const result = {
1973
+ package: info.packageName,
1974
+ version: info.version,
1975
+ type: info.type,
1976
+ platform: normalizePlatform(info.platform),
1977
+ schemas: mergedSchemas
1978
+ };
1979
+ if (info.hints) {
1980
+ if (section === "hints" || section === "all") {
1981
+ result.hints = info.hints;
1982
+ } else {
1983
+ const hintSummary = {};
1984
+ for (const [key, hint] of Object.entries(info.hints)) {
1985
+ const h = hint;
1986
+ hintSummary[key] = { text: h.text };
1565
1987
  }
1566
- } catch (error) {
1567
- return mcpError12(error, isAuthError(error) ? AUTH_HINT : void 0);
1988
+ result.hints = hintSummary;
1568
1989
  }
1569
1990
  }
1991
+ if (section === "examples" || section === "all") {
1992
+ result.examples = info.examples;
1993
+ } else {
1994
+ result.exampleSummaries = info.exampleSummaries;
1995
+ }
1996
+ return mcpResult12(result);
1997
+ } catch (error) {
1998
+ return mcpError12(
1999
+ error,
2000
+ "Use package_search to browse available package names."
2001
+ );
2002
+ }
2003
+ }
2004
+ function registerGetPackageSchemaTool(server) {
2005
+ const spec = createPackageGetToolSpec();
2006
+ server.registerTool(
2007
+ spec.name,
2008
+ {
2009
+ title: spec.title,
2010
+ description: spec.description,
2011
+ inputSchema: spec.inputSchema,
2012
+ // No outputSchema: removed to avoid SDK -32602 crashes on unexpected field values
2013
+ annotations: spec.annotations
2014
+ },
2015
+ // SDK infers handler type from inputSchema shape; ToolSpec.handler is the
2016
+ // type-erased (input: unknown) => Promise<unknown> form by design.
2017
+ spec.handler
1570
2018
  );
1571
2019
  }
1572
2020
 
2021
+ // src/server.ts
2022
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2023
+
1573
2024
  // src/resources/package-schemas.ts
1574
2025
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
1575
2026
  import { fetchPackageSchema } from "@walkeros/core";
1576
- function registerPackageSchemaResources(server2) {
2027
+ function registerPackageSchemaResources(server) {
1577
2028
  const template = new ResourceTemplate("walkeros://schema/{packageName}", {
1578
2029
  list: async () => {
1579
2030
  const catalog = await fetchCatalog();
@@ -1587,7 +2038,7 @@ function registerPackageSchemaResources(server2) {
1587
2038
  };
1588
2039
  }
1589
2040
  });
1590
- server2.registerResource(
2041
+ server.registerResource(
1591
2042
  "package-schema",
1592
2043
  template,
1593
2044
  {
@@ -1614,12 +2065,12 @@ function registerPackageSchemaResources(server2) {
1614
2065
 
1615
2066
  // src/resources/references.ts
1616
2067
  import { schemas as schemas5 } from "@walkeros/core/dev";
1617
- function registerReferenceResources(server2) {
1618
- server2.resource(
2068
+ function registerReferenceResources(server) {
2069
+ server.resource(
1619
2070
  "flow-schema",
1620
2071
  "walkeros://reference/flow-schema",
1621
2072
  {
1622
- description: "JSON Schema for Flow.Config \u2014 the complete flow configuration structure",
2073
+ description: "JSON Schema for Flow.Json \u2014 the complete flow configuration structure",
1623
2074
  mimeType: "application/json"
1624
2075
  },
1625
2076
  async () => ({
@@ -1632,7 +2083,7 @@ function registerReferenceResources(server2) {
1632
2083
  ]
1633
2084
  })
1634
2085
  );
1635
- server2.resource(
2086
+ server.resource(
1636
2087
  "event-model",
1637
2088
  "walkeros://reference/event-model",
1638
2089
  {
@@ -1649,7 +2100,7 @@ function registerReferenceResources(server2) {
1649
2100
  ]
1650
2101
  })
1651
2102
  );
1652
- server2.resource(
2103
+ server.resource(
1653
2104
  "mapping",
1654
2105
  "walkeros://reference/mapping",
1655
2106
  {
@@ -1675,7 +2126,7 @@ function registerReferenceResources(server2) {
1675
2126
  ]
1676
2127
  })
1677
2128
  );
1678
- server2.resource(
2129
+ server.resource(
1679
2130
  "consent",
1680
2131
  "walkeros://reference/consent",
1681
2132
  {
@@ -1692,11 +2143,11 @@ function registerReferenceResources(server2) {
1692
2143
  ]
1693
2144
  })
1694
2145
  );
1695
- server2.resource(
2146
+ server.resource(
1696
2147
  "variables",
1697
2148
  "walkeros://reference/variables",
1698
2149
  {
1699
- description: "walkerOS variable patterns: $var, $env, $def, $contract, $code, $store substitution",
2150
+ description: "walkerOS variable patterns: $var, $env, $def, $contract, $code, $store, $secret substitution",
1700
2151
  mimeType: "application/json"
1701
2152
  },
1702
2153
  async () => ({
@@ -1705,16 +2156,18 @@ function registerReferenceResources(server2) {
1705
2156
  uri: "walkeros://reference/variables",
1706
2157
  text: JSON.stringify(
1707
2158
  {
2159
+ separatorRule: "`.` for names and paths; `:` for literal values or raw-code payloads.",
1708
2160
  patterns: {
1709
2161
  "$var.name": "Variable substitution \u2014 cascade: step settings > flow settings > config variables",
1710
2162
  "$env.NAME": "Environment variable \u2014 $env.GA_ID reads process.env.GA_ID",
1711
- "$env.NAME:default": "Environment variable with fallback \u2014 $env.GA_ID:G-DEFAULT",
2163
+ "$env.NAME:default": "Environment variable with fallback \u2014 $env.GA_ID:G-DEFAULT (the `:` is the literal default separator)",
1712
2164
  "$def.name": "Definition reference \u2014 reusable config blocks from definitions section",
1713
2165
  "$def.name.path.deep": "Nested definition access \u2014 $def.ga4Events.purchase",
1714
2166
  "$contract.name": "Contract reference \u2014 links to named contract for validation",
1715
2167
  "$contract.name.path": "Nested contract access \u2014 $contract.ecommerce.product",
1716
- "$code:(expr)": "Inline JavaScript \u2014 $code:(event) => event.data.price * 100",
1717
- "$store:storeId": "Store injection in env values \u2014 wires runtime store access"
2168
+ "$code:(expr)": "Inline JavaScript \u2014 $code:(event) => event.data.price * 100 (the `:` carries the raw-code payload)",
2169
+ "$store.storeId": "Store injection in env values \u2014 wires runtime store access",
2170
+ "$secret.NAME": "Secret injection \u2014 resolved server-side at deploy/runtime"
1718
2171
  },
1719
2172
  cascade: {
1720
2173
  priority: [
@@ -1745,7 +2198,7 @@ function registerReferenceResources(server2) {
1745
2198
  ]
1746
2199
  })
1747
2200
  );
1748
- server2.resource(
2201
+ server.resource(
1749
2202
  "contract",
1750
2203
  "walkeros://reference/contract",
1751
2204
  {
@@ -1762,7 +2215,7 @@ function registerReferenceResources(server2) {
1762
2215
  ]
1763
2216
  })
1764
2217
  );
1765
- server2.resource(
2218
+ server.resource(
1766
2219
  "examples",
1767
2220
  "walkeros://reference/examples",
1768
2221
  {
@@ -1791,7 +2244,7 @@ function registerReferenceResources(server2) {
1791
2244
  };
1792
2245
  }
1793
2246
  );
1794
- server2.resource(
2247
+ server.resource(
1795
2248
  "openapi",
1796
2249
  "walkeros://reference/openapi",
1797
2250
  {
@@ -1820,7 +2273,7 @@ function registerReferenceResources(server2) {
1820
2273
  };
1821
2274
  }
1822
2275
  );
1823
- server2.resource(
2276
+ server.resource(
1824
2277
  "packages",
1825
2278
  "walkeros://reference/packages",
1826
2279
  {
@@ -1843,17 +2296,17 @@ function registerReferenceResources(server2) {
1843
2296
  }
1844
2297
 
1845
2298
  // src/prompts/add-step.ts
1846
- import { z as z13 } from "zod";
1847
- function registerAddStepPrompt(server2) {
1848
- server2.registerPrompt(
2299
+ import { z as z12 } from "zod";
2300
+ function registerAddStepPrompt(server) {
2301
+ server.registerPrompt(
1849
2302
  "add-step",
1850
2303
  {
1851
2304
  description: "Add a source, destination, transformer, or store step to a flow configuration. Guides through package selection, config scaffolding, and wiring.",
1852
2305
  argsSchema: {
1853
- stepType: z13.string().optional().describe(
2306
+ stepType: z12.string().optional().describe(
1854
2307
  "Type of step to add: source, destination, transformer, or store"
1855
2308
  ),
1856
- flowPath: z13.string().optional().describe("Path to the flow.json file to modify")
2309
+ flowPath: z12.string().optional().describe("Path to the flow.json file to modify")
1857
2310
  }
1858
2311
  },
1859
2312
  async ({ stepType, flowPath }) => ({
@@ -1881,7 +2334,7 @@ function registerAddStepPrompt(server2) {
1881
2334
  "- Read the walkeros://reference/flow-schema resource to understand connection rules.",
1882
2335
  "- Sources connect to pre-collector transformers via `next`.",
1883
2336
  "- Destinations connect to post-collector transformers via `before`.",
1884
- "- Stores are passive \u2014 referenced via `$store:storeName` in env values.",
2337
+ "- Stores are passive \u2014 referenced via `$store.storeName` in env values.",
1885
2338
  "- Use variables ($var) for values that change between environments.",
1886
2339
  "- 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.",
1887
2340
  "- If $meta.exports lists named exports, set the `code` field on the step to the chosen export name. If only one export exists, use it automatically."
@@ -1894,14 +2347,14 @@ function registerAddStepPrompt(server2) {
1894
2347
  }
1895
2348
 
1896
2349
  // src/prompts/setup-mapping.ts
1897
- import { z as z14 } from "zod";
1898
- function registerSetupMappingPrompt(server2) {
1899
- server2.registerPrompt(
2350
+ import { z as z13 } from "zod";
2351
+ function registerSetupMappingPrompt(server) {
2352
+ server.registerPrompt(
1900
2353
  "setup-mapping",
1901
2354
  {
1902
2355
  description: "Set up event mapping for any step in a flow. Teaches mapping syntax and uses package examples as templates.",
1903
2356
  argsSchema: {
1904
- stepName: z14.string().optional().describe('Step name in the flow (e.g., "gtag", "meta", "express")')
2357
+ stepName: z13.string().optional().describe('Step name in the flow (e.g., "gtag", "meta", "express")')
1905
2358
  }
1906
2359
  },
1907
2360
  async ({ stepName }) => ({
@@ -1939,14 +2392,14 @@ function registerSetupMappingPrompt(server2) {
1939
2392
  }
1940
2393
 
1941
2394
  // src/prompts/manage-contract.ts
1942
- import { z as z15 } from "zod";
1943
- function registerManageContractPrompt(server2) {
1944
- server2.registerPrompt(
2395
+ import { z as z14 } from "zod";
2396
+ function registerManageContractPrompt(server) {
2397
+ server.registerPrompt(
1945
2398
  "manage-contract",
1946
2399
  {
1947
2400
  description: "Create or update event contracts for a flow. Can generate contracts from existing mappings or scaffold mappings from contracts.",
1948
2401
  argsSchema: {
1949
- direction: z15.string().optional().describe(
2402
+ direction: z14.string().optional().describe(
1950
2403
  'Direction: "from-mappings" (extract contract from existing mappings), "from-scratch" (create new contract), or "to-mappings" (scaffold mappings from contract)'
1951
2404
  )
1952
2405
  }
@@ -1984,14 +2437,14 @@ function registerManageContractPrompt(server2) {
1984
2437
  }
1985
2438
 
1986
2439
  // src/prompts/use-definitions.ts
1987
- import { z as z16 } from "zod";
1988
- function registerUseDefinitionsPrompt(server2) {
1989
- server2.registerPrompt(
2440
+ import { z as z15 } from "zod";
2441
+ function registerUseDefinitionsPrompt(server) {
2442
+ server.registerPrompt(
1990
2443
  "use-definitions",
1991
2444
  {
1992
2445
  description: "Extract shared patterns into definitions and variables for DRY, environment-aware flow configurations.",
1993
2446
  argsSchema: {
1994
- flowPath: z16.string().optional().describe("Path to the flow.json file to analyze")
2447
+ flowPath: z15.string().optional().describe("Path to the flow.json file to analyze")
1995
2448
  }
1996
2449
  },
1997
2450
  async ({ flowPath }) => ({
@@ -2017,7 +2470,7 @@ function registerUseDefinitionsPrompt(server2) {
2017
2470
  "- `$def.name` and `$def.name.path.deep` \u2014 definition references",
2018
2471
  "- `$contract.name` \u2014 contract references",
2019
2472
  "- `$code:(expr)` \u2014 inline JavaScript functions",
2020
- "- `$store:storeId` \u2014 store injection in env values",
2473
+ "- `$store.storeId` \u2014 store injection in env values",
2021
2474
  "",
2022
2475
  "Look for:",
2023
2476
  "- Same API keys or URLs across multiple destinations \u2192 $var or $env",
@@ -2031,15 +2484,8 @@ function registerUseDefinitionsPrompt(server2) {
2031
2484
  );
2032
2485
  }
2033
2486
 
2034
- // src/index.ts
2035
- setClientContext({ type: "mcp", version: "3.4.2" });
2036
- var server = new McpServer(
2037
- {
2038
- name: "walkeros-flow",
2039
- version: "3.4.2"
2040
- },
2041
- {
2042
- instructions: `walkerOS is an open-source, privacy-first event data collection platform. Define event pipelines as code using JSON flow configurations.
2487
+ // src/instructions.ts
2488
+ var SERVER_INSTRUCTIONS = `walkerOS is an open-source, privacy-first event data collection platform. Define event pipelines as code using JSON flow configurations.
2043
2489
 
2044
2490
  ## Rules
2045
2491
 
@@ -2071,10 +2517,10 @@ Every component in a flow is a **step**: sources capture events, transformers pr
2071
2517
 
2072
2518
  \`\`\`json
2073
2519
  {
2074
- "version": 3,
2520
+ "version": 4,
2075
2521
  "flows": {
2076
2522
  "default": {
2077
- "web": {},
2523
+ "config": { "platform": "web" },
2078
2524
  "sources": { "<name>": { "package": "<npm-package>", "config": {} } },
2079
2525
  "destinations": { "<name>": { "package": "<npm-package>", "config": { "settings": {} } } }
2080
2526
  }
@@ -2082,8 +2528,8 @@ Every component in a flow is a **step**: sources capture events, transformers pr
2082
2528
  }
2083
2529
  \`\`\`
2084
2530
 
2085
- - \`version: 3\` is required
2086
- - Each flow must have exactly one of \`web: {}\` or \`server: {}\`
2531
+ - \`version: 4\` is required
2532
+ - Each flow declares its target via \`config.platform\` (\`"web"\` or \`"server"\`)
2087
2533
  - Destination settings go inside \`config.settings\`, not directly on the destination
2088
2534
  - Event format: \`{ name: "entity action", data: {...}, entity: "...", action: "..." }\`
2089
2535
 
@@ -2096,42 +2542,551 @@ Every component in a flow is a **step**: sources capture events, transformers pr
2096
2542
 
2097
2543
  ## Simulation Tips
2098
2544
 
2099
- - Destinations with \`require: ["consent"]\` stay **pending** until a \`"walker consent"\` event fires. Simulation will error "not found" for pending destinations \u2014 remove \`require\` from config when testing with \`flow_simulate\`.
2545
+ - Destinations with \`require: ["consent"]\` stay **pending** until a \`"walker consent"\` event fires. Simulation will error "not found" for pending destinations, remove \`require\` from config when testing with \`flow_simulate\`.
2100
2546
  - Destinations with \`consent: { marketing: true }\` silently skip events that lack matching consent. Include \`consent\` in the event: \`{ name: "page view", data: {...}, consent: { marketing: true } }\`.
2101
2547
  - **Mapping** transforms event names and data at the destination level. Events without a matching mapping rule pass through unmodified.
2102
- - **Policy** modifies the event before mapping runs \u2014 use it to inject computed fields or redact sensitive data.
2548
+ - **Policy** modifies the event before mapping runs, use it to inject computed fields or redact sensitive data.
2103
2549
 
2104
2550
  ## Reference Resources
2105
2551
 
2106
- 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\`.`
2107
- }
2108
- );
2109
- registerFlowValidateTool(server);
2110
- registerFlowBundleTool(server);
2111
- registerFlowSimulateTool(server);
2112
- registerFlowPushTool(server);
2113
- registerFlowExamplesTool(server);
2114
- registerPackageSearchTool(server);
2115
- registerGetPackageSchemaTool(server);
2116
- registerFlowLoadTool(server);
2117
- registerFeedbackTool(server);
2118
- registerAuthTool(server);
2119
- registerProjectManageTool(server);
2120
- registerFlowManageTool(server);
2121
- registerDeployTool(server);
2122
- registerPackageSchemaResources(server);
2123
- registerReferenceResources(server);
2124
- registerAddStepPrompt(server);
2125
- registerSetupMappingPrompt(server);
2126
- registerManageContractPrompt(server);
2127
- registerUseDefinitionsPrompt(server);
2128
- async function main() {
2129
- const transport = new StdioServerTransport();
2130
- await server.connect(transport);
2131
- console.error("walkerOS Flow MCP server running on stdio");
2132
- }
2133
- main().catch((error) => {
2134
- console.error("Failed to start Flow MCP server:", error);
2135
- process.exit(1);
2136
- });
2552
+ 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\`.`;
2553
+
2554
+ // src/telemetry.ts
2555
+ import { randomUUID } from "crypto";
2556
+ import { telemetry } from "@walkeros/cli";
2557
+ async function createMcpEmitter(opts) {
2558
+ const clientName = opts.clientInfo?.name || "unknown";
2559
+ const session = randomUUID();
2560
+ const emitter = await telemetry.createEmitter({
2561
+ source: {
2562
+ type: "mcp",
2563
+ platform: "server"
2564
+ },
2565
+ packageVersion: opts.packageVersion,
2566
+ session
2567
+ });
2568
+ return {
2569
+ async emitStart() {
2570
+ const ci = telemetry.getCiInfo();
2571
+ await emitter.send("mcp start", {
2572
+ ...ci,
2573
+ client: clientName
2574
+ });
2575
+ },
2576
+ async emitInvoke(tool, outcome, timingMs) {
2577
+ await emitter.send(
2578
+ "cmd invoke",
2579
+ { outcome, client: clientName },
2580
+ timingMs,
2581
+ { tool }
2582
+ );
2583
+ },
2584
+ async emitError(kind) {
2585
+ await emitter.send("error throw", { kind });
2586
+ }
2587
+ };
2588
+ }
2589
+
2590
+ // src/server.ts
2591
+ var currentEmitter;
2592
+ function wrapRegisteredToolsWithTelemetry(server) {
2593
+ const internal = server;
2594
+ const tools = internal._registeredTools;
2595
+ if (!tools) return;
2596
+ for (const [toolName, tool] of Object.entries(tools)) {
2597
+ const original = tool.handler;
2598
+ tool.handler = async (...args) => {
2599
+ const start = Date.now();
2600
+ try {
2601
+ const result = await original(...args);
2602
+ const emitter = currentEmitter;
2603
+ if (emitter) {
2604
+ emitter.emitInvoke(toolName, "success", Date.now() - start).catch(() => {
2605
+ });
2606
+ }
2607
+ return result;
2608
+ } catch (err) {
2609
+ const emitter = currentEmitter;
2610
+ if (emitter) {
2611
+ emitter.emitInvoke(toolName, "error", Date.now() - start).catch(() => {
2612
+ });
2613
+ }
2614
+ throw err;
2615
+ }
2616
+ };
2617
+ }
2618
+ }
2619
+ function createWalkerOSMcpServer(opts) {
2620
+ const packageVersion = opts.version ?? "0.0.0";
2621
+ const server = new McpServer(
2622
+ {
2623
+ name: "walkeros-flow",
2624
+ version: packageVersion
2625
+ },
2626
+ { instructions: SERVER_INSTRUCTIONS }
2627
+ );
2628
+ registerAuthTool(server, opts.client);
2629
+ registerProjectManageTool(server, opts.client);
2630
+ registerFlowManageTool(server, opts.client);
2631
+ registerDeployTool(server, opts.client);
2632
+ registerFeedbackTool(server, opts.client);
2633
+ registerFlowValidateTool(server);
2634
+ registerFlowBundleTool(server);
2635
+ registerFlowSimulateTool(server);
2636
+ registerFlowPushTool(server);
2637
+ registerFlowExamplesTool(server);
2638
+ registerFlowLoadTool(server);
2639
+ registerPackageSearchTool(server);
2640
+ registerGetPackageSchemaTool(server);
2641
+ registerPackageSchemaResources(server);
2642
+ registerReferenceResources(server);
2643
+ registerAddStepPrompt(server);
2644
+ registerSetupMappingPrompt(server);
2645
+ registerManageContractPrompt(server);
2646
+ registerUseDefinitionsPrompt(server);
2647
+ wrapRegisteredToolsWithTelemetry(server);
2648
+ const priorOnInitialized = server.server.oninitialized;
2649
+ server.server.oninitialized = () => {
2650
+ try {
2651
+ priorOnInitialized?.();
2652
+ } catch {
2653
+ }
2654
+ const clientInfo = server.server.getClientVersion();
2655
+ void createMcpEmitter({
2656
+ clientInfo,
2657
+ packageVersion
2658
+ }).then(async (emitter) => {
2659
+ currentEmitter = emitter;
2660
+ await emitter.emitStart();
2661
+ }).catch(() => {
2662
+ });
2663
+ };
2664
+ return server;
2665
+ }
2666
+
2667
+ // src/http-tool-client.ts
2668
+ import {
2669
+ listProjects,
2670
+ getProject,
2671
+ createProject,
2672
+ updateProject,
2673
+ deleteProject,
2674
+ setDefaultProject,
2675
+ getDefaultProject,
2676
+ listAllFlows,
2677
+ listFlows,
2678
+ getFlow,
2679
+ createFlow,
2680
+ updateFlow,
2681
+ deleteFlow,
2682
+ duplicateFlow,
2683
+ listPreviews,
2684
+ getPreview,
2685
+ createPreview,
2686
+ deletePreview,
2687
+ deploy,
2688
+ listDeployments,
2689
+ getDeploymentBySlug,
2690
+ deleteDeployment,
2691
+ requestDeviceCode,
2692
+ pollForToken,
2693
+ whoami,
2694
+ resolveToken,
2695
+ deleteConfig,
2696
+ feedback,
2697
+ getFeedbackPreference,
2698
+ setFeedbackPreference
2699
+ } from "@walkeros/cli";
2700
+ var HttpToolClient = class {
2701
+ async listProjects() {
2702
+ return listProjects();
2703
+ }
2704
+ async getProject(options) {
2705
+ return getProject(options);
2706
+ }
2707
+ async createProject(options) {
2708
+ return createProject(options);
2709
+ }
2710
+ async updateProject(options) {
2711
+ return updateProject(options);
2712
+ }
2713
+ async deleteProject(options) {
2714
+ return deleteProject(options);
2715
+ }
2716
+ setDefaultProject(projectId) {
2717
+ setDefaultProject(projectId);
2718
+ }
2719
+ getDefaultProject() {
2720
+ return getDefaultProject();
2721
+ }
2722
+ async listAllFlows(options) {
2723
+ return listAllFlows(options);
2724
+ }
2725
+ async listFlows(options) {
2726
+ return listFlows(options);
2727
+ }
2728
+ async getFlow(options) {
2729
+ return getFlow(options);
2730
+ }
2731
+ async createFlow(options) {
2732
+ return createFlow(options);
2733
+ }
2734
+ async updateFlow(options) {
2735
+ return updateFlow(options);
2736
+ }
2737
+ async deleteFlow(options) {
2738
+ return deleteFlow(options);
2739
+ }
2740
+ async duplicateFlow(options) {
2741
+ return duplicateFlow(options);
2742
+ }
2743
+ async listPreviews(options) {
2744
+ return listPreviews(options);
2745
+ }
2746
+ async getPreview(options) {
2747
+ return getPreview(options);
2748
+ }
2749
+ async createPreview(options) {
2750
+ return createPreview(options);
2751
+ }
2752
+ async deletePreview(options) {
2753
+ return deletePreview(options);
2754
+ }
2755
+ async deploy(options) {
2756
+ return deploy(options);
2757
+ }
2758
+ async listDeployments(options) {
2759
+ return listDeployments(options);
2760
+ }
2761
+ async getDeploymentBySlug(options) {
2762
+ return getDeploymentBySlug(options);
2763
+ }
2764
+ async deleteDeployment(options) {
2765
+ return deleteDeployment(options);
2766
+ }
2767
+ async requestDeviceCode() {
2768
+ return requestDeviceCode();
2769
+ }
2770
+ async pollForToken(deviceCode, options) {
2771
+ return pollForToken(deviceCode, options);
2772
+ }
2773
+ async whoami() {
2774
+ return whoami();
2775
+ }
2776
+ resolveToken() {
2777
+ return resolveToken();
2778
+ }
2779
+ deleteConfig() {
2780
+ return deleteConfig();
2781
+ }
2782
+ async submitFeedback(text, options) {
2783
+ await feedback(text, options);
2784
+ }
2785
+ getFeedbackPreference() {
2786
+ return getFeedbackPreference();
2787
+ }
2788
+ setFeedbackPreference(anonymous) {
2789
+ setFeedbackPreference(anonymous);
2790
+ }
2791
+ };
2792
+
2793
+ // src/http.ts
2794
+ import {
2795
+ WebStandardStreamableHTTPServerTransport
2796
+ } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
2797
+ function createStreamableHttpHandler(server, opts = {}) {
2798
+ const { onSessionInitialized, ...transportOpts } = opts;
2799
+ const userInit = transportOpts.onsessioninitialized;
2800
+ const transport = new WebStandardStreamableHTTPServerTransport({
2801
+ ...transportOpts,
2802
+ onsessioninitialized: async (sessionId) => {
2803
+ await userInit?.(sessionId);
2804
+ await onSessionInitialized?.(sessionId);
2805
+ }
2806
+ });
2807
+ const connectPromise = server.connect(transport);
2808
+ return async (request) => {
2809
+ await connectPromise;
2810
+ return transport.handleRequest(request);
2811
+ };
2812
+ }
2813
+
2814
+ // src/tool-definitions.ts
2815
+ import { z as z16 } from "zod";
2816
+ import { schemas as schemas6 } from "@walkeros/cli/dev";
2817
+ var TOOL_DEFINITIONS = [
2818
+ {
2819
+ name: "auth",
2820
+ title: "Authentication",
2821
+ description: "Manage walkerOS authentication. Check login status, log in via device code flow, or log out. No terminal or browser required, the MCP client handles the authorization URL.",
2822
+ inputSchema: {
2823
+ action: z16.enum(["status", "login", "logout"]),
2824
+ deviceCode: z16.string().optional()
2825
+ },
2826
+ annotations: {
2827
+ readOnlyHint: false,
2828
+ destructiveHint: true,
2829
+ idempotentHint: false,
2830
+ openWorldHint: true
2831
+ }
2832
+ },
2833
+ {
2834
+ name: "project_manage",
2835
+ title: "Project Management",
2836
+ description: "Manage walkerOS projects. List, create, update, delete projects, or set a default project for CLI operations.",
2837
+ inputSchema: {
2838
+ action: z16.enum([
2839
+ "list",
2840
+ "get",
2841
+ "create",
2842
+ "update",
2843
+ "delete",
2844
+ "set_default"
2845
+ ]),
2846
+ projectId: z16.string().optional(),
2847
+ name: z16.string().optional()
2848
+ },
2849
+ annotations: {
2850
+ readOnlyHint: false,
2851
+ destructiveHint: true,
2852
+ idempotentHint: false,
2853
+ openWorldHint: true
2854
+ }
2855
+ },
2856
+ {
2857
+ name: "flow_manage",
2858
+ title: "Flow Management",
2859
+ description: "Manage walkerOS flows and their previews. List/get/create/update/delete/duplicate flows, or create/inspect/delete preview bundles for testing flow changes on live sites.",
2860
+ inputSchema: {
2861
+ action: z16.enum([
2862
+ "list",
2863
+ "get",
2864
+ "create",
2865
+ "update",
2866
+ "delete",
2867
+ "duplicate",
2868
+ "preview_list",
2869
+ "preview_get",
2870
+ "preview_create",
2871
+ "preview_delete"
2872
+ ]),
2873
+ flowId: z16.string().optional(),
2874
+ projectId: z16.string().optional(),
2875
+ name: z16.string().optional(),
2876
+ content: z16.record(z16.string(), z16.unknown()).optional(),
2877
+ patch: z16.boolean().optional(),
2878
+ fields: z16.array(z16.string()).optional(),
2879
+ sort: z16.enum(["name", "updated_at", "created_at"]).optional(),
2880
+ order: z16.enum(["asc", "desc"]).optional(),
2881
+ includeDeleted: z16.boolean().optional(),
2882
+ previewId: z16.string().optional(),
2883
+ flowName: z16.string().optional(),
2884
+ flowSettingsId: z16.string().optional(),
2885
+ siteUrl: z16.string().optional()
2886
+ },
2887
+ annotations: {
2888
+ readOnlyHint: false,
2889
+ destructiveHint: true,
2890
+ idempotentHint: false,
2891
+ openWorldHint: true
2892
+ }
2893
+ },
2894
+ {
2895
+ name: "deploy_manage",
2896
+ title: "Deploy Management",
2897
+ description: "Deploy walkerOS flows and manage deployments. For get/delete actions pass flowId (required) plus optional slug to disambiguate when a flow has multiple active deployments. If a flow has >=2 active deployments and no slug is supplied, the tool returns a MULTIPLE_DEPLOYMENTS error with a details[] list showing each deployment's slug, type, status, and updatedAt.",
2898
+ inputSchema: {
2899
+ action: z16.enum(["deploy", "list", "get", "delete"]),
2900
+ projectId: z16.string().optional(),
2901
+ flowId: z16.string().optional(),
2902
+ slug: z16.string().optional(),
2903
+ type: z16.enum(["web", "server"]).optional(),
2904
+ status: z16.string().optional(),
2905
+ wait: z16.boolean().optional(),
2906
+ flowName: z16.string().optional()
2907
+ },
2908
+ annotations: {
2909
+ readOnlyHint: false,
2910
+ destructiveHint: true,
2911
+ idempotentHint: false,
2912
+ openWorldHint: true
2913
+ }
2914
+ },
2915
+ {
2916
+ name: "flow_validate",
2917
+ title: "Validate Flow",
2918
+ 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.",
2919
+ inputSchema: schemas6.ValidateInputShape,
2920
+ annotations: {
2921
+ readOnlyHint: true,
2922
+ destructiveHint: false,
2923
+ idempotentHint: true,
2924
+ openWorldHint: false
2925
+ }
2926
+ },
2927
+ {
2928
+ name: "flow_bundle",
2929
+ title: "Bundle Flow",
2930
+ 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.",
2931
+ inputSchema: {
2932
+ ...schemas6.BundleInputShape,
2933
+ remote: z16.boolean().optional(),
2934
+ content: z16.record(z16.string(), z16.unknown()).optional()
2935
+ },
2936
+ annotations: {
2937
+ readOnlyHint: false,
2938
+ destructiveHint: false,
2939
+ idempotentHint: false,
2940
+ openWorldHint: true
2941
+ }
2942
+ },
2943
+ {
2944
+ name: "flow_simulate",
2945
+ title: "Simulate Flow",
2946
+ description: 'Simulate events through a walkerOS flow without making real API calls. For destinations: event is a walkerOS event { name: "entity action", data: {...} }. For sources: event is { content: ..., trigger?: { type?, options? }, env?: {...} }. Use step to target a specific step. Use flow_examples to discover available test data. IMPORTANT: Destinations with require (e.g. require: ["consent"]) stay pending until that collector event fires, 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.',
2947
+ inputSchema: {
2948
+ configPath: schemas6.SimulateInputShape.configPath,
2949
+ event: z16.union([z16.record(z16.string(), z16.unknown()), z16.string()]).optional(),
2950
+ flow: schemas6.SimulateInputShape.flow,
2951
+ platform: schemas6.SimulateInputShape.platform,
2952
+ step: schemas6.SimulateInputShape.step,
2953
+ verbose: z16.boolean().optional()
2954
+ },
2955
+ annotations: {
2956
+ readOnlyHint: true,
2957
+ destructiveHint: false,
2958
+ idempotentHint: true,
2959
+ openWorldHint: false
2960
+ }
2961
+ },
2962
+ {
2963
+ name: "flow_push",
2964
+ title: "Push Events",
2965
+ description: "Push a real event through a walkerOS flow to actual destinations. Makes real API calls to real endpoints. Best suited for server-side flows, web flows should use flow_simulate for testing.",
2966
+ inputSchema: {
2967
+ configPath: schemas6.PushInputShape.configPath,
2968
+ event: z16.record(z16.string(), z16.unknown()),
2969
+ flow: schemas6.PushInputShape.flow,
2970
+ platform: schemas6.PushInputShape.platform
2971
+ },
2972
+ annotations: {
2973
+ readOnlyHint: false,
2974
+ destructiveHint: true,
2975
+ idempotentHint: false,
2976
+ openWorldHint: true
2977
+ }
2978
+ },
2979
+ {
2980
+ name: "flow_examples",
2981
+ title: "Flow Examples",
2982
+ 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.",
2983
+ inputSchema: {
2984
+ configPath: z16.string().min(1),
2985
+ flow: z16.string().optional(),
2986
+ step: z16.string().optional(),
2987
+ full: z16.boolean().optional(),
2988
+ includeHidden: z16.boolean().optional()
2989
+ },
2990
+ annotations: {
2991
+ readOnlyHint: true,
2992
+ destructiveHint: false,
2993
+ idempotentHint: true,
2994
+ openWorldHint: false
2995
+ }
2996
+ },
2997
+ {
2998
+ name: "flow_load",
2999
+ title: "Load or Create Flow",
3000
+ 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.",
3001
+ inputSchema: {
3002
+ source: z16.string().optional(),
3003
+ platform: z16.enum(["web", "server"]).optional()
3004
+ },
3005
+ annotations: {
3006
+ readOnlyHint: true,
3007
+ destructiveHint: false,
3008
+ idempotentHint: true,
3009
+ openWorldHint: true
3010
+ }
3011
+ },
3012
+ {
3013
+ name: "package_search",
3014
+ title: "Search Package",
3015
+ description: "Start here for package discovery. Never guess package names, use this tool first to find exact names. Without package name: returns catalog filtered by type/platform. With package name: returns metadata, hint keys, and example summaries.",
3016
+ inputSchema: {
3017
+ package: z16.string().min(1).optional(),
3018
+ type: z16.enum(["source", "destination", "transformer", "store"]).optional(),
3019
+ platform: z16.enum(["web", "server"]).optional(),
3020
+ version: z16.string().optional()
3021
+ },
3022
+ annotations: {
3023
+ readOnlyHint: true,
3024
+ destructiveHint: false,
3025
+ idempotentHint: true,
3026
+ openWorldHint: false
3027
+ }
3028
+ },
3029
+ {
3030
+ name: "package_get",
3031
+ title: "Get Package",
3032
+ description: 'Requires exact package name, do not guess names, use package_search first to find them. Returns schemas + hint texts + example summaries by default (lightweight). Use section parameter for full content: "hints" (with code blocks), "examples" (full in/out data), or "all".',
3033
+ inputSchema: {
3034
+ package: z16.string().min(1),
3035
+ version: z16.string().optional(),
3036
+ section: z16.enum(["hints", "examples", "all"]).optional()
3037
+ },
3038
+ annotations: {
3039
+ readOnlyHint: true,
3040
+ destructiveHint: false,
3041
+ idempotentHint: true,
3042
+ openWorldHint: true
3043
+ }
3044
+ },
3045
+ {
3046
+ name: "feedback",
3047
+ title: "Send Feedback",
3048
+ description: "Send feedback about walkerOS",
3049
+ inputSchema: {
3050
+ text: z16.string(),
3051
+ anonymous: z16.boolean().optional()
3052
+ },
3053
+ annotations: {
3054
+ readOnlyHint: false,
3055
+ destructiveHint: false,
3056
+ idempotentHint: false,
3057
+ openWorldHint: true
3058
+ }
3059
+ }
3060
+ ];
3061
+
3062
+ // src/index.ts
3063
+ function createToolHandlers(client) {
3064
+ const specs = [
3065
+ createAuthToolSpec(client),
3066
+ createProjectManageToolSpec(client),
3067
+ createFlowManageToolSpec(client),
3068
+ createDeployManageToolSpec(client),
3069
+ createFeedbackToolSpec(client),
3070
+ createFlowValidateToolSpec(),
3071
+ createFlowBundleToolSpec(),
3072
+ createFlowSimulateToolSpec(),
3073
+ createFlowPushToolSpec(),
3074
+ createFlowExamplesToolSpec(),
3075
+ createFlowLoadToolSpec(),
3076
+ createPackageSearchToolSpec(),
3077
+ createPackageGetToolSpec()
3078
+ ];
3079
+ return Object.fromEntries(specs.map((s) => [s.name, s]));
3080
+ }
3081
+ export {
3082
+ HttpToolClient,
3083
+ TOOL_DEFINITIONS,
3084
+ createStreamableHttpHandler,
3085
+ createToolHandlers,
3086
+ createWalkerOSMcpServer,
3087
+ flowCanvasResult,
3088
+ isFlowCanvasResult,
3089
+ redactNestedStrings,
3090
+ wrapUserData
3091
+ };
2137
3092
  //# sourceMappingURL=index.js.map