@@ -1706,3 +1706,102 @@ def modulo(x: int, y: int) -> int:
17061706 # Should fall back to annotations.title
17071707 mcp_tool = tool .to_mcp_tool ()
17081708 assert mcp_tool .title == "Annotation Title"
1709+
1710+
1711+ class TestToolNameValidation :
1712+ """Tests for tool name validation per MCP specification (SEP-986)."""
1713+
1714+ @pytest .fixture
1715+ def caplog_for_mcp_validation (self , caplog ):
1716+ """Capture logs from the MCP SDK's tool name validation logger."""
1717+ import logging
1718+
1719+ caplog .set_level (logging .WARNING )
1720+ logger = logging .getLogger ("mcp.shared.tool_name_validation" )
1721+ original_level = logger .level
1722+ logger .setLevel (logging .WARNING )
1723+ logger .addHandler (caplog .handler )
1724+ try :
1725+ yield caplog
1726+ finally :
1727+ logger .removeHandler (caplog .handler )
1728+ logger .setLevel (original_level )
1729+
1730+ @pytest .mark .parametrize (
1731+ "name" ,
1732+ [
1733+ "valid_tool" ,
1734+ "valid-tool" ,
1735+ "valid.tool" ,
1736+ "ValidTool" ,
1737+ "tool123" ,
1738+ "a" ,
1739+ "a" * 128 ,
1740+ ],
1741+ )
1742+ def test_valid_tool_names_no_warnings (self , name , caplog_for_mcp_validation ):
1743+ """Valid tool names should not produce warnings."""
1744+
1745+ def fn () -> str :
1746+ return "test"
1747+
1748+ tool = Tool .from_function (fn , name = name )
1749+ assert tool .name == name
1750+ assert "Tool name validation warning" not in caplog_for_mcp_validation .text
1751+
1752+ def test_tool_name_with_spaces_warns (self , caplog_for_mcp_validation ):
1753+ """Tool names with spaces should produce a warning."""
1754+
1755+ def fn () -> str :
1756+ return "test"
1757+
1758+ tool = Tool .from_function (fn , name = "my tool" )
1759+ assert tool .name == "my tool"
1760+ assert "Tool name validation warning" in caplog_for_mcp_validation .text
1761+ assert "contains spaces" in caplog_for_mcp_validation .text
1762+
1763+ def test_tool_name_with_invalid_chars_warns (self , caplog_for_mcp_validation ):
1764+ """Tool names with invalid characters should produce a warning."""
1765+
1766+ def fn () -> str :
1767+ return "test"
1768+
1769+ tool = Tool .from_function (fn , name = "tool@name!" )
1770+ assert tool .name == "tool@name!"
1771+ assert "Tool name validation warning" in caplog_for_mcp_validation .text
1772+ assert "invalid characters" in caplog_for_mcp_validation .text
1773+
1774+ def test_tool_name_too_long_warns (self , caplog_for_mcp_validation ):
1775+ """Tool names exceeding 128 characters should produce a warning."""
1776+
1777+ def fn () -> str :
1778+ return "test"
1779+
1780+ long_name = "a" * 129
1781+ tool = Tool .from_function (fn , name = long_name )
1782+ assert tool .name == long_name
1783+ assert "Tool name validation warning" in caplog_for_mcp_validation .text
1784+ assert "exceeds maximum length" in caplog_for_mcp_validation .text
1785+
1786+ def test_tool_name_with_leading_dash_warns (self , caplog_for_mcp_validation ):
1787+ """Tool names starting with dash should produce a warning."""
1788+
1789+ def fn () -> str :
1790+ return "test"
1791+
1792+ tool = Tool .from_function (fn , name = "-tool" )
1793+ assert tool .name == "-tool"
1794+ assert "Tool name validation warning" in caplog_for_mcp_validation .text
1795+ assert "starts or ends with a dash" in caplog_for_mcp_validation .text
1796+
1797+ def test_tool_still_created_despite_warnings (self , caplog_for_mcp_validation ):
1798+ """Tools with invalid names should still be created (SHOULD not MUST)."""
1799+
1800+ def add (a : int , b : int ) -> int :
1801+ return a + b
1802+
1803+ tool = Tool .from_function (add , name = "invalid tool name!" )
1804+ assert tool .name == "invalid tool name!"
1805+ assert tool .parameters is not None
1806+ assert "a" in tool .parameters ["properties" ]
1807+ assert "b" in tool .parameters ["properties" ]
0 commit comments