mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-24 06:24:18 +02:00
Merge pull request #982 from jlowin/title-field-support
Add title field support to FastMCP components
This commit is contained in:
commit
89a4d83332
7 changed files with 148 additions and 2 deletions
|
|
@ -99,6 +99,7 @@ class Prompt(FastMCPComponent, ABC):
|
|||
"name": self.name,
|
||||
"description": self.description,
|
||||
"arguments": arguments,
|
||||
"title": self.title,
|
||||
}
|
||||
return MCPPrompt(**kwargs | overrides)
|
||||
|
||||
|
|
@ -106,6 +107,7 @@ class Prompt(FastMCPComponent, ABC):
|
|||
def from_function(
|
||||
fn: Callable[..., PromptResult | Awaitable[PromptResult]],
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
|
|
@ -119,7 +121,12 @@ class Prompt(FastMCPComponent, ABC):
|
|||
- A sequence of any of the above
|
||||
"""
|
||||
return FunctionPrompt.from_function(
|
||||
fn=fn, name=name, description=description, tags=tags, enabled=enabled
|
||||
fn=fn,
|
||||
name=name,
|
||||
title=title,
|
||||
description=description,
|
||||
tags=tags,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
|
|
@ -141,6 +148,7 @@ class FunctionPrompt(Prompt):
|
|||
cls,
|
||||
fn: Callable[..., PromptResult | Awaitable[PromptResult]],
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
|
|
@ -232,6 +240,7 @@ class FunctionPrompt(Prompt):
|
|||
|
||||
return cls(
|
||||
name=func_name,
|
||||
title=title,
|
||||
description=description,
|
||||
arguments=arguments,
|
||||
tags=tags or set(),
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class Resource(FastMCPComponent, abc.ABC):
|
|||
fn: Callable[[], Any],
|
||||
uri: str | AnyUrl,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
mime_type: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
|
|
@ -74,6 +75,7 @@ class Resource(FastMCPComponent, abc.ABC):
|
|||
fn=fn,
|
||||
uri=uri,
|
||||
name=name,
|
||||
title=title,
|
||||
description=description,
|
||||
mime_type=mime_type,
|
||||
tags=tags,
|
||||
|
|
@ -111,6 +113,7 @@ class Resource(FastMCPComponent, abc.ABC):
|
|||
"name": self.name,
|
||||
"description": self.description,
|
||||
"mimeType": self.mime_type,
|
||||
"title": self.title,
|
||||
}
|
||||
return MCPResource(**kwargs | overrides)
|
||||
|
||||
|
|
@ -149,6 +152,7 @@ class FunctionResource(Resource):
|
|||
fn: Callable[[], Any],
|
||||
uri: str | AnyUrl,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
mime_type: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
|
|
@ -161,6 +165,7 @@ class FunctionResource(Resource):
|
|||
fn=fn,
|
||||
uri=uri,
|
||||
name=name or fn.__name__,
|
||||
title=title,
|
||||
description=description or inspect.getdoc(fn),
|
||||
mime_type=mime_type or "text/plain",
|
||||
tags=tags or set(),
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ class ResourceTemplate(FastMCPComponent):
|
|||
fn: Callable[..., Any],
|
||||
uri_template: str,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
mime_type: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
|
|
@ -95,6 +96,7 @@ class ResourceTemplate(FastMCPComponent):
|
|||
fn=fn,
|
||||
uri_template=uri_template,
|
||||
name=name,
|
||||
title=title,
|
||||
description=description,
|
||||
mime_type=mime_type,
|
||||
tags=tags,
|
||||
|
|
@ -144,6 +146,7 @@ class ResourceTemplate(FastMCPComponent):
|
|||
"name": self.name,
|
||||
"description": self.description,
|
||||
"mimeType": self.mime_type,
|
||||
"title": self.title,
|
||||
}
|
||||
return MCPResourceTemplate(**kwargs | overrides)
|
||||
|
||||
|
|
@ -197,6 +200,7 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|||
fn: Callable[..., Any],
|
||||
uri_template: str,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
mime_type: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
|
|
@ -278,6 +282,7 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|||
return cls(
|
||||
uri_template=uri_template,
|
||||
name=func_name,
|
||||
title=title,
|
||||
description=description,
|
||||
mime_type=mime_type or "text/plain",
|
||||
fn=fn,
|
||||
|
|
|
|||
|
|
@ -790,6 +790,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name_or_fn: AnyFunction,
|
||||
*,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
||||
|
|
@ -804,6 +805,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name_or_fn: str | None = None,
|
||||
*,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
||||
|
|
@ -817,6 +819,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name_or_fn: str | AnyFunction | None = None,
|
||||
*,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
||||
|
|
@ -897,6 +900,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
tool = Tool.from_function(
|
||||
fn,
|
||||
name=tool_name,
|
||||
title=title,
|
||||
description=description,
|
||||
tags=tags,
|
||||
output_schema=output_schema,
|
||||
|
|
@ -928,6 +932,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
return partial(
|
||||
self.tool,
|
||||
name=tool_name,
|
||||
title=title,
|
||||
description=description,
|
||||
tags=tags,
|
||||
output_schema=output_schema,
|
||||
|
|
@ -1016,6 +1021,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
uri: str,
|
||||
*,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
mime_type: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
|
|
@ -1107,6 +1113,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
fn=fn,
|
||||
uri_template=uri,
|
||||
name=name,
|
||||
title=title,
|
||||
description=description,
|
||||
mime_type=mime_type,
|
||||
tags=tags,
|
||||
|
|
@ -1119,6 +1126,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
fn=fn,
|
||||
uri=uri,
|
||||
name=name,
|
||||
title=title,
|
||||
description=description,
|
||||
mime_type=mime_type,
|
||||
tags=tags,
|
||||
|
|
@ -1158,6 +1166,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name_or_fn: AnyFunction,
|
||||
*,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
|
|
@ -1169,6 +1178,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name_or_fn: str | None = None,
|
||||
*,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
|
|
@ -1179,6 +1189,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name_or_fn: str | AnyFunction | None = None,
|
||||
*,
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
|
|
@ -1275,6 +1286,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
prompt = Prompt.from_function(
|
||||
fn=fn,
|
||||
name=prompt_name,
|
||||
title=title,
|
||||
description=description,
|
||||
tags=tags,
|
||||
enabled=enabled,
|
||||
|
|
@ -1303,6 +1315,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
return partial(
|
||||
self.prompt,
|
||||
name=prompt_name,
|
||||
title=title,
|
||||
description=description,
|
||||
tags=tags,
|
||||
enabled=enabled,
|
||||
|
|
|
|||
|
|
@ -135,12 +135,20 @@ class Tool(FastMCPComponent):
|
|||
pass # No context available
|
||||
|
||||
def to_mcp_tool(self, **overrides: Any) -> MCPTool:
|
||||
if self.title:
|
||||
title = self.title
|
||||
elif self.annotations and self.annotations.title:
|
||||
title = self.annotations.title
|
||||
else:
|
||||
title = None
|
||||
|
||||
kwargs = {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"inputSchema": self.parameters,
|
||||
"outputSchema": self.output_schema,
|
||||
"annotations": self.annotations,
|
||||
"title": title,
|
||||
}
|
||||
return MCPTool(**kwargs | overrides)
|
||||
|
||||
|
|
@ -148,6 +156,7 @@ class Tool(FastMCPComponent):
|
|||
def from_function(
|
||||
fn: Callable[..., Any],
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
annotations: ToolAnnotations | None = None,
|
||||
|
|
@ -160,6 +169,7 @@ class Tool(FastMCPComponent):
|
|||
return FunctionTool.from_function(
|
||||
fn=fn,
|
||||
name=name,
|
||||
title=title,
|
||||
description=description,
|
||||
tags=tags,
|
||||
annotations=annotations,
|
||||
|
|
@ -219,6 +229,7 @@ class FunctionTool(Tool):
|
|||
cls,
|
||||
fn: Callable[..., Any],
|
||||
name: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
annotations: ToolAnnotations | None = None,
|
||||
|
|
@ -250,6 +261,7 @@ class FunctionTool(Tool):
|
|||
return cls(
|
||||
fn=parsed_fn.fn,
|
||||
name=name or parsed_fn.name,
|
||||
title=title,
|
||||
description=description or parsed_fn.description,
|
||||
parameters=parsed_fn.input_schema,
|
||||
output_schema=output_schema,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ class FastMCPComponent(FastMCPBaseModel):
|
|||
name: str = Field(
|
||||
description="The name of the component.",
|
||||
)
|
||||
title: str | None = Field(
|
||||
default=None,
|
||||
description="The title of the component for display purposes.",
|
||||
)
|
||||
description: str | None = Field(
|
||||
default=None,
|
||||
description="The description of the component.",
|
||||
|
|
@ -64,7 +68,7 @@ class FastMCPComponent(FastMCPBaseModel):
|
|||
return self.model_dump() == other.model_dump()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(name={self.name!r}, description={self.description!r}, tags={self.tags}, enabled={self.enabled})"
|
||||
return f"{self.__class__.__name__}(name={self.name!r}, title={self.title!r}, description={self.description!r}, tags={self.tags}, enabled={self.enabled})"
|
||||
|
||||
def enable(self) -> None:
|
||||
"""Enable the component."""
|
||||
|
|
@ -73,3 +77,7 @@ class FastMCPComponent(FastMCPBaseModel):
|
|||
def disable(self) -> None:
|
||||
"""Disable the component."""
|
||||
self.enabled = False
|
||||
|
||||
def get_display_name(self) -> str:
|
||||
"""Get the display name for this component, preferring title over name."""
|
||||
return self.title if self.title is not None else self.name
|
||||
|
|
|
|||
|
|
@ -1225,3 +1225,97 @@ class TestAutomaticStructuredContent:
|
|||
assert result.data.name == "Bob"
|
||||
assert result.data.age == 25
|
||||
assert result.data.verified is True
|
||||
|
||||
|
||||
class TestToolTitle:
|
||||
"""Tests for tool title functionality."""
|
||||
|
||||
def test_tool_with_title(self):
|
||||
"""Test that tools can have titles and they appear in MCP conversion."""
|
||||
|
||||
def calculate(x: int, y: int) -> int:
|
||||
"""Calculate the sum of two numbers."""
|
||||
return x + y
|
||||
|
||||
tool = Tool.from_function(
|
||||
calculate,
|
||||
name="calc",
|
||||
title="Advanced Calculator Tool",
|
||||
description="Custom description",
|
||||
)
|
||||
|
||||
assert tool.name == "calc"
|
||||
assert tool.title == "Advanced Calculator Tool"
|
||||
assert tool.description == "Custom description"
|
||||
|
||||
# Test MCP conversion includes title
|
||||
mcp_tool = tool.to_mcp_tool()
|
||||
assert mcp_tool.name == "calc"
|
||||
assert (
|
||||
hasattr(mcp_tool, "title") and mcp_tool.title == "Advanced Calculator Tool"
|
||||
)
|
||||
|
||||
def test_tool_without_title(self):
|
||||
"""Test that tools without titles use name as display name."""
|
||||
|
||||
def multiply(a: int, b: int) -> int:
|
||||
return a * b
|
||||
|
||||
tool = Tool.from_function(multiply)
|
||||
|
||||
assert tool.name == "multiply"
|
||||
assert tool.title is None
|
||||
|
||||
# Test MCP conversion doesn't include title when None
|
||||
mcp_tool = tool.to_mcp_tool()
|
||||
assert mcp_tool.name == "multiply"
|
||||
assert not hasattr(mcp_tool, "title") or mcp_tool.title is None
|
||||
|
||||
def test_tool_title_priority(self):
|
||||
"""Test that explicit title takes priority over annotations.title."""
|
||||
from mcp.types import ToolAnnotations
|
||||
|
||||
def divide(x: int, y: int) -> float:
|
||||
"""Divide two numbers."""
|
||||
return x / y
|
||||
|
||||
# Test with both explicit title and annotations.title
|
||||
annotations = ToolAnnotations(title="Annotation Title")
|
||||
tool = Tool.from_function(
|
||||
divide,
|
||||
name="div",
|
||||
title="Explicit Title",
|
||||
annotations=annotations,
|
||||
)
|
||||
|
||||
assert tool.title == "Explicit Title"
|
||||
assert tool.annotations is not None
|
||||
assert tool.annotations.title == "Annotation Title"
|
||||
|
||||
# Explicit title should take priority
|
||||
mcp_tool = tool.to_mcp_tool()
|
||||
assert mcp_tool.title == "Explicit Title"
|
||||
|
||||
def test_tool_annotations_title_fallback(self):
|
||||
"""Test that annotations.title is used when no explicit title is provided."""
|
||||
from mcp.types import ToolAnnotations
|
||||
|
||||
def modulo(x: int, y: int) -> int:
|
||||
"""Get modulo of two numbers."""
|
||||
return x % y
|
||||
|
||||
# Test with only annotations.title (no explicit title)
|
||||
annotations = ToolAnnotations(title="Annotation Title")
|
||||
tool = Tool.from_function(
|
||||
modulo,
|
||||
name="mod",
|
||||
annotations=annotations,
|
||||
)
|
||||
|
||||
assert tool.title is None
|
||||
assert tool.annotations is not None
|
||||
assert tool.annotations.title == "Annotation Title"
|
||||
|
||||
# Should fall back to annotations.title
|
||||
mcp_tool = tool.to_mcp_tool()
|
||||
assert mcp_tool.title == "Annotation Title"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue