diff --git a/src/mcp/types.py b/src/mcp/types.py index 871322740..1b9e5d215 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -1,7 +1,7 @@ from collections.abc import Callable from typing import Annotated, Any, Generic, Literal, TypeAlias, TypeVar -from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel +from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel, StringConstraints from pydantic.networks import AnyUrl, UrlConstraints from typing_extensions import deprecated @@ -199,7 +199,7 @@ class EmptyResult(Result): class BaseMetadata(BaseModel): """Base class for entities with name and optional title fields.""" - name: str + name: Annotated[str, StringConstraints(min_length=1, max_length=128, pattern=r"^[A-Za-z0-9_.-]+$")] """The programmatic name of the entity.""" title: str | None = None @@ -891,6 +891,11 @@ class Tool(BaseMetadata): """ model_config = ConfigDict(extra="allow") + """ + See [MCP specification](https://modelcontextprotocol.io/specification/draft/server/tools#tool-names) + for more information on tool naming conventions. + """ + class ListToolsResult(PaginatedResult): """The server's response to a tools/list request from the client.""" diff --git a/tests/test_types.py b/tests/test_types.py index 415eba66a..371efca58 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -9,6 +9,7 @@ InitializeRequestParams, JSONRPCMessage, JSONRPCRequest, + Tool, ) @@ -56,3 +57,18 @@ async def test_method_initialization(): assert initialize_request.method == "initialize", "method should be set to 'initialize'" assert initialize_request.params is not None assert initialize_request.params.protocolVersion == LATEST_PROTOCOL_VERSION + + +@pytest.mark.parametrize( + "name", + [ + "getUser", + "DATA_EXPORT_v2", + "admin.tools.list", + "a", + "Z9_.-", + "x" * 128, # max length + ], +) +def test_tool_allows_valid_names(name: str) -> None: + Tool(name=name, inputSchema={"type": "object"})