8000 Add title to tools, resources, prompts (#972) · modelcontextprotocol/python-sdk@a2f8766 · GitHub
[go: up one dir, main page]

Skip to content

Commit a2f8766

Browse files
Add title to tools, resources, prompts (#972)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 029f434 commit a2f8766

File tree

18 files changed

+481
-46
lines changed

18 files changed

+481
-46
lines changed

README.md

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -212,13 +212,13 @@ from mcp.server.fastmcp import FastMCP
212212
mcp = FastMCP("My App")
213213

214214

215-
@mcp.resource("config://app")
215+
@mcp.resource("config://app", title="Application Configuration")
216216
def get_config() -> str:
217217
"""Static configuration data"""
218218
return "App configuration here"
219219

220220

221-
@mcp.resource("users://{user_id}/profile")
221+
@mcp.resource("users://{user_id}/profile", title="User Profile")
222222
def get_user_profile(user_id: str) -> str:
223223
"""Dynamic user data"""
224224
return f"Profile data for user {user_id}"
@@ -235,13 +235,13 @@ from mcp.server.fastmcp import FastMCP
235235
mcp = FastMCP("My App")
236236

237237

238-
@mcp.tool()
238+
@mcp.tool(title="BMI Calculator")
239239
def calculate_bmi(weight_kg: float, height_m: float) -> float:
240240
"""Calculate BMI given weight in kg and height in meters"""
241241
return weight_kg / (height_m**2)
242242

243243

244-
@mcp.tool()
244+
@mcp.tool(title="Weather Fetcher")
245245
async def fetch_weather(city: str) -> str:
246246
"""Fetch current weather for a city"""
247247
async with httpx.AsyncClient() as client:
@@ -260,12 +260,12 @@ from mcp.server.fastmcp.prompts import base
260260
mcp = FastMCP("My App")
261261

262262

263-
@mcp.prompt()
263+
@mcp.prompt(title="Code Review")
264264
def review_code(code: str) -> str:
265265
return f"Please review this code:\n\n{code}"
266266

267267

268-
@mcp.prompt()
268+
@mcp.prompt(title="Debug Assistant")
269269
def debug_error(error: str) -> list[base.Message]:
270270
return [
271271
base.UserMessage("I'm seeing this error:"),
@@ -918,6 +918,42 @@ async def main():
918918
tool_result = await session.call_tool("echo", {"message": "hello"})
919919
```
920920

921+
### Client Display Utilities
922+
923+
When building MCP clients, the SDK provides utilities to help display human-readable names for tools, resources, and prompts:
924+
925+
```python
926+
from mcp.shared.metadata_utils import get_display_name
927+
from mcp.client.session import ClientSession
928+
929+
930+
async def display_tools(session: ClientSession):
931+
"""Display available tools with human-readable names"""
932+
tools_response = await session.list_tools()
933+
934+
for tool in tools_response.tools:
935+
# get_display_name() returns the title if available, otherwise the name
936+
display_name = get_display_name(tool)
937+
print(f"Tool: {display_name}")
938+
if tool.description:
939+
print(f" {tool.description}")
940+
941+
942+
async def display_resources(session: ClientSession):
943+
"""Display available resources with human-readable names"""
944+
resources_response = await session.list_resources()
945+
946+
for resource in resources_response.resources:
947+
display_name = get_display_name(resource)
948+
print(f"Resource: {display_name} ({resource.uri})")
949+
```
950+
951+
The `get_display_name()` function implements the proper precedence rules for displaying names:
952+
- For tools: `title` > `annotations.title` > `name`
953+
- For other objects: `title` > `name`
954+
955+
This ensures your client UI shows the most user-friendly names that servers provide.
956+
921957
### OAuth Authentication for Clients
922958

923959
The SDK includes [authorization support](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) for connecting to protected MCP servers:

examples/clients/simple-chatbot/mcp_simple_chatbot/main.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ async def list_tools(self) -> list[Any]:
123123
for item in tools_response:
124124
if isinstance(item, tuple) and item[0] == "tools":
125125
tools.extend(
126-
Tool(tool.name, tool.description, tool.inputSchema)
126+
Tool(tool.name, tool.description, tool.inputSchema, tool.title)
127127
for tool in item[1]
128128
)
129129

@@ -189,9 +189,14 @@ class Tool:
189189
"""Represents a tool with its properties and formatting."""
190190

191191
def __init__(
192-
self, name: str, description: str, input_schema: dict[str, Any]
192+
self,
193+
name: str,
194+
description: str,
195+
input_schema: dict[str, Any],
196+
title: str | None = None,
193197
) -> None:
194198
self.name: str = name
199+
self.title: str | None = title
195200
self.description: str = description
196201
self.input_schema: dict[str, Any] = input_schema
197202

@@ -211,13 +216,20 @@ def format_for_llm(self) -> str:
211216
arg_desc += " (required)"
212217
args_desc.append(arg_desc)
213218

214-
return f"""
215-
Tool: {self.name}
216-
Description: {self.description}
219+
# Build the formatted output with title as a separate field
220+
output = f"Tool: {self.name}\n"
221+
222+
# Add human-readable title if available
223+
if self.title:
224+
output += f"User-readable title: {self.title}\n"
225+
226+
output += f"""Description: {self.description}
217227
Arguments:
218228
{chr(10).join(args_desc)}
219229
"""
220230

231+
return output
232+
221233

222234
class LLMClient:
223235
"""Manages communication with the LLM provider."""

examples/servers/simple-prompt/mcp_simple_prompt/server.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ async def list_prompts() -> list[types.Prompt]:
5353
return [
5454
types.Prompt(
5555
name="simple",
56+
title="Simple Assistant Prompt",
5657
description="A simple prompt that can take optional context and topic "
5758
"arguments",
5859
arguments=[

examples/servers/simple-resource/mcp_simple_resource/server.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@
55
from pydantic import AnyUrl, FileUrl
66

77
SAMPLE_RESOURCES = {
8-
"greeting": "Hello! This is a sample text resource.",
9-
"help": "This server provides a few sample text resources for testing.",
10-
"about": "This is the simple-resource MCP server implementation.",
8+
"greeting": {
9+
"content": "Hello! This is a sample text resource.",
10+
"title": "Welcome Message",
11+
},
12+
"help": {
13+
"content": "This server provides a few sample text resources for testing.",
14+
"title": "Help Documentation",
15+
},
16+
"about": {
17+
"content": "This is the simple-resource MCP server implementation.",
18+
"title": "About This Server",
19+
},
1120
}
1221

1322

@@ -28,6 +37,7 @@ async def list_resources() -> list[types.Resource]:
2837
types.Resource(
2938
uri=FileUrl(f"file:///{name}.txt"),
3039
name=name,
40+
title=SAMPLE_RESOURCES[name]["title"],
3141
description=f"A sample text resource named {name}",
3242
mimeType="text/plain",
3343
)
@@ -43,7 +53,7 @@ async def read_resource(uri: AnyUrl) -> str | bytes:
4353
if name not in SAMPLE_RESOURCES:
4454
raise ValueError(f"Unknown resource: {uri}")
4555

46-
return SAMPLE_RESOURCES[name]
56+
return SAMPLE_RESOURCES[name]["content"]
4757

4858
if transport == "sse":
4959
from mcp.server.sse import SseServerTransport

examples/servers/simple-tool/mcp_simple_tool/server.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ async def list_tools() -> list[types.Tool]:
4141
return [
4242
types.Tool(
4343
name="fetch",
44+
title="Website Fetcher",
4445
description="Fetches a website and returns its content",
4546
inputSchema={
4647
"type": "object",

src/mcp/server/fastmcp/prompts/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class Prompt(BaseModel):
5858
"""A prompt template that can be rendered with parameters."""
5959

6060
name: str = Field(description="Name of the prompt")
61+
title: str | None = Field(None, description="Human-readable title of the prompt")
6162
description: str | None = Field(None, description="Description of what the prompt does")
6263
arguments: list[PromptArgument] | None = Field(None, description="Arguments that can be passed to the prompt")
6364
fn: Callable[..., PromptResult | Awaitable[PromptResult]] = Field(exclude=True)
@@ -67,6 +68,7 @@ def from_function(
6768
cls,
6869
fn: Callable[..., PromptResult | Awaitable[PromptResult]],
6970
name: str | None = None,
71+
title: str | None = None,
7072
description: str | None = None,
7173
) -> "Prompt":
7274
"""Create a Prompt from a function.
@@ -103,6 +105,7 @@ def from_function(
103105

104106
return cls(
105107
name=func_name,
108+
title=title,
106109
description=description or fn.__doc__ or "",
107110
arguments=arguments,
108111
fn=fn,

src/mcp/server/fastmcp/resources/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Resource(BaseModel, abc.ABC):
2121

2222
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(default=..., description="URI of the resource")
2323
name: str | None = Field(description="Name of the resource", default=None)
24+
title: str | None = Field(description="Human-readable title of the resource", default=None)
2425
description: str | None = Field(description="Description of the resource", default=None)
2526
mime_type: str = Field(
2627
default="text/plain",

src/mcp/server/fastmcp/resources/resource_manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def add_template(
5151
fn: Callable[..., Any],
5252
uri_template: str,
5353
name: str | None = None,
54+
title: str | None = None,
5455
description: str | None = None,
5556
mime_type: str | None = None,
5657
) -> ResourceTemplate:
@@ -59,6 +60,7 @@ def add_template(
5960
fn,
6061
uri_template=uri_template,
6162
name=name,
63+
title=title,
6264
description=description,
6365
mime_type=mime_type,
6466
)

src/mcp/server/fastmcp/resources/templates.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class ResourceTemplate(BaseModel):
1717

1818
uri_template: str = Field(description="URI template with parameters (e.g. weather://{city}/current)")
1919
name: str = Field(description="Name of the resource")
20+
title: str | None = Field(description="Human-readable title of the resource", default=None)
2021
description: str | None = Field(description="Description of what the resource does")
2122
mime_type: str = Field(default="text/plain", description="MIME type of the resource content")
2223
fn: Callable[..., Any] = Field(exclude=True)
@@ -28,6 +29,7 @@ def from_function(
2829
fn: Callable[..., Any],
2930
uri_template: str,
3031
name: str | None = None,
32+
title: str | None = None,
3133
description: str | None = None,
3234
mime_type: str | None = None,
3335
) -> ResourceTemplate:
@@ -45,6 +47,7 @@ def from_function(
4547
return cls(
4648
uri_template=uri_template,
4749
name=func_name,
50+
title=title,
4851
description=description or fn.__doc__ or "",
4952
mime_type=mime_type or "text/plain",
5053
fn=fn,
@@ -71,6 +74,7 @@ async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
7174
return FunctionResource(
7275
uri=uri, # type: ignore
7376
name=self.name,
77+
title=self.title,
7478
description=self.description,
7579
mime_type=self.mime_type,
7680
fn=lambda: result, # Capture result in closure

src/mcp/server/fastmcp/resources/types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def from_function(
7272
fn: Callable[..., Any],
7373
uri: str,
7474
name: str | None = None,
75+
title: str | None = None,
7576
description: str | None = None,
7677
mime_type: str | None = None,
7778
) -> "FunctionResource":
@@ -86,6 +87,7 @@ def from_function(
8687
return cls(
8788
uri=AnyUrl(uri),
8889
name=func_name,
90+
title=title,
8991
description=description or fn.__doc__ or "",
9092
mime_type=mime_type or "text/plain",
9193
fn=fn,

0 commit comments

Comments
 (0)
0