FuncMetadata.pre_parse_json mis-detects str | None as non-string and corrupts JSON-looking string arguments
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 72/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Ít trao đổi
- Công nghệ
- python
- Lĩnh vực
- backend-api-design
Hướng nghiên cứu
Bắt đầu trong mcp/server/fastmcp/utilities/func_metadata.py tại FuncMetadata.pre_parse_json và tái hiện vấn đề với ví dụ str | None trong báo cáo. Kiểm tra cách xử lý annotation cho các union, sau đó xác minh rằng các chuỗi có dạng JSON vẫn là chuỗi, trong khi các annotation không phải chuỗi vẫn được phân tích trước và model đối số được xác thực thành công.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Title: FuncMetadata.pre_parse_json mis-detects str | None as non-string and corrupts JSON-looking string arguments
Environment
mcpversion: 1.27.0- Python: 3.10.12
- Transport: streamable-http (also affects stdio — the bug is transport-agnostic)
Summary
FuncMetadata.pre_parse_json() in mcp/server/fastmcp/utilities/func_metadata.py decides whether to json.loads() a string argument based on:
if isinstance(data_value, str) and field_info.annotation is not str:
This check is meant to catch cases where a client (e.g. Claude Desktop) stringifies a list/dict argument that should really be a Python object. But field_info.annotation is not str is True for Optional[str] / str | None as well, since that annotation is not literally str. So any optional string parameter gets the same treatment as a list/dict/model parameter.
If the caller passes a valid string value for such a parameter that also happens to parse as a JSON object or array — e.g. a JSON-serialized template body like '{"blocks": [...]}' — the value silently gets replaced with a dict/list before the pydantic argument model is validated. Validation then fails with something like:
1 validation error for my_tool_nameArguments
body
Input should be a valid string [type=string_type, input_value={'blocks': [...]}, input_type=dict]
...even though the caller sent a perfectly valid string and the tool signature explicitly declares body: str | None.
Minimal repro
from typing import Any
import json
from mcp.server.fastmcp.utilities.func_metadata import func_metadata
async def my_tool(body: str | None = None) -> dict[str, Any]:
return {"body": body}
meta = func_metadata(my_tool)
data = {"body": json.dumps({"blocks": ["a", "b"]})}
new_data = meta.pre_parse_json(data)
print(type(new_data["body"])) # <class 'dict'> -- should be <class 'str'>
meta.arg_model.model_validate(new_data) # raises: Input should be a valid string
Expected behavior
A parameter typed str | None (or any Union that includes str) should not have its string value re-interpreted as JSON, since the raw string is already a valid value for that field. Pre-parsing should only kick in when a plain str could never satisfy the annotation (e.g. list[str], dict[str, Any], a Pydantic model, int, etc.).
Suggested fix
Replace the identity check with one that walks Union/X | Y members:
def _annotation_accepts_str(annotation: Any) -> bool:
origin = typing.get_origin(annotation)
if origin is typing.Union or origin is types.UnionType:
return any(_annotation_accepts_str(arg) for arg in typing.get_args(annotation))
return annotation is str
# in pre_parse_json:
if isinstance(data_value, str) and not _annotation_accepts_str(field_info.annotation):
...
Impact
Any FastMCP tool with an Optional[str] (or str | None) parameter breaks whenever a caller passes a string value that happens to be valid JSON for an object/array (JSON-in-a-string payloads: template bodies, block-based editor content, serialized configs, etc.). We hit this in production with a Unisender email-template MCP server where body: str | None holds a JSON block structure — every update_email_template / create_email_template call with a block-based template failed validation until we monkey-patched FuncMetadata.pre_parse_json locally with the fix above.
- Ngôn ngữ chính
- Python
- Star
- 24.3k
- Fork
- 4k
- Merge trung bình
- 1 ngày 19 phút
- Pull request đã merge (30 ngày)
- 29
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của modelcontextprotocol/python-sdk
-
Streamable HTTP client logs a WARNING for valid 202 Accepted on session termination (DELETE) Đang mởv1 v2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
modelcontextprotocol/python-sdk#3546 · 5 bình luận ·
-
v1 v2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
modelcontextprotocol/python-sdk#3545 · 1 bình luận ·
-
v1 v2
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 91/100
modelcontextprotocol/python-sdk#3508 · 2 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 64/100
modelcontextprotocol/python-sdk#3504 ·
-
v1 v2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
modelcontextprotocol/python-sdk#3492 · 1 bình luận ·
Tất cả issue của modelcontextprotocol/python-sdk
Issue tương tự
-
documentation help wanted
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 90/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 90/100
simonw/sqlite-utils#872 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100