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