Describe the bug
TOOL_NAME_REGEX in src/mcp/shared/tool_name_validation.py is end-anchored with $:
TOOL_NAME_REGEX = re.compile(r"^[A-Za-z0-9._-]{1,128}$")
In Python's default (non-MULTILINE) mode, $ matches at end-of-string or immediately before a single trailing \n. So a tool name ending in exactly one newline passes validation, even though \n is not in the allowed character set.
The length guard uses len(), which counts the \n, so "a" * 127 + "\n" (length 128) slips past both the length check and the character check.
Embedded newlines and other trailing control characters are already rejected correctly. Only the single-trailing-newline case leaks.
To Reproduce
from mcp.shared.tool_name_validation import validate_tool_name, validate_and_warn_tool_name
print(validate_tool_name("evil_tool\n").is_valid) # True (expected: False)
print(validate_and_warn_tool_name("evil_tool\n")) # True, and no warning is logged
print(validate_tool_name("a" * 127 + "\n").is_valid) # True, length 128 passes too
Expected behavior
validate_tool_name("evil_tool\n").is_valid should be False, since \n is not one of the allowed characters.
Additional context
Anchoring the end with \Z (strict end-of-string) instead of $ fixes it, and no valid name is affected: re.match(r"^[A-Za-z0-9._-]{1,128}\Z", "abc") still matches, while "abc\n" correctly fails.
I have an open PR with this fix and a regression test: #3076. Filing this issue to track it, per CONTRIBUTING ("Bug fixes for clear, reproducible issues are welcome—but still create an issue to track the fix").
Describe the bug
TOOL_NAME_REGEX in src/mcp/shared/tool_name_validation.py is end-anchored with $:
In Python's default (non-MULTILINE) mode, $ matches at end-of-string or immediately before a single trailing \n. So a tool name ending in exactly one newline passes validation, even though \n is not in the allowed character set.
The length guard uses len(), which counts the \n, so "a" * 127 + "\n" (length 128) slips past both the length check and the character check.
Embedded newlines and other trailing control characters are already rejected correctly. Only the single-trailing-newline case leaks.
To Reproduce
Expected behavior
validate_tool_name("evil_tool\n").is_valid should be False, since \n is not one of the allowed characters.
Additional context
Anchoring the end with \Z (strict end-of-string) instead of $ fixes it, and no valid name is affected: re.match(r"^[A-Za-z0-9._-]{1,128}\Z", "abc") still matches, while "abc\n" correctly fails.
I have an open PR with this fix and a regression test: #3076. Filing this issue to track it, per CONTRIBUTING ("Bug fixes for clear, reproducible issues are welcome—but still create an issue to track the fix").