Skip to content

Commit 4679f7d

Browse files
fix(server): omit derived tool schema titles
1 parent 6705402 commit 4679f7d

3 files changed

Lines changed: 30 additions & 2 deletions

File tree

src/mcp/server/mcpserver/tools/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
returns_input_required,
2222
)
2323
from mcp.server.mcpserver.utilities.context_injection import find_context_parameter
24-
from mcp.server.mcpserver.utilities.func_metadata import FuncMetadata, func_metadata
24+
from mcp.server.mcpserver.utilities.func_metadata import FuncMetadata, NoTitleJsonSchema, func_metadata
2525
from mcp.shared._callable_inspection import is_async_callable
2626
from mcp.shared.exceptions import MCPError
2727
from mcp.shared.tool_name_validation import validate_and_warn_tool_name
@@ -103,7 +103,7 @@ def from_function(
103103
skip_names=skip_names,
104104
structured_output=structured_output,
105105
)
106-
parameters = func_arg_metadata.arg_model.model_json_schema(by_alias=True)
106+
parameters = func_arg_metadata.arg_model.model_json_schema(by_alias=True, schema_generator=NoTitleJsonSchema)
107107

108108
# Match `model_dump_one_level`'s kwarg keys (alias when present, else field name)
109109
# so a by-name resolver param resolves to a key that exists at call time.

src/mcp/server/mcpserver/utilities/func_metadata.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ def emit_warning(self, kind: JsonSchemaWarningKind, detail: str) -> None:
7474
raise ValueError(f"JSON schema warning: {kind} - {detail}")
7575

7676

77+
class NoTitleJsonSchema(StrictJsonSchema):
78+
"""A strict JSON schema generator that omits titles derived from field names."""
79+
80+
def field_title_should_be_set(self, schema: Any) -> bool:
81+
"""Return false so Pydantic does not add a title derived from the field name."""
82+
return False
83+
84+
7785
_LOCAL_DEFS_PREFIX = "#/$defs/"
7886

7987

tests/server/mcpserver/test_title.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""Integration tests for title field functionality."""
22

3+
from typing import Annotated
4+
35
import pytest
46
from mcp_types import Prompt, Resource, ResourceTemplate, Tool, ToolAnnotations
7+
from pydantic import Field
58

69
from mcp import Client
710
from mcp.server.mcpserver import MCPServer
@@ -87,6 +90,23 @@ def tool_with_both(message: str) -> str: # pragma: no cover
8790
assert both.title == "Primary Title"
8891

8992

93+
@pytest.mark.anyio
94+
async def test_tool_input_schema_omits_derived_titles_but_preserves_explicit_titles():
95+
"""Tool schemas omit Pydantic's field-name titles while preserving explicit titles."""
96+
mcp = MCPServer(name="SchemaTitleServer")
97+
98+
@mcp.tool()
99+
def typed_tool(required: str, explicit: Annotated[int, Field(title="Explicit Count")]) -> str: # pragma: no cover
100+
return f"{required}: {explicit}"
101+
102+
async with Client(mcp) as client:
103+
tools = await client.list_tools()
104+
105+
properties = tools.tools[0].input_schema["properties"]
106+
assert properties["required"] == {"type": "string"}
107+
assert properties["explicit"] == {"title": "Explicit Count", "type": "integer"}
108+
109+
90110
@pytest.mark.anyio
91111
async def test_prompt_title():
92112
"""Test that prompt titles work correctly."""

0 commit comments

Comments
 (0)