modelcontextprotocol / modelcontextprotocol/python-sdk
coerce_request_id() folds non-canonical numeric strings, conflating wire-distinct JSON-RPC ids
まだ誰も着手していません。
- 主要言語
- Python
- スター
- 24.3k
- フォーク
- 4k
- 平均マージ
- 1日 1時間
- マージ済み PR(30日)
- 31
説明
Initial Checks
- I confirm that I'm using the newest release of my line (the latest 2.x, or the latest 1.x if I'm still on v1)
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Release line
2.x (current stable)
Description
On current main, coerce_request_id() in src/mcp/shared/dispatcher.py folds a stringified id to int with a bare int(request_id). That accepts a lot more than a canonical integer string, so ids that are distinct on the JSON-RPC wire collapse onto one correlation key:
coerce_request_id('007') -> 7
coerce_request_id('+7') -> 7
coerce_request_id('1_000') -> 1000
coerce_request_id(' 7 ') -> 7
coerce_request_id('٧') -> 7
This shared key backs _pending (response correlation), _in_flight (cancellation), and progress-token routing. So a peer, or a caller using CallOptions["request_id"], that uses e.g. 10 and "1_0" as two ids has them merged. A notifications/cancelled for one hits the other, and the first can't be cancelled. It's the same cross-wiring class as #3060, but between ids that are genuinely different on the wire.
JSON-RPC 2.0 treats String and Number ids as distinct value types, and the function's own intent (and its test, "7" -> 7) is the canonical "peer stringified an int" case. The forms above aren't that. They're an artifact of Python's int(). The docstring says "matches the TS SDK", but JS Number("1_000") is NaN and Number("0x10") is 16, so today's behavior matches neither.
The narrow fix is to fold only when the string equals str(int(s)), which keeps the intended "7" -> 7 and "-3" -> -3 behavior and leaves every other string a distinct id. I have a patch and a regression test for it (both dispatchers share the function) and I'm happy to open a PR once this is triaged and assigned.
Disclosure: I used an AI coding assistant to help explore the code and draft this. I verified the reproduction and understand the fix myself.
Example Code
from mcp.shared.dispatcher import coerce_request_id
for raw in ("007", "+7", "1_000", " 7 ", "٧"):
print(repr(raw), "->", repr(coerce_request_id(raw)))
# every line prints an int, so all five distinct wire ids share one key
Python & MCP Python SDK
Python 3.11.14, python-sdk main @ 5bc9e07, macOS
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
src/mcp/shared/dispatcher.py の coerce_request_id() から始め、"7" を 7 に変換する意図された動作について、既存のテストを読んでください。非カノニカルな数値文字列の回帰テストを追加し、保留中のレスポンス、キャンセル、progress-token のルーティングにわたって、異なる wire id が別々のままになることを確認してください。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- python
- 領域
- api, backend
- issue の種類
- バグ
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 活発さ
- 活発
- 明瞭さ
- 明確に書かれている
- 初心者へのやさしさ
- 84/100