mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-21 13:04:18 +02:00
Add title field support to FastMCP components
- Add optional title field to FastMCPComponent base class - Support title parameter in all component decorators (@tool, @resource, @prompt) - Include title in MCP protocol serialization when provided - Add get_display_name() method to prefer title over name for display - Add comprehensive tests for title functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a513408320
commit
24deabf568
7 changed files with 102 additions and 2 deletions
|
|
@ -100,12 +100,16 @@ class Prompt(FastMCPComponent, ABC):
|
|||
"description": self.description,
|
||||
"arguments": arguments,
|
||||
}
|
||||
# Add title field if provided
|
||||
if self.title is not None:
|
||||
kwargs["title"] = self.title
|
||||
return MCPPrompt(**kwargs | overrides)
|
||||
|
||||
@staticmethod
|
||||
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 +123,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 +150,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 +242,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,
|
||||
|
|
@ -112,6 +114,9 @@ class Resource(FastMCPComponent, abc.ABC):
|
|||
"description": self.description,
|
||||
"mimeType": self.mime_type,
|
||||
}
|
||||
# Add title field if provided
|
||||
if self.title is not None:
|
||||
kwargs["title"] = self.title
|
||||
return MCPResource(**kwargs | overrides)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
|
@ -149,6 +154,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 +167,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,
|
||||
|
|
@ -145,6 +147,9 @@ class ResourceTemplate(FastMCPComponent):
|
|||
"description": self.description,
|
||||
"mimeType": self.mime_type,
|
||||
}
|
||||
# Add title field if provided
|
||||
if self.title is not None:
|
||||
kwargs["title"] = self.title
|
||||
return MCPResourceTemplate(**kwargs | overrides)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -197,6 +202,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 +284,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,
|
||||
|
|
|
|||
|
|
@ -142,12 +142,16 @@ class Tool(FastMCPComponent):
|
|||
"outputSchema": self.output_schema,
|
||||
"annotations": self.annotations,
|
||||
}
|
||||
# Add title field if provided
|
||||
if self.title is not None:
|
||||
kwargs["title"] = self.title
|
||||
return MCPTool(**kwargs | overrides)
|
||||
|
||||
@staticmethod
|
||||
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 +164,7 @@ class Tool(FastMCPComponent):
|
|||
return FunctionTool.from_function(
|
||||
fn=fn,
|
||||
name=name,
|
||||
title=title,
|
||||
description=description,
|
||||
tags=tags,
|
||||
annotations=annotations,
|
||||
|
|
@ -219,6 +224,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 +256,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,50 @@ 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"
|
||||
assert tool.get_display_name() == "Advanced Calculator Tool"
|
||||
|
||||
# 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
|
||||
assert tool.get_display_name() == "multiply"
|
||||
|
||||
# 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue