Add title to tools, resources, prompts (#972)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
029f434109
commit
a2f8766a8a
10 changed files with 481 additions and 46 deletions
|
|
@ -58,6 +58,7 @@ class Prompt(BaseModel):
|
|||
"""A prompt template that can be rendered with parameters."""
|
||||
|
||||
name: str = Field(description="Name of the prompt")
|
||||
title: str | None = Field(None, description="Human-readable title of the prompt")
|
||||
description: str | None = Field(None, description="Description of what the prompt does")
|
||||
arguments: list[PromptArgument] | None = Field(None, description="Arguments that can be passed to the prompt")
|
||||
fn: Callable[..., PromptResult | Awaitable[PromptResult]] = Field(exclude=True)
|
||||
|
|
@ -67,6 +68,7 @@ class Prompt(BaseModel):
|
|||
cls,
|
||||
fn: Callable[..., PromptResult | Awaitable[PromptResult]],
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
) -> "Prompt":
|
||||
"""Create a Prompt from a function.
|
||||
|
|
@ -103,6 +105,7 @@ class Prompt(BaseModel):
|
|||
|
||||
return cls(
|
||||
name=func_name,
|
||||
title=title,
|
||||
description=description or fn.__doc__ or "",
|
||||
arguments=arguments,
|
||||
fn=fn,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class Resource(BaseModel, abc.ABC):
|
|||
|
||||
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(default=..., description="URI of the resource")
|
||||
name: str | None = Field(description="Name of the resource", default=None)
|
||||
title: str | None = Field(description="Human-readable title of the resource", default=None)
|
||||
description: str | None = Field(description="Description of the resource", default=None)
|
||||
mime_type: str = Field(
|
||||
default="text/plain",
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ class ResourceManager:
|
|||
fn: Callable[..., Any],
|
||||
uri_template: str,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
mime_type: str | None = None,
|
||||
) -> ResourceTemplate:
|
||||
|
|
@ -59,6 +60,7 @@ class ResourceManager:
|
|||
fn,
|
||||
uri_template=uri_template,
|
||||
name=name,
|
||||
title=title,
|
||||
description=description,
|
||||
mime_type=mime_type,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class ResourceTemplate(BaseModel):
|
|||
|
||||
uri_template: str = Field(description="URI template with parameters (e.g. weather://{city}/current)")
|
||||
name: str = Field(description="Name of the resource")
|
||||
title: str | None = Field(description="Human-readable title of the resource", default=None)
|
||||
description: str | None = Field(description="Description of what the resource does")
|
||||
mime_type: str = Field(default="text/plain", description="MIME type of the resource content")
|
||||
fn: Callable[..., Any] = Field(exclude=True)
|
||||
|
|
@ -28,6 +29,7 @@ class ResourceTemplate(BaseModel):
|
|||
fn: Callable[..., Any],
|
||||
uri_template: str,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
mime_type: str | None = None,
|
||||
) -> ResourceTemplate:
|
||||
|
|
@ -45,6 +47,7 @@ class ResourceTemplate(BaseModel):
|
|||
return cls(
|
||||
uri_template=uri_template,
|
||||
name=func_name,
|
||||
title=title,
|
||||
description=description or fn.__doc__ or "",
|
||||
mime_type=mime_type or "text/plain",
|
||||
fn=fn,
|
||||
|
|
@ -71,6 +74,7 @@ class ResourceTemplate(BaseModel):
|
|||
return FunctionResource(
|
||||
uri=uri, # type: ignore
|
||||
name=self.name,
|
||||
title=self.title,
|
||||
description=self.description,
|
||||
mime_type=self.mime_type,
|
||||
fn=lambda: result, # Capture result in closure
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ class FunctionResource(Resource):
|
|||
fn: Callable[..., Any],
|
||||
uri: str,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
mime_type: str | None = None,
|
||||
) -> "FunctionResource":
|
||||
|
|
@ -86,6 +87,7 @@ class FunctionResource(Resource):
|
|||
return cls(
|
||||
uri=AnyUrl(uri),
|
||||
name=func_name,
|
||||
title=title,
|
||||
description=description or fn.__doc__ or "",
|
||||
mime_type=mime_type or "text/plain",
|
||||
fn=fn,
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ class FastMCP:
|
|||
return [
|
||||
MCPTool(
|
||||
name=info.name,
|
||||
title=info.title,
|
||||
description=info.description,
|
||||
inputSchema=info.parameters,
|
||||
annotations=info.annotations,
|
||||
|
|
@ -270,6 +271,7 @@ class FastMCP:
|
|||
MCPResource(
|
||||
uri=resource.uri,
|
||||
name=resource.name or "",
|
||||
title=resource.title,
|
||||
description=resource.description,
|
||||
mimeType=resource.mime_type,
|
||||
)
|
||||
|
|
@ -282,6 +284,7 @@ class FastMCP:
|
|||
MCPResourceTemplate(
|
||||
uriTemplate=template.uri_template,
|
||||
name=template.name,
|
||||
title=template.title,
|
||||
description=template.description,
|
||||
)
|
||||
for template in templates
|
||||
|
|
@ -305,6 +308,7 @@ class FastMCP:
|
|||
self,
|
||||
fn: AnyFunction,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
annotations: ToolAnnotations | None = None,
|
||||
) -> None:
|
||||
|
|
@ -316,14 +320,16 @@ class FastMCP:
|
|||
Args:
|
||||
fn: The function to register as a tool
|
||||
name: Optional name for the tool (defaults to function name)
|
||||
title: Optional human-readable title for the tool
|
||||
description: Optional description of what the tool does
|
||||
annotations: Optional ToolAnnotations providing additional tool information
|
||||
"""
|
||||
self._tool_manager.add_tool(fn, name=name, description=description, annotations=annotations)
|
||||
self._tool_manager.add_tool(fn, name=name, title=title, description=description, annotations=annotations)
|
||||
|
||||
def tool(
|
||||
self,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
annotations: ToolAnnotations | None = None,
|
||||
) -> Callable[[AnyFunction], AnyFunction]:
|
||||
|
|
@ -335,6 +341,7 @@ class FastMCP:
|
|||
|
||||
Args:
|
||||
name: Optional name for the tool (defaults to function name)
|
||||
title: Optional human-readable title for the tool
|
||||
description: Optional description of what the tool does
|
||||
annotations: Optional ToolAnnotations providing additional tool information
|
||||
|
||||
|
|
@ -360,7 +367,7 @@ class FastMCP:
|
|||
)
|
||||
|
||||
def decorator(fn: AnyFunction) -> AnyFunction:
|
||||
self.add_tool(fn, name=name, description=description, annotations=annotations)
|
||||
self.add_tool(fn, name=name, title=title, description=description, annotations=annotations)
|
||||
return fn
|
||||
|
||||
return decorator
|
||||
|
|
@ -396,6 +403,7 @@ class FastMCP:
|
|||
uri: str,
|
||||
*,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
mime_type: str | None = None,
|
||||
) -> Callable[[AnyFunction], AnyFunction]:
|
||||
|
|
@ -413,6 +421,7 @@ class FastMCP:
|
|||
Args:
|
||||
uri: URI for the resource (e.g. "resource://my-resource" or "resource://{param}")
|
||||
name: Optional name for the resource
|
||||
title: Optional human-readable title for the resource
|
||||
description: Optional description of the resource
|
||||
mime_type: Optional MIME type for the resource
|
||||
|
||||
|
|
@ -462,6 +471,7 @@ class FastMCP:
|
|||
fn=fn,
|
||||
uri_template=uri,
|
||||
name=name,
|
||||
title=title,
|
||||
description=description,
|
||||
mime_type=mime_type,
|
||||
)
|
||||
|
|
@ -471,6 +481,7 @@ class FastMCP:
|
|||
fn=fn,
|
||||
uri=uri,
|
||||
name=name,
|
||||
title=title,
|
||||
description=description,
|
||||
mime_type=mime_type,
|
||||
)
|
||||
|
|
@ -487,11 +498,14 @@ class FastMCP:
|
|||
"""
|
||||
self._prompt_manager.add_prompt(prompt)
|
||||
|
||||
def prompt(self, name: str | None = None, description: str | None = None) -> Callable[[AnyFunction], AnyFunction]:
|
||||
def prompt(
|
||||
self, name: str | None = None, title: str | None = None, description: str | None = None
|
||||
) -> Callable[[AnyFunction], AnyFunction]:
|
||||
"""Decorator to register a prompt.
|
||||
|
||||
Args:
|
||||
name: Optional name for the prompt (defaults to function name)
|
||||
title: Optional human-readable title for the prompt
|
||||
description: Optional description of what the prompt does
|
||||
|
||||
Example:
|
||||
|
|
@ -529,7 +543,7 @@ class FastMCP:
|
|||
)
|
||||
|
||||
def decorator(func: AnyFunction) -> AnyFunction:
|
||||
prompt = Prompt.from_function(func, name=name, description=description)
|
||||
prompt = Prompt.from_function(func, name=name, title=title, description=description)
|
||||
self.add_prompt(prompt)
|
||||
return func
|
||||
|
||||
|
|
@ -831,6 +845,7 @@ class FastMCP:
|
|||
return [
|
||||
MCPPrompt(
|
||||
name=prompt.name,
|
||||
title=prompt.title,
|
||||
description=prompt.description,
|
||||
arguments=[
|
||||
MCPPromptArgument(
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class Tool(BaseModel):
|
|||
|
||||
fn: Callable[..., Any] = Field(exclude=True)
|
||||
name: str = Field(description="Name of the tool")
|
||||
title: str | None = Field(None, description="Human-readable title of the tool")
|
||||
description: str = Field(description="Description of what the tool does")
|
||||
parameters: dict[str, Any] = Field(description="JSON schema for tool parameters")
|
||||
fn_metadata: FuncMetadata = Field(
|
||||
|
|
@ -36,6 +37,7 @@ class Tool(BaseModel):
|
|||
cls,
|
||||
fn: Callable[..., Any],
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
context_kwarg: str | None = None,
|
||||
annotations: ToolAnnotations | None = None,
|
||||
|
|
@ -69,6 +71,7 @@ class Tool(BaseModel):
|
|||
return cls(
|
||||
fn=fn,
|
||||
name=func_name,
|
||||
title=title,
|
||||
description=func_doc,
|
||||
parameters=parameters,
|
||||
fn_metadata=func_arg_metadata,
|
||||
|
|
|
|||
|
|
@ -46,11 +46,12 @@ class ToolManager:
|
|||
self,
|
||||
fn: Callable[..., Any],
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
annotations: ToolAnnotations | None = None,
|
||||
) -> Tool:
|
||||
"""Add a tool to the server."""
|
||||
tool = Tool.from_function(fn, name=name, description=description, annotations=annotations)
|
||||
tool = Tool.from_function(fn, name=name, title=title, description=description, annotations=annotations)
|
||||
existing = self._tools.get(tool.name)
|
||||
if existing:
|
||||
if self.warn_on_duplicate_tools:
|
||||
|
|
|
|||
45
src/mcp/shared/metadata_utils.py
Normal file
45
src/mcp/shared/metadata_utils.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"""Utility functions for working with metadata in MCP types.
|
||||
|
||||
These utilities are primarily intended for client-side usage to properly display
|
||||
human-readable names in user interfaces in a spec compliant way.
|
||||
"""
|
||||
|
||||
from mcp.types import Implementation, Prompt, Resource, ResourceTemplate, Tool
|
||||
|
||||
|
||||
def get_display_name(obj: Tool | Resource | Prompt | ResourceTemplate | Implementation) -> str:
|
||||
"""
|
||||
Get the display name for an MCP object with proper precedence.
|
||||
|
||||
This is a client-side utility function designed to help MCP clients display
|
||||
human-readable names in their user interfaces. When servers provide a 'title'
|
||||
field, it should be preferred over the programmatic 'name' field for display.
|
||||
|
||||
For tools: title > annotations.title > name
|
||||
For other objects: title > name
|
||||
|
||||
Example:
|
||||
# In a client displaying available tools
|
||||
tools = await session.list_tools()
|
||||
for tool in tools.tools:
|
||||
display_name = get_display_name(tool)
|
||||
print(f"Available tool: {display_name}")
|
||||
|
||||
Args:
|
||||
obj: An MCP object with name and optional title fields
|
||||
|
||||
Returns:
|
||||
The display name to use for UI presentation
|
||||
"""
|
||||
if isinstance(obj, Tool):
|
||||
# Tools have special precedence: title > annotations.title > name
|
||||
if hasattr(obj, "title") and obj.title is not None:
|
||||
return obj.title
|
||||
if obj.annotations and hasattr(obj.annotations, "title") and obj.annotations.title is not None:
|
||||
return obj.annotations.title
|
||||
return obj.name
|
||||
else:
|
||||
# All other objects: title > name
|
||||
if hasattr(obj, "title") and obj.title is not None:
|
||||
return obj.title
|
||||
return obj.name
|
||||
|
|
@ -196,10 +196,26 @@ class EmptyResult(Result):
|
|||
"""A response that indicates success but carries no data."""
|
||||
|
||||
|
||||
class Implementation(BaseModel):
|
||||
"""Describes the name and version of an MCP implementation."""
|
||||
class BaseMetadata(BaseModel):
|
||||
"""Base class for entities with name and optional title fields."""
|
||||
|
||||
name: str
|
||||
"""The programmatic name of the entity."""
|
||||
|
||||
title: str | None = None
|
||||
"""
|
||||
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
|
||||
even by those unfamiliar with domain-specific terminology.
|
||||
|
||||
If not provided, the name should be used for display (except for Tool,
|
||||
where `annotations.title` should be given precedence over using `name`,
|
||||
if present).
|
||||
"""
|
||||
|
||||
|
||||
class Implementation(BaseMetadata):
|
||||
"""Describes the name and version of an MCP implementation."""
|
||||
|
||||
version: str
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
|
@ -382,13 +398,11 @@ class Annotations(BaseModel):
|
|||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
class Resource(BaseModel):
|
||||
class Resource(BaseMetadata):
|
||||
"""A known resource that the server is capable of reading."""
|
||||
|
||||
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
|
||||
"""The URI of this resource."""
|
||||
name: str
|
||||
"""A human-readable name for this resource."""
|
||||
description: str | None = None
|
||||
"""A description of what this resource represents."""
|
||||
mimeType: str | None = None
|
||||
|
|
@ -409,7 +423,7 @@ class Resource(BaseModel):
|
|||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
class ResourceTemplate(BaseModel):
|
||||
class ResourceTemplate(BaseMetadata):
|
||||
"""A template description for resources available on the server."""
|
||||
|
||||
uriTemplate: str
|
||||
|
|
@ -417,8 +431,6 @@ class ResourceTemplate(BaseModel):
|
|||
A URI template (according to RFC 6570) that can be used to construct resource
|
||||
URIs.
|
||||
"""
|
||||
name: str
|
||||
"""A human-readable name for the type of resource this template refers to."""
|
||||
description: str | None = None
|
||||
"""A human-readable description of what this template is for."""
|
||||
mimeType: str | None = None
|
||||
|
|
@ -601,11 +613,9 @@ class PromptArgument(BaseModel):
|
|||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
class Prompt(BaseModel):
|
||||
class Prompt(BaseMetadata):
|
||||
"""A prompt or prompt template that the server offers."""
|
||||
|
||||
name: str
|
||||
"""The name of the prompt or prompt template."""
|
||||
description: str | None = None
|
||||
"""An optional description of what this prompt provides."""
|
||||
arguments: list[PromptArgument] | None = None
|
||||
|
|
@ -808,11 +818,9 @@ class ToolAnnotations(BaseModel):
|
|||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
class Tool(BaseModel):
|
||||
class Tool(BaseMetadata):
|
||||
"""Definition for a tool the client can call."""
|
||||
|
||||
name: str
|
||||
"""The name of the tool."""
|
||||
description: str | None = None
|
||||
"""A human-readable description of the tool."""
|
||||
inputSchema: dict[str, Any]
|
||||
|
|
|
|||
Loading…
Reference in a new issue