Spaces:
Running
Running
Jeremiah Lowin commited on
Add meta parameter support to tools, resources, templates, and prompts decorators (#1294)
Browse files- docs/servers/prompts.mdx +8 -1
- docs/servers/resources.mdx +8 -1
- docs/servers/tools.mdx +7 -0
- src/fastmcp/prompts/prompt.py +4 -0
- src/fastmcp/resources/resource.py +4 -0
- src/fastmcp/resources/template.py +4 -0
- src/fastmcp/server/server.py +16 -0
- src/fastmcp/tools/tool.py +4 -0
- tests/prompts/test_prompt.py +15 -0
- tests/resources/test_resource_template_meta.py +25 -0
- tests/resources/test_resources.py +20 -0
- tests/server/test_server.py +61 -0
- tests/tools/test_tool.py +16 -0
docs/servers/prompts.mdx
CHANGED
|
@@ -65,7 +65,8 @@ While FastMCP infers the name and description from your function, you can overri
|
|
| 65 |
@mcp.prompt(
|
| 66 |
name="analyze_data_request", # Custom prompt name
|
| 67 |
description="Creates a request to analyze data with specific parameters", # Custom description
|
| 68 |
-
tags={"analysis", "data"}
|
|
|
|
| 69 |
)
|
| 70 |
def data_analysis_prompt(
|
| 71 |
data_uri: str = Field(description="The URI of the resource containing the data."),
|
|
@@ -91,6 +92,12 @@ def data_analysis_prompt(
|
|
| 91 |
<ParamField body="enabled" type="bool" default="True">
|
| 92 |
A boolean to enable or disable the prompt. See [Disabling Prompts](#disabling-prompts) for more information
|
| 93 |
</ParamField>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
</Card>
|
| 95 |
|
| 96 |
### Argument Types
|
|
|
|
| 65 |
@mcp.prompt(
|
| 66 |
name="analyze_data_request", # Custom prompt name
|
| 67 |
description="Creates a request to analyze data with specific parameters", # Custom description
|
| 68 |
+
tags={"analysis", "data"}, # Optional categorization tags
|
| 69 |
+
meta={"version": "1.1", "author": "data-team"} # Custom metadata
|
| 70 |
)
|
| 71 |
def data_analysis_prompt(
|
| 72 |
data_uri: str = Field(description="The URI of the resource containing the data."),
|
|
|
|
| 92 |
<ParamField body="enabled" type="bool" default="True">
|
| 93 |
A boolean to enable or disable the prompt. See [Disabling Prompts](#disabling-prompts) for more information
|
| 94 |
</ParamField>
|
| 95 |
+
|
| 96 |
+
<ParamField body="meta" type="dict[str, Any] | None">
|
| 97 |
+
<VersionBadge version="2.11.0" />
|
| 98 |
+
|
| 99 |
+
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.
|
| 100 |
+
</ParamField>
|
| 101 |
</Card>
|
| 102 |
|
| 103 |
### Argument Types
|
docs/servers/resources.mdx
CHANGED
|
@@ -73,7 +73,8 @@ mcp = FastMCP(name="DataServer")
|
|
| 73 |
name="ApplicationStatus", # Custom name
|
| 74 |
description="Provides the current status of the application.", # Custom description
|
| 75 |
mime_type="application/json", # Explicit MIME type
|
| 76 |
-
tags={"monitoring", "status"} # Categorization tags
|
|
|
|
| 77 |
)
|
| 78 |
def get_application_status() -> dict:
|
| 79 |
"""Internal function description (ignored if description is provided above)."""
|
|
@@ -116,6 +117,12 @@ def get_application_status() -> dict:
|
|
| 116 |
</ParamField>
|
| 117 |
</Expandable>
|
| 118 |
</ParamField>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
</Card>
|
| 120 |
|
| 121 |
### Return Values
|
|
|
|
| 73 |
name="ApplicationStatus", # Custom name
|
| 74 |
description="Provides the current status of the application.", # Custom description
|
| 75 |
mime_type="application/json", # Explicit MIME type
|
| 76 |
+
tags={"monitoring", "status"}, # Categorization tags
|
| 77 |
+
meta={"version": "2.1", "team": "infrastructure"} # Custom metadata
|
| 78 |
)
|
| 79 |
def get_application_status() -> dict:
|
| 80 |
"""Internal function description (ignored if description is provided above)."""
|
|
|
|
| 117 |
</ParamField>
|
| 118 |
</Expandable>
|
| 119 |
</ParamField>
|
| 120 |
+
|
| 121 |
+
<ParamField body="meta" type="dict[str, Any] | None">
|
| 122 |
+
<VersionBadge version="2.11.0" />
|
| 123 |
+
|
| 124 |
+
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.
|
| 125 |
+
</ParamField>
|
| 126 |
</Card>
|
| 127 |
|
| 128 |
### Return Values
|
docs/servers/tools.mdx
CHANGED
|
@@ -58,6 +58,7 @@ While FastMCP infers the name and description from your function, you can overri
|
|
| 58 |
name="find_products", # Custom tool name for the LLM
|
| 59 |
description="Search the product catalog with optional category filtering.", # Custom description
|
| 60 |
tags={"catalog", "search"}, # Optional tags for organization/filtering
|
|
|
|
| 61 |
)
|
| 62 |
def search_products_implementation(query: str, category: str | None = None) -> list[dict]:
|
| 63 |
"""Internal function description (ignored if description is provided above)."""
|
|
@@ -107,6 +108,12 @@ def search_products_implementation(query: str, category: str | None = None) -> l
|
|
| 107 |
</ParamField>
|
| 108 |
</Expandable>
|
| 109 |
</ParamField>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
</Card>
|
| 111 |
|
| 112 |
|
|
|
|
| 58 |
name="find_products", # Custom tool name for the LLM
|
| 59 |
description="Search the product catalog with optional category filtering.", # Custom description
|
| 60 |
tags={"catalog", "search"}, # Optional tags for organization/filtering
|
| 61 |
+
meta={"version": "1.2", "author": "product-team"} # Custom metadata
|
| 62 |
)
|
| 63 |
def search_products_implementation(query: str, category: str | None = None) -> list[dict]:
|
| 64 |
"""Internal function description (ignored if description is provided above)."""
|
|
|
|
| 108 |
</ParamField>
|
| 109 |
</Expandable>
|
| 110 |
</ParamField>
|
| 111 |
+
|
| 112 |
+
<ParamField body="meta" type="dict[str, Any] | None">
|
| 113 |
+
<VersionBadge version="2.11.0" />
|
| 114 |
+
|
| 115 |
+
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.
|
| 116 |
+
</ParamField>
|
| 117 |
</Card>
|
| 118 |
|
| 119 |
|
src/fastmcp/prompts/prompt.py
CHANGED
|
@@ -117,6 +117,7 @@ class Prompt(FastMCPComponent, ABC):
|
|
| 117 |
description: str | None = None,
|
| 118 |
tags: set[str] | None = None,
|
| 119 |
enabled: bool | None = None,
|
|
|
|
| 120 |
) -> FunctionPrompt:
|
| 121 |
"""Create a Prompt from a function.
|
| 122 |
|
|
@@ -133,6 +134,7 @@ class Prompt(FastMCPComponent, ABC):
|
|
| 133 |
description=description,
|
| 134 |
tags=tags,
|
| 135 |
enabled=enabled,
|
|
|
|
| 136 |
)
|
| 137 |
|
| 138 |
@abstractmethod
|
|
@@ -158,6 +160,7 @@ class FunctionPrompt(Prompt):
|
|
| 158 |
description: str | None = None,
|
| 159 |
tags: set[str] | None = None,
|
| 160 |
enabled: bool | None = None,
|
|
|
|
| 161 |
) -> FunctionPrompt:
|
| 162 |
"""Create a Prompt from a function.
|
| 163 |
|
|
@@ -252,6 +255,7 @@ class FunctionPrompt(Prompt):
|
|
| 252 |
tags=tags or set(),
|
| 253 |
enabled=enabled if enabled is not None else True,
|
| 254 |
fn=fn,
|
|
|
|
| 255 |
)
|
| 256 |
|
| 257 |
def _convert_string_arguments(self, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
|
|
| 117 |
description: str | None = None,
|
| 118 |
tags: set[str] | None = None,
|
| 119 |
enabled: bool | None = None,
|
| 120 |
+
meta: dict[str, Any] | None = None,
|
| 121 |
) -> FunctionPrompt:
|
| 122 |
"""Create a Prompt from a function.
|
| 123 |
|
|
|
|
| 134 |
description=description,
|
| 135 |
tags=tags,
|
| 136 |
enabled=enabled,
|
| 137 |
+
meta=meta,
|
| 138 |
)
|
| 139 |
|
| 140 |
@abstractmethod
|
|
|
|
| 160 |
description: str | None = None,
|
| 161 |
tags: set[str] | None = None,
|
| 162 |
enabled: bool | None = None,
|
| 163 |
+
meta: dict[str, Any] | None = None,
|
| 164 |
) -> FunctionPrompt:
|
| 165 |
"""Create a Prompt from a function.
|
| 166 |
|
|
|
|
| 255 |
tags=tags or set(),
|
| 256 |
enabled=enabled if enabled is not None else True,
|
| 257 |
fn=fn,
|
| 258 |
+
meta=meta,
|
| 259 |
)
|
| 260 |
|
| 261 |
def _convert_string_arguments(self, kwargs: dict[str, Any]) -> dict[str, Any]:
|
src/fastmcp/resources/resource.py
CHANGED
|
@@ -76,6 +76,7 @@ class Resource(FastMCPComponent, abc.ABC):
|
|
| 76 |
tags: set[str] | None = None,
|
| 77 |
enabled: bool | None = None,
|
| 78 |
annotations: Annotations | None = None,
|
|
|
|
| 79 |
) -> FunctionResource:
|
| 80 |
return FunctionResource.from_function(
|
| 81 |
fn=fn,
|
|
@@ -87,6 +88,7 @@ class Resource(FastMCPComponent, abc.ABC):
|
|
| 87 |
tags=tags,
|
| 88 |
enabled=enabled,
|
| 89 |
annotations=annotations,
|
|
|
|
| 90 |
)
|
| 91 |
|
| 92 |
@field_validator("mime_type", mode="before")
|
|
@@ -172,6 +174,7 @@ class FunctionResource(Resource):
|
|
| 172 |
tags: set[str] | None = None,
|
| 173 |
enabled: bool | None = None,
|
| 174 |
annotations: Annotations | None = None,
|
|
|
|
| 175 |
) -> FunctionResource:
|
| 176 |
"""Create a FunctionResource from a function."""
|
| 177 |
if isinstance(uri, str):
|
|
@@ -186,6 +189,7 @@ class FunctionResource(Resource):
|
|
| 186 |
tags=tags or set(),
|
| 187 |
enabled=enabled if enabled is not None else True,
|
| 188 |
annotations=annotations,
|
|
|
|
| 189 |
)
|
| 190 |
|
| 191 |
async def read(self) -> str | bytes:
|
|
|
|
| 76 |
tags: set[str] | None = None,
|
| 77 |
enabled: bool | None = None,
|
| 78 |
annotations: Annotations | None = None,
|
| 79 |
+
meta: dict[str, Any] | None = None,
|
| 80 |
) -> FunctionResource:
|
| 81 |
return FunctionResource.from_function(
|
| 82 |
fn=fn,
|
|
|
|
| 88 |
tags=tags,
|
| 89 |
enabled=enabled,
|
| 90 |
annotations=annotations,
|
| 91 |
+
meta=meta,
|
| 92 |
)
|
| 93 |
|
| 94 |
@field_validator("mime_type", mode="before")
|
|
|
|
| 174 |
tags: set[str] | None = None,
|
| 175 |
enabled: bool | None = None,
|
| 176 |
annotations: Annotations | None = None,
|
| 177 |
+
meta: dict[str, Any] | None = None,
|
| 178 |
) -> FunctionResource:
|
| 179 |
"""Create a FunctionResource from a function."""
|
| 180 |
if isinstance(uri, str):
|
|
|
|
| 189 |
tags=tags or set(),
|
| 190 |
enabled=enabled if enabled is not None else True,
|
| 191 |
annotations=annotations,
|
| 192 |
+
meta=meta,
|
| 193 |
)
|
| 194 |
|
| 195 |
async def read(self) -> str | bytes:
|
src/fastmcp/resources/template.py
CHANGED
|
@@ -96,6 +96,7 @@ class ResourceTemplate(FastMCPComponent):
|
|
| 96 |
tags: set[str] | None = None,
|
| 97 |
enabled: bool | None = None,
|
| 98 |
annotations: Annotations | None = None,
|
|
|
|
| 99 |
) -> FunctionResourceTemplate:
|
| 100 |
return FunctionResourceTemplate.from_function(
|
| 101 |
fn=fn,
|
|
@@ -107,6 +108,7 @@ class ResourceTemplate(FastMCPComponent):
|
|
| 107 |
tags=tags,
|
| 108 |
enabled=enabled,
|
| 109 |
annotations=annotations,
|
|
|
|
| 110 |
)
|
| 111 |
|
| 112 |
@field_validator("mime_type", mode="before")
|
|
@@ -219,6 +221,7 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|
| 219 |
tags: set[str] | None = None,
|
| 220 |
enabled: bool | None = None,
|
| 221 |
annotations: Annotations | None = None,
|
|
|
|
| 222 |
) -> FunctionResourceTemplate:
|
| 223 |
"""Create a template from a function."""
|
| 224 |
from fastmcp.server.context import Context
|
|
@@ -304,4 +307,5 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|
| 304 |
tags=tags or set(),
|
| 305 |
enabled=enabled if enabled is not None else True,
|
| 306 |
annotations=annotations,
|
|
|
|
| 307 |
)
|
|
|
|
| 96 |
tags: set[str] | None = None,
|
| 97 |
enabled: bool | None = None,
|
| 98 |
annotations: Annotations | None = None,
|
| 99 |
+
meta: dict[str, Any] | None = None,
|
| 100 |
) -> FunctionResourceTemplate:
|
| 101 |
return FunctionResourceTemplate.from_function(
|
| 102 |
fn=fn,
|
|
|
|
| 108 |
tags=tags,
|
| 109 |
enabled=enabled,
|
| 110 |
annotations=annotations,
|
| 111 |
+
meta=meta,
|
| 112 |
)
|
| 113 |
|
| 114 |
@field_validator("mime_type", mode="before")
|
|
|
|
| 221 |
tags: set[str] | None = None,
|
| 222 |
enabled: bool | None = None,
|
| 223 |
annotations: Annotations | None = None,
|
| 224 |
+
meta: dict[str, Any] | None = None,
|
| 225 |
) -> FunctionResourceTemplate:
|
| 226 |
"""Create a template from a function."""
|
| 227 |
from fastmcp.server.context import Context
|
|
|
|
| 307 |
tags=tags or set(),
|
| 308 |
enabled=enabled if enabled is not None else True,
|
| 309 |
annotations=annotations,
|
| 310 |
+
meta=meta,
|
| 311 |
)
|
src/fastmcp/server/server.py
CHANGED
|
@@ -871,6 +871,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 871 |
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
| 872 |
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
| 873 |
exclude_args: list[str] | None = None,
|
|
|
|
| 874 |
enabled: bool | None = None,
|
| 875 |
) -> FunctionTool: ...
|
| 876 |
|
|
@@ -886,6 +887,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 886 |
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
| 887 |
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
| 888 |
exclude_args: list[str] | None = None,
|
|
|
|
| 889 |
enabled: bool | None = None,
|
| 890 |
) -> Callable[[AnyFunction], FunctionTool]: ...
|
| 891 |
|
|
@@ -900,6 +902,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 900 |
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
| 901 |
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
| 902 |
exclude_args: list[str] | None = None,
|
|
|
|
| 903 |
enabled: bool | None = None,
|
| 904 |
) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
|
| 905 |
"""Decorator to register a tool.
|
|
@@ -923,6 +926,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 923 |
output_schema: Optional JSON schema for the tool's output
|
| 924 |
annotations: Optional annotations about the tool's behavior
|
| 925 |
exclude_args: Optional list of argument names to exclude from the tool schema
|
|
|
|
| 926 |
enabled: Optional boolean to enable or disable the tool
|
| 927 |
|
| 928 |
Examples:
|
|
@@ -981,6 +985,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 981 |
output_schema=output_schema,
|
| 982 |
annotations=annotations,
|
| 983 |
exclude_args=exclude_args,
|
|
|
|
| 984 |
serializer=self._tool_serializer,
|
| 985 |
enabled=enabled,
|
| 986 |
)
|
|
@@ -1013,6 +1018,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1013 |
output_schema=output_schema,
|
| 1014 |
annotations=annotations,
|
| 1015 |
exclude_args=exclude_args,
|
|
|
|
| 1016 |
enabled=enabled,
|
| 1017 |
)
|
| 1018 |
|
|
@@ -1111,6 +1117,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1111 |
tags: set[str] | None = None,
|
| 1112 |
enabled: bool | None = None,
|
| 1113 |
annotations: Annotations | dict[str, Any] | None = None,
|
|
|
|
| 1114 |
) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
|
| 1115 |
"""Decorator to register a function as a resource.
|
| 1116 |
|
|
@@ -1135,6 +1142,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1135 |
tags: Optional set of tags for categorizing the resource
|
| 1136 |
enabled: Optional boolean to enable or disable the resource
|
| 1137 |
annotations: Optional annotations about the resource's behavior
|
|
|
|
| 1138 |
|
| 1139 |
Examples:
|
| 1140 |
Register a resource with a custom name:
|
|
@@ -1208,6 +1216,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1208 |
tags=tags,
|
| 1209 |
enabled=enabled,
|
| 1210 |
annotations=annotations,
|
|
|
|
| 1211 |
)
|
| 1212 |
self.add_template(template)
|
| 1213 |
return template
|
|
@@ -1222,6 +1231,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1222 |
tags=tags,
|
| 1223 |
enabled=enabled,
|
| 1224 |
annotations=annotations,
|
|
|
|
| 1225 |
)
|
| 1226 |
self.add_resource(resource)
|
| 1227 |
return resource
|
|
@@ -1266,6 +1276,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1266 |
description: str | None = None,
|
| 1267 |
tags: set[str] | None = None,
|
| 1268 |
enabled: bool | None = None,
|
|
|
|
| 1269 |
) -> FunctionPrompt: ...
|
| 1270 |
|
| 1271 |
@overload
|
|
@@ -1278,6 +1289,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1278 |
description: str | None = None,
|
| 1279 |
tags: set[str] | None = None,
|
| 1280 |
enabled: bool | None = None,
|
|
|
|
| 1281 |
) -> Callable[[AnyFunction], FunctionPrompt]: ...
|
| 1282 |
|
| 1283 |
def prompt(
|
|
@@ -1289,6 +1301,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1289 |
description: str | None = None,
|
| 1290 |
tags: set[str] | None = None,
|
| 1291 |
enabled: bool | None = None,
|
|
|
|
| 1292 |
) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt:
|
| 1293 |
"""Decorator to register a prompt.
|
| 1294 |
|
|
@@ -1309,6 +1322,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1309 |
description: Optional description of what the prompt does
|
| 1310 |
tags: Optional set of tags for categorizing the prompt
|
| 1311 |
enabled: Optional boolean to enable or disable the prompt
|
|
|
|
| 1312 |
|
| 1313 |
Examples:
|
| 1314 |
|
|
@@ -1386,6 +1400,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1386 |
description=description,
|
| 1387 |
tags=tags,
|
| 1388 |
enabled=enabled,
|
|
|
|
| 1389 |
)
|
| 1390 |
self.add_prompt(prompt)
|
| 1391 |
|
|
@@ -1415,6 +1430,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1415 |
description=description,
|
| 1416 |
tags=tags,
|
| 1417 |
enabled=enabled,
|
|
|
|
| 1418 |
)
|
| 1419 |
|
| 1420 |
async def run_stdio_async(self, show_banner: bool = True) -> None:
|
|
|
|
| 871 |
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
| 872 |
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
| 873 |
exclude_args: list[str] | None = None,
|
| 874 |
+
meta: dict[str, Any] | None = None,
|
| 875 |
enabled: bool | None = None,
|
| 876 |
) -> FunctionTool: ...
|
| 877 |
|
|
|
|
| 887 |
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
| 888 |
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
| 889 |
exclude_args: list[str] | None = None,
|
| 890 |
+
meta: dict[str, Any] | None = None,
|
| 891 |
enabled: bool | None = None,
|
| 892 |
) -> Callable[[AnyFunction], FunctionTool]: ...
|
| 893 |
|
|
|
|
| 902 |
output_schema: dict[str, Any] | None | NotSetT = NotSet,
|
| 903 |
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
| 904 |
exclude_args: list[str] | None = None,
|
| 905 |
+
meta: dict[str, Any] | None = None,
|
| 906 |
enabled: bool | None = None,
|
| 907 |
) -> Callable[[AnyFunction], FunctionTool] | FunctionTool:
|
| 908 |
"""Decorator to register a tool.
|
|
|
|
| 926 |
output_schema: Optional JSON schema for the tool's output
|
| 927 |
annotations: Optional annotations about the tool's behavior
|
| 928 |
exclude_args: Optional list of argument names to exclude from the tool schema
|
| 929 |
+
meta: Optional meta information about the tool
|
| 930 |
enabled: Optional boolean to enable or disable the tool
|
| 931 |
|
| 932 |
Examples:
|
|
|
|
| 985 |
output_schema=output_schema,
|
| 986 |
annotations=annotations,
|
| 987 |
exclude_args=exclude_args,
|
| 988 |
+
meta=meta,
|
| 989 |
serializer=self._tool_serializer,
|
| 990 |
enabled=enabled,
|
| 991 |
)
|
|
|
|
| 1018 |
output_schema=output_schema,
|
| 1019 |
annotations=annotations,
|
| 1020 |
exclude_args=exclude_args,
|
| 1021 |
+
meta=meta,
|
| 1022 |
enabled=enabled,
|
| 1023 |
)
|
| 1024 |
|
|
|
|
| 1117 |
tags: set[str] | None = None,
|
| 1118 |
enabled: bool | None = None,
|
| 1119 |
annotations: Annotations | dict[str, Any] | None = None,
|
| 1120 |
+
meta: dict[str, Any] | None = None,
|
| 1121 |
) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
|
| 1122 |
"""Decorator to register a function as a resource.
|
| 1123 |
|
|
|
|
| 1142 |
tags: Optional set of tags for categorizing the resource
|
| 1143 |
enabled: Optional boolean to enable or disable the resource
|
| 1144 |
annotations: Optional annotations about the resource's behavior
|
| 1145 |
+
meta: Optional meta information about the resource
|
| 1146 |
|
| 1147 |
Examples:
|
| 1148 |
Register a resource with a custom name:
|
|
|
|
| 1216 |
tags=tags,
|
| 1217 |
enabled=enabled,
|
| 1218 |
annotations=annotations,
|
| 1219 |
+
meta=meta,
|
| 1220 |
)
|
| 1221 |
self.add_template(template)
|
| 1222 |
return template
|
|
|
|
| 1231 |
tags=tags,
|
| 1232 |
enabled=enabled,
|
| 1233 |
annotations=annotations,
|
| 1234 |
+
meta=meta,
|
| 1235 |
)
|
| 1236 |
self.add_resource(resource)
|
| 1237 |
return resource
|
|
|
|
| 1276 |
description: str | None = None,
|
| 1277 |
tags: set[str] | None = None,
|
| 1278 |
enabled: bool | None = None,
|
| 1279 |
+
meta: dict[str, Any] | None = None,
|
| 1280 |
) -> FunctionPrompt: ...
|
| 1281 |
|
| 1282 |
@overload
|
|
|
|
| 1289 |
description: str | None = None,
|
| 1290 |
tags: set[str] | None = None,
|
| 1291 |
enabled: bool | None = None,
|
| 1292 |
+
meta: dict[str, Any] | None = None,
|
| 1293 |
) -> Callable[[AnyFunction], FunctionPrompt]: ...
|
| 1294 |
|
| 1295 |
def prompt(
|
|
|
|
| 1301 |
description: str | None = None,
|
| 1302 |
tags: set[str] | None = None,
|
| 1303 |
enabled: bool | None = None,
|
| 1304 |
+
meta: dict[str, Any] | None = None,
|
| 1305 |
) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt:
|
| 1306 |
"""Decorator to register a prompt.
|
| 1307 |
|
|
|
|
| 1322 |
description: Optional description of what the prompt does
|
| 1323 |
tags: Optional set of tags for categorizing the prompt
|
| 1324 |
enabled: Optional boolean to enable or disable the prompt
|
| 1325 |
+
meta: Optional meta information about the prompt
|
| 1326 |
|
| 1327 |
Examples:
|
| 1328 |
|
|
|
|
| 1400 |
description=description,
|
| 1401 |
tags=tags,
|
| 1402 |
enabled=enabled,
|
| 1403 |
+
meta=meta,
|
| 1404 |
)
|
| 1405 |
self.add_prompt(prompt)
|
| 1406 |
|
|
|
|
| 1430 |
description=description,
|
| 1431 |
tags=tags,
|
| 1432 |
enabled=enabled,
|
| 1433 |
+
meta=meta,
|
| 1434 |
)
|
| 1435 |
|
| 1436 |
async def run_stdio_async(self, show_banner: bool = True) -> None:
|
src/fastmcp/tools/tool.py
CHANGED
|
@@ -165,6 +165,7 @@ class Tool(FastMCPComponent):
|
|
| 165 |
exclude_args: list[str] | None = None,
|
| 166 |
output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
|
| 167 |
serializer: Callable[[Any], str] | None = None,
|
|
|
|
| 168 |
enabled: bool | None = None,
|
| 169 |
) -> FunctionTool:
|
| 170 |
"""Create a Tool from a function."""
|
|
@@ -178,6 +179,7 @@ class Tool(FastMCPComponent):
|
|
| 178 |
exclude_args=exclude_args,
|
| 179 |
output_schema=output_schema,
|
| 180 |
serializer=serializer,
|
|
|
|
| 181 |
enabled=enabled,
|
| 182 |
)
|
| 183 |
|
|
@@ -240,6 +242,7 @@ class FunctionTool(Tool):
|
|
| 240 |
exclude_args: list[str] | None = None,
|
| 241 |
output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
|
| 242 |
serializer: Callable[[Any], str] | None = None,
|
|
|
|
| 243 |
enabled: bool | None = None,
|
| 244 |
) -> FunctionTool:
|
| 245 |
"""Create a Tool from a function."""
|
|
@@ -272,6 +275,7 @@ class FunctionTool(Tool):
|
|
| 272 |
annotations=annotations,
|
| 273 |
tags=tags or set(),
|
| 274 |
serializer=serializer,
|
|
|
|
| 275 |
enabled=enabled if enabled is not None else True,
|
| 276 |
)
|
| 277 |
|
|
|
|
| 165 |
exclude_args: list[str] | None = None,
|
| 166 |
output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
|
| 167 |
serializer: Callable[[Any], str] | None = None,
|
| 168 |
+
meta: dict[str, Any] | None = None,
|
| 169 |
enabled: bool | None = None,
|
| 170 |
) -> FunctionTool:
|
| 171 |
"""Create a Tool from a function."""
|
|
|
|
| 179 |
exclude_args=exclude_args,
|
| 180 |
output_schema=output_schema,
|
| 181 |
serializer=serializer,
|
| 182 |
+
meta=meta,
|
| 183 |
enabled=enabled,
|
| 184 |
)
|
| 185 |
|
|
|
|
| 242 |
exclude_args: list[str] | None = None,
|
| 243 |
output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet,
|
| 244 |
serializer: Callable[[Any], str] | None = None,
|
| 245 |
+
meta: dict[str, Any] | None = None,
|
| 246 |
enabled: bool | None = None,
|
| 247 |
) -> FunctionTool:
|
| 248 |
"""Create a Tool from a function."""
|
|
|
|
| 275 |
annotations=annotations,
|
| 276 |
tags=tags or set(),
|
| 277 |
serializer=serializer,
|
| 278 |
+
meta=meta,
|
| 279 |
enabled=enabled if enabled is not None else True,
|
| 280 |
)
|
| 281 |
|
tests/prompts/test_prompt.py
CHANGED
|
@@ -482,3 +482,18 @@ class TestPromptArgumentDescriptions:
|
|
| 482 |
"Provide as a JSON string matching the following schema:"
|
| 483 |
not in arg.description
|
| 484 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
"Provide as a JSON string matching the following schema:"
|
| 483 |
not in arg.description
|
| 484 |
)
|
| 485 |
+
|
| 486 |
+
def test_prompt_meta_parameter(self):
|
| 487 |
+
"""Test that meta parameter is properly handled."""
|
| 488 |
+
|
| 489 |
+
def test_prompt(message: str) -> str:
|
| 490 |
+
return f"Response: {message}"
|
| 491 |
+
|
| 492 |
+
meta_data = {"version": "3.0", "type": "prompt"}
|
| 493 |
+
prompt = Prompt.from_function(test_prompt, meta=meta_data)
|
| 494 |
+
|
| 495 |
+
assert prompt.meta == meta_data
|
| 496 |
+
mcp_prompt = prompt.to_mcp_prompt()
|
| 497 |
+
# MCP prompt includes fastmcp meta, so check that our meta is included
|
| 498 |
+
assert mcp_prompt.meta is not None
|
| 499 |
+
assert meta_data.items() <= mcp_prompt.meta.items()
|
tests/resources/test_resource_template_meta.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastmcp.resources import ResourceTemplate
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class TestResourceTemplateMeta:
|
| 5 |
+
"""Test ResourceTemplate meta functionality."""
|
| 6 |
+
|
| 7 |
+
def test_template_meta_parameter(self):
|
| 8 |
+
"""Test that meta parameter is properly handled."""
|
| 9 |
+
|
| 10 |
+
def template_func(param: str) -> str:
|
| 11 |
+
return f"Result: {param}"
|
| 12 |
+
|
| 13 |
+
meta_data = {"version": "2.0", "template": "test"}
|
| 14 |
+
template = ResourceTemplate.from_function(
|
| 15 |
+
fn=template_func,
|
| 16 |
+
uri_template="test://{param}",
|
| 17 |
+
name="test_template",
|
| 18 |
+
meta=meta_data,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
assert template.meta == meta_data
|
| 22 |
+
mcp_template = template.to_mcp_template()
|
| 23 |
+
# MCP template includes fastmcp meta, so check that our meta is included
|
| 24 |
+
assert mcp_template.meta is not None
|
| 25 |
+
assert meta_data.items() <= mcp_template.meta.items()
|
tests/resources/test_resources.py
CHANGED
|
@@ -93,3 +93,23 @@ class TestResourceValidation:
|
|
| 93 |
|
| 94 |
with pytest.raises(TypeError, match="abstract method"):
|
| 95 |
ConcreteResource(uri=AnyUrl("test://test"), name="test") # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
with pytest.raises(TypeError, match="abstract method"):
|
| 95 |
ConcreteResource(uri=AnyUrl("test://test"), name="test") # type: ignore
|
| 96 |
+
|
| 97 |
+
def test_resource_meta_parameter(self):
|
| 98 |
+
"""Test that meta parameter is properly handled."""
|
| 99 |
+
|
| 100 |
+
def resource_func() -> str:
|
| 101 |
+
return "test content"
|
| 102 |
+
|
| 103 |
+
meta_data = {"version": "1.0", "category": "test"}
|
| 104 |
+
resource = Resource.from_function(
|
| 105 |
+
fn=resource_func,
|
| 106 |
+
uri="resource://test",
|
| 107 |
+
name="test_resource",
|
| 108 |
+
meta=meta_data,
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
assert resource.meta == meta_data
|
| 112 |
+
mcp_resource = resource.to_mcp_resource()
|
| 113 |
+
# MCP resource includes fastmcp meta, so check that our meta is included
|
| 114 |
+
assert mcp_resource.meta is not None
|
| 115 |
+
assert meta_data.items() <= mcp_resource.meta.items()
|
tests/server/test_server.py
CHANGED
|
@@ -421,6 +421,22 @@ class TestToolDecorator:
|
|
| 421 |
def my_function(x: int) -> str:
|
| 422 |
return f"Result: {x}"
|
| 423 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
|
| 425 |
class TestResourceDecorator:
|
| 426 |
async def test_no_resources_before_decorator(self):
|
|
@@ -584,6 +600,21 @@ class TestResourceDecorator:
|
|
| 584 |
result = await client.read_resource("resource://data")
|
| 585 |
assert result[0].text == "Static Hello, world!" # type: ignore[attr-defined]
|
| 586 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 587 |
|
| 588 |
class TestTemplateDecorator:
|
| 589 |
async def test_template_decorator(self):
|
|
@@ -733,6 +764,21 @@ class TestTemplateDecorator:
|
|
| 733 |
assert template.uri_template == "resource://{param*}"
|
| 734 |
assert template.name == "template_resource"
|
| 735 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 736 |
|
| 737 |
class TestPromptDecorator:
|
| 738 |
async def test_prompt_decorator(self):
|
|
@@ -988,6 +1034,21 @@ class TestPromptDecorator:
|
|
| 988 |
message = result.messages[0]
|
| 989 |
assert message.content.text == "Static Hello, world!" # type: ignore[attr-defined]
|
| 990 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 991 |
|
| 992 |
class TestResourcePrefixHelpers:
|
| 993 |
@pytest.mark.parametrize(
|
|
|
|
| 421 |
def my_function(x: int) -> str:
|
| 422 |
return f"Result: {x}"
|
| 423 |
|
| 424 |
+
async def test_tool_decorator_with_meta(self):
|
| 425 |
+
"""Test that meta parameter is passed through the tool decorator."""
|
| 426 |
+
mcp = FastMCP()
|
| 427 |
+
|
| 428 |
+
meta_data = {"version": "1.0", "author": "test"}
|
| 429 |
+
|
| 430 |
+
@mcp.tool(meta=meta_data)
|
| 431 |
+
def multiply(a: int, b: int) -> int:
|
| 432 |
+
"""Multiply two numbers."""
|
| 433 |
+
return a * b
|
| 434 |
+
|
| 435 |
+
tools_dict = await mcp.get_tools()
|
| 436 |
+
tool = tools_dict["multiply"]
|
| 437 |
+
|
| 438 |
+
assert tool.meta == meta_data
|
| 439 |
+
|
| 440 |
|
| 441 |
class TestResourceDecorator:
|
| 442 |
async def test_no_resources_before_decorator(self):
|
|
|
|
| 600 |
result = await client.read_resource("resource://data")
|
| 601 |
assert result[0].text == "Static Hello, world!" # type: ignore[attr-defined]
|
| 602 |
|
| 603 |
+
async def test_resource_decorator_with_meta(self):
|
| 604 |
+
"""Test that meta parameter is passed through the resource decorator."""
|
| 605 |
+
mcp = FastMCP()
|
| 606 |
+
|
| 607 |
+
meta_data = {"version": "1.0", "author": "test"}
|
| 608 |
+
|
| 609 |
+
@mcp.resource("resource://data", meta=meta_data)
|
| 610 |
+
def get_data() -> str:
|
| 611 |
+
return "Hello, world!"
|
| 612 |
+
|
| 613 |
+
resources_dict = await mcp.get_resources()
|
| 614 |
+
resource = resources_dict["resource://data"]
|
| 615 |
+
|
| 616 |
+
assert resource.meta == meta_data
|
| 617 |
+
|
| 618 |
|
| 619 |
class TestTemplateDecorator:
|
| 620 |
async def test_template_decorator(self):
|
|
|
|
| 764 |
assert template.uri_template == "resource://{param*}"
|
| 765 |
assert template.name == "template_resource"
|
| 766 |
|
| 767 |
+
async def test_template_decorator_with_meta(self):
|
| 768 |
+
"""Test that meta parameter is passed through the template decorator."""
|
| 769 |
+
mcp = FastMCP()
|
| 770 |
+
|
| 771 |
+
meta_data = {"version": "2.0", "template": "test"}
|
| 772 |
+
|
| 773 |
+
@mcp.resource("resource://{param}/data", meta=meta_data)
|
| 774 |
+
def get_template_data(param: str) -> str:
|
| 775 |
+
return f"Data for {param}"
|
| 776 |
+
|
| 777 |
+
templates_dict = await mcp.get_resource_templates()
|
| 778 |
+
template = templates_dict["resource://{param}/data"]
|
| 779 |
+
|
| 780 |
+
assert template.meta == meta_data
|
| 781 |
+
|
| 782 |
|
| 783 |
class TestPromptDecorator:
|
| 784 |
async def test_prompt_decorator(self):
|
|
|
|
| 1034 |
message = result.messages[0]
|
| 1035 |
assert message.content.text == "Static Hello, world!" # type: ignore[attr-defined]
|
| 1036 |
|
| 1037 |
+
async def test_prompt_decorator_with_meta(self):
|
| 1038 |
+
"""Test that meta parameter is passed through the prompt decorator."""
|
| 1039 |
+
mcp = FastMCP()
|
| 1040 |
+
|
| 1041 |
+
meta_data = {"version": "3.0", "type": "prompt"}
|
| 1042 |
+
|
| 1043 |
+
@mcp.prompt(meta=meta_data)
|
| 1044 |
+
def test_prompt(message: str) -> str:
|
| 1045 |
+
return f"Response: {message}"
|
| 1046 |
+
|
| 1047 |
+
prompts_dict = await mcp.get_prompts()
|
| 1048 |
+
prompt = prompts_dict["test_prompt"]
|
| 1049 |
+
|
| 1050 |
+
assert prompt.meta == meta_data
|
| 1051 |
+
|
| 1052 |
|
| 1053 |
class TestResourcePrefixHelpers:
|
| 1054 |
@pytest.mark.parametrize(
|
tests/tools/test_tool.py
CHANGED
|
@@ -44,6 +44,22 @@ class TestToolFromFunction:
|
|
| 44 |
}
|
| 45 |
assert tool.output_schema == expected_schema
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
async def test_async_function(self):
|
| 48 |
"""Test registering and running an async function."""
|
| 49 |
|
|
|
|
| 44 |
}
|
| 45 |
assert tool.output_schema == expected_schema
|
| 46 |
|
| 47 |
+
def test_meta_parameter(self):
|
| 48 |
+
"""Test that meta parameter is properly handled."""
|
| 49 |
+
|
| 50 |
+
def multiply(a: int, b: int) -> int:
|
| 51 |
+
"""Multiply two numbers."""
|
| 52 |
+
return a * b
|
| 53 |
+
|
| 54 |
+
meta_data = {"version": "1.0", "author": "test"}
|
| 55 |
+
tool = Tool.from_function(multiply, meta=meta_data)
|
| 56 |
+
|
| 57 |
+
assert tool.meta == meta_data
|
| 58 |
+
mcp_tool = tool.to_mcp_tool()
|
| 59 |
+
# MCP tool includes fastmcp meta, so check that our meta is included
|
| 60 |
+
assert mcp_tool.meta is not None
|
| 61 |
+
assert meta_data.items() <= mcp_tool.meta.items()
|
| 62 |
+
|
| 63 |
async def test_async_function(self):
|
| 64 |
"""Test registering and running an async function."""
|
| 65 |
|