@cyanheads/mcp-ts-core 0.13.2 → 0.13.4
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/AGENTS.md +12 -10
- package/CLAUDE.md +12 -10
- package/README.md +2 -2
- package/changelog/0.13.x/0.13.3.md +44 -0
- package/changelog/0.13.x/0.13.4.md +65 -0
- package/changelog/template.md +7 -7
- package/dist/config/appRoot.d.ts.map +1 -1
- package/dist/config/appRoot.js +48 -16
- package/dist/config/appRoot.js.map +1 -1
- package/dist/core/app.d.ts +20 -0
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +1 -0
- package/dist/core/app.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/linter/rules/schema-rules.d.ts +19 -0
- package/dist/linter/rules/schema-rules.d.ts.map +1 -1
- package/dist/linter/rules/schema-rules.js +36 -0
- package/dist/linter/rules/schema-rules.js.map +1 -1
- package/dist/linter/rules/tool-rules.d.ts +17 -0
- package/dist/linter/rules/tool-rules.d.ts.map +1 -1
- package/dist/linter/rules/tool-rules.js +97 -1
- package/dist/linter/rules/tool-rules.js.map +1 -1
- package/dist/mcp-server/handlerContext.d.ts +6 -0
- package/dist/mcp-server/handlerContext.d.ts.map +1 -1
- package/dist/mcp-server/handlerContext.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +17 -9
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts +114 -0
- package/dist/mcp-server/tools/utils/inputPrevalidation.d.ts.map +1 -0
- package/dist/mcp-server/tools/utils/inputPrevalidation.js +428 -0
- package/dist/mcp-server/tools/utils/inputPrevalidation.js.map +1 -0
- package/dist/mcp-server/tools/utils/strictenRecord.d.ts +42 -0
- package/dist/mcp-server/tools/utils/strictenRecord.d.ts.map +1 -0
- package/dist/mcp-server/tools/utils/strictenRecord.js +48 -0
- package/dist/mcp-server/tools/utils/strictenRecord.js.map +1 -0
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts +27 -0
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.js +35 -6
- package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +28 -4
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +245 -23
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/services/canvas/core/sqlGate.d.ts +14 -1
- package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
- package/dist/services/canvas/core/sqlGate.js +69 -7
- package/dist/services/canvas/core/sqlGate.js.map +1 -1
- package/dist/services/canvas/index.d.ts +1 -1
- package/dist/services/canvas/index.d.ts.map +1 -1
- package/dist/services/canvas/index.js +1 -1
- package/dist/services/canvas/index.js.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts +20 -0
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +75 -25
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
- package/dist/services/canvas/types.d.ts +6 -1
- package/dist/services/canvas/types.d.ts.map +1 -1
- package/dist/types-global/errors.d.ts.map +1 -1
- package/dist/types-global/errors.js +4 -3
- package/dist/types-global/errors.js.map +1 -1
- package/dist/utils/index.d.ts +3 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.d.ts +33 -0
- package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.js +45 -3
- package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
- package/dist/utils/network/httpError.d.ts +13 -2
- package/dist/utils/network/httpError.d.ts.map +1 -1
- package/dist/utils/network/httpError.js +4 -2
- package/dist/utils/network/httpError.js.map +1 -1
- package/dist/utils/network/pacer.d.ts +117 -0
- package/dist/utils/network/pacer.d.ts.map +1 -0
- package/dist/utils/network/pacer.js +304 -0
- package/dist/utils/network/pacer.js.map +1 -0
- package/dist/utils/network/retry.d.ts +119 -3
- package/dist/utils/network/retry.d.ts.map +1 -1
- package/dist/utils/network/retry.js +176 -35
- package/dist/utils/network/retry.js.map +1 -1
- package/dist/utils/security/rateLimiter.d.ts +19 -1
- package/dist/utils/security/rateLimiter.d.ts.map +1 -1
- package/dist/utils/security/rateLimiter.js +49 -1
- package/dist/utils/security/rateLimiter.js.map +1 -1
- package/dist/utils/telemetry/attributes.d.ts +19 -0
- package/dist/utils/telemetry/attributes.d.ts.map +1 -1
- package/dist/utils/telemetry/attributes.js +30 -0
- package/dist/utils/telemetry/attributes.js.map +1 -1
- package/framework-skills/add-tool/SKILL.md +43 -2
- package/framework-skills/api-canvas/SKILL.md +8 -4
- package/framework-skills/api-config/SKILL.md +4 -4
- package/framework-skills/api-errors/SKILL.md +19 -10
- package/framework-skills/api-linter/SKILL.md +48 -3
- package/framework-skills/api-telemetry/SKILL.md +26 -2
- package/framework-skills/api-utils/SKILL.md +7 -3
- package/framework-skills/api-utils/references/security.md +2 -2
- package/framework-skills/design-mcp-server/SKILL.md +17 -2
- package/framework-skills/field-test/SKILL.md +3 -1
- package/framework-skills/git-wrapup/SKILL.md +90 -68
- package/framework-skills/orchestrations/SKILL.md +3 -3
- package/framework-skills/release-and-publish/SKILL.md +10 -10
- package/framework-skills/release-pr-review/SKILL.md +19 -26
- package/framework-skills/report-issue-framework/SKILL.md +6 -35
- package/framework-skills/report-issue-local/SKILL.md +6 -36
- package/framework-skills/tool-defs-analysis/SKILL.md +4 -4
- package/package.json +7 -7
- package/templates/.github/ISSUE_TEMPLATE/bug_report.yml +2 -2
- package/templates/.github/ISSUE_TEMPLATE/feature_request.yml +2 -2
- package/templates/.github/workflows/codeql.yml +39 -0
- package/templates/AGENTS.md +3 -1
- package/templates/CLAUDE.md +3 -1
- package/templates/changelog/template.md +7 -7
- package/templates/package.json +2 -2
- package/templates/src/index.ts +4 -3
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The ordered step raw `tools/call` arguments pass through before
|
|
3
|
+
* — and, for one stage, after — the `input` schema parses them.
|
|
4
|
+
*
|
|
5
|
+
* Strict inputs (#232) reject an unrecognized root key by name, which is right
|
|
6
|
+
* for a misspelling the caller can fix and wrong for the three cases collected
|
|
7
|
+
* here, where the arguments the model wrote are correct and something between
|
|
8
|
+
* the model and the schema is not:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Client-added keys (#453)** — a placeholder, a call description, a call
|
|
11
|
+
* id, or an `arguments`-level `_meta` block the model never wrote and cannot
|
|
12
|
+
* remove. Dropped before parsing.
|
|
13
|
+
* 2. **Key aliases (#452)** — a declared `inputAliases` entry, or the same name
|
|
14
|
+
* in another case style (`max_results` for `maxResults`). Rewritten to the
|
|
15
|
+
* canonical key before parsing. A one-to-one mapping fixed ahead of time, not
|
|
16
|
+
* the nearest-key guess #232 rejected.
|
|
17
|
+
* 3. **Representation repair (#234)** — a JSON-stringified array where an array
|
|
18
|
+
* was declared. Applied only *after* the parse has already failed, and kept
|
|
19
|
+
* only when it flips the arguments from invalid to valid: the author's schema
|
|
20
|
+
* is the sole arbiter, so a repair can never touch input that was already
|
|
21
|
+
* valid.
|
|
22
|
+
*
|
|
23
|
+
* All three are on by default and each has a server-level switch on
|
|
24
|
+
* `createApp({ input })` / `createWorkerHandler({ input })`. None of them
|
|
25
|
+
* changes what `tools/list` advertises, and none appears in a response: a drop,
|
|
26
|
+
* a rewrite, and a repair each emit one debug log and one counter increment
|
|
27
|
+
* instead.
|
|
28
|
+
*
|
|
29
|
+
* The split between the two is deliberate. Counter attributes are bounded and
|
|
30
|
+
* author- or framework-defined — the ignore-list entry that matched, the
|
|
31
|
+
* declared key a rewrite resolved to — because a metric label carrying the
|
|
32
|
+
* caller's own key text mints a permanent time series per spelling a client
|
|
33
|
+
* invents (#114). The raw key and alias go to the debug log, which is where an
|
|
34
|
+
* operator looks when a counter shows a new client artifact and where
|
|
35
|
+
* cardinality costs nothing.
|
|
36
|
+
*
|
|
37
|
+
* @module src/mcp-server/tools/utils/inputPrevalidation
|
|
38
|
+
*/
|
|
39
|
+
import { type RequestContext } from '../../../utils/internal/requestContext.js';
|
|
40
|
+
import type { AnyToolDefinition } from './toolDefinition.js';
|
|
41
|
+
/**
|
|
42
|
+
* Server-level switches for the pre-validation step, set once via
|
|
43
|
+
* `createApp({ input })`. Every stage is on with no configuration; an entry
|
|
44
|
+
* here either extends a stage or turns it off for the whole server. There is no
|
|
45
|
+
* per-tool switch — a definition's only say is the `inputAliases` it declares.
|
|
46
|
+
*/
|
|
47
|
+
export interface InputHandlingOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Rewrite an undeclared root key whose case-folded form (`-`/`_` stripped,
|
|
50
|
+
* lowercased) matches exactly one declared key. `false` leaves declared
|
|
51
|
+
* `inputAliases` working and restores rejection for undeclared variants.
|
|
52
|
+
* Default `true`.
|
|
53
|
+
*/
|
|
54
|
+
caseStyleAliases?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Retry a failed parse once against repaired argument *values* — a
|
|
57
|
+
* JSON-stringified array parsed back into an array. `false` restores the
|
|
58
|
+
* single-parse behavior exactly. Default `true`.
|
|
59
|
+
*/
|
|
60
|
+
coerce?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Additional root keys to drop when the tool does not declare them, on top of
|
|
63
|
+
* the built-in client artifacts (`_meta`, `tool_call_description`,
|
|
64
|
+
* `toolCallId`). `false` disables the stage, so every undeclared key is
|
|
65
|
+
* rejected by name as it was before. Default: the built-in list only.
|
|
66
|
+
*/
|
|
67
|
+
ignoreKeys?: readonly string[] | false;
|
|
68
|
+
}
|
|
69
|
+
/** One increment per repaired call, not per repaired value. */
|
|
70
|
+
export declare function countCoerced(toolName: string, context: RequestContext | undefined): void;
|
|
71
|
+
/**
|
|
72
|
+
* `Max-Results`, `max_results`, and `maxResults` all fold to `maxresults`.
|
|
73
|
+
*
|
|
74
|
+
* Exported so `lint:mcp` decides alias ambiguity by the same fold the rewrite
|
|
75
|
+
* uses — a rule computing its own would pass a definition the runtime declines
|
|
76
|
+
* to rewrite, or fail one it rewrites happily.
|
|
77
|
+
*/
|
|
78
|
+
export declare function foldArgumentKey(key: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* Runs the pre-parse half of the step — drop client-added keys, then rewrite
|
|
81
|
+
* key aliases — and returns the arguments to parse.
|
|
82
|
+
*
|
|
83
|
+
* Returns the caller's own object unchanged when nothing applied, so the common
|
|
84
|
+
* path allocates nothing. Non-object arguments are handed straight back: the
|
|
85
|
+
* schema's own rejection already says what arrived.
|
|
86
|
+
*/
|
|
87
|
+
export declare function prevalidateToolArguments(def: AnyToolDefinition, args: unknown, options: InputHandlingOptions | undefined, context: RequestContext | undefined): unknown;
|
|
88
|
+
/**
|
|
89
|
+
* Parses back a string whose trimmed form is a JSON array, at each path the
|
|
90
|
+
* failed parse's issues named. Keys are never added, dropped, or renamed, and
|
|
91
|
+
* a value no issue points at is returned untouched.
|
|
92
|
+
*
|
|
93
|
+
* **Targeted, not a full walk.** A rejection names exactly the values the schema
|
|
94
|
+
* could not accept, and repairing anything else loses repairs that should have
|
|
95
|
+
* succeeded: one call carrying a stringified array for an array field *and* a
|
|
96
|
+
* free-text field legitimately holding `"[1,2]"` would have both rewritten, the
|
|
97
|
+
* re-parse would fail on the free-text field, and the whole call would be
|
|
98
|
+
* rejected over a value that was valid all along. An `invalid_union` issue's
|
|
99
|
+
* outer path counts like any other — that is the value the union rejected.
|
|
100
|
+
*
|
|
101
|
+
* `JSON.parse` is the exact inverse of the `JSON.stringify` that produced the
|
|
102
|
+
* value, so this undoes a known encoding rather than matching a nearest
|
|
103
|
+
* candidate — the distinction that makes it safe where nearest-key matching is
|
|
104
|
+
* not (#232). Safety does not rest on that alone: {@link parseToolArguments}
|
|
105
|
+
* runs this only after validation has already failed and keeps the result only
|
|
106
|
+
* when it then passes.
|
|
107
|
+
*
|
|
108
|
+
* Returns the argument itself when nothing was repairable, which is the signal
|
|
109
|
+
* the caller uses to skip the second parse.
|
|
110
|
+
*/
|
|
111
|
+
export declare function repairRepresentations(args: unknown, issues: ReadonlyArray<{
|
|
112
|
+
readonly path: ReadonlyArray<PropertyKey>;
|
|
113
|
+
}>): unknown;
|
|
114
|
+
//# sourceMappingURL=inputPrevalidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inputPrevalidation.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/utils/inputPrevalidation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAMH,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,oCAAoC,CAAC;AAW5C,OAAO,KAAK,EAAE,iBAAiB,EAAmB,MAAM,qBAAqB,CAAC;AAM9E;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAAC;CACxC;AAoED,+DAA+D;AAC/D,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,CAexF;AA8CD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAyND;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,iBAAiB,EACtB,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,oBAAoB,GAAG,SAAS,EACzC,OAAO,EAAE,cAAc,GAAG,SAAS,GAClC,OAAO,CAcT;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,aAAa,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;CAAE,CAAC,GACnE,OAAO,CAST"}
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The ordered step raw `tools/call` arguments pass through before
|
|
3
|
+
* — and, for one stage, after — the `input` schema parses them.
|
|
4
|
+
*
|
|
5
|
+
* Strict inputs (#232) reject an unrecognized root key by name, which is right
|
|
6
|
+
* for a misspelling the caller can fix and wrong for the three cases collected
|
|
7
|
+
* here, where the arguments the model wrote are correct and something between
|
|
8
|
+
* the model and the schema is not:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Client-added keys (#453)** — a placeholder, a call description, a call
|
|
11
|
+
* id, or an `arguments`-level `_meta` block the model never wrote and cannot
|
|
12
|
+
* remove. Dropped before parsing.
|
|
13
|
+
* 2. **Key aliases (#452)** — a declared `inputAliases` entry, or the same name
|
|
14
|
+
* in another case style (`max_results` for `maxResults`). Rewritten to the
|
|
15
|
+
* canonical key before parsing. A one-to-one mapping fixed ahead of time, not
|
|
16
|
+
* the nearest-key guess #232 rejected.
|
|
17
|
+
* 3. **Representation repair (#234)** — a JSON-stringified array where an array
|
|
18
|
+
* was declared. Applied only *after* the parse has already failed, and kept
|
|
19
|
+
* only when it flips the arguments from invalid to valid: the author's schema
|
|
20
|
+
* is the sole arbiter, so a repair can never touch input that was already
|
|
21
|
+
* valid.
|
|
22
|
+
*
|
|
23
|
+
* All three are on by default and each has a server-level switch on
|
|
24
|
+
* `createApp({ input })` / `createWorkerHandler({ input })`. None of them
|
|
25
|
+
* changes what `tools/list` advertises, and none appears in a response: a drop,
|
|
26
|
+
* a rewrite, and a repair each emit one debug log and one counter increment
|
|
27
|
+
* instead.
|
|
28
|
+
*
|
|
29
|
+
* The split between the two is deliberate. Counter attributes are bounded and
|
|
30
|
+
* author- or framework-defined — the ignore-list entry that matched, the
|
|
31
|
+
* declared key a rewrite resolved to — because a metric label carrying the
|
|
32
|
+
* caller's own key text mints a permanent time series per spelling a client
|
|
33
|
+
* invents (#114). The raw key and alias go to the debug log, which is where an
|
|
34
|
+
* operator looks when a counter shows a new client artifact and where
|
|
35
|
+
* cardinality costs nothing.
|
|
36
|
+
*
|
|
37
|
+
* @module src/mcp-server/tools/utils/inputPrevalidation
|
|
38
|
+
*/
|
|
39
|
+
import { logger } from '../../../utils/internal/logger.js';
|
|
40
|
+
import { requestContextService, withExtra, } from '../../../utils/internal/requestContext.js';
|
|
41
|
+
import { ATTR_MCP_INPUT_ALIAS_KIND, ATTR_MCP_INPUT_COERCION, ATTR_MCP_INPUT_IGNORE_RULE, ATTR_MCP_INPUT_TARGET, ATTR_MCP_TOOL_NAME, } from '../../../utils/telemetry/attributes.js';
|
|
42
|
+
import { createCounter } from '../../../utils/telemetry/metrics.js';
|
|
43
|
+
import { scanHeaderDesignations } from './headerParam.js';
|
|
44
|
+
import { inputVariants, isDiscriminatedUnionSchema, zodDef } from './schemaShape.js';
|
|
45
|
+
/**
|
|
46
|
+
* Root keys known to be added by a client rather than written by the model, so
|
|
47
|
+
* rejecting them fails a call whose arguments were all correct. `_meta` belongs
|
|
48
|
+
* on `params`, not inside `arguments`; the other two are call bookkeeping.
|
|
49
|
+
*/
|
|
50
|
+
const BUILT_IN_IGNORED_KEYS = ['_meta', 'tool_call_description', 'toolCallId'];
|
|
51
|
+
/** The one repair {@link repairRepresentations} performs, as the counter labels it. */
|
|
52
|
+
const COERCION_KIND_STRINGIFIED_ARRAY = 'stringified_array';
|
|
53
|
+
/**
|
|
54
|
+
* What the ignored-key counter reports for a key the underscore heuristic
|
|
55
|
+
* dropped. The key itself is caller-supplied and stays in the debug log.
|
|
56
|
+
*/
|
|
57
|
+
const UNDERSCORE_RULE = 'underscore_prefix';
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Telemetry
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
let ignoredKeyCounter;
|
|
62
|
+
let aliasedCounter;
|
|
63
|
+
let coercedCounter;
|
|
64
|
+
/**
|
|
65
|
+
* Lazily created — a server whose callers never trip a stage emits no series.
|
|
66
|
+
*
|
|
67
|
+
* `rule` is the ignore-list entry that matched or {@link UNDERSCORE_RULE}, never
|
|
68
|
+
* the dropped key itself: the key is the caller's text, and one client inventing
|
|
69
|
+
* `_callId`, `_call_id`, `_callID` would mint three permanent series (#114). The
|
|
70
|
+
* ignore list is author- and framework-defined, so it is safe to name.
|
|
71
|
+
*/
|
|
72
|
+
function countIgnoredKey(toolName, rule) {
|
|
73
|
+
ignoredKeyCounter ??= createCounter('mcp.input.ignored_key', 'Client-added root argument keys dropped before validation', '{keys}');
|
|
74
|
+
ignoredKeyCounter.add(1, {
|
|
75
|
+
[ATTR_MCP_TOOL_NAME]: toolName,
|
|
76
|
+
[ATTR_MCP_INPUT_IGNORE_RULE]: rule,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Labelled by the canonical *target* and which half of the stage fired — both
|
|
81
|
+
* bounded by the definition. The alias is the caller's text, and the case-style
|
|
82
|
+
* half accepts every `-`/`_`/case permutation of a declared key, so labelling it
|
|
83
|
+
* would put an unbounded set on a permanent series (#114).
|
|
84
|
+
*/
|
|
85
|
+
function countAliased(toolName, target, kind) {
|
|
86
|
+
aliasedCounter ??= createCounter('mcp.input.aliased', 'Root argument keys rewritten to their canonical spelling', '{keys}');
|
|
87
|
+
aliasedCounter.add(1, {
|
|
88
|
+
[ATTR_MCP_TOOL_NAME]: toolName,
|
|
89
|
+
[ATTR_MCP_INPUT_TARGET]: target,
|
|
90
|
+
[ATTR_MCP_INPUT_ALIAS_KIND]: kind,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** One increment per repaired call, not per repaired value. */
|
|
94
|
+
export function countCoerced(toolName, context) {
|
|
95
|
+
coercedCounter ??= createCounter('mcp.input.coerced', 'Tool calls that validated only after a representation repair', '{calls}');
|
|
96
|
+
coercedCounter.add(1, {
|
|
97
|
+
[ATTR_MCP_TOOL_NAME]: toolName,
|
|
98
|
+
[ATTR_MCP_INPUT_COERCION]: COERCION_KIND_STRINGIFIED_ARRAY,
|
|
99
|
+
});
|
|
100
|
+
debugLog(`Tool '${toolName}': arguments validated after repairing a stringified array.`, context, { toolName, coercion: COERCION_KIND_STRINGIFIED_ARRAY });
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The step's own debug channel. A pre-validation decision is invisible in the
|
|
104
|
+
* response by design, so the log is the only place a server operator sees that
|
|
105
|
+
* a key was dropped or moved.
|
|
106
|
+
*/
|
|
107
|
+
function debugLog(message, context, extra) {
|
|
108
|
+
const base = context ?? requestContextService.createRequestContext({ operation: 'ToolInputPrevalidation' });
|
|
109
|
+
logger.debug(message, withExtra(base, extra));
|
|
110
|
+
}
|
|
111
|
+
const plans = new WeakMap();
|
|
112
|
+
/**
|
|
113
|
+
* `Max-Results`, `max_results`, and `maxResults` all fold to `maxresults`.
|
|
114
|
+
*
|
|
115
|
+
* Exported so `lint:mcp` decides alias ambiguity by the same fold the rewrite
|
|
116
|
+
* uses — a rule computing its own would pass a definition the runtime declines
|
|
117
|
+
* to rewrite, or fail one it rewrites happily.
|
|
118
|
+
*/
|
|
119
|
+
export function foldArgumentKey(key) {
|
|
120
|
+
return key.replaceAll(/[-_]/g, '').toLowerCase();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* True when the author opened the object themselves. `.strict()` also records a
|
|
124
|
+
* catchall — a `ZodNever` one — so the type of the catchall, not its presence,
|
|
125
|
+
* is what separates an open root from a strict one.
|
|
126
|
+
*/
|
|
127
|
+
function isOpenObject(schema) {
|
|
128
|
+
const catchall = schema.def.catchall;
|
|
129
|
+
return catchall !== undefined && zodDef(catchall)?.type !== 'never';
|
|
130
|
+
}
|
|
131
|
+
function buildVariantPlan(schema) {
|
|
132
|
+
const keys = new Set(Object.keys(schema.shape));
|
|
133
|
+
const folded = new Map();
|
|
134
|
+
for (const key of keys) {
|
|
135
|
+
const fold = foldArgumentKey(key);
|
|
136
|
+
folded.set(fold, folded.has(fold) ? null : key);
|
|
137
|
+
}
|
|
138
|
+
return { folded, keys, open: isOpenObject(schema) };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Derives the plan for a definition's input root, memoized on the root schema —
|
|
142
|
+
* the header scan emits JSON Schema, which is far too much work to repeat per
|
|
143
|
+
* call.
|
|
144
|
+
*/
|
|
145
|
+
function planFor(input) {
|
|
146
|
+
const cached = plans.get(input);
|
|
147
|
+
if (cached)
|
|
148
|
+
return cached;
|
|
149
|
+
const variants = inputVariants(input).map((schema) => ({
|
|
150
|
+
plan: buildVariantPlan(schema),
|
|
151
|
+
schema,
|
|
152
|
+
}));
|
|
153
|
+
const allKeys = new Set();
|
|
154
|
+
for (const variant of variants)
|
|
155
|
+
for (const key of variant.plan.keys)
|
|
156
|
+
allKeys.add(key);
|
|
157
|
+
// A union root can carry no valid designation at all (every field sits under
|
|
158
|
+
// `oneOf`), and an invalid one already failed in `tool()`, so this only ever
|
|
159
|
+
// collects root properties of an object root.
|
|
160
|
+
const scan = scanHeaderDesignations(input);
|
|
161
|
+
const headerTargets = new Set(scan?.valid
|
|
162
|
+
? scan.designations.flatMap(({ path }) => {
|
|
163
|
+
const step = path.length === 1 ? path[0] : undefined;
|
|
164
|
+
return step?.kind === 'property' ? [step.key] : [];
|
|
165
|
+
})
|
|
166
|
+
: []);
|
|
167
|
+
const discriminator = isDiscriminatedUnionSchema(input)
|
|
168
|
+
? zodDef(input)?.discriminator
|
|
169
|
+
: undefined;
|
|
170
|
+
const plan = {
|
|
171
|
+
allKeys,
|
|
172
|
+
anyOpen: variants.some((variant) => variant.plan.open),
|
|
173
|
+
discriminator,
|
|
174
|
+
headerTargets,
|
|
175
|
+
underscoreDeclared: [...allKeys].some((key) => key.startsWith('_')),
|
|
176
|
+
variants,
|
|
177
|
+
};
|
|
178
|
+
plans.set(input, plan);
|
|
179
|
+
return plan;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The variant the arguments select, or `undefined` when nothing selects one.
|
|
183
|
+
*
|
|
184
|
+
* An object root has exactly one. A union root has no single declared-key set
|
|
185
|
+
* until the discriminator is read, and a call whose discriminator is absent or
|
|
186
|
+
* unrecognized fails on the discriminator either way — so the rewrite stage
|
|
187
|
+
* declines rather than guessing a branch.
|
|
188
|
+
*/
|
|
189
|
+
function selectVariant(plan, args) {
|
|
190
|
+
if (plan.discriminator === undefined)
|
|
191
|
+
return plan.variants[0]?.plan;
|
|
192
|
+
if (!Object.hasOwn(args, plan.discriminator))
|
|
193
|
+
return undefined;
|
|
194
|
+
const value = args[plan.discriminator];
|
|
195
|
+
for (const variant of plan.variants) {
|
|
196
|
+
const field = variant.schema.shape[plan.discriminator];
|
|
197
|
+
if (field?.safeParse(value).success)
|
|
198
|
+
return variant.plan;
|
|
199
|
+
}
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Stage 1 — drop client-added keys (#453)
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
/**
|
|
206
|
+
* Drops a root key the tool does not declare when it is either underscore-
|
|
207
|
+
* prefixed or on the ignore list. Three boundaries:
|
|
208
|
+
*
|
|
209
|
+
* - **A declared key is never dropped**, including a declared underscore-prefixed
|
|
210
|
+
* one. On a union root "declared" is every variant's root keys, since the
|
|
211
|
+
* matching variant is unknown until the discriminator is read.
|
|
212
|
+
* - **An author-opened root is untouched** — its extra keys reach the handler
|
|
213
|
+
* today, and dropping them would delete data the author accepts on purpose.
|
|
214
|
+
* - **The underscore rule is off for a tool declaring any underscore-prefixed
|
|
215
|
+
* key.** Otherwise a misspelled `_cursor` would be silently dropped instead of
|
|
216
|
+
* rejected by name, which is the failure #232 removed. The ignore list still
|
|
217
|
+
* applies there.
|
|
218
|
+
*/
|
|
219
|
+
function dropIgnoredKeys(def, args, plan, options, context) {
|
|
220
|
+
if (options?.ignoreKeys === false || plan.anyOpen)
|
|
221
|
+
return args;
|
|
222
|
+
const ignored = new Set([...BUILT_IN_IGNORED_KEYS, ...(options?.ignoreKeys ?? [])]);
|
|
223
|
+
const underscoreRule = !plan.underscoreDeclared;
|
|
224
|
+
let kept;
|
|
225
|
+
for (const key of Object.keys(args)) {
|
|
226
|
+
if (plan.allKeys.has(key))
|
|
227
|
+
continue;
|
|
228
|
+
const listed = ignored.has(key);
|
|
229
|
+
if (!listed && !(underscoreRule && key.startsWith('_')))
|
|
230
|
+
continue;
|
|
231
|
+
// Spread copies own properties by definition, so a caller's own `__proto__`
|
|
232
|
+
// survives as a key instead of re-prototyping the copy; `delete` on that own
|
|
233
|
+
// property is equally safe.
|
|
234
|
+
kept ??= { ...args };
|
|
235
|
+
delete kept[key];
|
|
236
|
+
countIgnoredKey(def.name, listed ? key : UNDERSCORE_RULE);
|
|
237
|
+
debugLog(`Tool '${def.name}': dropped client-added argument key '${key}'.`, context, {
|
|
238
|
+
toolName: def.name,
|
|
239
|
+
ignoredKey: key,
|
|
240
|
+
ignoreRule: listed ? key : UNDERSCORE_RULE,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return kept ?? args;
|
|
244
|
+
}
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Stage 2 — key aliases (#452)
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
/**
|
|
249
|
+
* Rewrites root keys to their canonical spelling in two passes: the definition's
|
|
250
|
+
* declared `inputAliases`, then — unless `caseStyleAliases: false` — any
|
|
251
|
+
* undeclared key whose case-folded form names exactly one declared key.
|
|
252
|
+
*
|
|
253
|
+
* A rewrite applies only when the target key is absent; with both present the
|
|
254
|
+
* arguments pass through and the strict rejection fires as it does today. An
|
|
255
|
+
* author-opened root is never rewritten (an unknown key there is already
|
|
256
|
+
* accepted verbatim), and a `headerParam`-designated target is never rewritten
|
|
257
|
+
* *to*: the SDK cross-checks the `Mcp-Param-<Name>` header against the raw body
|
|
258
|
+
* before dispatch, so a later rewrite would hand the handler a value no
|
|
259
|
+
* intermediary attested.
|
|
260
|
+
*/
|
|
261
|
+
function applyAliases(def, args, plan, options, context) {
|
|
262
|
+
const declared = def.inputAliases;
|
|
263
|
+
const caseStyle = options?.caseStyleAliases !== false;
|
|
264
|
+
if (plan.anyOpen || (!declared && !caseStyle))
|
|
265
|
+
return args;
|
|
266
|
+
const variant = selectVariant(plan, args);
|
|
267
|
+
if (!variant)
|
|
268
|
+
return args;
|
|
269
|
+
let rewritten;
|
|
270
|
+
const current = () => rewritten ?? args;
|
|
271
|
+
const rewritable = (alias, target) => Object.hasOwn(current(), alias) &&
|
|
272
|
+
!variant.keys.has(alias) &&
|
|
273
|
+
variant.keys.has(target) &&
|
|
274
|
+
!plan.headerTargets.has(target) &&
|
|
275
|
+
!Object.hasOwn(current(), target);
|
|
276
|
+
const move = (alias, target, kind) => {
|
|
277
|
+
// `target` is a declared key read off the Zod shape, never caller text — an
|
|
278
|
+
// object literal resolves `__proto__` to the prototype setter rather than a
|
|
279
|
+
// key, so a shape cannot declare one and this assignment cannot reach it.
|
|
280
|
+
// `alias` is caller text, but it is only read and deleted, both own-property
|
|
281
|
+
// operations. The spread defines rather than assigns, so an own `__proto__`
|
|
282
|
+
// elsewhere in the arguments survives the copy.
|
|
283
|
+
rewritten ??= { ...args };
|
|
284
|
+
rewritten[target] = rewritten[alias];
|
|
285
|
+
delete rewritten[alias];
|
|
286
|
+
countAliased(def.name, target, kind);
|
|
287
|
+
debugLog(`Tool '${def.name}': rewrote argument key '${alias}' to '${target}'.`, context, {
|
|
288
|
+
toolName: def.name,
|
|
289
|
+
alias,
|
|
290
|
+
target,
|
|
291
|
+
aliasKind: kind,
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
for (const [alias, target] of Object.entries(declared ?? {})) {
|
|
295
|
+
if (rewritable(alias, target))
|
|
296
|
+
move(alias, target, 'declared');
|
|
297
|
+
}
|
|
298
|
+
if (caseStyle) {
|
|
299
|
+
// Snapshot first: `move` only ever removes an undeclared key and adds a
|
|
300
|
+
// declared one, so the remaining candidates are unaffected.
|
|
301
|
+
for (const key of Object.keys(current())) {
|
|
302
|
+
if (variant.keys.has(key))
|
|
303
|
+
continue;
|
|
304
|
+
const target = variant.folded.get(foldArgumentKey(key));
|
|
305
|
+
if (target && rewritable(key, target))
|
|
306
|
+
move(key, target, 'case_style');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return rewritten ?? args;
|
|
310
|
+
}
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
// Public entry points
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
/**
|
|
315
|
+
* Runs the pre-parse half of the step — drop client-added keys, then rewrite
|
|
316
|
+
* key aliases — and returns the arguments to parse.
|
|
317
|
+
*
|
|
318
|
+
* Returns the caller's own object unchanged when nothing applied, so the common
|
|
319
|
+
* path allocates nothing. Non-object arguments are handed straight back: the
|
|
320
|
+
* schema's own rejection already says what arrived.
|
|
321
|
+
*/
|
|
322
|
+
export function prevalidateToolArguments(def, args, options, context) {
|
|
323
|
+
if (args === null || typeof args !== 'object' || Array.isArray(args))
|
|
324
|
+
return args;
|
|
325
|
+
const plan = planFor(def.input);
|
|
326
|
+
if (plan.variants.length === 0)
|
|
327
|
+
return args;
|
|
328
|
+
const record = args;
|
|
329
|
+
return applyAliases(def, dropIgnoredKeys(def, record, plan, options, context), plan, options, context);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Parses back a string whose trimmed form is a JSON array, at each path the
|
|
333
|
+
* failed parse's issues named. Keys are never added, dropped, or renamed, and
|
|
334
|
+
* a value no issue points at is returned untouched.
|
|
335
|
+
*
|
|
336
|
+
* **Targeted, not a full walk.** A rejection names exactly the values the schema
|
|
337
|
+
* could not accept, and repairing anything else loses repairs that should have
|
|
338
|
+
* succeeded: one call carrying a stringified array for an array field *and* a
|
|
339
|
+
* free-text field legitimately holding `"[1,2]"` would have both rewritten, the
|
|
340
|
+
* re-parse would fail on the free-text field, and the whole call would be
|
|
341
|
+
* rejected over a value that was valid all along. An `invalid_union` issue's
|
|
342
|
+
* outer path counts like any other — that is the value the union rejected.
|
|
343
|
+
*
|
|
344
|
+
* `JSON.parse` is the exact inverse of the `JSON.stringify` that produced the
|
|
345
|
+
* value, so this undoes a known encoding rather than matching a nearest
|
|
346
|
+
* candidate — the distinction that makes it safe where nearest-key matching is
|
|
347
|
+
* not (#232). Safety does not rest on that alone: {@link parseToolArguments}
|
|
348
|
+
* runs this only after validation has already failed and keeps the result only
|
|
349
|
+
* when it then passes.
|
|
350
|
+
*
|
|
351
|
+
* Returns the argument itself when nothing was repairable, which is the signal
|
|
352
|
+
* the caller uses to skip the second parse.
|
|
353
|
+
*/
|
|
354
|
+
export function repairRepresentations(args, issues) {
|
|
355
|
+
let repaired = args;
|
|
356
|
+
for (const issue of issues) {
|
|
357
|
+
if (issue.path.length === 0)
|
|
358
|
+
continue;
|
|
359
|
+
const current = readAt(repaired, issue.path);
|
|
360
|
+
const next = repairValue(current);
|
|
361
|
+
if (next !== current)
|
|
362
|
+
repaired = writeAt(repaired, issue.path, next);
|
|
363
|
+
}
|
|
364
|
+
return repaired;
|
|
365
|
+
}
|
|
366
|
+
/** The one repair: a JSON-array string back into the array it encodes. */
|
|
367
|
+
function repairValue(value) {
|
|
368
|
+
if (typeof value !== 'string')
|
|
369
|
+
return value;
|
|
370
|
+
const trimmed = value.trim();
|
|
371
|
+
if (!trimmed.startsWith('['))
|
|
372
|
+
return value;
|
|
373
|
+
try {
|
|
374
|
+
const parsed = JSON.parse(trimmed);
|
|
375
|
+
return Array.isArray(parsed) ? parsed : value;
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/** The value at `path`, or `undefined` when nothing owns one there. */
|
|
382
|
+
function readAt(root, path) {
|
|
383
|
+
let cursor = root;
|
|
384
|
+
for (const segment of path) {
|
|
385
|
+
if (cursor === null || typeof cursor !== 'object')
|
|
386
|
+
return undefined;
|
|
387
|
+
if (!Object.hasOwn(cursor, segment))
|
|
388
|
+
return undefined;
|
|
389
|
+
cursor = cursor[segment];
|
|
390
|
+
}
|
|
391
|
+
return cursor;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Replaces the value at `path`, copying each container along the way and
|
|
395
|
+
* leaving the caller's own objects untouched.
|
|
396
|
+
*
|
|
397
|
+
* Object copies go through `Object.fromEntries`, which *defines* each property
|
|
398
|
+
* rather than assigning it. `copy[key] = value` would route a caller's own
|
|
399
|
+
* `__proto__` key — which `JSON.parse` creates as an ordinary own data property
|
|
400
|
+
* — through `Object.prototype`'s setter: the key would vanish and the copy's
|
|
401
|
+
* prototype would become whatever the caller sent, which Zod then reads
|
|
402
|
+
* inherited values from. Defining keeps the key and the prototype, and keeps
|
|
403
|
+
* this function's promise that no key is added, dropped, or renamed.
|
|
404
|
+
*/
|
|
405
|
+
function writeAt(root, path, value) {
|
|
406
|
+
const [head, ...rest] = path;
|
|
407
|
+
if (head === undefined)
|
|
408
|
+
return value;
|
|
409
|
+
if (Array.isArray(root)) {
|
|
410
|
+
const index = Number(head);
|
|
411
|
+
if (!Number.isInteger(index) || index < 0 || index >= root.length)
|
|
412
|
+
return root;
|
|
413
|
+
const copy = [...root];
|
|
414
|
+
copy[index] = writeAt(root[index], rest, value);
|
|
415
|
+
return copy;
|
|
416
|
+
}
|
|
417
|
+
if (root !== null && typeof root === 'object') {
|
|
418
|
+
const key = String(head);
|
|
419
|
+
if (!Object.hasOwn(root, key))
|
|
420
|
+
return root;
|
|
421
|
+
return Object.fromEntries(Object.entries(root).map(([entryKey, entryValue]) => [
|
|
422
|
+
entryKey,
|
|
423
|
+
entryKey === key ? writeAt(entryValue, rest, value) : entryValue,
|
|
424
|
+
]));
|
|
425
|
+
}
|
|
426
|
+
return root;
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=inputPrevalidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inputPrevalidation.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/utils/inputPrevalidation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview What `strictenInput` threw away, kept where `lint:mcp` can read
|
|
3
|
+
* it (#358, #394).
|
|
4
|
+
*
|
|
5
|
+
* Zod 4 keys `.describe()` and `.meta()` to the schema **instance**, in
|
|
6
|
+
* `z.globalRegistry`, and `.strict()` is `catchall(z.never())` — a clone with no
|
|
7
|
+
* `_zod.parent` link, so it inherits no registry entry. `strictenInput` stores
|
|
8
|
+
* that clone, and whatever the author attached to the input root is gone:
|
|
9
|
+
* silently, since the linter reads the same stored schema the wire does and a
|
|
10
|
+
* root carries no describe anything would ask for.
|
|
11
|
+
*
|
|
12
|
+
* `tool()` is the only place both instances exist, so it records the discard
|
|
13
|
+
* here on the way past. The record is a symbol-keyed, non-enumerable property:
|
|
14
|
+
* invisible to `Object.keys`, `JSON.stringify`, a spread, `tools/list`, the
|
|
15
|
+
* server manifest and `_meta` alike, so nothing about what a client sees
|
|
16
|
+
* changes. The symbol comes from the global registry rather than a module
|
|
17
|
+
* binding, because `lint:mcp` can load the framework twice — the linter from
|
|
18
|
+
* the installed package, the definitions from wherever the server imports them
|
|
19
|
+
* — and a module-local key (or a `WeakMap`) would not be shared across the two.
|
|
20
|
+
*
|
|
21
|
+
* Re-applying the discarded entry to the strictened schema is separate, held
|
|
22
|
+
* work: it would change the advertised `inputSchema` bytes. This half changes
|
|
23
|
+
* none of them.
|
|
24
|
+
*
|
|
25
|
+
* @module src/mcp-server/tools/utils/strictenRecord
|
|
26
|
+
*/
|
|
27
|
+
/** One schema instance whose registry entry `strictenInput` did not carry over. */
|
|
28
|
+
export interface StrictenDiscard {
|
|
29
|
+
/** The registry keys that were lost — `description` for `.describe()`, else `.meta()` keys. */
|
|
30
|
+
readonly keys: readonly string[];
|
|
31
|
+
/** Where it sat, in the linter's path vocabulary: `input`, or `input|<i>` for a variant. */
|
|
32
|
+
readonly scope: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Attaches the discards to a definition without making it part of the
|
|
36
|
+
* definition's value. Only called when something was actually discarded, so an
|
|
37
|
+
* unaffected definition carries no property at all.
|
|
38
|
+
*/
|
|
39
|
+
export declare function recordStrictenDiscard(definition: object, discards: readonly StrictenDiscard[]): void;
|
|
40
|
+
/** The discards `tool()` recorded, or `undefined` when nothing was discarded. */
|
|
41
|
+
export declare function readStrictenDiscard(definition: unknown): readonly StrictenDiscard[] | undefined;
|
|
42
|
+
//# sourceMappingURL=strictenRecord.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strictenRecord.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/utils/strictenRecord.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B,+FAA+F;IAC/F,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAID;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,SAAS,eAAe,EAAE,GACnC,IAAI,CAON;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,OAAO,GAAG,SAAS,eAAe,EAAE,GAAG,SAAS,CAI/F"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview What `strictenInput` threw away, kept where `lint:mcp` can read
|
|
3
|
+
* it (#358, #394).
|
|
4
|
+
*
|
|
5
|
+
* Zod 4 keys `.describe()` and `.meta()` to the schema **instance**, in
|
|
6
|
+
* `z.globalRegistry`, and `.strict()` is `catchall(z.never())` — a clone with no
|
|
7
|
+
* `_zod.parent` link, so it inherits no registry entry. `strictenInput` stores
|
|
8
|
+
* that clone, and whatever the author attached to the input root is gone:
|
|
9
|
+
* silently, since the linter reads the same stored schema the wire does and a
|
|
10
|
+
* root carries no describe anything would ask for.
|
|
11
|
+
*
|
|
12
|
+
* `tool()` is the only place both instances exist, so it records the discard
|
|
13
|
+
* here on the way past. The record is a symbol-keyed, non-enumerable property:
|
|
14
|
+
* invisible to `Object.keys`, `JSON.stringify`, a spread, `tools/list`, the
|
|
15
|
+
* server manifest and `_meta` alike, so nothing about what a client sees
|
|
16
|
+
* changes. The symbol comes from the global registry rather than a module
|
|
17
|
+
* binding, because `lint:mcp` can load the framework twice — the linter from
|
|
18
|
+
* the installed package, the definitions from wherever the server imports them
|
|
19
|
+
* — and a module-local key (or a `WeakMap`) would not be shared across the two.
|
|
20
|
+
*
|
|
21
|
+
* Re-applying the discarded entry to the strictened schema is separate, held
|
|
22
|
+
* work: it would change the advertised `inputSchema` bytes. This half changes
|
|
23
|
+
* none of them.
|
|
24
|
+
*
|
|
25
|
+
* @module src/mcp-server/tools/utils/strictenRecord
|
|
26
|
+
*/
|
|
27
|
+
const STRICTEN_DISCARD = Symbol.for('@cyanheads/mcp-ts-core:strictenDiscard');
|
|
28
|
+
/**
|
|
29
|
+
* Attaches the discards to a definition without making it part of the
|
|
30
|
+
* definition's value. Only called when something was actually discarded, so an
|
|
31
|
+
* unaffected definition carries no property at all.
|
|
32
|
+
*/
|
|
33
|
+
export function recordStrictenDiscard(definition, discards) {
|
|
34
|
+
Object.defineProperty(definition, STRICTEN_DISCARD, {
|
|
35
|
+
configurable: true,
|
|
36
|
+
enumerable: false,
|
|
37
|
+
value: discards,
|
|
38
|
+
writable: false,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** The discards `tool()` recorded, or `undefined` when nothing was discarded. */
|
|
42
|
+
export function readStrictenDiscard(definition) {
|
|
43
|
+
if (definition === null || typeof definition !== 'object')
|
|
44
|
+
return undefined;
|
|
45
|
+
const value = definition[STRICTEN_DISCARD];
|
|
46
|
+
return Array.isArray(value) ? value : undefined;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=strictenRecord.js.map
|