Skip to content

Commit 7723356

Browse files
committed
fix: reject trailing newline in tool-name validation
TOOL_NAME_REGEX was end-anchored with $, which in Python's default mode also matches just before a single trailing newline. So validate_tool_name("x\n") returned is_valid=True with no warning, and a 127-char name plus "\n" (len 128) slipped past both the length and character checks. Anchor with \Z so a trailing newline is treated as the disallowed character it is.
1 parent 9bdc03d commit 7723356

2 files changed

Lines changed: 20 additions & 2 deletions

File tree

src/mcp/shared/tool_name_validation.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
logger = logging.getLogger(__name__)
1919

20-
# Regular expression for valid tool names according to SEP-986 specification
21-
TOOL_NAME_REGEX = re.compile(r"^[A-Za-z0-9._-]{1,128}$")
20+
# Regular expression for valid tool names according to SEP-986 specification.
21+
# End-anchored with \Z rather than $: in Python's default mode $ also matches
22+
# just before a single trailing newline, which would let "name\n" validate.
23+
TOOL_NAME_REGEX = re.compile(r"^[A-Za-z0-9._-]{1,128}\Z")
2224

2325
# SEP reference URL for warning messages
2426
SEP_986_URL = "https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names"

tests/shared/test_tool_name_validation.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@ def test_validate_tool_name_rejects_invalid_characters(tool_name: str, expected_
8080
assert any("invalid characters" in w and expected_char in w for w in result.warnings)
8181

8282

83+
@pytest.mark.parametrize(
84+
"tool_name",
85+
["valid_name\n", "a" * 127 + "\n"],
86+
ids=["trailing_newline", "max_length_plus_newline"],
87+
)
88+
def test_validate_tool_name_rejects_trailing_newline(tool_name: str) -> None:
89+
"""A trailing newline is not an allowed character and must be rejected.
90+
91+
Regression test: Python's ``$`` (unlike ``\\Z``) also matches just before a
92+
single trailing newline, so a name like ``"valid_name\\n"`` slipped through.
93+
"""
94+
result = validate_tool_name(tool_name)
95+
assert result.is_valid is False
96+
assert any("invalid characters" in w for w in result.warnings)
97+
98+
8399
def test_validate_tool_name_rejects_multiple_invalid_chars() -> None:
84100
"""Names with multiple invalid chars should list all of them."""
85101
result = validate_tool_name("user name@domain,com")

0 commit comments

Comments
 (0)