Skip to content

fix(model): read a request id written in float form as that integer - #1346

Merged
connor4312 merged 2 commits into
modelcontextprotocol:mainfrom
talhayme:fix/1345-request-with-invalid-id
Oct 17, 2026
Merged

connor4312 merged 2 commits into
modelcontextprotocol:mainfrom
talhayme:fix/1345-request-with-invalid-id

Conversation

@talhayme

@talhayme talhayme commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1345. #1287 already makes a message with id and method parse only as a request, which fixes the unusable-id case. This PR now covers the one part that still fails on main.

Change: NumberOrString reads an integral number written in float form (1.0, 1e2) as that integer, up to 2^53. Some JSON encoders write every number that way; such a request used to be rejected and got no response.

Tests: integral_id_in_float_form_is_that_integer fails on main and passes with the change. The unusable-id tests in tests/test_deserialization.rs and async_rw.rs pass on main too; I kept them as regression tests for #1287's behaviour.

No public API change. cargo test for rmcp and cargo +nightly fmt pass.

AI disclosure: written with Claude Code; I reviewed the change and ran the checks above.

🤖 Generated with Claude Code

@github-actions github-actions Bot added T-test Testing related changes T-core Core library changes T-transport Transport layer changes labels Oct 12, 2026
@gocamille

Copy link
Copy Markdown
Contributor

FYI, #1287 merged earlier today and seems to overlap with this. It checks which keys a message has before picking a variant, so method + id can only parse as a request now.

I ran this PR's tests on current main without the model.rs change. The two unusable-id tests already pass there. integral_id_in_float_form_is_that_integer still fails, so the float-form id handling still looks needed. notification_still_parses also fails, because main now rejects "id": null with method. Might be worth a rebase to see what's still needed.

A JSON-RPC id of 1.0 or 1e2 is an integer written the way some encoders
write every number. It used to be rejected, so the request got no
response. NumberOrString now accepts a float that an f64 represents
exactly as an integer (up to 2^53) and reads it as that integer.

modelcontextprotocol#1287 already makes a message with an id and a method parse only as a
request, so the earlier change to JsonRpcNotification is dropped; the
tests for an unusable id stay as regression tests.

Refs modelcontextprotocol#1345

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@talhayme
talhayme force-pushed the fix/1345-request-with-invalid-id branch from 66db2cb to 7022d1f Compare October 14, 2026 19:28
@talhayme talhayme changed the title fix(model): reject a request whose id is not an integer or a string fix(model): read a request id written in float form as that integer Oct 14, 2026
@talhayme

Copy link
Copy Markdown
Contributor Author

Thanks, you're right. I rebased on main: #1287 covers the unusable-id case, so I dropped the JsonRpcNotification change and kept those tests as regression tests. What remains is the float-form id (1.0, 1e2), which still fails on main. I also dropped the "id": null notification case, since main now rejects it and that seems right. Updated the title to match.

AI disclosure: rebased and drafted with Claude Code; I ran the tests and reviewed it.

Comment thread crates/rmcp/src/model.rs Outdated
/// `f64` represents exactly.
fn integral_f64_to_i64(f: f64) -> Option<i64> {
const MAX_EXACT: f64 = 9_007_199_254_740_992.0; // 2^53
(f.fract() == 0.0 && f.abs() <= MAX_EXACT).then_some(f as i64)

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.

Small one: I think <= here lets 2^53 + 1 slip through. 9007199254740993.0 rounds to 2^53 when it's parsed, so it passes the check and comes out as 9007199254740992. I tried it on this branch and that's what I got.

Probably won't come up much, since a client sending float ids likely can't hold that value anyway. But < seems safer, since 2^53 as an f64 could be either number:

Suggested change
(f.fract() == 0.0 && f.abs() <= MAX_EXACT).then_some(f as i64)
(f.fract() == 0.0 && f.abs() < MAX_EXACT).then_some(f as i64)

Might be worth adding "9007199254740993.0" to request_with_unusable_id_is_rejected too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, thanks. Confirmed on the branch: 9007199254740993.0 came out as 9007199254740992. Switched to < and added that id to request_with_unusable_id_is_rejected (300f4b1).

AI disclosure: fixed and tested with Claude Code; I reviewed it.

…er was sent

2^53 + 1 written in float form parses to the same f64 as 2^53, so
accepting 2^53 silently turned 9007199254740993.0 into 9007199254740992.
The exact-integer range is now strictly below 2^53. Added that id to
request_with_unusable_id_is_rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gocamille

Copy link
Copy Markdown
Contributor

Thanks for the update! #1346 is your only open PR now, so you should be able to mark it ready for review. Once it's ready I'll approve the CI runs and finish my review.

Small one while you're in there: would you mind switching "Refs #1345" to "Fixes #1345" so the issue closes on merge?

@talhayme
talhayme marked this pull request as ready for review October 16, 2026 21:50
@talhayme
talhayme requested a review from a team as a code owner October 16, 2026 21:50

@connor4312 connor4312 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

lgtm, thank you for the contribution!

@connor4312
connor4312 merged commit 017e1c3 into modelcontextprotocol:main Oct 17, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-core Core library changes T-test Testing related changes T-transport Transport layer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A request whose id is not an i64 or a string is parsed as a notification

3 participants