Repository: modelcontextprotocol/python-sdk

Issue:
Input should be a valid string [type=string_type, input_value=123, input_type=int]
MCP Server tools code:
`@mcp.tool(description="查询物流信息")
async def query_logistics(order_id: str) -> str:
  """查询物流信息。当用户需要根据订单号查询物流信息时，调用此工具

  Args:
      order_id: 订单号
  Returns:
      物流信息的字符串描述
  """
  # 统一的物流信息数据
  tracking_info = [
    {"time": "2024-01-20 10:00:00", "status": "包裹已揽收",
     "location": "深圳转运中心"},
    {"time": "2024-01-20 15:30:00", "status": "运输中", "location": "深圳市"},
    {"time": "2024-01-21 09:00:00", "status": "到达目的地",
     "location": "北京市"},
    {"time": "2024-01-21 14:00:00", "status": "派送中",
     "location": "北京市朝阳区"},
    {"time": "2024-01-21 16:30:00", "status": "已签收",
     "location": "北京市朝阳区三里屯"}
  ]

  # 格式化物流信息
  result = f"物流单号：{order_id}\n\n物流轨迹：\n"
  for item in tracking_info:
    result += f"[{item['time']}] {item['status']} - {item['location']}\n"

  return result
`
MCP client execute code：
`  self._streams_context = sse_client(url=server_url)
        streams = await self._streams_context.__aenter__()

        self._session_context = ClientSession(*streams)
        self.session: ClientSession = await self._session_context.__aenter__()

        # Initialize
        await self.session.initialize()

        # List available tools to verify connection
        print("Initialized SSE client...")
        print("Listing tools...")
        response = await self.session.list_tools()
        tools = response.tools
        print("\nConnected to server with tools:", [tool.name for tool in tools])

        result = await self.session.call_tool("query_logistics", {"order_id":"123"})
        print("Result of query_logistics tool:", result)`
error info：
Connected to server with tools: ['query_logistics']
Result of add tool: meta=None content=[TextContent(type='text', text='Error executing tool query_logistics: 1 validation error for query_logisticsArguments\norder_id\n  Input should be a valid string [type=string_type, input_value=123, input_type=int]\n    For further information visit https://errors.pydantic.dev/2.10/v/string_type', annotations=None)] isError=True

Discussion:
@Kludex: Can you please format your code, and try to translate to English? 🙏 @w0807c: > Can you please format your code, and try to translate to English? 🙏
MCP SDK version: 1.4.1
MCP server code：
````
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("order-mcp-server", log_level="ERROR")
@mcp.tool(description="Query logistics information")
async def query_logistics(order_id: str) -> str:

  //Search for logistics information. 
  //When users need to query logistics information based on the order number, call this tool
  result="";
  return result

if __name__ == "__main__":
    mcp.run(transport="sse")
```
`
MCP client code：
`    self._session_context = ClientSession(*streams)
    self.session: ClientSession = await self._session_context.__aenter__()

    # Initialize
    await self.session.initialize()

    # List available tools to verify connection
    print("Initialized SSE client...")
    print("Listing tools...")
    response = await self.session.list_tools()
    tools = response.tools
    print("\nConnected to server with tools:", [tool.name for tool in tools])

    result = await self.session.call_tool("query_logistics", {"order_id":"123"})
    print("Result of query_logistics tool:", result)`
`
error info ：
`Connected to server with tools: ['query_logistics']
Result of add tool: meta=None content=[TextContent(type='text', text='Error executing tool query_logistics: 1 validation error for query_logisticsArguments\norder_id\n Input should be a valid string [type=string_type, input_value=123, input_type=int]\n For further information visit https://errors.pydantic.dev/2.10/v/string_type', annotations=None)] isError=True
`
Data type conversion error
@daviddoran-stripe: This is the same as this issue from the original FastMCP repo:
https://github.com/jlowin/fastmcp/issues/62

It was fixed in FastMCP Dec 5th 2024:
https://github.com/jlowin/fastmcp/commit/07fc1042b5d7a9db287d23da4d9cc5c7c9dbb792

But it looks like in merging FastMCP into the official Python SDK this fix was lost.@daviddoran-stripe: @w0807c This issue is _not_ fixed in this repository. I recommend reopening the issue, if possible.@JohnUiterwyk: @Kludex are you able to reopen this?

ive been able to replicate this bug as well with 1.5.0 

the offending line is here:
https://github.com/modelcontextprotocol/python-sdk/blob/9ae4df85fbab97bf476ddd160b766ca4c208cd13/src/mcp/server/fastmcp/utilities/func_metadata.py#L91

@daviddoran-stripe correctly pointed out the fix was not migrated to the mcp sdk.@cola-93: Same issue here~@kafkasl: +1@kafkasl: I opened a PR 

> This is the same as this issue from the original FastMCP repo: [jlowin/fastmcp#62](https://github.com/jlowin/fastmcp/issues/62)
> 
> It was fixed in FastMCP Dec 5th 2024: [jlowin/fastmcp@07fc104](https://github.com/jlowin/fastmcp/commit/07fc1042b5d7a9db287d23da4d9cc5c7c9dbb792)
> 
> But it looks like in merging FastMCP into the official Python SDK this fix was lost.

I opened a PR with the fix you pointed out. Hopefully it will be merged soon. Sonnet 3.7 is clever enough to escape the strings, but other models fail miserably with this bug.

https://github.com/modelcontextprotocol/python-sdk/pull/384@dsp-ant: fixed

Solution patch:
diff --git a/src/mcp/server/fastmcp/utilities/func_metadata.py b/src/mcp/server/fastmcp/utilities/func_metadata.py
index cf93049e3..629580e18 100644
--- a/src/mcp/server/fastmcp/utilities/func_metadata.py
+++ b/src/mcp/server/fastmcp/utilities/func_metadata.py
@@ -88,7 +88,7 @@ def pre_parse_json(self, data: dict[str, Any]) -> dict[str, Any]:
                     pre_parsed = json.loads(data[field_name])
                 except json.JSONDecodeError:
                     continue  # Not JSON - skip
-                if isinstance(pre_parsed, str):
+                if isinstance(pre_parsed, (str, int, float)):
                     # This is likely that the raw value is e.g. `"hello"` which we
                     # Should really be parsed as '"hello"' in Python - but if we parse
                     # it as JSON it'll turn into just 'hello'. So we skip it.


Test patch:
diff --git a/tests/server/fastmcp/test_func_metadata.py b/tests/server/fastmcp/test_func_metadata.py
index 6461648eb..b1828ffe9 100644
--- a/tests/server/fastmcp/test_func_metadata.py
+++ b/tests/server/fastmcp/test_func_metadata.py
@@ -399,3 +399,18 @@ def test_complex_function_json_schema():
         "title": "complex_arguments_fnArguments",
         "type": "object",
     }
+
+
+def test_str_vs_int():
+    """
+    Test that string values are kept as strings even when they contain numbers,
+    while numbers are parsed correctly.
+    """
+
+    def func_with_str_and_int(a: str, b: int):
+        return a
+
+    meta = func_metadata(func_with_str_and_int)
+    result = meta.pre_parse_json({"a": "123", "b": 123})
+    assert result["a"] == "123"
+    assert result["b"] == 123