@wootsup/mcp 0.3.0 → 0.4.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.
Files changed (141) hide show
  1. package/CHANGELOG.md +14 -5
  2. package/dist/catalog/build-catalog.d.ts +31 -0
  3. package/dist/catalog/build-catalog.js +68 -0
  4. package/dist/catalog/build-catalog.js.map +1 -0
  5. package/dist/index.js +37 -5
  6. package/dist/index.js.map +1 -1
  7. package/dist/modules/apimapper/auto-layout.d.ts +21 -0
  8. package/dist/modules/apimapper/auto-layout.js +54 -0
  9. package/dist/modules/apimapper/auto-layout.js.map +1 -0
  10. package/dist/modules/apimapper/client.d.ts +54 -4
  11. package/dist/modules/apimapper/client.js +145 -14
  12. package/dist/modules/apimapper/client.js.map +1 -1
  13. package/dist/modules/apimapper/connections-format.d.ts +31 -1
  14. package/dist/modules/apimapper/connections-format.js +97 -5
  15. package/dist/modules/apimapper/connections-format.js.map +1 -1
  16. package/dist/modules/apimapper/connections.d.ts +9 -7
  17. package/dist/modules/apimapper/connections.js +225 -58
  18. package/dist/modules/apimapper/connections.js.map +1 -1
  19. package/dist/modules/apimapper/credentials.js +86 -14
  20. package/dist/modules/apimapper/credentials.js.map +1 -1
  21. package/dist/modules/apimapper/elicitation.d.ts +29 -0
  22. package/dist/modules/apimapper/elicitation.js +62 -0
  23. package/dist/modules/apimapper/elicitation.js.map +1 -1
  24. package/dist/modules/apimapper/example-extract.d.ts +13 -0
  25. package/dist/modules/apimapper/example-extract.js +111 -0
  26. package/dist/modules/apimapper/example-extract.js.map +1 -0
  27. package/dist/modules/apimapper/filter-operators.d.ts +24 -0
  28. package/dist/modules/apimapper/filter-operators.js +103 -0
  29. package/dist/modules/apimapper/filter-operators.js.map +1 -0
  30. package/dist/modules/apimapper/flows-format.js +92 -22
  31. package/dist/modules/apimapper/flows-format.js.map +1 -1
  32. package/dist/modules/apimapper/flows.d.ts +8 -7
  33. package/dist/modules/apimapper/flows.js +216 -44
  34. package/dist/modules/apimapper/flows.js.map +1 -1
  35. package/dist/modules/apimapper/gateway/advanced-read-tool.d.ts +9 -0
  36. package/dist/modules/apimapper/gateway/advanced-read-tool.js +172 -0
  37. package/dist/modules/apimapper/gateway/advanced-read-tool.js.map +1 -0
  38. package/dist/modules/apimapper/gateway/advanced-tool.js +39 -130
  39. package/dist/modules/apimapper/gateway/advanced-tool.js.map +1 -1
  40. package/dist/modules/apimapper/gateway/collect-module-tools.d.ts +17 -0
  41. package/dist/modules/apimapper/gateway/collect-module-tools.js +44 -0
  42. package/dist/modules/apimapper/gateway/collect-module-tools.js.map +1 -0
  43. package/dist/modules/apimapper/gateway/essentials.d.ts +1 -1
  44. package/dist/modules/apimapper/gateway/essentials.js +19 -7
  45. package/dist/modules/apimapper/gateway/essentials.js.map +1 -1
  46. package/dist/modules/apimapper/gateway/gateway-shared.d.ts +21 -0
  47. package/dist/modules/apimapper/gateway/gateway-shared.js +124 -0
  48. package/dist/modules/apimapper/gateway/gateway-shared.js.map +1 -0
  49. package/dist/modules/apimapper/gateway/test-support.d.ts +1 -17
  50. package/dist/modules/apimapper/gateway/test-support.js +4 -33
  51. package/dist/modules/apimapper/gateway/test-support.js.map +1 -1
  52. package/dist/modules/apimapper/get-skill-cores.d.ts +4 -0
  53. package/dist/modules/apimapper/get-skill-cores.js +220 -0
  54. package/dist/modules/apimapper/get-skill-cores.js.map +1 -0
  55. package/dist/modules/apimapper/get-skill.d.ts +1 -1
  56. package/dist/modules/apimapper/get-skill.js +30 -3
  57. package/dist/modules/apimapper/get-skill.js.map +1 -1
  58. package/dist/modules/apimapper/graph-builder.d.ts +85 -2
  59. package/dist/modules/apimapper/graph-builder.js +151 -15
  60. package/dist/modules/apimapper/graph-builder.js.map +1 -1
  61. package/dist/modules/apimapper/graph.js +115 -15
  62. package/dist/modules/apimapper/graph.js.map +1 -1
  63. package/dist/modules/apimapper/index.js +25 -13
  64. package/dist/modules/apimapper/index.js.map +1 -1
  65. package/dist/modules/apimapper/jmespath-test.d.ts +4 -0
  66. package/dist/modules/apimapper/jmespath-test.js +152 -0
  67. package/dist/modules/apimapper/jmespath-test.js.map +1 -0
  68. package/dist/modules/apimapper/library.js +131 -8
  69. package/dist/modules/apimapper/library.js.map +1 -1
  70. package/dist/modules/apimapper/list-footer.d.ts +27 -0
  71. package/dist/modules/apimapper/list-footer.js +57 -0
  72. package/dist/modules/apimapper/list-footer.js.map +1 -0
  73. package/dist/modules/apimapper/local-sources.js +88 -31
  74. package/dist/modules/apimapper/local-sources.js.map +1 -1
  75. package/dist/modules/apimapper/mcp-client-identity.d.ts +32 -0
  76. package/dist/modules/apimapper/mcp-client-identity.js +70 -0
  77. package/dist/modules/apimapper/mcp-client-identity.js.map +1 -0
  78. package/dist/modules/apimapper/merge-constants.d.ts +6 -0
  79. package/dist/modules/apimapper/merge-constants.js +26 -0
  80. package/dist/modules/apimapper/merge-constants.js.map +1 -0
  81. package/dist/modules/apimapper/node-schema.d.ts +52 -2
  82. package/dist/modules/apimapper/node-schema.js +95 -4
  83. package/dist/modules/apimapper/node-schema.js.map +1 -1
  84. package/dist/modules/apimapper/onboarding.d.ts +29 -0
  85. package/dist/modules/apimapper/onboarding.js +117 -9
  86. package/dist/modules/apimapper/onboarding.js.map +1 -1
  87. package/dist/modules/apimapper/read-cache.d.ts +16 -3
  88. package/dist/modules/apimapper/read-cache.js +59 -4
  89. package/dist/modules/apimapper/read-cache.js.map +1 -1
  90. package/dist/modules/apimapper/render/index.js +26 -5
  91. package/dist/modules/apimapper/render/index.js.map +1 -1
  92. package/dist/modules/apimapper/resource-id.d.ts +13 -0
  93. package/dist/modules/apimapper/resource-id.js +69 -0
  94. package/dist/modules/apimapper/resource-id.js.map +1 -0
  95. package/dist/modules/apimapper/tool-result.d.ts +20 -0
  96. package/dist/modules/apimapper/tool-result.js +67 -5
  97. package/dist/modules/apimapper/tool-result.js.map +1 -1
  98. package/dist/modules/apimapper/toolslist-size.d.ts +10 -10
  99. package/dist/modules/apimapper/toolslist-size.js +29 -18
  100. package/dist/modules/apimapper/toolslist-size.js.map +1 -1
  101. package/dist/modules/apimapper/types.d.ts +13 -0
  102. package/dist/modules/apimapper/types.js +1 -1
  103. package/dist/modules/apimapper/types.js.map +1 -1
  104. package/dist/modules/apimapper/whitelist-drift.js +16 -1
  105. package/dist/modules/apimapper/whitelist-drift.js.map +1 -1
  106. package/dist/modules/apimapper/workflows.js +221 -32
  107. package/dist/modules/apimapper/workflows.js.map +1 -1
  108. package/dist/modules/apimapper/yootheme-binding.js +103 -22
  109. package/dist/modules/apimapper/yootheme-binding.js.map +1 -1
  110. package/dist/platform/index.js +7 -0
  111. package/dist/platform/index.js.map +1 -1
  112. package/dist/proxy/bridge.d.ts +35 -0
  113. package/dist/proxy/bridge.js +129 -0
  114. package/dist/proxy/bridge.js.map +1 -0
  115. package/dist/proxy/mode.d.ts +9 -0
  116. package/dist/proxy/mode.js +20 -0
  117. package/dist/proxy/mode.js.map +1 -0
  118. package/dist/setup/probe-auth.d.ts +51 -0
  119. package/dist/setup/probe-auth.js +141 -0
  120. package/dist/setup/probe-auth.js.map +1 -0
  121. package/dist/setup-cli.d.ts +9 -0
  122. package/dist/setup-cli.js +34 -0
  123. package/dist/setup-cli.js.map +1 -1
  124. package/dist/sites/loader.d.ts +7 -0
  125. package/dist/sites/loader.js +16 -1
  126. package/dist/sites/loader.js.map +1 -1
  127. package/dist/skill-instructions.d.ts +14 -1
  128. package/dist/skill-instructions.js +30 -6
  129. package/dist/skill-instructions.js.map +1 -1
  130. package/manifest.json +2 -2
  131. package/package.json +3 -2
  132. package/skills/apimapper/SKILL.md +78 -3
  133. package/skills/apimapper/reference/dynamize-existing-layout.md +158 -0
  134. package/skills/apimapper/reference/jmespath-cookbook.md +241 -0
  135. package/skills/apimapper/reference/jmespath-pitfalls.md +81 -0
  136. package/skills/apimapper/reference/library-template-discovery.md +1 -1
  137. package/skills/apimapper/reference/merge-two-sources-on-key.md +117 -12
  138. package/skills/apimapper/reference/oauth.md +143 -52
  139. package/skills/apimapper/reference/troubleshooting.md +2 -2
  140. package/skills/apimapper/reference/yootheme-source-to-builder-handoff.md +348 -0
  141. package/skills/apimapper/reference/yootheme.md +75 -44
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The row count at/above which the toolkit switches to the 2000-char compact
3
+ * level (per DETAIL_LIMITS: 21+ items → compact). Kept in sync with the
4
+ * toolkit's documented thresholds.
5
+ */
6
+ export declare const COMPACT_ROW_THRESHOLD = 21;
7
+ /**
8
+ * Build the overflow note for a list of `count` rows of `entity` (e.g.
9
+ * "connections", "flows", "library items"). Returns "" when the list is small
10
+ * enough that the toolkit renders it without compaction (no truncation risk).
11
+ */
12
+ export declare function listOverflowNote(count: number, entity: string): string;
13
+ /**
14
+ * Append the overflow note to `footer` when `count` rows would be compacted.
15
+ * Returns `footer` unchanged for small lists. Safe to call unconditionally
16
+ * from a list tool's footer construction.
17
+ */
18
+ export declare function withOverflowFooter(footer: string, count: number, entity: string): string;
19
+ /**
20
+ * F198 (Desktop-E2E #2 forensics, 2026-06-12): the honest "N more" note for a
21
+ * list that was hard-CAPPED by the requested `limit` — i.e. `totalMatched`
22
+ * exceeded `limit`, so `totalMatched - limit` rows were dropped from the slice.
23
+ * Unlike `listOverflowNote` (which fires on the COMPACT render threshold), this
24
+ * fires on real data loss so the agent knows to narrow or raise the limit.
25
+ * Returns "" when nothing was dropped.
26
+ */
27
+ export declare function truncatedByLimitNote(totalMatched: number, limit: number, entity: string): string;
@@ -0,0 +1,57 @@
1
+ // src/modules/apimapper/list-footer.ts — Minor (2026-06-10)
2
+ //
3
+ // The @getimo/mcp-toolkit `tableResult` renders the LLM-visible ASCII table at
4
+ // a detail level chosen by row count: 1-5 → full (8000 chars), 6-20 → standard
5
+ // (4000), 21+ → compact (2000). In compact mode a long list is silently
6
+ // truncated to 2000 chars — the LLM sees the first rows and may conclude
7
+ // "that's the whole list". This helper appends an honest
8
+ // "… N items shown — use filters to narrow" note to a list tool's footer when
9
+ // the row count crosses the compaction threshold, so the agent knows to
10
+ // filter rather than assume completeness.
11
+ /**
12
+ * The row count at/above which the toolkit switches to the 2000-char compact
13
+ * level (per DETAIL_LIMITS: 21+ items → compact). Kept in sync with the
14
+ * toolkit's documented thresholds.
15
+ */
16
+ export const COMPACT_ROW_THRESHOLD = 21;
17
+ /**
18
+ * Build the overflow note for a list of `count` rows of `entity` (e.g.
19
+ * "connections", "flows", "library items"). Returns "" when the list is small
20
+ * enough that the toolkit renders it without compaction (no truncation risk).
21
+ */
22
+ export function listOverflowNote(count, entity) {
23
+ if (count < COMPACT_ROW_THRESHOLD) {
24
+ return "";
25
+ }
26
+ return (`… ${count} ${entity} shown in a compact table (the LLM-visible text is ` +
27
+ `capped, so some rows may be truncated) — use filters to narrow ` +
28
+ `(e.g. source / name_query) rather than assuming this is the full set.`);
29
+ }
30
+ /**
31
+ * Append the overflow note to `footer` when `count` rows would be compacted.
32
+ * Returns `footer` unchanged for small lists. Safe to call unconditionally
33
+ * from a list tool's footer construction.
34
+ */
35
+ export function withOverflowFooter(footer, count, entity) {
36
+ const note = listOverflowNote(count, entity);
37
+ if (note === "") {
38
+ return footer;
39
+ }
40
+ return footer ? `${footer}\n${note}` : note;
41
+ }
42
+ /**
43
+ * F198 (Desktop-E2E #2 forensics, 2026-06-12): the honest "N more" note for a
44
+ * list that was hard-CAPPED by the requested `limit` — i.e. `totalMatched`
45
+ * exceeded `limit`, so `totalMatched - limit` rows were dropped from the slice.
46
+ * Unlike `listOverflowNote` (which fires on the COMPACT render threshold), this
47
+ * fires on real data loss so the agent knows to narrow or raise the limit.
48
+ * Returns "" when nothing was dropped.
49
+ */
50
+ export function truncatedByLimitNote(totalMatched, limit, entity) {
51
+ const dropped = totalMatched - limit;
52
+ if (dropped <= 0)
53
+ return "";
54
+ return (`${dropped} more ${entity} not shown — filter with name_query or raise the limit ` +
55
+ `(showing ${limit} of ${totalMatched}).`);
56
+ }
57
+ //# sourceMappingURL=list-footer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-footer.js","sourceRoot":"","sources":["../../../src/modules/apimapper/list-footer.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,wEAAwE;AACxE,yEAAyE;AACzE,yDAAyD;AACzD,8EAA8E;AAC9E,wEAAwE;AACxE,0CAA0C;AAE1C;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAExC;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa,EAAE,MAAc;IAC5D,IAAI,KAAK,GAAG,qBAAqB,EAAE,CAAC;QAClC,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,CACL,KAAK,KAAK,IAAI,MAAM,qDAAqD;QACzE,iEAAiE;QACjE,uEAAuE,CACxE,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAc,EACd,KAAa,EACb,MAAc;IAEd,MAAM,IAAI,GAAG,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC7C,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QAChB,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,YAAoB,EACpB,KAAa,EACb,MAAc;IAEd,MAAM,OAAO,GAAG,YAAY,GAAG,KAAK,CAAC;IACrC,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC5B,OAAO,CACL,GAAG,OAAO,SAAS,MAAM,yDAAyD;QAClF,YAAY,KAAK,OAAO,YAAY,IAAI,CACzC,CAAC;AACJ,CAAC"}
@@ -1,8 +1,28 @@
1
1
  import { z } from "zod";
