diff --git a/src/mcp/shared/tool_name_validation.py b/src/mcp/shared/tool_name_validation.py index f35efa5a61..96c34f7826 100644 --- a/src/mcp/shared/tool_name_validation.py +++ b/src/mcp/shared/tool_name_validation.py @@ -77,7 +77,7 @@ def validate_tool_name(name: str) -> ToolNameValidationResult: warnings.append("Tool name starts or ends with a dot, which may cause parsing issues in some contexts") # Check for invalid characters - if not TOOL_NAME_REGEX.match(name): + if not TOOL_NAME_REGEX.fullmatch(name): # Find all invalid characters (unique, preserving order) invalid_chars: list[str] = [] seen: set[str] = set() diff --git a/tests/shared/test_tool_name_validation.py b/tests/shared/test_tool_name_validation.py index 4746f3f9f8..e24be1cf7b 100644 --- a/tests/shared/test_tool_name_validation.py +++ b/tests/shared/test_tool_name_validation.py @@ -66,12 +66,17 @@ def test_rejects_name_exceeding_max_length(self) -> None: ("get,user,profile", "','"), ("user/profile/update", "'/'"), ("user@domain.com", "'@'"), + # a single trailing newline slipped past `$` with re.match + ("valid_name\n", "'\\n'"), + ("a" * 127 + "\n", "'\\n'"), ], ids=[ "with_spaces", "with_commas", "with_slashes", "with_at_symbol", + "with_trailing_newline", + "max_length_with_trailing_newline", ], ) def test_rejects_invalid_characters(self, tool_name: str, expected_char: str) -> None: