@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.
- package/CHANGELOG.md +14 -5
- package/dist/catalog/build-catalog.d.ts +31 -0
- package/dist/catalog/build-catalog.js +68 -0
- package/dist/catalog/build-catalog.js.map +1 -0
- package/dist/index.js +37 -5
- package/dist/index.js.map +1 -1
- package/dist/modules/apimapper/auto-layout.d.ts +21 -0
- package/dist/modules/apimapper/auto-layout.js +54 -0
- package/dist/modules/apimapper/auto-layout.js.map +1 -0
- package/dist/modules/apimapper/client.d.ts +54 -4
- package/dist/modules/apimapper/client.js +145 -14
- package/dist/modules/apimapper/client.js.map +1 -1
- package/dist/modules/apimapper/connections-format.d.ts +31 -1
- package/dist/modules/apimapper/connections-format.js +97 -5
- package/dist/modules/apimapper/connections-format.js.map +1 -1
- package/dist/modules/apimapper/connections.d.ts +9 -7
- package/dist/modules/apimapper/connections.js +225 -58
- package/dist/modules/apimapper/connections.js.map +1 -1
- package/dist/modules/apimapper/credentials.js +86 -14
- package/dist/modules/apimapper/credentials.js.map +1 -1
- package/dist/modules/apimapper/elicitation.d.ts +29 -0
- package/dist/modules/apimapper/elicitation.js +62 -0
- package/dist/modules/apimapper/elicitation.js.map +1 -1
- package/dist/modules/apimapper/example-extract.d.ts +13 -0
- package/dist/modules/apimapper/example-extract.js +111 -0
- package/dist/modules/apimapper/example-extract.js.map +1 -0
- package/dist/modules/apimapper/filter-operators.d.ts +24 -0
- package/dist/modules/apimapper/filter-operators.js +103 -0
- package/dist/modules/apimapper/filter-operators.js.map +1 -0
- package/dist/modules/apimapper/flows-format.js +92 -22
- package/dist/modules/apimapper/flows-format.js.map +1 -1
- package/dist/modules/apimapper/flows.d.ts +8 -7
- package/dist/modules/apimapper/flows.js +216 -44
- package/dist/modules/apimapper/flows.js.map +1 -1
- package/dist/modules/apimapper/gateway/advanced-read-tool.d.ts +9 -0
- package/dist/modules/apimapper/gateway/advanced-read-tool.js +172 -0
- package/dist/modules/apimapper/gateway/advanced-read-tool.js.map +1 -0
- package/dist/modules/apimapper/gateway/advanced-tool.js +39 -130
- package/dist/modules/apimapper/gateway/advanced-tool.js.map +1 -1
- package/dist/modules/apimapper/gateway/collect-module-tools.d.ts +17 -0
- package/dist/modules/apimapper/gateway/collect-module-tools.js +44 -0
- package/dist/modules/apimapper/gateway/collect-module-tools.js.map +1 -0
- package/dist/modules/apimapper/gateway/essentials.d.ts +1 -1
- package/dist/modules/apimapper/gateway/essentials.js +19 -7
- package/dist/modules/apimapper/gateway/essentials.js.map +1 -1
- package/dist/modules/apimapper/gateway/gateway-shared.d.ts +21 -0
- package/dist/modules/apimapper/gateway/gateway-shared.js +124 -0
- package/dist/modules/apimapper/gateway/gateway-shared.js.map +1 -0
- package/dist/modules/apimapper/gateway/test-support.d.ts +1 -17
- package/dist/modules/apimapper/gateway/test-support.js +4 -33
- package/dist/modules/apimapper/gateway/test-support.js.map +1 -1
- package/dist/modules/apimapper/get-skill-cores.d.ts +4 -0
- package/dist/modules/apimapper/get-skill-cores.js +220 -0
- package/dist/modules/apimapper/get-skill-cores.js.map +1 -0
- package/dist/modules/apimapper/get-skill.d.ts +1 -1
- package/dist/modules/apimapper/get-skill.js +30 -3
- package/dist/modules/apimapper/get-skill.js.map +1 -1
- package/dist/modules/apimapper/graph-builder.d.ts +85 -2
- package/dist/modules/apimapper/graph-builder.js +151 -15
- package/dist/modules/apimapper/graph-builder.js.map +1 -1
- package/dist/modules/apimapper/graph.js +115 -15
- package/dist/modules/apimapper/graph.js.map +1 -1
- package/dist/modules/apimapper/index.js +25 -13
- package/dist/modules/apimapper/index.js.map +1 -1
- package/dist/modules/apimapper/jmespath-test.d.ts +4 -0
- package/dist/modules/apimapper/jmespath-test.js +152 -0
- package/dist/modules/apimapper/jmespath-test.js.map +1 -0
- package/dist/modules/apimapper/library.js +131 -8
- package/dist/modules/apimapper/library.js.map +1 -1
- package/dist/modules/apimapper/list-footer.d.ts +27 -0
- package/dist/modules/apimapper/list-footer.js +57 -0
- package/dist/modules/apimapper/list-footer.js.map +1 -0
- package/dist/modules/apimapper/local-sources.js +88 -31
- package/dist/modules/apimapper/local-sources.js.map +1 -1
- package/dist/modules/apimapper/mcp-client-identity.d.ts +32 -0
- package/dist/modules/apimapper/mcp-client-identity.js +70 -0
- package/dist/modules/apimapper/mcp-client-identity.js.map +1 -0
- package/dist/modules/apimapper/merge-constants.d.ts +6 -0
- package/dist/modules/apimapper/merge-constants.js +26 -0
- package/dist/modules/apimapper/merge-constants.js.map +1 -0
- package/dist/modules/apimapper/node-schema.d.ts +52 -2
- package/dist/modules/apimapper/node-schema.js +95 -4
- package/dist/modules/apimapper/node-schema.js.map +1 -1
- package/dist/modules/apimapper/onboarding.d.ts +29 -0
- package/dist/modules/apimapper/onboarding.js +117 -9
- package/dist/modules/apimapper/onboarding.js.map +1 -1
- package/dist/modules/apimapper/read-cache.d.ts +16 -3
- package/dist/modules/apimapper/read-cache.js +59 -4
- package/dist/modules/apimapper/read-cache.js.map +1 -1
- package/dist/modules/apimapper/render/index.js +26 -5
- package/dist/modules/apimapper/render/index.js.map +1 -1
- package/dist/modules/apimapper/resource-id.d.ts +13 -0
- package/dist/modules/apimapper/resource-id.js +69 -0
- package/dist/modules/apimapper/resource-id.js.map +1 -0
- package/dist/modules/apimapper/tool-result.d.ts +20 -0
- package/dist/modules/apimapper/tool-result.js +67 -5
- package/dist/modules/apimapper/tool-result.js.map +1 -1
- package/dist/modules/apimapper/toolslist-size.d.ts +10 -10
- package/dist/modules/apimapper/toolslist-size.js +29 -18
- package/dist/modules/apimapper/toolslist-size.js.map +1 -1
- package/dist/modules/apimapper/types.d.ts +13 -0
- package/dist/modules/apimapper/types.js +1 -1
- package/dist/modules/apimapper/types.js.map +1 -1
- package/dist/modules/apimapper/whitelist-drift.js +16 -1
- package/dist/modules/apimapper/whitelist-drift.js.map +1 -1
- package/dist/modules/apimapper/workflows.js +221 -32
- package/dist/modules/apimapper/workflows.js.map +1 -1
- package/dist/modules/apimapper/yootheme-binding.js +103 -22
- package/dist/modules/apimapper/yootheme-binding.js.map +1 -1
- package/dist/platform/index.js +7 -0
- package/dist/platform/index.js.map +1 -1
- package/dist/proxy/bridge.d.ts +35 -0
- package/dist/proxy/bridge.js +129 -0
- package/dist/proxy/bridge.js.map +1 -0
- package/dist/proxy/mode.d.ts +9 -0
- package/dist/proxy/mode.js +20 -0
- package/dist/proxy/mode.js.map +1 -0
- package/dist/setup/probe-auth.d.ts +51 -0
- package/dist/setup/probe-auth.js +141 -0
- package/dist/setup/probe-auth.js.map +1 -0
- package/dist/setup-cli.d.ts +9 -0
- package/dist/setup-cli.js +34 -0
- package/dist/setup-cli.js.map +1 -1
- package/dist/sites/loader.d.ts +7 -0
- package/dist/sites/loader.js +16 -1
- package/dist/sites/loader.js.map +1 -1
- package/dist/skill-instructions.d.ts +14 -1
- package/dist/skill-instructions.js +30 -6
- package/dist/skill-instructions.js.map +1 -1
- package/manifest.json +2 -2
- package/package.json +3 -2
- package/skills/apimapper/SKILL.md +78 -3
- package/skills/apimapper/reference/dynamize-existing-layout.md +158 -0
- package/skills/apimapper/reference/jmespath-cookbook.md +241 -0
- package/skills/apimapper/reference/jmespath-pitfalls.md +81 -0
- package/skills/apimapper/reference/library-template-discovery.md +1 -1
- package/skills/apimapper/reference/merge-two-sources-on-key.md +117 -12
- package/skills/apimapper/reference/oauth.md +143 -52
- package/skills/apimapper/reference/troubleshooting.md +2 -2
- package/skills/apimapper/reference/yootheme-source-to-builder-handoff.md +348 -0
- 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/
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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({
|
|
97
|
+
"\n\nExample:\n apimapper_local_source_filter_options({ type_id: 'wordpress/post', filter_name: 'category' })",
|
|
61
98
|
inputSchema: {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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({
|
|
139
|
+
"\n\nExample:\n apimapper_local_source_query({ content_type: 'wordpress/posts', limit: 100 })",
|
|
90
140
|
inputSchema: {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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":"
|
|
1
|
+
{"version":3,"file":"local-sources.js","sourceRoot":"","sources":["../../../src/modules/apimapper/local-sources.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAE/D,qEAAqE;AACrE,2EAA2E;AAC3E,8EAA8E;AAC9E,6EAA6E;AAC7E,+EAA+E;AAC/E,6EAA6E;AAC7E,8EAA8E;AAC9E,+DAA+D;AAC/D,+EAA+E;AAC/E,yDAAyD;AACzD,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AACxD,MAAM,sBAAsB,GAC1B,wGAAwG;IACxG,kFAAkF;IAClF,6DAA6D,CAAC;AAEhE,SAAS,qBAAqB;IAC5B,OAAO,CAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,KAAK,CACJ,oBAAoB,EACpB,uGAAuG,CACxG,CAAC;AACN,CAAC;AAED,wEAAwE;AACxE,wEAAwE;AACxE,6EAA6E;AAC7E,qEAAqE;AACrE,0EAA0E;AAC1E,iFAAiF;AACjF,yEAAyE;AACzE,8EAA8E;AAC9E,8EAA8E;AAC9E,4EAA4E;AAE5E,MAAM,UAAU,wBAAwB,CAAC,MAAqB;IAC5D,sEAAsE;IACtE,MAAM,CAAC,YAAY,CACjB,8BAA8B,EAC9B;QACE,KAAK,EAAE,yBAAyB;QAChC,WAAW,EACT,6GAA6G;YAC7G,6EAA6E;YAC7E,kDAAkD;QACpD,WAAW,EAAE,EAAE;QACf,WAAW,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;KAC7E,EACD,KAAK,IAAI,EAAE;QACT,MAAM,CAAC,GAAG,MAAM,OAAO,CAAwB,sBAAsB,CAAC,CAAC;QACvE,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YACf,OAAO,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,OAAO,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACjF,CAAC,CACF,CAAC;IAEF,sEAAsE;IACtE,4EAA4E;IAC5E,0EAA0E;IAC1E,0CAA0C;IAC1C,MAAM,CAAC,YAAY,CACjB,+BAA+B,EAC/B;QACE,KAAK,EAAE,yBAAyB;QAChC,WAAW,EAAE,qEAAqE;YAClF,wEAAwE;YACxE,8EAA8E;QAC9E,WAAW,EAAE;YACX,gEAAgE;YAChE,qEAAqE;YACrE,4CAA4C;YAC5C,OAAO,EAAE,qBAAqB,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;YAC5E,uEAAuE;YACvE,+DAA+D;YAC/D,uEAAuE;YACvE,oEAAoE;YACpE,iEAAiE;YACjE,YAAY,EAAE,qBAAqB,EAAE;iBAClC,QAAQ,EAAE;iBACV,QAAQ,CAAC,mGAAmG,CAAC;SACjH;QACD,WAAW,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;KAC7E,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE;QAClC,MAAM,cAAc,GAAG,OAAO,IAAI,YAAY,CAAC;QAC/C,IAAI,cAAc,KAAK,SAAS,IAAI,cAAc,KAAK,EAAE,EAAE,CAAC;YAC1D,OAAO,WAAW,CAAC;gBACjB,OAAO,EAAE,yGAAyG;gBAClH,IAAI,EAAE,aAAa;gBACnB,UAAU,EAAE,8DAA8D;gBAC1E,OAAO,EAAE,EAAE;aACZ,CAAC,CAAC;QACL,CAAC;QACD,MAAM,CAAC,GAAG,MAAM,OAAO,CAAU,yBAAyB,2BAA2B,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QACzG,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YACf,OAAO,eAAe,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,OAAO,EAAE,4BAA4B,EAAE,CAAC,CAAC;QACpG,CAAC;QACD,OAAO,YAAY,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC,CACF,CAAC;IAEF,sEAAsE;IACtE,MAAM,CAAC,YAAY,CACjB,uCAAuC,EACvC;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,0FAA0F;YAC1F,+GAA+G;QACjH,WAAW,EAAE;YACX,mEAAmE;YACnE,gCAAgC;YAChC,OAAO,EAAE,qBAAqB,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;YAC5E,6DAA6D;YAC7D,oEAAoE;YACpE,YAAY,EAAE,qBAAqB,EAAE;iBAClC,QAAQ,EAAE;iBACV,QAAQ,CAAC,mGAAmG,CAAC;YAChH,WAAW,EAAE,CAAC;iBACX,MAAM,EAAE;iBACR,KAAK,CAAC,WAAW,EAAE,oCAAoC,CAAC;iBACxD,QAAQ,CAAC,uDAAuD,CAAC;SACrE;QACD,WAAW,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,iCAAiC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;KACrF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,EAAE,EAAE;QAC/C,MAAM,cAAc,GAAG,OAAO,IAAI,YAAY,CAAC;QAC/C,IAAI,cAAc,KAAK,SAAS,IAAI,cAAc,KAAK,EAAE,EAAE,CAAC;YAC1D,OAAO,WAAW,CAAC;gBACjB,OAAO,EAAE,yGAAyG;gBAClH,IAAI,EAAE,aAAa;gBACnB,UAAU,EAAE,8DAA8D;gBAC1E,OAAO,EAAE,EAAE,WAAW,EAAE;aACzB,CAAC,CAAC;QACL,CAAC;QACD,MAAM,CAAC,GAAG,MAAM,OAAO,CACrB,iCAAiC,2BAA2B,CAAC,cAAc,CAAC,IAAI,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAClH,CAAC;QACF,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YACf,OAAO,eAAe,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE,oCAAoC,EAAE,CAAC,CAAC;QACzH,CAAC;QACD,OAAO,YAAY,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC,CACF,CAAC;IAEF,sEAAsE;IACtE,MAAM,CAAC,YAAY,CACjB,8BAA8B,EAC9B;QACE,KAAK,EAAE,yBAAyB;QAChC,kEAAkE;QAClE,mEAAmE;QACnE,mEAAmE;QACnE,WAAW,EACT,4FAA4F;YAC5F,4FAA4F;YAC5F,sFAAsF;YACtF,4DAA4D;YAC5D,+FAA+F;QACjG,WAAW,EAAE;YACX,kEAAkE;YAClE,gEAAgE;YAChE,uEAAuE;YACvE,sEAAsE;YACtE,mEAAmE;YACnE,uEAAuE;YACvE,YAAY,EAAE,qBAAqB,EAAE,CAAC,QAAQ,CAC5C,wFAAwF;gBACtF,mFAAmF;gBACnF,sFAAsF;gBACtF,wDAAwD;gBACxD,kFAAkF,CACrF;YACD,OAAO,EAAE,CAAC;iBACP,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;iBAC/B,QAAQ,EAAE;iBACV,QAAQ,CAAC,8DAA8D,CAAC;YAC3E,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CACpD,wGAAwG,CACzG;YACD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAC3C,uLAAuL,CACxL;YACD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;YAC7E,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC;SAC3E;QACD,WAAW,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;KACxE,EACD,KAAK,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;QACjE,0EAA0E;QAC1E,kEAAkE;QAClE,yEAAyE;QACzE,wEAAwE;QACxE,+CAA+C;QAC/C,MAAM,IAAI,GAA4B;YACpC,WAAW,EAAE,YAAY,EAAE,4BAA4B;YACvD,KAAK;YACL,MAAM;YACN,aAAa,EAAE,KAAK;SACrB,CAAC;QACF,IAAI,OAAO;YAAE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACpC,IAAI,OAAO;YAAE,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC;QACjC,MAAM,CAAC,GAAG,MAAM,OAAO,CAMrB,sBAAsB,EACtB,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAC/C,CAAC;QACF,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YACf,wEAAwE;YACxE,wEAAwE;YACxE,sEAAsE;YACtE,yEAAyE;YACzE,yEAAyE;YACzE,yEAAyE;YACzE,gBAAgB;YAChB,MAAM,OAAO,GAAG,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC;YAClC,OAAO,eAAe,CAAC,CAAC,EAAE;gBACtB,YAAY;gBACZ,KAAK;gBACL,MAAM;gBACN,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACvE,EAAE,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,OAAO,YAAY,CACjB;YACE,EAAE,EAAE,IAAI;YACR,YAAY;YACZ,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,IAAI,KAAK,CAAC,MAAM;YACpC,QAAQ,EAAE,KAAK,CAAC,MAAM;YACtB,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;YACzB,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,uBAAuB,KAAK,CAAC,MAAM,SAAS,CAAC,CAAC,CAAC,SAAS;SACnF,EACD,KAAK,EACL,EAAE,QAAQ,EAAE,IAAI,EAAE,CACnB,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -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.
|
|
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.
|
|
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.
|