2
- import { formatResult, readOnly } from "@getimo/mcp-toolkit";
2
+ import { formatResult, errorResult, readOnly } from "@getimo/mcp-toolkit";
3
3
  import { request } from "./client.js";
4
4
  import { restErrorResult } from "./tool-result.js";
5
5
  import { encodePathPreservingSlashes } from "./normalizers.js";
6
+ // F157 — content-type / type-id validation, uniform across the three
7
+ // local-source read tools (schema, filter_options, query). Before this the
8
+ // three tools each declared their own `.regex(/^[a-z]+\/[a-z0-9_-]+$/)` which
9
+ // HARD-rejected a bare single-segment type like `post` at the Zod boundary —
10
+ // even though the PHP layer already does a did-you-mean lookup and accepts the
11
+ // platform-qualified form `wordpress/post`. A cold agent that learned `post`
12
+ // from the YOOtheme source dropdown (which shows the bare content-type) got a
13
+ // schema reject with no hint. We now accept BOTH the canonical
14
+ // `platform/type` form AND a bare single segment, and let the server resolve /
15
+ // did-you-mean. The min(1) floor keeps empty-string out.
16
+ const LOCAL_SOURCE_TYPE_RE = /^[a-z]+(\/[a-z0-9_-]+)?$/;
17
+ const LOCAL_SOURCE_TYPE_HINT = 'Local source type id. Canonical form is "platform/type" (e.g., "wordpress/posts", "joomla/articles"); ' +
18
+ 'a bare single segment (e.g., "post") is also accepted and resolved server-side. ' +
19
+ "Use apimapper_local_source_types to discover the exact ids.";
20
+ function localSourceTypeSchema() {
21
+ return z
22
+ .string()
23
+ .min(1)
24
+ .regex(LOCAL_SOURCE_TYPE_RE, 'Must be a lowercase "platform/type" (e.g., "wordpress/posts") or a bare single segment (e.g., "post")');
25
+ }
6
26
  // rc.13 Welle 3 (2026-05-20) — error branches upgraded to the toolkit's
