diff --git a/src/mcp/types.py b/src/mcp/types.py index 871322740..775498914 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 @@ -868,9 +868,13 @@ class ToolAnnotations(BaseModel): model_config = ConfigDict(extra="allow") -class Tool(BaseMetadata): +class Tool(BaseModel): """Definition for a tool the client can call.""" + name: Annotated[str, StringConstraints(min_length=1, max_length=128, pattern=r"^[A-Za-z0-9_.-]+$")] + """The programmatic name of the tool. Must match pattern: A-Z, a-z, 0-9, underscore (_), dash (-), dot (.).""" + title: str | None = None + """A human-readable title for the tool.""" description: str | None = None """A human-readable description of the tool.""" inputSchema: dict[str, Any] @@ -891,6 +895,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"})