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