7
27
  // errorResult builder. Object-passthrough judgment (W3.1 note 2): every
8
28
  // local-source SUCCESS payload is arbitrary-shape data with NO stable column
@@ -17,7 +37,7 @@ export function registerLocalSourceTools(server) {
17
37
  // ── apimapper_local_source_types ───────────────────────────────────
18
38
  server.registerTool("apimapper_local_source_types", {
19
39
  title: "List Local Source Types",
20
- description: "List available local-source types (e.g., wordpress/posts, wordpress/users, joomla/articles). " +
40
+ description: "List available local-source types (e.g., wordpress/post, wordpress/page, joomla/article, joomla/category). " +
21
41
  "Local sources don't need a connection — they query WP/Joomla internal data." +
22
42
  "\n\nExample:\n apimapper_local_source_types({})",
23
43
  inputSchema: {},
@@ -40,16 +60,33 @@ export function registerLocalSourceTools(server) {
40
60
  "Use apimapper_local_source_types to discover available type IDs first." +
41
61
  "\n\nExample:\n apimapper_local_source_schema({ type_id: 'wordpress/post' })",
42
62
  inputSchema: {
43
- type_id: z
44
- .string()
45
- .regex(/^[a-z]+\/[a-z0-9_-]+$/, "Must be in the form 'platform/type', e.g., 'wordpress/posts'")
46
- .describe('Local source type id in "platform/type" form (e.g., "wordpress/posts", "joomla/articles"). Use apimapper_local_source_types.'),
63
+ // F157: lenient validation (canonical "platform/type" OR a bare
64
+ // single segment like "post") via the shared helper. Optional so the
65
+ // F69 `content_type`-only call path parses.
66
+ type_id: localSourceTypeSchema().optional().describe(LOCAL_SOURCE_TYPE_HINT),
67
+ // F69 (2026-06-10): `content_type` alias. apimapper_local_source_query
68
+ // (the tool agents reach for first) keys this same value under
69
+ // `content_type`; agents carried that name over to the schema tool and
70
+ // hit "Invalid arguments". Accept both — `type_id` is canonical and
71
+ // wins on conflict. Same lenient validation as `type_id` (F157).
72
+ content_type: localSourceTypeSchema()
73
+ .optional()
74
+ .describe('Alias of `type_id` (the key apimapper_local_source_query uses). `type_id` wins when both are set.'),
47
75
  },
48
76
  annotations: readOnly({ title: "Get Local Source Schema", openWorld: true }),
49
- }, async ({ type_id }) => {
50
- const r = await request(`/local-sources/schema/${encodePathPreservingSlashes(type_id)}`);
77
+ }, async ({ type_id, content_type }) => {
78
+ const resolvedTypeId = type_id ?? content_type;
79
+ if (resolvedTypeId === undefined || resolvedTypeId === "") {
80
+ return errorResult({
81
+ message: "Provide `type_id` (canonical) or `content_type` (alias) in 'platform/type' form, e.g. 'wordpress/post'.",
82
+ code: "bad_request",
83
+ suggestion: "Use apimapper_local_source_types to discover valid type ids.",
84
+ details: {},
85
+ });
86
+ }
87
+ const r = await request(`/local-sources/schema/${encodePathPreservingSlashes(resolvedTypeId)}`);
51
88
  if (!r.success) {
52
- return restErrorResult(r, { type_id }, { message: "local source schema failed" });
89
+ return restErrorResult(r, { type_id: resolvedTypeId }, { message: "local source schema failed" });
53
90
  }
54
91
  return formatResult(r.data ?? {}, false, { maxChars: 4000 });
55
92
  });
@@ -57,22 +94,35 @@ export function registerLocalSourceTools(server) {
57
94
  server.registerTool("apimapper_local_source_filter_options", {
58
95
  title: "Get Filter Options",
59
96
  description: "Fetch dropdown options for a filter on a local source (e.g., available categories/tags)." +
60
- "\n\nExample:\n apimapper_local_source_filter_options({ type: 'wordpress/posts', field: 'category' })",
97
+ "\n\nExample:\n apimapper_local_source_filter_options({ type_id: 'wordpress/post', filter_name: 'category' })",
61
98
  inputSchema: {
62
- type_id: z
63
- .string()
64
- .regex(/^[a-z]+\/[a-z0-9_-]+$/, "Must be 'platform/type' form")
65
- .describe('Local source type id in "platform/type" form. Use apimapper_local_source_types.'),
99
+ // F157: lenient validation via the shared helper; optional for the
100
+ // F69 `content_type`-only path.
101
+ type_id: localSourceTypeSchema().optional().describe(LOCAL_SOURCE_TYPE_HINT),
102
+ // F69 (2026-06-10): `content_type` alias same rationale as
103
+ // local_source_schema. `type_id` is canonical and wins on conflict.
104
+ content_type: localSourceTypeSchema()
105
+ .optional()
106
+ .describe('Alias of `type_id` (the key apimapper_local_source_query uses). `type_id` wins when both are set.'),
66
107
  filter_name: z
67
108
  .string()
68
109
  .regex(/^[a-z_]+$/, "Must be lowercase with underscores")
69
110
  .describe('Filter field name (e.g., "category", "tag", "author")'),
70
111
  },
71
112
  annotations: readOnly({ title: "Get Local Source Filter Options", openWorld: true }),
72
- }, async ({ type_id, filter_name }) => {
73
- const r = await request(`/local-sources/filter-options/${encodePathPreservingSlashes(type_id)}/${encodeURIComponent(filter_name)}`);
113
+ }, async ({ type_id, content_type, filter_name }) => {
114
+ const resolvedTypeId = type_id ?? content_type;
115
+ if (resolvedTypeId === undefined || resolvedTypeId === "") {
116
+ return errorResult({
117
+ message: "Provide `type_id` (canonical) or `content_type` (alias) in 'platform/type' form, e.g. 'wordpress/post'.",
118
+ code: "bad_request",
119
+ suggestion: "Use apimapper_local_source_types to discover valid type ids.",
120
+ details: { filter_name },
121
+ });
122
+ }
123
+ const r = await request(`/local-sources/filter-options/${encodePathPreservingSlashes(resolvedTypeId)}/${encodeURIComponent(filter_name)}`);
74
124
  if (!r.success) {
75
- return restErrorResult(r, { type_id, filter_name }, { message: "local source filter options failed" });
125
+ return restErrorResult(r, { type_id: resolvedTypeId, filter_name }, { message: "local source filter options failed" });
76
126
  }
77
127
  return formatResult(r.data ?? {}, false, { maxChars: 4000 });
78
128
  });
@@ -86,12 +136,19 @@ export function registerLocalSourceTools(server) {
86
136
  "the matching items in one call. Body uses camelCase `contentType` (FlowExecutor node-data " +
87
137
  "convention). For AI agents: prefer a higher `limit` (up to 500) over iterating with " +
88
138
  "`offset` — most installs fit comfortably in a single page." +
89
- "\n\nExample:\n apimapper_local_source_query({ contentType: 'wordpress/posts', limit: 100 })",
139
+ "\n\nExample:\n apimapper_local_source_query({ content_type: 'wordpress/posts', limit: 100 })",
90
140
  inputSchema: {
91
- content_type: z
92
- .string()
93
- .regex(/^[a-z]+\/[a-z0-9_-]+$/, "Must be 'platform/type' form")
94
- .describe('Content type in "platform/type" form (e.g., "wordpress/posts"). Use apimapper_local_source_types.'),
141
+ // F4 + F157: lenient validation via the shared helper — canonical
142
+ // "platform/type" OR a bare single segment ("post"). The strict
143
+ // slash-only regex used to reject a bare "post" at the schema boundary
144
+ // BEFORE the server's structured `invalid_content_type` error (P0-C5)
145
+ // could fire; the helper accepts both and lets the server stay the
146
+ // authority on content-type validity (empty string is still rejected).
147
+ content_type: localSourceTypeSchema().describe('Content type. Canonical form "platform/type" (e.g., "wordpress/posts"); a bare single ' +
148
+ 'segment (e.g., "post") is also accepted — the server canonicalizes it and, if it ' +
149
+ "can't, replies with a structured invalid_content_type error listing the valid types " +
150
+ "and a did-you-mean. Use apimapper_local_source_types. " +
151
+ "Sent on the wire as camelCase `contentType` (FlowExecutor node-data convention)."),
95
152
  filters: z
96
153
  .record(z.string(), z.unknown())
97
154
  .optional()
@@ -120,19 +177,19 @@ export function registerLocalSourceTools(server) {
120
177
  body.sort = orderby;
121
178
  const r = await request("/local-sources/query", { method: "POST", body: JSON.stringify(body) });
122
179
  if (!r.success) {
123
- // F-A5-02: on 403 feature-gate response the PHP body carries
124
- // `error_code` + `upgrade` hints surface them to the agent (in the
125
- // errorResult details) so it can route the user to the upgrade flow
126
- // instead of a generic error.
127
- const dataObj = r.data && typeof r.data === "object"
128
- ? r.data
129
- : {};
180
+ // F-A5-02 / F4 / F11 (2026-06-09): on a non-2xx the structured PHP body
181
+ // lives in `r.errorBody` (NOT `r.data`, which is undefined on failure
182
+ // the old read was a no-op). `restErrorResult` now lifts the server's
183
+ // `error_code` + `valid_types` + `suggestion` (e.g. invalid_content_type
184
+ // 400, P0-C5) verbatim. Here we additionally thread the 403 feature-gate
185
+ // `upgrade` hint into the details so the agent can route the user to the
186
+ // upgrade flow.
187
+ const errBody = r.errorBody ?? {};
130
188
  return restErrorResult(r, {
131
189
  content_type,
132
190
  limit,
133
191
  offset,
134
- error_code: typeof dataObj.error_code === "string" ? dataObj.error_code : undefined,
135
- upgrade: dataObj.upgrade,
192
+ ...(errBody.upgrade !== undefined ? { upgrade: errBody.upgrade } : {}),
136
193
  }, { message: "local source query failed" });
137
194
  }
138
195
  const items = Array.isArray(r.data?.items) ? r.data.items : [];
@@ -1 +1 @@
1
- {"version":3,"file":"local-sources.js","sourceRoot":"","sources":["../../../src/modules/apimapper/local-sources.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"local-sources.js","sourceRoot":"","sources":["../../../src/modules/apimapper/local-sources.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Sanitize a raw `name/version` identity into a header-safe string.
3
+ *
4
+ * - Strips CR/LF and other ASCII control chars (header-injection defense).
5
+ * - Collapses runs of whitespace to single spaces and trims.
6
+ * - Caps the length to {@link MAX_CLIENT_LEN}.
7
+ * - Returns `null` for empty / whitespace-only input.
8
+ *
9
+ * @param raw the candidate identity string (may be undefined)
10
+ * @returns a sanitized non-empty string, or null
11
+ */
12
+ export declare function sanitizeMcpClient(raw: string | undefined | null): string | null;
13
+ /**
14
+ * Build a `name/version` identity from the SDK `Implementation` shape and
15
+ * stash it for the outbound chokepoint. Called once after the stdio handshake.
16
+ *
17
+ * Empty / missing clientInfo → the slot stays null and no header is attached.
18
+ *
19
+ * @param info the connecting client's implementation info (name+version),
20
+ * or undefined when the SDK could not determine it.
21
+ */
22
+ export declare function setMcpClient(info: {
23
+ name?: string;
24
+ version?: string;
25
+ } | undefined): void;
26
+ /**
27
+ * The sanitized connected-client identity, or null when no client info was
28
+ * available. Read by `performFetch` to attach the `X-MCP-Client` header.
29
+ */
30
+ export declare function getMcpClient(): string | null;
31
+ /** Test-only reset of the module-scoped slot. */
32
+ export declare function __resetMcpClientForTests(): void;
@@ -0,0 +1,70 @@
1
+ // src/modules/apimapper/mcp-client-identity.ts
2
+ //
3
+ // Connected-client identity carrier (Task Q1).
4
+ //
5
+ // The MCP SDK exposes the connecting AI client's name+version via
6
+ // `server.server.getClientVersion()` AFTER the stdio handshake completes.
7
+ // We stash a sanitized `name/version` string in a module-scoped slot here so
8
+ // the outbound REST chokepoint (`performFetch` in client.ts) can attach it as
9
+ // the `X-MCP-Client` header on every request — WITHOUT a circular import
10
+ // between `index.ts` (which owns the server) and `client.ts` (which owns the
11
+ // fetch). `index.ts` calls `setMcpClient()` once post-connect; `client.ts`
12
+ // calls `getMcpClient()` per request.
13
+ //
14
+ // The value is attacker-INFLUENCEABLE (any MCP client picks its own
15
+ // clientInfo), so we treat it as untrusted: strip CR/LF (header-injection
16
+ // defense), collapse internal control chars, and cap the length. The REST
17
+ // side maps it to a known provider slug and NEVER renders the raw string.
18
+ /** Max wire length for the X-MCP-Client header value. Generous but bounded. */
19
+ const MAX_CLIENT_LEN = 128;
20
+ let currentClient = null;
21
+ /**
22
+ * Sanitize a raw `name/version` identity into a header-safe string.
23
+ *
24
+ * - Strips CR/LF and other ASCII control chars (header-injection defense).
25
+ * - Collapses runs of whitespace to single spaces and trims.
26
+ * - Caps the length to {@link MAX_CLIENT_LEN}.
27
+ * - Returns `null` for empty / whitespace-only input.
28
+ *
29
+ * @param raw the candidate identity string (may be undefined)
30
+ * @returns a sanitized non-empty string, or null
31
+ */
32
+ export function sanitizeMcpClient(raw) {
33
+ if (typeof raw !== "string")
34
+ return null;
35
+ // eslint-disable-next-line no-control-regex -- intentional: strip ASCII control chars (CR/LF header injection)
36
+ const stripped = raw.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
37
+ if (stripped.length === 0)
38
+ return null;
39
+ return stripped.slice(0, MAX_CLIENT_LEN);
40
+ }
41
+ /**
42
+ * Build a `name/version` identity from the SDK `Implementation` shape and
43
+ * stash it for the outbound chokepoint. Called once after the stdio handshake.
44
+ *
45
+ * Empty / missing clientInfo → the slot stays null and no header is attached.
46
+ *
47
+ * @param info the connecting client's implementation info (name+version),
48
+ * or undefined when the SDK could not determine it.
49
+ */
50
+ export function setMcpClient(info) {
51
+ const name = typeof info?.name === "string" ? info.name.trim() : "";
52
+ if (!name) {
53
+ currentClient = null;
54
+ return;
55
+ }
56
+ const version = typeof info?.version === "string" && info.version.trim() ? info.version.trim() : "unknown";
57
+ currentClient = sanitizeMcpClient(`${name}/${version}`);
58
+ }
59
+ /**
60
+ * The sanitized connected-client identity, or null when no client info was
61
+ * available. Read by `performFetch` to attach the `X-MCP-Client` header.
62
+ */
63
+ export function getMcpClient() {
64
+ return currentClient;
65
+ }
66
+ /** Test-only reset of the module-scoped slot. */
67
+ export function __resetMcpClientForTests() {
68
+ currentClient = null;
69
+ }
70
+ //# sourceMappingURL=mcp-client-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-client-identity.js","sourceRoot":"","sources":["../../../src/modules/apimapper/mcp-client-identity.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,EAAE;AACF,+CAA+C;AAC/C,EAAE;AACF,kEAAkE;AAClE,0EAA0E;AAC1E,6EAA6E;AAC7E,8EAA8E;AAC9E,yEAAyE;AACzE,6EAA6E;AAC7E,2EAA2E;AAC3E,sCAAsC;AACtC,EAAE;AACF,oEAAoE;AACpE,0EAA0E;AAC1E,0EAA0E;AAC1E,0EAA0E;AAE1E,+EAA+E;AAC/E,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,IAAI,aAAa,GAAkB,IAAI,CAAC;AAExC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAA8B;IAC9D,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,+GAA+G;IAC/G,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACxF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,IAAqD;IAChF,MAAM,IAAI,GAAG,OAAO,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,aAAa,GAAG,IAAI,CAAC;QACrB,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3G,aAAa,GAAG,iBAAiB,CAAC,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,wBAAwB;IACtC,aAAa,GAAG,IAAI,CAAC;AACvB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare const MERGE_STRATEGIES: readonly ["concat", "join"];
2
+ export type MergeStrategyCanonical = (typeof MERGE_STRATEGIES)[number];
3
+ export declare const MERGE_STRATEGIES_INPUT: readonly ["join", "concat", "append"];
4
+ export type MergeStrategyInput = (typeof MERGE_STRATEGIES_INPUT)[number];
5
+ export declare const MERGE_JOIN_TYPES: readonly ["left", "right", "inner", "outer"];
6
+ export type MergeJoinType = (typeof MERGE_JOIN_TYPES)[number];
@@ -0,0 +1,26 @@
1
+ // src/modules/apimapper/merge-constants.ts — single source of merge enums.
2
+ //
3
+ // F156 — before this module the merge `strategy` + `joinType` enums were
4
+ // declared THREE times with divergent values:
5
+ // • node-schema.ts mergeNodeSchema: strategy ['concat','join'],
6
+ // joinType ['left','right','inner','outer'] (the raw-graph write path)
7
+ // • workflows.ts MergeSpec / flow_change_merge_strategy: strategy
8
+ // ['join','concat','append'], joinType ['left','inner'] (composite path)
9
+ // A cold agent that learned `joinType:'outer'` from one path got a hard Zod
10
+ // reject on the other, and `append` was accepted in one place but not the
11
+ // other. Centralising the enums here removes that drift.
12
+ //
13
+ // Wire contract (ground truth — MergeNodeExecutor.php:50 + the FlowCompiler
14
+ // normaliser): the backend persists only 'concat' / 'join'. 'append' is a
15
+ // customer-facing UI alias that normalizeMergeStrategy() folds to 'concat'.
16
+ //
17
+ // - MERGE_STRATEGIES = the CANONICAL set the raw node schema enforces.
18
+ // - MERGE_STRATEGIES_INPUT = the canonical set PLUS the 'append' alias, for
19
+ // the agent-facing composite tools that accept it
20
+ // and normalize down to 'concat'.
21
+ // - MERGE_JOIN_TYPES = the full SQL-style join kinds the executor
22
+ // supports (left/right/inner/outer).
23
+ export const MERGE_STRATEGIES = ["concat", "join"];
24
+ export const MERGE_STRATEGIES_INPUT = ["join", "concat", "append"];
25
+ export const MERGE_JOIN_TYPES = ["left", "right", "inner", "outer"];
26
+ //# sourceMappingURL=merge-constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-constants.js","sourceRoot":"","sources":["../../../src/modules/apimapper/merge-constants.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,EAAE;AACF,yEAAyE;AACzE,8CAA8C;AAC9C,kEAAkE;AAClE,4EAA4E;AAC5E,oEAAoE;AACpE,8EAA8E;AAC9E,4EAA4E;AAC5E,0EAA0E;AAC1E,yDAAyD;AACzD,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,4EAA4E;AAC5E,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,gFAAgF;AAChF,gEAAgE;AAChE,2EAA2E;AAC3E,mEAAmE;AAEnE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAU,CAAC;AAG5D,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAG5E,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAU,CAAC"}
@@ -35,7 +35,13 @@ export declare const filterNodeSchema: z.ZodObject<{
35
35
  y: z.ZodNumber;
36
36
  }, z.core.$strip>;
37
37
  data: z.ZodObject<{
38
- conditions: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
38
+ conditions: z.ZodArray<z.ZodObject<{
39
+ field: z.ZodString;
40
+ operator: z.ZodEnum<{
41
+ [x: string]: string;
42
+ }>;
43
+ value: z.ZodOptional<z.ZodUnknown>;
44
+ }, z.core.$loose>>;
39
45
  logic: z.ZodOptional<z.ZodEnum<{
40
46
  or: "or";
41
47
  and: "and";
@@ -75,6 +81,16 @@ export declare const mergeNodeSchema: z.ZodObject<{
75
81
  }>>;
76
82
  }, z.core.$loose>;
77
83
  }, z.core.$strip>;
84
+ export declare const OUTPUT_NODE_TYPES: readonly ["output", "output-yootheme", "output-shortcode"];
85
+ export type OutputNodeType = (typeof OUTPUT_NODE_TYPES)[number];
86
+ /**
87
+ * True when a node is any kind of terminal output node. Mirrors
88
+ * FlowCompiler::isOutputNodeType (PHP). Accepts a minimal `{ type }` shape so
89
+ * it works on both strict FlowNodes and loose wire objects.
90
+ */
91
+ export declare function isOutputNode(node: {
92
+ type?: unknown;
93
+ } | null | undefined): boolean;
78
94
  export declare const outputNodeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
79
95
  id: z.ZodString;
80
96
  type: z.ZodLiteral<"output">;
@@ -143,7 +159,13 @@ export declare const nodeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
143
159
  y: z.ZodNumber;
144
160
  }, z.core.$strip>;
145
161
  data: z.ZodObject<{
146
- conditions: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
162
+ conditions: z.ZodArray<z.ZodObject<{
163
+ field: z.ZodString;
164
+ operator: z.ZodEnum<{
165
+ [x: string]: string;
166
+ }>;
167
+ value: z.ZodOptional<z.ZodUnknown>;
168
+ }, z.core.$loose>>;
147
169
  logic: z.ZodOptional<z.ZodEnum<{
148
170
  or: "or";
149
171
  and: "and";
@@ -240,6 +262,34 @@ export declare const edgeSchema: z.ZodObject<{
240
262
  targetHandle: z.ZodOptional<z.ZodString>;
241
263
  }, z.core.$strip>;
242
264
  export type FlowEdge = z.infer<typeof edgeSchema>;
265
+ /**
266
+ * Stamp a stable `id` on every edge that lacks one, at the create/update wire
267
+ * boundary (flow_create / flow_update / graph_preview / graph_validate).
268
+ *
269
+ * Why this exists (F5, 2026-06-09): `edgeSchema` marks `id` OPTIONAL — the AI
270
+ * legitimately omits it — but the React-Flow runtime + the backend graph
271
+ * validator key edges by `id` and reject an id-less edge with a misleading
272
+ * "connectivity" error. Rather than make `id` required at the schema (which
273
+ * would force every caller to invent ids), we generate a deterministic one
274
+ * here so BOTH the schema and the runtime accept id-less edges. `buildFlowGraph`
275
+ * already emits ids for its synthetic graphs; this closes the gap for
276
+ * hand-authored `flow_create` / `flow_update` graphs.
277
+ *
278
+ * Id shape: `e-<source>-<target>` (matches the convention `buildFlowGraph`
279
+ * already uses). Parallel edges between the same node pair — e.g. the two
280
+ * inputs of a merge node — get a `-2`, `-3`, … suffix. A generated id never
281
+ * collides with a caller-supplied id (or an already-generated one): the suffix
282
+ * search skips any id already taken in this batch.
283
+ *
284
+ * Pure + non-mutating: returns a NEW array of NEW edge objects; the input is
285
+ * left untouched. A non-array input yields `[]` (defensive — the wire boundary
286
+ * already validated, but a direct programmatic caller might not have).
287
+ */
288
+ export declare function ensureEdgeIds<E extends {
289
+ id?: unknown;
290
+ source?: unknown;
291
+ target?: unknown;
292
+ }>(edges: E[]): E[];
243
293
  /**
244
294
  * W1.18 (F-32): case-insensitive substring filter applied in-memory after
245
295
  * the upstream list fetch.