@wootsup/mcp 0.1.0-rc.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/dist/index.d.ts +19 -0
- package/dist/index.js +72 -6
- package/dist/index.js.map +1 -1
- package/dist/modules/apimapper/cache.d.ts +2 -2
- package/dist/modules/apimapper/cache.js +107 -25
- package/dist/modules/apimapper/cache.js.map +1 -1
- package/dist/modules/apimapper/client.d.ts +40 -0
- package/dist/modules/apimapper/client.js +82 -12
- package/dist/modules/apimapper/client.js.map +1 -1
- package/dist/modules/apimapper/connections-format.d.ts +51 -0
- package/dist/modules/apimapper/connections-format.js +261 -0
- package/dist/modules/apimapper/connections-format.js.map +1 -0
- package/dist/modules/apimapper/connections-trim.d.ts +82 -0
- package/dist/modules/apimapper/connections-trim.js +224 -0
- package/dist/modules/apimapper/connections-trim.js.map +1 -0
- package/dist/modules/apimapper/connections.d.ts +14 -2
- package/dist/modules/apimapper/connections.js +447 -143
- package/dist/modules/apimapper/connections.js.map +1 -1
- package/dist/modules/apimapper/credentials-format.d.ts +21 -0
- package/dist/modules/apimapper/credentials-format.js +145 -0
- package/dist/modules/apimapper/credentials-format.js.map +1 -0
- package/dist/modules/apimapper/credentials.d.ts +12 -2
- package/dist/modules/apimapper/credentials.js +253 -72
- package/dist/modules/apimapper/credentials.js.map +1 -1
- package/dist/modules/apimapper/diagnose.d.ts +54 -2
- package/dist/modules/apimapper/diagnose.js +193 -11
- package/dist/modules/apimapper/diagnose.js.map +1 -1
- package/dist/modules/apimapper/elicitation.d.ts +54 -0
- package/dist/modules/apimapper/elicitation.js +90 -0
- package/dist/modules/apimapper/elicitation.js.map +1 -0
- package/dist/modules/apimapper/flows-format.d.ts +50 -0
- package/dist/modules/apimapper/flows-format.js +318 -0
- package/dist/modules/apimapper/flows-format.js.map +1 -0
- package/dist/modules/apimapper/flows.d.ts +13 -2
- package/dist/modules/apimapper/flows.js +325 -118
- package/dist/modules/apimapper/flows.js.map +1 -1
- package/dist/modules/apimapper/gateway/advanced-tool.d.ts +9 -0
- package/dist/modules/apimapper/gateway/advanced-tool.js +214 -0
- package/dist/modules/apimapper/gateway/advanced-tool.js.map +1 -0
- package/dist/modules/apimapper/gateway/capturing-server.d.ts +81 -0
- package/dist/modules/apimapper/gateway/capturing-server.js +87 -0
- package/dist/modules/apimapper/gateway/capturing-server.js.map +1 -0
- package/dist/modules/apimapper/gateway/essentials.d.ts +4 -0
- package/dist/modules/apimapper/gateway/essentials.js +28 -0
- package/dist/modules/apimapper/gateway/essentials.js.map +1 -0
- package/dist/modules/apimapper/gateway/test-support.d.ts +17 -0
- package/dist/modules/apimapper/gateway/test-support.js +43 -0
- package/dist/modules/apimapper/gateway/test-support.js.map +1 -0
- package/dist/modules/apimapper/get-skill.d.ts +3 -3
- package/dist/modules/apimapper/get-skill.js +4 -2
- package/dist/modules/apimapper/get-skill.js.map +1 -1
- package/dist/modules/apimapper/graph-builder.js +1 -1
- package/dist/modules/apimapper/graph-builder.js.map +1 -1
- package/dist/modules/apimapper/graph.d.ts +2 -2
- package/dist/modules/apimapper/graph.js +165 -34
- package/dist/modules/apimapper/graph.js.map +1 -1
- package/dist/modules/apimapper/index.d.ts +17 -1
- package/dist/modules/apimapper/index.js +66 -17
- package/dist/modules/apimapper/index.js.map +1 -1
- package/dist/modules/apimapper/inspect.d.ts +3 -2
- package/dist/modules/apimapper/inspect.js +97 -13
- package/dist/modules/apimapper/inspect.js.map +1 -1
- package/dist/modules/apimapper/library.d.ts +2 -2
- package/dist/modules/apimapper/library.js +303 -60
- package/dist/modules/apimapper/library.js.map +1 -1
- package/dist/modules/apimapper/license-format.d.ts +22 -0
- package/dist/modules/apimapper/license-format.js +149 -0
- package/dist/modules/apimapper/license-format.js.map +1 -0
- package/dist/modules/apimapper/license.d.ts +16 -2
- package/dist/modules/apimapper/license.js +85 -37
- package/dist/modules/apimapper/license.js.map +1 -1
- package/dist/modules/apimapper/local-sources.d.ts +2 -2
- package/dist/modules/apimapper/local-sources.js +58 -30
- package/dist/modules/apimapper/local-sources.js.map +1 -1
- package/dist/modules/apimapper/misc.d.ts +30 -2
- package/dist/modules/apimapper/misc.js +129 -50
- package/dist/modules/apimapper/misc.js.map +1 -1
- package/dist/modules/apimapper/node-schema.d.ts +52 -0
- package/dist/modules/apimapper/node-schema.js +70 -2
- package/dist/modules/apimapper/node-schema.js.map +1 -1
- package/dist/modules/apimapper/normalizers.d.ts +1 -0
- package/dist/modules/apimapper/normalizers.js +51 -0
- package/dist/modules/apimapper/normalizers.js.map +1 -1
- package/dist/modules/apimapper/onboarding.d.ts +48 -2
- package/dist/modules/apimapper/onboarding.js +324 -17
- package/dist/modules/apimapper/onboarding.js.map +1 -1
- package/dist/modules/apimapper/read-cache.d.ts +31 -2
- package/dist/modules/apimapper/read-cache.js +20 -6
- package/dist/modules/apimapper/read-cache.js.map +1 -1
- package/dist/modules/apimapper/render/_shared.d.ts +24 -0
- package/dist/modules/apimapper/render/_shared.js +84 -0
- package/dist/modules/apimapper/render/_shared.js.map +1 -0
- package/dist/modules/apimapper/render/dag.d.ts +18 -0
- package/dist/modules/apimapper/render/dag.js +70 -0
- package/dist/modules/apimapper/render/dag.js.map +1 -0
- package/dist/modules/apimapper/render/index.d.ts +2 -0
- package/dist/modules/apimapper/render/index.js +112 -0
- package/dist/modules/apimapper/render/index.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/chart-bar.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/chart-bar.js +70 -0
- package/dist/modules/apimapper/render/renderers/chart-bar.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/chart-line.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/chart-line.js +71 -0
- package/dist/modules/apimapper/render/renderers/chart-line.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/diff.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/diff.js +154 -0
- package/dist/modules/apimapper/render/renderers/diff.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/flow-diagram.d.ts +1 -0
- package/dist/modules/apimapper/render/renderers/flow-diagram.js +180 -0
- package/dist/modules/apimapper/render/renderers/flow-diagram.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/json-tree.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/json-tree.js +87 -0
- package/dist/modules/apimapper/render/renderers/json-tree.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/schema-diagram.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/schema-diagram.js +83 -0
- package/dist/modules/apimapper/render/renderers/schema-diagram.js.map +1 -0
- package/dist/modules/apimapper/render/renderers/table.d.ts +2 -0
- package/dist/modules/apimapper/render/renderers/table.js +75 -0
- package/dist/modules/apimapper/render/renderers/table.js.map +1 -0
- package/dist/modules/apimapper/render/schemas.d.ts +23 -0
- package/dist/modules/apimapper/render/schemas.js +56 -0
- package/dist/modules/apimapper/render/schemas.js.map +1 -0
- package/dist/modules/apimapper/render/secret-masking.d.ts +5 -0
- package/dist/modules/apimapper/render/secret-masking.js +51 -0
- package/dist/modules/apimapper/render/secret-masking.js.map +1 -0
- package/dist/modules/apimapper/render/sidecar.d.ts +21 -0
- package/dist/modules/apimapper/render/sidecar.js +66 -0
- package/dist/modules/apimapper/render/sidecar.js.map +1 -0
- package/dist/modules/apimapper/render/token-cap.d.ts +21 -0
- package/dist/modules/apimapper/render/token-cap.js +57 -0
- package/dist/modules/apimapper/render/token-cap.js.map +1 -0
- package/dist/modules/apimapper/schema.d.ts +2 -2
- package/dist/modules/apimapper/schema.js +100 -32
- package/dist/modules/apimapper/schema.js.map +1 -1
- package/dist/modules/apimapper/settings-format.d.ts +23 -0
- package/dist/modules/apimapper/settings-format.js +135 -0
- package/dist/modules/apimapper/settings-format.js.map +1 -0
- package/dist/modules/apimapper/settings.d.ts +2 -2
- package/dist/modules/apimapper/settings.js +101 -40
- package/dist/modules/apimapper/settings.js.map +1 -1
- package/dist/modules/apimapper/skill-resources.d.ts +2 -2
- package/dist/modules/apimapper/skill-resources.js.map +1 -1
- package/dist/modules/apimapper/token-baseline.harness.d.ts +91 -0
- package/dist/modules/apimapper/token-baseline.harness.js +291 -0
- package/dist/modules/apimapper/token-baseline.harness.js.map +1 -0
- package/dist/modules/apimapper/toolslist-size.d.ts +55 -0
- package/dist/modules/apimapper/toolslist-size.js +190 -0
- package/dist/modules/apimapper/toolslist-size.js.map +1 -0
- package/dist/modules/apimapper/types.d.ts +23 -8
- package/dist/modules/apimapper/types.js +26 -1
- package/dist/modules/apimapper/types.js.map +1 -1
- package/dist/modules/apimapper/use-profile.d.ts +21 -0
- package/dist/modules/apimapper/use-profile.js +56 -2
- package/dist/modules/apimapper/use-profile.js.map +1 -1
- package/dist/modules/apimapper/workflows.d.ts +2 -2
- package/dist/modules/apimapper/workflows.js +143 -16
- package/dist/modules/apimapper/workflows.js.map +1 -1
- package/dist/platform/index.js +44 -5
- package/dist/platform/index.js.map +1 -1
- package/dist/setup-cli.d.ts +53 -0
- package/dist/setup-cli.js +135 -6
- package/dist/setup-cli.js.map +1 -1
- package/docs/architecture.md +1 -1
- package/docs/tools.md +1 -1
- package/manifest.json +12 -3
- package/package.json +9 -4
- package/skills/apimapper/SKILL.md +1 -1
- package/skills/apimapper/reference/render.md +132 -0
- package/skills/apimapper/reference/troubleshooting.md +1 -1
- package/skills/apimapper/reference/yootheme.md +1 -1
|
@@ -1,79 +1,88 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { formatResult,
|
|
2
|
+
import { formatResult, tableResult, errorResult, readOnly, creating, mutating, destructive, createProgressReporter, elicitChoice, } from "@getimo/mcp-toolkit";
|
|
3
3
|
import { request, hintFor } from "./client.js";
|
|
4
|
-
import {
|
|
4
|
+
import { toRows } from "./types.js";
|
|
5
|
+
import { nodeSchema, edgeSchema, filterByNameQuery } from "./node-schema.js";
|
|
5
6
|
import { unwrapEntity } from "./envelope.js";
|
|
6
|
-
|
|
7
|
+
import { ambiguityFallbackError, } from "./elicitation.js";
|
|
8
|
+
import { buildFlowVisualization, SIDECAR_RESPONSE_MAX_CHARS, } from "./render/sidecar.js";
|
|
9
|
+
import { FLOW_TABLE_COLUMNS, FLOW_COMPACT_COLUMNS, FLOW_LIST_NEXT_STEPS, isFlowCompiled, mapFlowRow, compactFlowRow, buildFlowDetail, buildFlowTraceTimeline, buildImportValidateDetail, } from "./flows-format.js";
|
|
10
|
+
/**
|
|
11
|
+
* Register the flow CRUD + compile + detect-schema + trace + export/import
|
|
12
|
+
* tools.
|
|
13
|
+
*
|
|
14
|
+
* @param server the tool registrar (essentials forward, rest captured).
|
|
15
|
+
* @param elicitation optional elicitation capability (the real McpServer, or
|
|
16
|
+
* any `{ server: { elicitInput } }`). Supplied only by the host wiring in
|
|
17
|
+
* index.ts. When omitted, `flow_compile` falls back to a structured error
|
|
18
|
+
* on genuine flow ambiguity instead of prompting.
|
|
19
|
+
*/
|
|
20
|
+
export function registerFlowTools(server, elicitation) {
|
|
7
21
|
// ── apimapper_flow_list ────────────────────────────────────────────
|
|
8
22
|
server.registerTool("apimapper_flow_list", {
|
|
9
23
|
title: "List Flows",
|
|
10
|
-
|
|
11
|
-
|
|
24
|
+
// rc.10 A5 (2026-05-19) — anti-thrash hint: AI was defaulting to
|
|
25
|
+
// limit:500 reflexively even though typical installs have <50 flows.
|
|
26
|
+
description: "List all API Mapper flows. The default limit of 100 already covers a typical install — " +
|
|
27
|
+
"most customers have 5-50 flows, increase the limit only if you have a specific reason. " +
|
|
28
|
+
"Use apimapper_flow_get for the full structure (nodes + edges) of a single flow." +
|
|
29
|
+
"\n\nExample:\n apimapper_flow_list({}) // returns up to 100 flows in one call\n" +
|
|
30
|
+
" apimapper_flow_list({ compiled: 'no' }) // filter to draft/incomplete flows",
|
|
12
31
|
inputSchema: {
|
|
13
32
|
source: z
|
|
14
33
|
.enum(["user", "demo", "library", "devtools", "all"])
|
|
15
34
|
.default("all")
|
|
16
35
|
.describe("Filter by origin"),
|
|
17
36
|
compiled: z.enum(["any", "yes", "no"]).default("any").describe("Filter by compile status"),
|
|
18
|
-
limit: z.number().min(1).max(500).default(100).describe("Max items (1-500)"),
|
|
37
|
+
limit: z.number().min(1).max(500).default(100).describe("Max items (1-500). Default 100 already covers a typical install — do not raise unless you have evidence the list is larger."),
|
|
38
|
+
// W1.18 (F-32) — case-insensitive substring filter applied
|
|
39
|
+
// in-memory AFTER the upstream fetch (no per-name REST call).
|
|
40
|
+
// Min 2 chars at the schema boundary blocks the single-char
|
|
41
|
+
// probe-thrash pattern observed in the Maria-walkthrough logs.
|
|
42
|
+
name_query: z
|
|
43
|
+
.string()
|
|
44
|
+
.min(2)
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Case-insensitive substring filter on flow name. Applied in-memory after the upstream fetch — does NOT change the REST query. Must be 2+ characters; single-char probes are blocked. Combine with `source`/`compiled` for narrower results."),
|
|
19
47
|
},
|
|
20
|
-
annotations: readOnly(),
|
|
21
|
-
}, async ({ source, compiled, limit }) => {
|
|
48
|
+
annotations: readOnly({ title: "List Flows", openWorld: true }),
|
|
49
|
+
}, async ({ source, compiled, limit, name_query }) => {
|
|
22
50
|
const r = await request("/flows");
|
|
23
51
|
if (!r.success) {
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}, true);
|
|
52
|
+
return errorResult({
|
|
53
|
+
message: r.error ?? "flow list failed",
|
|
54
|
+
code: r.errorCode ?? (r.status ? String(r.status) : undefined),
|
|
55
|
+
suggestion: hintFor(r.errorCode),
|
|
56
|
+
details: { source, compiled, limit, name_query },
|
|
57
|
+
});
|
|
31
58
|
}
|
|
32
59
|
let items = Array.isArray(r.data?.flows) ? r.data.flows : [];
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// downstream filtering + display logic uniform regardless of platform.
|
|
37
|
-
const isCompiledOf = (f) => {
|
|
38
|
-
if (typeof f.is_compiled === "boolean")
|
|
39
|
-
return f.is_compiled;
|
|
40
|
-
const raw = f;
|
|
41
|
-
if (typeof raw.compiledAt === "string" && raw.compiledAt.length > 0)
|
|
42
|
-
return true;
|
|
43
|
-
if (Array.isArray(raw.compiled) && raw.compiled.length > 0)
|
|
44
|
-
return true;
|
|
45
|
-
return false;
|
|
46
|
-
};
|
|
60
|
+
// `is_compiled` is derived from the REST shape (compiledAt / compiled[])
|
|
61
|
+
// by isFlowCompiled in flows-format.ts — shared between the filter and
|
|
62
|
+
// the row map so display + filtering stay uniform.
|
|
47
63
|
if (source !== "all")
|
|
48
64
|
items = items.filter((f) => f.source === source);
|
|
49
65
|
if (compiled === "yes")
|
|
50
|
-
items = items.filter((f) =>
|
|
66
|
+
items = items.filter((f) => isFlowCompiled(f));
|
|
51
67
|
if (compiled === "no")
|
|
52
|
-
items = items.filter((f) => !
|
|
68
|
+
items = items.filter((f) => !isFlowCompiled(f));
|
|
69
|
+
// W1.18 (F-32) — filter BEFORE slice so limit applies to the matched
|
|
70
|
+
// subset, not the haystack. e.g. limit=10 + name_query='pexels'
|
|
71
|
+
// returns up to 10 pexels-matching flows, not the first 10 flows
|
|
72
|
+
// filtered to pexels-matching.
|
|
73
|
+
items = filterByNameQuery(items, name_query);
|
|
53
74
|
items = items.slice(0, limit);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// Width 36 accommodates 30+ char IDs (e.g. legacy template
|
|
64
|
-
// flows like flow_calendly_jla_229fcbce95b7 = 30 chars).
|
|
65
|
-
// Truncation produces orphan IDs that downstream tools fail
|
|
66
|
-
// to resolve via flow_get. The +6 headroom matches the
|
|
67
|
-
// longest historical IDs without bloating the table.
|
|
68
|
-
{ key: "id", label: "ID", width: 36 },
|
|
69
|
-
{ key: "name", label: "NAME", width: 32 },
|
|
70
|
-
{ key: "source", label: "SRC", width: 10 },
|
|
71
|
-
{ key: "nodes", label: "N", width: 4 },
|
|
72
|
-
{ key: "compiled", label: "C", width: 3 },
|
|
73
|
-
{ key: "types", label: "TYPES", width: 36 },
|
|
74
|
-
],
|
|
75
|
+
// W3.1 — tableResult: ASCII table for the LLM + typed DataTable payload
|
|
76
|
+
// for the Rich Card. T1 (W3.2): explicit compactColumns/compactMap drop
|
|
77
|
+
// NODES/TYPES at 21+ rows. IA-7: id stays llmOnly. IA-10: the footer
|
|
78
|
+
// carries the next-step guidance.
|
|
79
|
+
return tableResult(toRows(items), {
|
|
80
|
+
columns: FLOW_TABLE_COLUMNS,
|
|
81
|
+
compactColumns: FLOW_COMPACT_COLUMNS,
|
|
82
|
+
map: mapFlowRow,
|
|
83
|
+
compactMap: compactFlowRow,
|
|
75
84
|
header: (n) => `${n} flows`,
|
|
76
|
-
footer:
|
|
85
|
+
footer: FLOW_LIST_NEXT_STEPS,
|
|
77
86
|
});
|
|
78
87
|
});
|
|
79
88
|
// ── apimapper_flow_get ─────────────────────────────────────────────
|
|
@@ -84,19 +93,34 @@ export function registerFlowTools(server) {
|
|
|
84
93
|
inputSchema: {
|
|
85
94
|
id: z.string().describe('Flow ID (e.g., "flow_aCfOEpwCtVZnPFwPOwO61"). Use apimapper_flow_list.'),
|
|
86
95
|
},
|
|
87
|
-
annotations: readOnly(),
|
|
96
|
+
annotations: readOnly({ title: "Get Flow", openWorld: true }),
|
|
88
97
|
}, async ({ id }) => {
|
|
89
98
|
// PHP wraps via fromControllerResponse($response, 'flow') →
|
|
90
99
|
// `{success:true, flow:{…}}`. Audit: F-A2-01.
|
|
91
100
|
const r = await request(`/flows/${encodeURIComponent(id)}`);
|
|
92
101
|
if (!r.success) {
|
|
93
|
-
return
|
|
102
|
+
return errorResult({
|
|
103
|
+
message: r.error ?? "flow get failed",
|
|
104
|
+
code: r.errorCode ?? (r.status ? String(r.status) : undefined),
|
|
105
|
+
suggestion: hintFor(r.errorCode),
|
|
106
|
+
details: { id },
|
|
107
|
+
});
|
|
94
108
|
}
|
|
95
109
|
const flow = unwrapEntity(r.data, "flow");
|
|
96
110
|
if (!flow || Object.keys(flow).length === 0) {
|
|
97
|
-
return
|
|
111
|
+
return errorResult({
|
|
112
|
+
message: "flow not found",
|
|
113
|
+
code: r.status ? String(r.status) : "not_found",
|
|
114
|
+
suggestion: hintFor("not_found"),
|
|
115
|
+
details: { id },
|
|
116
|
+
});
|
|
98
117
|
}
|
|
99
|
-
|
|
118
|
+
// W3.1 — detailResult: grouped key-value detail for the Rich Card.
|
|
119
|
+
// IA-7: opaque IDs are copyable code entries. IA-10: a dedicated
|
|
120
|
+
// "Next steps" group carries the follow-up calls. The W2 flow
|
|
121
|
+
// visualization (ASCII diagram) is routed via detailResult.appendText
|
|
122
|
+
// so it reaches the LLM output without cluttering the Rich Card.
|
|
123
|
+
return buildFlowDetail(id, flow);
|
|
100
124
|
});
|
|
101
125
|
// ── apimapper_flow_create ──────────────────────────────────────────
|
|
102
126
|
server.registerTool("apimapper_flow_create", {
|
|
@@ -116,15 +140,17 @@ export function registerFlowTools(server) {
|
|
|
116
140
|
'source.connectionId, local-source.contentType, filter.conditions, ' +
|
|
117
141
|
'transform.expression, merge.strategy ∈ {concat, join}.'),
|
|
118
142
|
edges: z
|
|
119
|
-
.array(
|
|
120
|
-
.describe('Edge array. Each: {id
|
|
121
|
-
'
|
|
143
|
+
.array(edgeSchema)
|
|
144
|
+
.describe('Edge array. Each: {id?, source, target, sourceHandle?, targetHandle?}. ' +
|
|
145
|
+
'source + target are required non-empty node-ids. ' +
|
|
146
|
+
'For merge nodes: targetHandle="input-0" (first input) or "input-1" (second). ' +
|
|
147
|
+
'Legacy "input-A"/"input-B" still accepted for backward-compat.'),
|
|
122
148
|
viewport: z
|
|
123
149
|
.object({ x: z.number(), y: z.number(), zoom: z.number() })
|
|
124
150
|
.optional()
|
|
125
151
|
.describe('React-Flow viewport (e.g., {"x":0,"y":0,"zoom":1})'),
|
|
126
152
|
},
|
|
127
|
-
annotations: creating(),
|
|
153
|
+
annotations: creating({ title: "Create Flow", openWorld: true }),
|
|
128
154
|
}, async (input) => {
|
|
129
155
|
// PHP fromControllerResponse(_, 'flow', 201) → {success, flow:{…}}.
|
|
130
156
|
// Audit: F-A2-02.
|
|
@@ -133,23 +159,39 @@ export function registerFlowTools(server) {
|
|
|
133
159
|
body: JSON.stringify(input),
|
|
134
160
|
});
|
|
135
161
|
if (!r.success) {
|
|
136
|
-
return
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}, true);
|
|
162
|
+
return errorResult({
|
|
163
|
+
message: r.error ?? "flow create failed",
|
|
164
|
+
code: r.errorCode ?? (r.status ? String(r.status) : undefined),
|
|
165
|
+
suggestion: hintFor(r.errorCode),
|
|
166
|
+
details: { name: input.name, node_count: input.nodes.length },
|
|
167
|
+
});
|
|
143
168
|
}
|
|
144
169
|
const flow = unwrapEntity(r.data, "flow");
|
|
170
|
+
// Sidecar: flow_create always has input.nodes + input.edges
|
|
171
|
+
// (Zod-validated), so the visualization always renders unless
|
|
172
|
+
// APIMAPPER_AUTO_VISUALIZE_FLOWS="false". Additive + defensive —
|
|
173
|
+
// a render failure returns null and is omitted silently.
|
|
174
|
+
const viz = buildFlowVisualization({
|
|
175
|
+
nodes: input.nodes,
|
|
176
|
+
edges: input.edges,
|
|
177
|
+
id: flow?.id,
|
|
178
|
+
name: flow?.name,
|
|
179
|
+
});
|
|
145
180
|
return formatResult({
|
|
146
181
|
created: true,
|
|
147
182
|
id: flow?.id,
|
|
148
183
|
name: flow?.name,
|
|
149
184
|
node_count: flow?.node_count,
|
|
150
185
|
is_compiled: flow?.is_compiled ?? false,
|
|
151
|
-
|
|
152
|
-
|
|
186
|
+
...(viz && { _visualization: viz }),
|
|
187
|
+
// F-3.1 (Pass-1 consolidation): IA-10 uniformity — next_steps[]
|
|
188
|
+
// (array, even if single-element) matches the structured
|
|
189
|
+
// next-step convention used by the list-result footers and the
|
|
190
|
+
// detailResult builders. Flat `next: string` was the holdout.
|
|
191
|
+
next_steps: ["Call apimapper_flow_compile to publish to YOOtheme."],
|
|
192
|
+
}, false,
|
|
193
|
+
// Headroom for the sidecar — see SIDECAR_RESPONSE_MAX_CHARS.
|
|
194
|
+
{ maxChars: SIDECAR_RESPONSE_MAX_CHARS });
|
|
153
195
|
});
|
|
154
196
|
// ── apimapper_flow_update ──────────────────────────────────────────
|
|
155
197
|
server.registerTool("apimapper_flow_update", {
|
|
@@ -163,7 +205,7 @@ export function registerFlowTools(server) {
|
|
|
163
205
|
.record(z.string(), z.unknown())
|
|
164
206
|
.describe('Fields to patch (e.g., {"nodes":[...updated nodes...]} or {"name":"Renamed"})'),
|
|
165
207
|
},
|
|
166
|
-
annotations: mutating(),
|
|
208
|
+
annotations: mutating({ title: "Update Flow", openWorld: true }),
|
|
167
209
|
}, async ({ id, patch }) => {
|
|
168
210
|
// PHP fromControllerResponse(_, 'flow') → {success, flow:{…}}. On 409 the
|
|
169
211
|
// body shape is {currentVersion, error, code}. Audit: F-A2-03.
|
|
@@ -176,24 +218,44 @@ export function registerFlowTools(server) {
|
|
|
176
218
|
// re-read + retry without a separate fetch.
|
|
177
219
|
const envelope = (r.data && typeof r.data === "object" ? r.data : {});
|
|
178
220
|
const currentVersion = typeof envelope.currentVersion === "number" ? envelope.currentVersion : undefined;
|
|
179
|
-
return
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
currentVersion,
|
|
184
|
-
|
|
185
|
-
hint: hintFor(r.errorCode),
|
|
186
|
-
}, true);
|
|
221
|
+
return errorResult({
|
|
222
|
+
message: r.error ?? "flow update failed",
|
|
223
|
+
code: r.errorCode ?? (r.status ? String(r.status) : undefined),
|
|
224
|
+
suggestion: hintFor(r.errorCode),
|
|
225
|
+
details: { id, ...(currentVersion !== undefined ? { currentVersion } : {}) },
|
|
226
|
+
});
|
|
187
227
|
}
|
|
188
228
|
const flow = unwrapEntity(r.data, "flow");
|
|
229
|
+
// Sidecar: flow_update's patch is a generic record. The
|
|
230
|
+
// visualization is CONDITIONAL — render only when the patch
|
|
231
|
+
// carries a full graph (both nodes and edges as arrays). A
|
|
232
|
+
// name-only / metadata-only patch has no graph to draw.
|
|
233
|
+
const patchObj = (patch && typeof patch === "object" ? patch : {});
|
|
234
|
+
const patchHasGraph = Array.isArray(patchObj.nodes) && Array.isArray(patchObj.edges);
|
|
235
|
+
const viz = patchHasGraph
|
|
236
|
+
? buildFlowVisualization({
|
|
237
|
+
nodes: patchObj.nodes,
|
|
238
|
+
edges: patchObj.edges,
|
|
239
|
+
id: flow?.id,
|
|
240
|
+
name: flow?.name,
|
|
241
|
+
})
|
|
242
|
+
: null;
|
|
189
243
|
return formatResult({
|
|
190
244
|
updated: true,
|
|
191
245
|
id: flow?.id,
|
|
192
246
|
name: flow?.name,
|
|
193
247
|
version: flow?.version,
|
|
194
248
|
is_compiled: flow?.is_compiled,
|
|
195
|
-
|
|
196
|
-
|
|
249
|
+
...(viz && { _visualization: viz }),
|
|
250
|
+
// F-3.1 (Pass-1 consolidation): IA-10 uniformity — flat next has
|
|
251
|
+
// been migrated to next_steps[]. Empty array when no recompile is
|
|
252
|
+
// needed (so the key shape stays uniform across all paths).
|
|
253
|
+
next_steps: flow?.is_compiled
|
|
254
|
+
? ["Compile likely invalidated — call apimapper_flow_compile."]
|
|
255
|
+
: [],
|
|
256
|
+
}, false,
|
|
257
|
+
// Headroom for the (conditional) sidecar — see SIDECAR_RESPONSE_MAX_CHARS.
|
|
258
|
+
{ maxChars: SIDECAR_RESPONSE_MAX_CHARS });
|
|
197
259
|
});
|
|
198
260
|
// ── apimapper_flow_delete ──────────────────────────────────────────
|
|
199
261
|
server.registerTool("apimapper_flow_delete", {
|
|
@@ -208,7 +270,7 @@ export function registerFlowTools(server) {
|
|
|
208
270
|
.default(false)
|
|
209
271
|
.describe("Must be true to execute. On confirm:false, returns a preview."),
|
|
210
272
|
},
|
|
211
|
-
annotations: destructive(),
|
|
273
|
+
annotations: destructive({ title: "Delete Flow", openWorld: true }),
|
|
212
274
|
}, async ({ id, confirm }) => {
|
|
213
275
|
if (!confirm) {
|
|
214
276
|
// PHP fromControllerResponse(_, 'flow') wraps as {success, flow:{…}}.
|
|
@@ -232,7 +294,12 @@ export function registerFlowTools(server) {
|
|
|
232
294
|
}
|
|
233
295
|
const r = await request(`/flows/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
234
296
|
if (!r.success) {
|
|
235
|
-
return
|
|
297
|
+
return errorResult({
|
|
298
|
+
message: r.error ?? "flow delete failed",
|
|
299
|
+
code: r.errorCode ?? (r.status ? String(r.status) : undefined),
|
|
300
|
+
suggestion: hintFor(r.errorCode),
|
|
301
|
+
details: { id },
|
|
302
|
+
});
|
|
236
303
|
}
|
|
237
304
|
return formatResult({ deleted: true, id }, false, { maxChars: 1500 });
|
|
238
305
|
});
|
|
@@ -240,26 +307,94 @@ export function registerFlowTools(server) {
|
|
|
240
307
|
server.registerTool("apimapper_flow_compile", {
|
|
241
308
|
title: "Compile Flow",
|
|
242
309
|
description: "Compile flow into executable pipeline (validation + topo sort + step generation + schema). " +
|
|
243
|
-
"Required before flow appears in YOOtheme." +
|
|
244
|
-
"
|
|
310
|
+
"Required before flow appears in YOOtheme. " +
|
|
311
|
+
"If `id` is omitted, the flow is resolved automatically — when exactly one flow exists " +
|
|
312
|
+
"it is compiled; when several exist the user is asked to pick." +
|
|
313
|
+
"\n\nExample:\n apimapper_flow_compile({ id: 'flow_Z2fLg70M84' })\n" +
|
|
314
|
+
" apimapper_flow_compile({}) // resolves the flow when only one exists",
|
|
245
315
|
inputSchema: {
|
|
246
|
-
id: z
|
|
316
|
+
id: z
|
|
317
|
+
.string()
|
|
318
|
+
.optional()
|
|
319
|
+
.describe("Flow ID. Use apimapper_flow_list to find. Omit to resolve automatically " +
|
|
320
|
+
"when the install has a single flow."),
|
|
247
321
|
},
|
|
248
|
-
annotations: mutating(),
|
|
249
|
-
}, async ({ id }) => {
|
|
322
|
+
annotations: mutating({ title: "Compile Flow", openWorld: true }),
|
|
323
|
+
}, async ({ id }, extra) => {
|
|
324
|
+
// W3.5 — coarse progress side-channel. `null` when the caller sent no
|
|
325
|
+
// progressToken; `progress?.report(...)` then no-ops.
|
|
326
|
+
const progress = extra ? createProgressReporter(extra) : null;
|
|
327
|
+
// W3.6 — resolve the flow when `id` is omitted. Exactly 1 flow →
|
|
328
|
+
// auto-pick; >1 → elicitChoice; 0 → structured error; elicitChoice
|
|
329
|
+
// null (unsupported client / declined) → structured candidate-list
|
|
330
|
+
// error. Done BEFORE the first progress report so a discovery round
|
|
331
|
+
// trip is not mistaken for compile progress.
|
|
332
|
+
let resolvedId = id;
|
|
333
|
+
if (!resolvedId) {
|
|
334
|
+
const lr = await request("/flows");
|
|
335
|
+
if (!lr.success) {
|
|
336
|
+
return errorResult({
|
|
337
|
+
message: lr.error ?? "flow lookup failed",
|
|
338
|
+
code: lr.errorCode ?? (lr.status ? String(lr.status) : undefined),
|
|
339
|
+
suggestion: hintFor(lr.errorCode),
|
|
340
|
+
details: {},
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const flows = Array.isArray(lr.data?.flows) ? lr.data.flows : [];
|
|
344
|
+
if (flows.length === 0) {
|
|
345
|
+
return errorResult({
|
|
346
|
+
message: "No flows exist to compile.",
|
|
347
|
+
code: "flow_not_found",
|
|
348
|
+
suggestion: "Create one with apimapper_flow_create, then compile it.",
|
|
349
|
+
details: {},
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if (flows.length === 1) {
|
|
353
|
+
resolvedId = flows[0].id;
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
const candidates = flows.map((f) => ({
|
|
357
|
+
id: f.id,
|
|
358
|
+
label: f.name,
|
|
359
|
+
}));
|
|
360
|
+
const picked = elicitation
|
|
361
|
+
? await elicitChoice(elicitation, "Several flows exist. Pick the flow to compile.", candidates.map((c) => c.id))
|
|
362
|
+
: null;
|
|
363
|
+
if (picked === null) {
|
|
364
|
+
return ambiguityFallbackError({
|
|
365
|
+
code: "flow_ambiguous",
|
|
366
|
+
paramName: "id",
|
|
367
|
+
what: "flows",
|
|
368
|
+
candidates,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
resolvedId = picked;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
await progress?.report(0, 1, "Compiling flow…");
|
|
250
375
|
// PHP success body: {compiled: <CompiledFlow>, flowId: <id>}. Errors are
|
|
251
376
|
// surfaced via HandlerResult::error(), not inside the 200 body. Audit:
|
|
252
377
|
// F-A2-05 — the previous `errors[]` + `is_compiled===false` branch was
|
|
253
378
|
// dead code.
|
|
254
|
-
const r = await request(`/flows/${encodeURIComponent(
|
|
379
|
+
const r = await request(`/flows/${encodeURIComponent(resolvedId)}/compile`, { method: "POST" });
|
|
255
380
|
if (!r.success) {
|
|
256
|
-
return
|
|
381
|
+
return errorResult({
|
|
382
|
+
message: r.error ?? "flow compile failed",
|
|
383
|
+
code: r.errorCode ?? (r.status ? String(r.status) : undefined),
|
|
384
|
+
suggestion: hintFor(r.errorCode),
|
|
385
|
+
details: { id: resolvedId },
|
|
386
|
+
});
|
|
257
387
|
}
|
|
388
|
+
await progress?.report(1, 1, "Flow compiled");
|
|
258
389
|
const dataObj = r.data && typeof r.data === "object" ? r.data : {};
|
|
259
390
|
return formatResult({
|
|
260
391
|
compiled: true,
|
|
261
|
-
id: dataObj.flowId ??
|
|
262
|
-
|
|
392
|
+
id: dataObj.flowId ?? resolvedId,
|
|
393
|
+
// F-3.1 (Pass-1 consolidation): IA-10 uniformity — next_steps[]
|
|
394
|
+
// matches the structured shape used by other tools.
|
|
395
|
+
next_steps: [
|
|
396
|
+
"Source now appears in YOOtheme dropdown. Use apimapper_graph_preview to test items.",
|
|
397
|
+
],
|
|
263
398
|
}, false, { maxChars: 2500 });
|
|
264
399
|
});
|
|
265
400
|
// ── apimapper_flow_detect_schema ───────────────────────────────────
|
|
@@ -271,15 +406,30 @@ export function registerFlowTools(server) {
|
|
|
271
406
|
inputSchema: {
|
|
272
407
|
id: z.string().describe("Flow ID. Use apimapper_flow_list to find."),
|
|
273
408
|
},
|
|
274
|
-
annotations: mutating(),
|
|
275
|
-
}, async ({ id }) => {
|
|
276
|
-
//
|
|
409
|
+
annotations: mutating({ title: "Detect Flow Schema", openWorld: true }),
|
|
410
|
+
}, async ({ id }, extra) => {
|
|
411
|
+
// W3.5 — coarse progress side-channel. `null` when the caller sent no
|
|
412
|
+
// progressToken; `progress?.report(...)` then no-ops.
|
|
413
|
+
const progress = extra ? createProgressReporter(extra) : null;
|
|
414
|
+
await progress?.report(0, 1, "Sampling upstream API to detect schema…");
|
|
415
|
+
// PHP success body: {schema:{fields:[...], …}, sourceConnectionId,
|
|
416
|
+
// persisted, persist_error?}.
|
|
417
|
+
// H-2 fix (2026-05-27): backend now persists detected schema onto the
|
|
418
|
+
// output node so the next compile finds non-empty fields. `persisted`
|
|
419
|
+
// flag surfaces whether the write actually happened.
|
|
277
420
|
// Audit: F-A2-06.
|
|
278
421
|
const r = await request(`/flows/${encodeURIComponent(id)}/detect-schema`, { method: "POST" });
|
|
279
422
|
if (!r.success) {
|
|
280
|
-
return
|
|
423
|
+
return errorResult({
|
|
424
|
+
message: r.error ?? "flow detect-schema failed",
|
|
425
|
+
code: r.errorCode ?? (r.status ? String(r.status) : undefined),
|
|
426
|
+
suggestion: hintFor(r.errorCode),
|
|
427
|
+
details: { id },
|
|
428
|
+
});
|
|
281
429
|
}
|
|
430
|
+
await progress?.report(1, 1, "Schema detected");
|
|
282
431
|
const fields = Array.isArray(r.data?.schema?.fields) ? r.data.schema.fields : [];
|
|
432
|
+
const persisted = r.data?.persisted === true;
|
|
283
433
|
return formatResult({
|
|
284
434
|
detected: true,
|
|
285
435
|
id,
|
|
@@ -287,6 +437,16 @@ export function registerFlowTools(server) {
|
|
|
287
437
|
field_count: fields.length,
|
|
288
438
|
fields: fields.slice(0, 50),
|
|
289
439
|
schema: r.data?.schema,
|
|
440
|
+
persisted,
|
|
441
|
+
...(r.data?.persist_error ? { persist_error: r.data.persist_error } : {}),
|
|
442
|
+
next_steps: persisted
|
|
443
|
+
? [
|
|
444
|
+
"Schema persisted to flow. Call apimapper_flow_compile next to publish.",
|
|
445
|
+
]
|
|
446
|
+
: [
|
|
447
|
+
"Schema detected but NOT persisted (no output node yet, or save failed). " +
|
|
448
|
+
"Add an output node and call detect again, or inspect persist_error.",
|
|
449
|
+
],
|
|
290
450
|
}, false, { maxChars: 6000 });
|
|
291
451
|
});
|
|
292
452
|
// ── apimapper_flow_trace ───────────────────────────────────────────
|
|
@@ -298,43 +458,74 @@ export function registerFlowTools(server) {
|
|
|
298
458
|
inputSchema: {
|
|
299
459
|
id: z.string().describe("Flow ID. Use apimapper_flow_list to find."),
|
|
300
460
|
},
|
|
301
|
-
annotations: readOnly(),
|
|
461
|
+
annotations: readOnly({ title: "Trace Flow", openWorld: true }),
|
|
302
462
|
}, async ({ id }) => {
|
|
303
463
|
// PHP success body: {trace: {steps: [...], total_ms}}. Audit: F-A2-07.
|
|
304
464
|
const r = await request(`/flows/${encodeURIComponent(id)}/trace`);
|
|
305
465
|
if (!r.success) {
|
|
306
|
-
return
|
|
466
|
+
return errorResult({
|
|
467
|
+
message: r.error ?? "flow trace failed",
|
|
468
|
+
code: r.errorCode ?? (r.status ? String(r.status) : undefined),
|
|
469
|
+
suggestion: hintFor(r.errorCode),
|
|
470
|
+
details: { id },
|
|
471
|
+
});
|
|
307
472
|
}
|
|
308
473
|
const trace = r.data?.trace;
|
|
309
474
|
const steps = Array.isArray(trace?.steps) ? trace.steps : [];
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}, false, { maxChars: 6000 });
|
|
475
|
+
// W3.1 — timelineResult: the trace's per-step timing maps onto a
|
|
476
|
+
// chronological event timeline (display="event-timeline"). The step
|
|
477
|
+
// cap is preserved (50) so a pathological trace cannot bloat the
|
|
478
|
+
// response. buildFlowTraceTimeline does the defensive field reads.
|
|
479
|
+
return buildFlowTraceTimeline(id, steps.slice(0, 50), trace?.total_ms);
|
|
316
480
|
});
|
|
317
481
|
// ── apimapper_flow_export ──────────────────────────────────────────
|
|
482
|
+
// F-24 (W1.13): the export bundle contains connection REFERENCES only —
|
|
483
|
+
// never the linked credentials. Surface that explicitly in the response
|
|
484
|
+
// so downstream agents/customers cannot fatally assume "flow backup" =
|
|
485
|
+
// "credentials backup". The disclaimer is also advertised at tool
|
|
486
|
+
// registration time so AI clients see it without invoking.
|
|
487
|
+
const FLOW_EXPORT_CREDS_DISCLAIMER = "Credentials are NOT included in this bundle. The importing site must " +
|
|
488
|
+
"re-link credentials via apimapper_credential_create / apimapper_oauth_authorize_begin " +
|
|
489
|
+
"or via Admin-UI Credentials tab. Connection references in the bundle preserve the " +
|
|
490
|
+
"credential SHAPE (auth_type) but contain no secrets.";
|
|
318
491
|
server.registerTool("apimapper_flow_export", {
|
|
319
492
|
title: "Export Flow",
|
|
320
|
-
description: "Export a flow as a portable
|
|
493
|
+
description: "Export a flow as a portable bundle for import on another site. " +
|
|
494
|
+
"Response includes `credentials_not_exported: true` and a `warning` string " +
|
|
495
|
+
"— credentials are NOT part of the bundle for security; the importing site " +
|
|
496
|
+
"must re-link them via apimapper_credential_create / apimapper_oauth_authorize_begin." +
|
|
321
497
|
"\n\nExample:\n apimapper_flow_export({ id: 'flow_Z2fLg70M84' })",
|
|
322
498
|
inputSchema: {
|
|
323
499
|
id: z.string().describe("Flow ID. Use apimapper_flow_list to find."),
|
|
324
500
|
},
|
|
325
|
-
annotations: readOnly(),
|
|
501
|
+
annotations: readOnly({ title: "Export Flow", openWorld: true }),
|
|
326
502
|
}, async ({ id }) => {
|
|
327
503
|
// PHP wraps as `{export: {version, flow, connectionReferences, …}}`. Wave
|
|
328
504
|
// 1C may rename `export` → `data` for round-trip symmetry; accept both
|
|
329
505
|
// shapes so the round-trip works regardless of which side ships first.
|
|
330
506
|
// Audit: F-A2-08.
|
|
331
|
-
|
|
507
|
+
// Defense-in-depth (A5-P2 / W1.26 follow-up): the PHP backend is expected
|
|
508
|
+
// to strip credentials before export, but if that regresses, secrets must
|
|
509
|
+
// not land in the LLM context. `sanitize: true` routes the response
|
|
510
|
+
// through `credential-sanitizer.ts` (SECRET_KEYS covers auth_data /
|
|
511
|
+
// bearer_token / refresh_token / client_secret / etc.). Mirrors the
|
|
512
|
+
// belt-and-braces pattern at credentials.ts (credential_list / get).
|
|
513
|
+
const r = await request(`/flows/${encodeURIComponent(id)}/export`, {}, { sanitize: true });
|
|
332
514
|
if (!r.success) {
|
|
333
|
-
return
|
|
515
|
+
return errorResult({
|
|
516
|
+
message: r.error ?? "flow export failed",
|
|
517
|
+
code: r.errorCode ?? (r.status ? String(r.status) : undefined),
|
|
518
|
+
suggestion: hintFor(r.errorCode),
|
|
519
|
+
details: { id },
|
|
520
|
+
});
|
|
334
521
|
}
|
|
335
522
|
const envelope = (r.data && typeof r.data === "object" ? r.data : {});
|
|
336
523
|
const bundle = envelope.data ?? envelope.export ?? envelope;
|
|
337
|
-
return formatResult(
|
|
524
|
+
return formatResult({
|
|
525
|
+
bundle: bundle ?? {},
|
|
526
|
+
credentials_not_exported: true,
|
|
527
|
+
warning: FLOW_EXPORT_CREDS_DISCLAIMER,
|
|
528
|
+
}, false, { maxChars: 12000 });
|
|
338
529
|
});
|
|
339
530
|
// ── apimapper_flow_import_validate ─────────────────────────────────
|
|
340
531
|
server.registerTool("apimapper_flow_import_validate", {
|
|
@@ -347,7 +538,7 @@ export function registerFlowTools(server) {
|
|
|
347
538
|
.record(z.string(), z.unknown())
|
|
348
539
|
.describe("Flow bundle JSON (from apimapper_flow_export or external source)"),
|
|
349
540
|
},
|
|
350
|
-
annotations: readOnly(),
|
|
541
|
+
annotations: readOnly({ title: "Validate Flow Import", openWorld: true }),
|
|
351
542
|
}, async ({ bundle }) => {
|
|
352
543
|
// PHP valid body: {valid, flowName, exportedFrom, connectionReferences}.
|
|
353
544
|
// No `issues`/`warnings` keys — invalid path squashes errors to a single
|
|
@@ -355,14 +546,25 @@ export function registerFlowTools(server) {
|
|
|
355
546
|
// Audit: F-A2-09.
|
|
356
547
|
const r = await request("/flows/import/validate", { method: "POST", body: JSON.stringify(bundle) });
|
|
357
548
|
if (!r.success) {
|
|
358
|
-
return
|
|
549
|
+
return errorResult({
|
|
550
|
+
message: r.error ?? "flow import-validate failed",
|
|
551
|
+
code: r.errorCode ?? (r.status ? String(r.status) : undefined),
|
|
552
|
+
suggestion: hintFor(r.errorCode),
|
|
553
|
+
details: {},
|
|
554
|
+
});
|
|
359
555
|
}
|
|
360
|
-
|
|
556
|
+
// W3.1 — detailResult: the validation result is a flat envelope
|
|
557
|
+
// ({valid, flowName, exportedFrom, connectionReferences}). The
|
|
558
|
+
// connection-reference array flattens to a count + per-item entries.
|
|
559
|
+
// IA-10: a "Next steps" group routes the AI to import or fix.
|
|
560
|
+
return buildImportValidateDetail({
|
|
361
561
|
valid: r.data?.valid ?? true,
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
562
|
+
flowName: r.data?.flowName,
|
|
563
|
+
exportedFrom: r.data?.exportedFrom,
|
|
564
|
+
connectionReferences: Array.isArray(r.data?.connectionReferences)
|
|
565
|
+
? r.data.connectionReferences
|
|
566
|
+
: [],
|
|
567
|
+
});
|
|
366
568
|
});
|
|
367
569
|
// ── apimapper_flow_import ──────────────────────────────────────────
|
|
368
570
|
server.registerTool("apimapper_flow_import", {
|
|
@@ -376,7 +578,7 @@ export function registerFlowTools(server) {
|
|
|
376
578
|
.describe("Flow bundle JSON (from apimapper_flow_export or external source)"),
|
|
377
579
|
rename_to: z.string().optional().describe("Optional override for imported flow name"),
|
|
378
580
|
},
|
|
379
|
-
annotations: creating(),
|
|
581
|
+
annotations: creating({ title: "Import Flow", openWorld: true }),
|
|
380
582
|
}, async ({ bundle, rename_to }) => {
|
|
381
583
|
// Server reads `$exportData = $data['data'] ?? $data` and then
|
|
382
584
|
// `$exportData['flow']['name']`. Mutate bundle accordingly.
|
|
@@ -398,7 +600,12 @@ export function registerFlowTools(server) {
|
|
|
398
600
|
body: JSON.stringify(body),
|
|
399
601
|
});
|
|
400
602
|
if (!r.success) {
|
|
401
|
-
return
|
|
603
|
+
return errorResult({
|
|
604
|
+
message: r.error ?? "flow import failed",
|
|
605
|
+
code: r.errorCode ?? (r.status ? String(r.status) : undefined),
|
|
606
|
+
suggestion: hintFor(r.errorCode),
|
|
607
|
+
details: { rename_to },
|
|
608
|
+
});
|
|
402
609
|
}
|
|
403
610
|
const flow = unwrapEntity(r.data, "flow");
|
|
404
611
|
return formatResult({ imported: true, id: flow?.id, name: flow?.name }, false, { maxChars: 2000 });
|