mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-21 04:54:17 +02:00
Add meta parameter support to tools, resources, templates, and prompts decorators (#1294)
This commit is contained in:
parent
7705858266
commit
3fc2510110
13 changed files with 192 additions and 2 deletions
|
|
@ -65,7 +65,8 @@ While FastMCP infers the name and description from your function, you can overri
|
|||
@mcp.prompt(
|
||||
name="analyze_data_request", # Custom prompt name
|
||||
description="Creates a request to analyze data with specific parameters", # Custom description
|
||||
tags={"analysis", "data"} # Optional categorization tags
|
||||
tags={"analysis", "data"}, # Optional categorization tags
|
||||
meta={"version": "1.1", "author": "data-team"} # Custom metadata
|
||||
)
|
||||
def data_analysis_prompt(
|
||||
data_uri: str = Field(description="The URI of the resource containing the data."),
|
||||
|
|
@ -91,6 +92,12 @@ def data_analysis_prompt(
|
|||
<ParamField body="enabled" type="bool" default="True">
|
||||
A boolean to enable or disable the prompt. See [Disabling Prompts](#disabling-prompts) for more information
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="meta" type="dict[str, Any] | None">
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
Optional meta information about the prompt. This data is passed through to the MCP client as the `_meta` field of the client-side prompt object and can be used for custom metadata, versioning, or other application-specific purposes.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Argument Types
|
||||
|
|
|
|||
|
|
@ -73,7 +73,8 @@ mcp = FastMCP(name="DataServer")
|
|||
name="ApplicationStatus", # Custom name
|
||||
description="Provides the current status of the application.", # Custom description
|
||||
mime_type="application/json", # Explicit MIME type
|
||||
tags={"monitoring", "status"} # Categorization tags
|
||||
tags={"monitoring", "status"}, # Categorization tags
|
||||
meta={"version": "2.1", "team": "infrastructure"} # Custom metadata
|
||||
)
|
||||
def get_application_status() -> dict:
|
||||
"""Internal function description (ignored if description is provided above)."""
|
||||
|
|
@ -116,6 +117,12 @@ def get_application_status() -> dict:
|
|||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="meta" type="dict[str, Any] | None">
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
Optional meta information about the resource. This data is passed through to the MCP client as the `_meta` field of the client-side resource object and can be used for custom metadata, versioning, or other application-specific purposes.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
### Return Values
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ While FastMCP infers the name and description from your function, you can overri
|
|||
name="find_products", # Custom tool name for the LLM
|
||||
description="Search the product catalog with optional category filtering.", # Custom description
|
||||
tags={"catalog", "search"}, # Optional tags for organization/filtering
|
||||
meta={"version": "1.2", "author": "product-team"} # Custom metadata
|
||||
)
|
||||
def search_products_implementation(query: str, category: str | None = None) -> list[dict]:
|
||||
"""Internal function description (ignored if description is provided above)."""
|
||||
|
|
@ -107,6 +108,12 @@ def search_products_implementation(query: str, category: str | None = None) -> l
|
|||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="meta" type="dict[str, Any] | None">
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
Optional meta information about the tool. This data is passed through to the MCP client as the `_meta` field of the client-side tool object and can be used for custom metadata, versioning, or other application-specific purposes.
|
||||
</ParamField>
|
||||
</Card>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ class Prompt(FastMCPComponent, ABC):
|
|||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> FunctionPrompt:
|
||||
"""Create a Prompt from a function.
|
||||
|
||||
|
|
@ -133,6 +134,7 @@ class Prompt(FastMCPComponent, ABC):
|
|||
description=description,
|
||||
tags=tags,
|
||||
enabled=enabled,
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
|
|
@ -158,6 +160,7 @@ class FunctionPrompt(Prompt):
|
|||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> FunctionPrompt:
|
||||
"""Create a Prompt from a function.
|
||||
|
||||
|
|
@ -252,6 +255,7 @@ class FunctionPrompt(Prompt):
|
|||
tags=tags or set(),
|
||||
enabled=enabled if enabled is not None else True,
|
||||
fn=fn,
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
def _convert_string_arguments(self, kwargs: dict[str, Any]) -> dict[str, Any]:
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ class Resource(FastMCPComponent, abc.ABC):
|
|||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
annotations: Annotations | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> FunctionResource:
|
||||
return FunctionResource.from_function(
|
||||
fn=fn,
|
||||
|
|
@ -87,6 +88,7 @@ class Resource(FastMCPComponent, abc.ABC):
|
|||
tags=tags,
|
||||
enabled=enabled,
|
||||
annotations=annotations,
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
@field_validator("mime_type", mode="before")
|
||||
|
|
@ -172,6 +174,7 @@ class FunctionResource(Resource):
|
|||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
annotations: Annotations | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> FunctionResource:
|
||||
"""Create a FunctionResource from a function."""
|
||||
if isinstance(uri, str):
|
||||
|
|
@ -186,6 +189,7 @@ class FunctionResource(Resource):
|
|||
tags=tags or set(),
|
||||
enabled=enabled if enabled is not None else True,
|
||||
annotations=annotations,
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
async def read(self) -> str | bytes:
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ class ResourceTemplate(FastMCPComponent):
|
|||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
annotations: Annotations | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> FunctionResourceTemplate:
|
||||
return FunctionResourceTemplate.from_function(
|
||||
fn=fn,
|
||||
|
|
@ -107,6 +108,7 @@ class ResourceTemplate(FastMCPComponent):
|
|||
tags=tags,
|
||||
enabled=enabled,
|
||||
annotations=annotations,
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
@field_validator("mime_type", mode="before")
|
||||
|
|
@ -219,6 +221,7 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
annotations: Annotations | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> FunctionResourceTemplate:
|
||||
"""Create a template from a function."""
|
||||
from fastmcp.server.context import Context
|
||||
|
|
@ -304,4 +307,5 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|||
tags=tags or set(),
|
||||
enabled=enabled if enabled is not None else True,
|
||||
annotations=annotations,
|
||||
meta=meta,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -871,6 +871,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool | None = None,
|
||||
) -> FunctionTool: ...
|
||||
|
||||
|
|
@ -886,6 +887,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool | None = None,
|
||||
) -> Callable[[AnyFunction], FunctionTool]: ...
|
||||
|
||||
|
|
@ -900,6 +902,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool | None = None,
|
||||
) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
|
||||
"""Decorator to register a tool.
|
||||
|
|
@ -923,6 +926,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
output_schema: Optional JSON schema for the tool's output
|
||||
annotations: Optional annotations about the tool's behavior
|
||||
exclude_args: Optional list of argument names to exclude from the tool schema
|
||||
meta: Optional meta information about the tool
|
||||
enabled: Optional boolean to enable or disable the tool
|
||||
|
||||
Examples:
|
||||
|
|
@ -981,6 +985,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
output_schema=output_schema,
|
||||
annotations=annotations,
|
||||
exclude_args=exclude_args,
|
||||
meta=meta,
|
||||
serializer=self._tool_serializer,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
|
@ -1013,6 +1018,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
output_schema=output_schema,
|
||||
annotations=annotations,
|
||||
exclude_args=exclude_args,
|
||||
meta=meta,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
||||
|
|
@ -1111,6 +1117,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
annotations: Annotations | dict[str, Any] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
|
||||
"""Decorator to register a function as a resource.
|
||||
|
||||
|
|
@ -1135,6 +1142,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
tags: Optional set of tags for categorizing the resource
|
||||
enabled: Optional boolean to enable or disable the resource
|
||||
annotations: Optional annotations about the resource's behavior
|
||||
meta: Optional meta information about the resource
|
||||
|
||||
Examples:
|
||||
Register a resource with a custom name:
|
||||
|
|
@ -1208,6 +1216,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
tags=tags,
|
||||
enabled=enabled,
|
||||
annotations=annotations,
|
||||
meta=meta,
|
||||
)
|
||||
self.add_template(template)
|
||||
return template
|
||||
|
|
@ -1222,6 +1231,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
tags=tags,
|
||||
enabled=enabled,
|
||||
annotations=annotations,
|
||||
meta=meta,
|
||||
)
|
||||
self.add_resource(resource)
|
||||
return resource
|
||||
|
|
@ -1266,6 +1276,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> FunctionPrompt: ...
|
||||
|
||||
@overload
|
||||
|
|
@ -1278,6 +1289,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> Callable[[AnyFunction], FunctionPrompt]: ...
|
||||
|
||||
def prompt(
|
||||
|
|
@ -1289,6 +1301,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt:
|
||||
"""Decorator to register a prompt.
|
||||
|
||||
|
|
@ -1309,6 +1322,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
description: Optional description of what the prompt does
|
||||
tags: Optional set of tags for categorizing the prompt
|
||||
enabled: Optional boolean to enable or disable the prompt
|
||||
meta: Optional meta information about the prompt
|
||||
|
||||
Examples:
|
||||
|
||||
|
|
@ -1386,6 +1400,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
description=description,
|
||||
tags=tags,
|
||||
enabled=enabled,
|
||||
meta=meta,
|
||||
)
|
||||
self.add_prompt(prompt)
|
||||
|
||||
|
|
@ -1415,6 +1430,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
description=description,
|
||||
tags=tags,
|
||||
enabled=enabled,
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
async def run_stdio_async(self, show_banner: bool = True) -> None:
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ class Tool(FastMCPComponent):
|
|||
exclude_args: list[str] | None = None,
|
||||
output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
|
||||
serializer: Callable[[Any], str] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool | None = None,
|
||||
) -> FunctionTool:
|
||||
"""Create a Tool from a function."""
|
||||
|
|
@ -178,6 +179,7 @@ class Tool(FastMCPComponent):
|
|||
exclude_args=exclude_args,
|
||||
output_schema=output_schema,
|
||||
serializer=serializer,
|
||||
meta=meta,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
||||
|
|
@ -240,6 +242,7 @@ class FunctionTool(Tool):
|
|||
exclude_args: list[str] | None = None,
|
||||
output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
|
||||
serializer: Callable[[Any], str] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
enabled: bool | None = None,
|
||||
) -> FunctionTool:
|
||||
"""Create a Tool from a function."""
|
||||
|
|
@ -272,6 +275,7 @@ class FunctionTool(Tool):
|
|||
annotations=annotations,
|
||||
tags=tags or set(),
|
||||
serializer=serializer,
|
||||
meta=meta,
|
||||
enabled=enabled if enabled is not None else True,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -482,3 +482,18 @@ class TestPromptArgumentDescriptions:
|
|||
"Provide as a JSON string matching the following schema:"
|
||||
not in arg.description
|
||||
)
|
||||
|
||||
def test_prompt_meta_parameter(self):
|
||||
"""Test that meta parameter is properly handled."""
|
||||
|
||||
def test_prompt(message: str) -> str:
|
||||
return f"Response: {message}"
|
||||
|
||||
meta_data = {"version": "3.0", "type": "prompt"}
|
||||
prompt = Prompt.from_function(test_prompt, meta=meta_data)
|
||||
|
||||
assert prompt.meta == meta_data
|
||||
mcp_prompt = prompt.to_mcp_prompt()
|
||||
# MCP prompt includes fastmcp meta, so check that our meta is included
|
||||
assert mcp_prompt.meta is not None
|
||||
assert meta_data.items() <= mcp_prompt.meta.items()
|
||||
|
|
|
|||
25
tests/resources/test_resource_template_meta.py
Normal file
25
tests/resources/test_resource_template_meta.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from fastmcp.resources import ResourceTemplate
|
||||
|
||||
|
||||
class TestResourceTemplateMeta:
|
||||
"""Test ResourceTemplate meta functionality."""
|
||||
|
||||
def test_template_meta_parameter(self):
|
||||
"""Test that meta parameter is properly handled."""
|
||||
|
||||
def template_func(param: str) -> str:
|
||||
return f"Result: {param}"
|
||||
|
||||
meta_data = {"version": "2.0", "template": "test"}
|
||||
template = ResourceTemplate.from_function(
|
||||
fn=template_func,
|
||||
uri_template="test://{param}",
|
||||
name="test_template",
|
||||
meta=meta_data,
|
||||
)
|
||||
|
||||
assert template.meta == meta_data
|
||||
mcp_template = template.to_mcp_template()
|
||||
# MCP template includes fastmcp meta, so check that our meta is included
|
||||
assert mcp_template.meta is not None
|
||||
assert meta_data.items() <= mcp_template.meta.items()
|
||||
|
|
@ -93,3 +93,23 @@ class TestResourceValidation:
|
|||
|
||||
with pytest.raises(TypeError, match="abstract method"):
|
||||
ConcreteResource(uri=AnyUrl("test://test"), name="test") # type: ignore
|
||||
|
||||
def test_resource_meta_parameter(self):
|
||||
"""Test that meta parameter is properly handled."""
|
||||
|
||||
def resource_func() -> str:
|
||||
return "test content"
|
||||
|
||||
meta_data = {"version": "1.0", "category": "test"}
|
||||
resource = Resource.from_function(
|
||||
fn=resource_func,
|
||||
uri="resource://test",
|
||||
name="test_resource",
|
||||
meta=meta_data,
|
||||
)
|
||||
|
||||
assert resource.meta == meta_data
|
||||
mcp_resource = resource.to_mcp_resource()
|
||||
# MCP resource includes fastmcp meta, so check that our meta is included
|
||||
assert mcp_resource.meta is not None
|
||||
assert meta_data.items() <= mcp_resource.meta.items()
|
||||
|
|
|
|||
|
|
@ -421,6 +421,22 @@ class TestToolDecorator:
|
|||
def my_function(x: int) -> str:
|
||||
return f"Result: {x}"
|
||||
|
||||
async def test_tool_decorator_with_meta(self):
|
||||
"""Test that meta parameter is passed through the tool decorator."""
|
||||
mcp = FastMCP()
|
||||
|
||||
meta_data = {"version": "1.0", "author": "test"}
|
||||
|
||||
@mcp.tool(meta=meta_data)
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two numbers."""
|
||||
return a * b
|
||||
|
||||
tools_dict = await mcp.get_tools()
|
||||
tool = tools_dict["multiply"]
|
||||
|
||||
assert tool.meta == meta_data
|
||||
|
||||
|
||||
class TestResourceDecorator:
|
||||
async def test_no_resources_before_decorator(self):
|
||||
|
|
@ -584,6 +600,21 @@ class TestResourceDecorator:
|
|||
result = await client.read_resource("resource://data")
|
||||
assert result[0].text == "Static Hello, world!" # type: ignore[attr-defined]
|
||||
|
||||
async def test_resource_decorator_with_meta(self):
|
||||
"""Test that meta parameter is passed through the resource decorator."""
|
||||
mcp = FastMCP()
|
||||
|
||||
meta_data = {"version": "1.0", "author": "test"}
|
||||
|
||||
@mcp.resource("resource://data", meta=meta_data)
|
||||
def get_data() -> str:
|
||||
return "Hello, world!"
|
||||
|
||||
resources_dict = await mcp.get_resources()
|
||||
resource = resources_dict["resource://data"]
|
||||
|
||||
assert resource.meta == meta_data
|
||||
|
||||
|
||||
class TestTemplateDecorator:
|
||||
async def test_template_decorator(self):
|
||||
|
|
@ -733,6 +764,21 @@ class TestTemplateDecorator:
|
|||
assert template.uri_template == "resource://{param*}"
|
||||
assert template.name == "template_resource"
|
||||
|
||||
async def test_template_decorator_with_meta(self):
|
||||
"""Test that meta parameter is passed through the template decorator."""
|
||||
mcp = FastMCP()
|
||||
|
||||
meta_data = {"version": "2.0", "template": "test"}
|
||||
|
||||
@mcp.resource("resource://{param}/data", meta=meta_data)
|
||||
def get_template_data(param: str) -> str:
|
||||
return f"Data for {param}"
|
||||
|
||||
templates_dict = await mcp.get_resource_templates()
|
||||
template = templates_dict["resource://{param}/data"]
|
||||
|
||||
assert template.meta == meta_data
|
||||
|
||||
|
||||
class TestPromptDecorator:
|
||||
async def test_prompt_decorator(self):
|
||||
|
|
@ -988,6 +1034,21 @@ class TestPromptDecorator:
|
|||
message = result.messages[0]
|
||||
assert message.content.text == "Static Hello, world!" # type: ignore[attr-defined]
|
||||
|
||||
async def test_prompt_decorator_with_meta(self):
|
||||
"""Test that meta parameter is passed through the prompt decorator."""
|
||||
mcp = FastMCP()
|
||||
|
||||
meta_data = {"version": "3.0", "type": "prompt"}
|
||||
|
||||
@mcp.prompt(meta=meta_data)
|
||||
def test_prompt(message: str) -> str:
|
||||
return f"Response: {message}"
|
||||
|
||||
prompts_dict = await mcp.get_prompts()
|
||||
prompt = prompts_dict["test_prompt"]
|
||||
|
||||
assert prompt.meta == meta_data
|
||||
|
||||
|
||||
class TestResourcePrefixHelpers:
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
|||
|
|
@ -44,6 +44,22 @@ class TestToolFromFunction:
|
|||
}
|
||||
assert tool.output_schema == expected_schema
|
||||
|
||||
def test_meta_parameter(self):
|
||||
"""Test that meta parameter is properly handled."""
|
||||
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two numbers."""
|
||||
return a * b
|
||||
|
||||
meta_data = {"version": "1.0", "author": "test"}
|
||||
tool = Tool.from_function(multiply, meta=meta_data)
|
||||
|
||||
assert tool.meta == meta_data
|
||||
mcp_tool = tool.to_mcp_tool()
|
||||
# MCP tool includes fastmcp meta, so check that our meta is included
|
||||
assert mcp_tool.meta is not None
|
||||
assert meta_data.items() <= mcp_tool.meta.items()
|
||||
|
||||
async def test_async_function(self):
|
||||
"""Test registering and running an async function."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue