Skip to content

Commit 626601e

Browse files
authored
Fix MCP CLI bridge numeric arg coercion for schema-less tools (#28005)
1 parent 9605c6f commit 626601e

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

actions/setup/js/mcp_cli_bridge.cjs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ function parseToolArgs(args, schemaProperties = {}) {
445445
/** @type {Record<string, unknown>} */
446446
const result = {};
447447
let jsonOutput = false;
448+
const hasSchemaProperties = Object.keys(schemaProperties).length > 0;
448449
const { normalizedSchemaKeyMap, ambiguousNormalizedSchemaKeys } = buildNormalizedSchemaKeyMap(schemaProperties);
449450

450451
for (let i = 0; i < args.length; i++) {
@@ -458,13 +459,13 @@ function parseToolArgs(args, schemaProperties = {}) {
458459
jsonOutput = true;
459460
} else {
460461
const canonicalKey = resolveSchemaPropertyKey(key, schemaProperties, normalizedSchemaKeyMap, ambiguousNormalizedSchemaKeys);
461-
result[canonicalKey] = coerceToolArgValue(canonicalKey, raw.slice(eqIdx + 1), schemaProperties[canonicalKey], result[canonicalKey]);
462+
result[canonicalKey] = coerceToolArgValue(canonicalKey, raw.slice(eqIdx + 1), schemaProperties[canonicalKey], result[canonicalKey], !hasSchemaProperties);
462463
}
463464
} else if (raw === "json") {
464465
jsonOutput = true;
465466
} else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
466467
const canonicalKey = resolveSchemaPropertyKey(raw, schemaProperties, normalizedSchemaKeyMap, ambiguousNormalizedSchemaKeys);
467-
result[canonicalKey] = coerceToolArgValue(canonicalKey, args[i + 1], schemaProperties[canonicalKey], result[canonicalKey]);
468+
result[canonicalKey] = coerceToolArgValue(canonicalKey, args[i + 1], schemaProperties[canonicalKey], result[canonicalKey], !hasSchemaProperties);
468469
i++;
469470
} else {
470471
const canonicalKey = resolveSchemaPropertyKey(raw, schemaProperties, normalizedSchemaKeyMap, ambiguousNormalizedSchemaKeys);
@@ -549,9 +550,10 @@ function resolveSchemaPropertyKey(key, schemaProperties, normalizedSchemaKeyMap,
549550
* @param {string} rawValue - Raw CLI value
550551
* @param {{type?: string|string[]}|undefined} schemaProperty - JSON schema property
551552
* @param {unknown} existingValue - Existing value (for repeated flags)
553+
* @param {boolean} [allowNumericFallback=false] - Allow numeric parsing when schema is unavailable
552554
* @returns {unknown}
553555
*/
554-
function coerceToolArgValue(key, rawValue, schemaProperty, existingValue) {
556+
function coerceToolArgValue(key, rawValue, schemaProperty, existingValue, allowNumericFallback = false) {
555557
/** @type {string[]} */
556558
const types = [];
557559
if (schemaProperty && typeof schemaProperty === "object" && "type" in schemaProperty && schemaProperty.type != null) {
@@ -620,6 +622,26 @@ function coerceToolArgValue(key, rawValue, schemaProperty, existingValue) {
620622
}
621623
}
622624

625+
// When schema metadata is unavailable (e.g. empty tools cache), apply
626+
// conservative numeric coercion fallback for CLI ergonomics.
627+
if (allowNumericFallback && types.length === 0) {
628+
const trimmedValue = rawValue.trim();
629+
630+
if (/^-?\d+$/.test(trimmedValue)) {
631+
const parsedInt = Number.parseInt(trimmedValue, 10);
632+
if (!Number.isNaN(parsedInt) && Number.isSafeInteger(parsedInt)) {
633+
return parsedInt;
634+
}
635+
}
636+
637+
if (/^-?(?:(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+)$/.test(trimmedValue)) {
638+
const parsedFloat = Number.parseFloat(trimmedValue);
639+
if (!Number.isNaN(parsedFloat) && Number.isFinite(parsedFloat)) {
640+
return parsedFloat;
641+
}
642+
}
643+
}
644+
623645
return rawValue;
624646
}
625647

actions/setup/js/mcp_cli_bridge.test.cjs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,33 @@ describe("mcp_cli_bridge.cjs", () => {
145145
});
146146
});
147147

148+
it("falls back to numeric coercion when schema properties are unavailable", () => {
149+
const { args } = parseToolArgs(["--count", "3", "--max_tokens", "3000"], {});
150+
151+
expect(args).toEqual({
152+
count: 3,
153+
max_tokens: 3000,
154+
});
155+
});
156+
157+
it("coerces scientific notation when schema properties are unavailable", () => {
158+
const { args } = parseToolArgs(["--max_tokens", "1e3", "--threshold", "-2E-4"], {});
159+
160+
expect(args).toEqual({
161+
max_tokens: 1000,
162+
threshold: -0.0002,
163+
});
164+
});
165+
166+
it("preserves non-numeric values when schema properties are unavailable", () => {
167+
const { args } = parseToolArgs(["--start_date", "-1d", "--workflow_name", "daily-issues-report"], {});
168+
169+
expect(args).toEqual({
170+
start_date: "-1d",
171+
workflow_name: "daily-issues-report",
172+
});
173+
});
174+
148175
it("treats MCP result envelopes with isError=true as errors", () => {
149176
formatResponse(
150177
{

0 commit comments

Comments
 (0)