Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
7be2f51
1
Parent(s): 494d787
Add tags to meta
Browse files
src/fastmcp/prompts/prompt.py
CHANGED
|
@@ -100,6 +100,7 @@ class Prompt(FastMCPComponent, ABC):
|
|
| 100 |
"description": self.description,
|
| 101 |
"arguments": arguments,
|
| 102 |
"title": self.title,
|
|
|
|
| 103 |
}
|
| 104 |
return MCPPrompt(**kwargs | overrides)
|
| 105 |
|
|
|
|
| 100 |
"description": self.description,
|
| 101 |
"arguments": arguments,
|
| 102 |
"title": self.title,
|
| 103 |
+
"_meta": self.get_meta(),
|
| 104 |
}
|
| 105 |
return MCPPrompt(**kwargs | overrides)
|
| 106 |
|
src/fastmcp/resources/resource.py
CHANGED
|
@@ -122,6 +122,7 @@ class Resource(FastMCPComponent, abc.ABC):
|
|
| 122 |
"mimeType": self.mime_type,
|
| 123 |
"title": self.title,
|
| 124 |
"annotations": self.annotations,
|
|
|
|
| 125 |
}
|
| 126 |
return MCPResource(**kwargs | overrides)
|
| 127 |
|
|
|
|
| 122 |
"mimeType": self.mime_type,
|
| 123 |
"title": self.title,
|
| 124 |
"annotations": self.annotations,
|
| 125 |
+
"_meta": self.get_meta(),
|
| 126 |
}
|
| 127 |
return MCPResource(**kwargs | overrides)
|
| 128 |
|
src/fastmcp/resources/template.py
CHANGED
|
@@ -154,6 +154,7 @@ class ResourceTemplate(FastMCPComponent):
|
|
| 154 |
"mimeType": self.mime_type,
|
| 155 |
"title": self.title,
|
| 156 |
"annotations": self.annotations,
|
|
|
|
| 157 |
}
|
| 158 |
return MCPResourceTemplate(**kwargs | overrides)
|
| 159 |
|
|
|
|
| 154 |
"mimeType": self.mime_type,
|
| 155 |
"title": self.title,
|
| 156 |
"annotations": self.annotations,
|
| 157 |
+
"_meta": self.get_meta(),
|
| 158 |
}
|
| 159 |
return MCPResourceTemplate(**kwargs | overrides)
|
| 160 |
|
src/fastmcp/tools/tool.py
CHANGED
|
@@ -145,6 +145,7 @@ class Tool(FastMCPComponent):
|
|
| 145 |
"outputSchema": self.output_schema,
|
| 146 |
"annotations": self.annotations,
|
| 147 |
"title": title,
|
|
|
|
| 148 |
}
|
| 149 |
return MCPTool(**kwargs | overrides)
|
| 150 |
|
|
|
|
| 145 |
"outputSchema": self.output_schema,
|
| 146 |
"annotations": self.annotations,
|
| 147 |
"title": title,
|
| 148 |
+
"_meta": self.get_meta(),
|
| 149 |
}
|
| 150 |
return MCPTool(**kwargs | overrides)
|
| 151 |
|
src/fastmcp/utilities/components.py
CHANGED
|
@@ -36,7 +36,9 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
| 36 |
default_factory=set,
|
| 37 |
description="Tags for the component.",
|
| 38 |
)
|
| 39 |
-
|
|
|
|
|
|
|
| 40 |
enabled: bool = Field(
|
| 41 |
default=True,
|
| 42 |
description="Whether the component is enabled.",
|
|
@@ -58,6 +60,10 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
| 58 |
"""
|
| 59 |
return self._key or self.name
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
def with_key(self, key: str) -> Self:
|
| 62 |
return self.model_copy(update={"_key": key})
|
| 63 |
|
|
|
|
| 36 |
default_factory=set,
|
| 37 |
description="Tags for the component.",
|
| 38 |
)
|
| 39 |
+
meta: dict[str, Any] = Field(
|
| 40 |
+
default_factory=dict, description="Meta information about the prompt"
|
| 41 |
+
)
|
| 42 |
enabled: bool = Field(
|
| 43 |
default=True,
|
| 44 |
description="Whether the component is enabled.",
|
|
|
|
| 60 |
"""
|
| 61 |
return self._key or self.name
|
| 62 |
|
| 63 |
+
def get_meta(self) -> dict[str, Any]:
|
| 64 |
+
"""Get the meta information about the component."""
|
| 65 |
+
return {"tags": list(self.tags)} | self.meta
|
| 66 |
+
|
| 67 |
def with_key(self, key: str) -> Self:
|
| 68 |
return self.model_copy(update={"_key": key})
|
| 69 |
|
tests/server/test_server_interactions.py
CHANGED
|
@@ -268,6 +268,21 @@ class TestToolTags:
|
|
| 268 |
result_2 = await client.call_tool("tool_2", {})
|
| 269 |
assert result_2.data == 2
|
| 270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
|
| 272 |
class TestToolReturnTypes:
|
| 273 |
async def test_string(self):
|
|
@@ -1544,6 +1559,26 @@ class TestResourceTags:
|
|
| 1544 |
with pytest.raises(McpError, match="Unknown resource"):
|
| 1545 |
await client.read_resource(AnyUrl("resource://1"))
|
| 1546 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1547 |
|
| 1548 |
class TestResourceContext:
|
| 1549 |
async def test_resource_with_context_annotation_gets_context(self):
|
|
@@ -1972,6 +2007,26 @@ class TestResourceTemplatesTags:
|
|
| 1972 |
result = await client.read_resource("resource://2/x")
|
| 1973 |
assert result[0].text == "Template resource 2: x" # type: ignore[attr-defined]
|
| 1974 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1975 |
|
| 1976 |
class TestResourceTemplateContext:
|
| 1977 |
async def test_resource_template_context(self):
|
|
@@ -2339,6 +2394,20 @@ class TestPrompts:
|
|
| 2339 |
prompt = prompts_dict["sample_prompt"]
|
| 2340 |
assert prompt.tags == {"example", "test-tag"}
|
| 2341 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2342 |
|
| 2343 |
class TestPromptEnabled:
|
| 2344 |
async def test_toggle_enabled(self):
|
|
|
|
| 268 |
result_2 = await client.call_tool("tool_2", {})
|
| 269 |
assert result_2.data == 2
|
| 270 |
|
| 271 |
+
async def test_tags_in_meta(self):
|
| 272 |
+
mcp = FastMCP()
|
| 273 |
+
|
| 274 |
+
@mcp.tool(tags={"tool-example", "test-tool-tag"})
|
| 275 |
+
def sample_tool(x: int) -> int:
|
| 276 |
+
"""A sample tool."""
|
| 277 |
+
return x * 2
|
| 278 |
+
|
| 279 |
+
async with Client(mcp) as client:
|
| 280 |
+
tools = await client.list_tools()
|
| 281 |
+
assert len(tools) == 1
|
| 282 |
+
tool = tools[0]
|
| 283 |
+
assert tool.meta is not None
|
| 284 |
+
assert set(tool.meta["tags"]) == {"tool-example", "test-tool-tag"}
|
| 285 |
+
|
| 286 |
|
| 287 |
class TestToolReturnTypes:
|
| 288 |
async def test_string(self):
|
|
|
|
| 1559 |
with pytest.raises(McpError, match="Unknown resource"):
|
| 1560 |
await client.read_resource(AnyUrl("resource://1"))
|
| 1561 |
|
| 1562 |
+
async def test_tags_in_meta(self):
|
| 1563 |
+
mcp = FastMCP()
|
| 1564 |
+
|
| 1565 |
+
@mcp.resource(
|
| 1566 |
+
uri="test://resource", tags={"resource-example", "test-resource-tag"}
|
| 1567 |
+
)
|
| 1568 |
+
def sample_resource() -> str:
|
| 1569 |
+
"""A sample resource."""
|
| 1570 |
+
return "resource content"
|
| 1571 |
+
|
| 1572 |
+
async with Client(mcp) as client:
|
| 1573 |
+
resources = await client.list_resources()
|
| 1574 |
+
assert len(resources) == 1
|
| 1575 |
+
resource = resources[0]
|
| 1576 |
+
assert resource.meta is not None
|
| 1577 |
+
assert set(resource.meta["tags"]) == {
|
| 1578 |
+
"resource-example",
|
| 1579 |
+
"test-resource-tag",
|
| 1580 |
+
}
|
| 1581 |
+
|
| 1582 |
|
| 1583 |
class TestResourceContext:
|
| 1584 |
async def test_resource_with_context_annotation_gets_context(self):
|
|
|
|
| 2007 |
result = await client.read_resource("resource://2/x")
|
| 2008 |
assert result[0].text == "Template resource 2: x" # type: ignore[attr-defined]
|
| 2009 |
|
| 2010 |
+
async def test_tags_in_meta(self):
|
| 2011 |
+
mcp = FastMCP()
|
| 2012 |
+
|
| 2013 |
+
@mcp.resource(
|
| 2014 |
+
"test://template/{id}", tags={"template-example", "test-template-tag"}
|
| 2015 |
+
)
|
| 2016 |
+
def sample_template(id: str) -> str:
|
| 2017 |
+
"""A sample resource template."""
|
| 2018 |
+
return f"template content for {id}"
|
| 2019 |
+
|
| 2020 |
+
async with Client(mcp) as client:
|
| 2021 |
+
templates = await client.list_resource_templates()
|
| 2022 |
+
assert len(templates) == 1
|
| 2023 |
+
template = templates[0]
|
| 2024 |
+
assert template.meta is not None
|
| 2025 |
+
assert set(template.meta["tags"]) == {
|
| 2026 |
+
"template-example",
|
| 2027 |
+
"test-template-tag",
|
| 2028 |
+
}
|
| 2029 |
+
|
| 2030 |
|
| 2031 |
class TestResourceTemplateContext:
|
| 2032 |
async def test_resource_template_context(self):
|
|
|
|
| 2394 |
prompt = prompts_dict["sample_prompt"]
|
| 2395 |
assert prompt.tags == {"example", "test-tag"}
|
| 2396 |
|
| 2397 |
+
async def test_tags_in_meta(self):
|
| 2398 |
+
mcp = FastMCP()
|
| 2399 |
+
|
| 2400 |
+
@mcp.prompt(tags={"example", "test-tag"})
|
| 2401 |
+
def sample_prompt() -> str:
|
| 2402 |
+
return "Hello, world!"
|
| 2403 |
+
|
| 2404 |
+
async with Client(mcp) as client:
|
| 2405 |
+
prompts = await client.list_prompts()
|
| 2406 |
+
assert len(prompts) == 1
|
| 2407 |
+
prompt = prompts[0]
|
| 2408 |
+
assert prompt.meta is not None
|
| 2409 |
+
assert set(prompt.meta["tags"]) == {"example", "test-tag"}
|
| 2410 |
+
|
| 2411 |
|
| 2412 |
class TestPromptEnabled:
|
| 2413 |
async def test_toggle_enabled(self):
|