Skip to content

Fix nested MCP integer coercion#10633

Open
princepal9120 wants to merge 2 commits into
warpdotdev:masterfrom
princepal9120:prince/10596-mcp-integer-coercion
Open

Fix nested MCP integer coercion#10633
princepal9120 wants to merge 2 commits into
warpdotdev:masterfrom
princepal9120:prince/10596-mcp-integer-coercion

Conversation

@princepal9120
Copy link
Copy Markdown
Contributor

@princepal9120 princepal9120 commented May 11, 2026

Summary

  • Walk MCP tool arguments recursively against their input schema before dispatch.
  • Coerce whole-number values for nested integer properties, array items, composition branches, nullable integer types, and local $refs.
  • Select a matching anyOf/oneOf branch before coercing, apply additionalProperties only to undeclared keys, and reject the f64 upper i64 boundary.
  • Add coverage for nested request data, timestamp filter shapes, branch selection, additionalProperties semantics, and the i64 upper rounding boundary.

Why
Strict MCP servers reject 5.0 for fields declared as integers. The previous helper only handled top-level type: integer properties, so common OpenAPI-derived schemas still leaked float JSON.

Tests

  • git diff --check
  • rustfmt app/src/ai/blocklist/action_model/execute/call_mcp_tool.rs app/src/ai/blocklist/action_model/execute/call_mcp_tool_tests.rs
  • Attempted cargo test -p warp call_mcp_tool, but this machine ran out of disk while compiling dependencies.

Fixes #10596

Coerce whole-number MCP tool arguments through nested JSON Schema shapes, including object properties, array items, oneOf/anyOf/allOf branches, nullable integer types, and local refs.

Fixes warpdotdev#10596
@cla-bot cla-bot Bot added the cla-signed label May 11, 2026
@github-actions github-actions Bot added the external-contributor Indicates that a PR has been opened by someone outside the Warp team. label May 11, 2026
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 11, 2026

@princepal9120

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR replaces the top-level-only MCP integer coercion helper with a recursive JSON Schema walker and adds coverage for nested objects, arrays, refs, nullable integer types, and composition branches.

Concerns

  • anyOf/oneOf schemas are traversed branch-by-branch without checking which branch the current value actually matches, so alternate branches can coerce fields that are not integers for the selected schema.
  • additionalProperties is applied to all object members, including properties explicitly declared in properties, which does not match JSON Schema semantics and can coerce named number fields.
  • The new f64 range guard can still accept 2^63 because i64::MAX as f64 rounds up, and the subsequent cast saturates to i64::MAX.

Verdict

Found: 0 critical, 3 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

continue;
}

for keyword in ["allOf", "anyOf", "oneOf"] {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] This applies every anyOf/oneOf branch without checking whether the current value matches that branch, so an alternate schema can coerce 1.0 to 1 for a field whose selected branch declares type: number. Only apply allOf unconditionally, and select a matching anyOf/oneOf branch before coercing.


if let Some(additional_properties) = schema.get("additionalProperties") {
if additional_properties.is_object() {
for property_value in object.values_mut() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] additionalProperties must only apply to keys not declared in properties; iterating over every object value also coerces named fields. A schema with properties.price as number and integer additionalProperties would turn price: 1.0 into 1.

if !float.is_finite() || float.fract() != 0.0 {
return;
}
if float < i64::MIN as f64 || float > i64::MAX as f64 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] This range check still allows 2^63 because i64::MAX as f64 rounds up; Rust's float-to-int cast then saturates to i64::MAX, changing the argument value. Reject values at and above the first unrepresentable upper bound before assigning.

@princepal9120 princepal9120 force-pushed the prince/10596-mcp-integer-coercion branch from 2191830 to e8d220f Compare May 13, 2026 04:48
@princepal9120
Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in the latest push:

  • anyOf and oneOf now select a matching branch before coercing. allOf remains unconditional.
  • additionalProperties now only applies to keys not declared in properties.
  • The f64 upper i64 boundary is rejected before casting, avoiding saturation of 2^63 to i64::MAX.
  • Added regression coverage for all three cases.

Local validation completed: git diff --check and rustfmt on the touched Rust files. I also attempted cargo test -p warp call_mcp_tool, but this runner ran out of disk while compiling dependencies.

/oz-review

@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 13, 2026

@princepal9120

This PR is not linked to an issue that is marked with ready-to-implement.

Issue-state enforcement details:

Readiness check:

To continue, link this PR to a same-repo issue such as Closes #123 in the PR description, and make sure that issue has ready-to-implement.

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@princepal9120

This PR is not linked to an issue that is marked with ready-to-implement.

Issue-state enforcement details:

  • Associated same-repo issues checked: #10596

  • Required readiness label: ready-to-implement

Readiness check:

  • #10596: missing ready-to-implement; readiness labels present: none

To continue, link this PR to a same-repo issue such as Closes #123 in the PR description, and make sure that issue has ready-to-implement.

Powered by Oz

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

Hi @princepal9120 — a reviewer requested changes on this PR and it hasn't had activity from you in 23 days. When you get a chance, please push updates or reply to the review so a reviewer can take another look. Without activity, this PR will be automatically closed after 30 days of inactivity.

@princepal9120
Copy link
Copy Markdown
Contributor Author

/oz-review

The three schema coercion concerns from the previous review have been addressed in the latest commits:

  • anyOf/oneOf now selects the matching branch before coercing (using schema_matches_value)
  • additionalProperties skips explicitly declared properties
  • f64 range guard uses I64_MAX_PLUS_ONE_F64 constant to correctly exclude 2^63

Issue #10596 also has the ready-to-implement label. Requesting re-review.

@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented Jun 7, 2026

@princepal9120

I'm re-reviewing this pull request in response to a review request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR extends MCP integer coercion to recurse through nested object schemas, arrays, local $refs, nullable integer types, composition branches, and additionalProperties cases before dispatching tool calls.

Concerns

  • The new branch-selection helper can select the wrong anyOf/oneOf branch because it only checks broad JSON types and present property schemas, ignoring common branch-discriminating keywords such as required, const/enum, and additionalProperties: false. That can still coerce values according to a branch that the argument object should not match.

Verdict

Found: 0 critical, 1 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz


for keyword in ["anyOf", "oneOf"] {
if let Some(schemas) = schema.get(keyword).and_then(|schemas| schemas.as_array()) {
if let Some(nested_schema) = schemas.iter().find(|nested_schema| {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] schema_matches_value ignores required, const/enum, and additionalProperties: false, so find can select a oneOf/anyOf branch just because its optional properties type-check. OpenAPI-derived schemas commonly use those keywords to discriminate branches, and picking the wrong branch can still coerce floats to integers for a schema branch that should not apply; honor those keywords or skip coercion when branch matching is ambiguous.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed external-contributor Indicates that a PR has been opened by someone outside the Warp team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Warp sends whole-number floats (5.0) to MCP tools instead of integers (5), violating tool input schemas

1 participant