Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
55471df
1
Parent(s): 788b7e2
Support enabled/disabled resources and templates
Browse files
src/fastmcp/resources/resource.py
CHANGED
|
@@ -52,6 +52,7 @@ class Resource(FastMCPComponent, abc.ABC):
|
|
| 52 |
description: str | None = None,
|
| 53 |
mime_type: str | None = None,
|
| 54 |
tags: set[str] | None = None,
|
|
|
|
| 55 |
) -> FunctionResource:
|
| 56 |
return FunctionResource.from_function(
|
| 57 |
fn=fn,
|
|
@@ -60,6 +61,7 @@ class Resource(FastMCPComponent, abc.ABC):
|
|
| 60 |
description=description,
|
| 61 |
mime_type=mime_type,
|
| 62 |
tags=tags,
|
|
|
|
| 63 |
)
|
| 64 |
|
| 65 |
@field_validator("mime_type", mode="before")
|
|
@@ -124,6 +126,7 @@ class FunctionResource(Resource):
|
|
| 124 |
description: str | None = None,
|
| 125 |
mime_type: str | None = None,
|
| 126 |
tags: set[str] | None = None,
|
|
|
|
| 127 |
) -> FunctionResource:
|
| 128 |
"""Create a FunctionResource from a function."""
|
| 129 |
if isinstance(uri, str):
|
|
@@ -135,6 +138,7 @@ class FunctionResource(Resource):
|
|
| 135 |
description=description or fn.__doc__,
|
| 136 |
mime_type=mime_type or "text/plain",
|
| 137 |
tags=tags or set(),
|
|
|
|
| 138 |
)
|
| 139 |
|
| 140 |
async def read(self) -> str | bytes:
|
|
|
|
| 52 |
description: str | None = None,
|
| 53 |
mime_type: str | None = None,
|
| 54 |
tags: set[str] | None = None,
|
| 55 |
+
enabled: bool | None = None,
|
| 56 |
) -> FunctionResource:
|
| 57 |
return FunctionResource.from_function(
|
| 58 |
fn=fn,
|
|
|
|
| 61 |
description=description,
|
| 62 |
mime_type=mime_type,
|
| 63 |
tags=tags,
|
| 64 |
+
enabled=enabled,
|
| 65 |
)
|
| 66 |
|
| 67 |
@field_validator("mime_type", mode="before")
|
|
|
|
| 126 |
description: str | None = None,
|
| 127 |
mime_type: str | None = None,
|
| 128 |
tags: set[str] | None = None,
|
| 129 |
+
enabled: bool | None = None,
|
| 130 |
) -> FunctionResource:
|
| 131 |
"""Create a FunctionResource from a function."""
|
| 132 |
if isinstance(uri, str):
|
|
|
|
| 138 |
description=description or fn.__doc__,
|
| 139 |
mime_type=mime_type or "text/plain",
|
| 140 |
tags=tags or set(),
|
| 141 |
+
enabled=enabled if enabled is not None else True,
|
| 142 |
)
|
| 143 |
|
| 144 |
async def read(self) -> str | bytes:
|
src/fastmcp/resources/template.py
CHANGED
|
@@ -70,6 +70,7 @@ class ResourceTemplate(FastMCPComponent):
|
|
| 70 |
description: str | None = None,
|
| 71 |
mime_type: str | None = None,
|
| 72 |
tags: set[str] | None = None,
|
|
|
|
| 73 |
) -> FunctionResourceTemplate:
|
| 74 |
return FunctionResourceTemplate.from_function(
|
| 75 |
fn=fn,
|
|
@@ -78,6 +79,7 @@ class ResourceTemplate(FastMCPComponent):
|
|
| 78 |
description=description,
|
| 79 |
mime_type=mime_type,
|
| 80 |
tags=tags,
|
|
|
|
| 81 |
)
|
| 82 |
|
| 83 |
@field_validator("mime_type", mode="before")
|
|
@@ -113,6 +115,7 @@ class ResourceTemplate(FastMCPComponent):
|
|
| 113 |
description=self.description,
|
| 114 |
mime_type=self.mime_type,
|
| 115 |
tags=self.tags,
|
|
|
|
| 116 |
)
|
| 117 |
|
| 118 |
def to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate:
|
|
@@ -155,6 +158,7 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|
| 155 |
description: str | None = None,
|
| 156 |
mime_type: str | None = None,
|
| 157 |
tags: set[str] | None = None,
|
|
|
|
| 158 |
) -> FunctionResourceTemplate:
|
| 159 |
"""Create a template from a function."""
|
| 160 |
from fastmcp.server.context import Context
|
|
@@ -237,4 +241,5 @@ class FunctionResourceTemplate(ResourceTemplate):
|
|
| 237 |
fn=fn,
|
| 238 |
parameters=parameters,
|
| 239 |
tags=tags or set(),
|
|
|
|
| 240 |
)
|
|
|
|
| 70 |
description: str | None = None,
|
| 71 |
mime_type: str | None = None,
|
| 72 |
tags: set[str] | None = None,
|
| 73 |
+
enabled: bool | None = None,
|
| 74 |
) -> FunctionResourceTemplate:
|
| 75 |
return FunctionResourceTemplate.from_function(
|
| 76 |
fn=fn,
|
|
|
|
| 79 |
description=description,
|
| 80 |
mime_type=mime_type,
|
| 81 |
tags=tags,
|
| 82 |
+
enabled=enabled,
|
| 83 |
)
|
| 84 |
|
| 85 |
@field_validator("mime_type", mode="before")
|
|
|
|
| 115 |
description=self.description,
|
| 116 |
mime_type=self.mime_type,
|
| 117 |
tags=self.tags,
|
| 118 |
+
enabled=self.enabled,
|
| 119 |
)
|
| 120 |
|
| 121 |
def to_mcp_template(self, **overrides: Any) -> MCPResourceTemplate:
|
|
|
|
| 158 |
description: str | None = None,
|
| 159 |
mime_type: str | None = None,
|
| 160 |
tags: set[str] | None = None,
|
| 161 |
+
enabled: bool | None = None,
|
| 162 |
) -> FunctionResourceTemplate:
|
| 163 |
"""Create a template from a function."""
|
| 164 |
from fastmcp.server.context import Context
|
|
|
|
| 241 |
fn=fn,
|
| 242 |
parameters=parameters,
|
| 243 |
tags=tags or set(),
|
| 244 |
+
enabled=enabled if enabled is not None else True,
|
| 245 |
)
|
src/fastmcp/server/server.py
CHANGED
|
@@ -667,11 +667,12 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 667 |
|
| 668 |
Args:
|
| 669 |
name_or_fn: Either a function (when used as @tool), a string name, or None
|
|
|
|
| 670 |
description: Optional description of what the tool does
|
| 671 |
tags: Optional set of tags for categorizing the tool
|
| 672 |
-
annotations: Optional annotations about the tool's behavior
|
| 673 |
exclude_args: Optional list of argument names to exclude from the tool schema
|
| 674 |
-
|
| 675 |
|
| 676 |
Example:
|
| 677 |
@server.tool
|
|
@@ -820,6 +821,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 820 |
description: str | None = None,
|
| 821 |
mime_type: str | None = None,
|
| 822 |
tags: set[str] | None = None,
|
|
|
|
| 823 |
) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
|
| 824 |
"""Decorator to register a function as a resource.
|
| 825 |
|
|
@@ -842,6 +844,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 842 |
description: Optional description of the resource
|
| 843 |
mime_type: Optional MIME type for the resource
|
| 844 |
tags: Optional set of tags for categorizing the resource
|
|
|
|
| 845 |
|
| 846 |
Example:
|
| 847 |
@server.resource("resource://my-resource")
|
|
@@ -906,6 +909,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 906 |
description=description,
|
| 907 |
mime_type=mime_type,
|
| 908 |
tags=tags,
|
|
|
|
| 909 |
)
|
| 910 |
self.add_template(template)
|
| 911 |
return template
|
|
@@ -917,6 +921,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 917 |
description=description,
|
| 918 |
mime_type=mime_type,
|
| 919 |
tags=tags,
|
|
|
|
| 920 |
)
|
| 921 |
self.add_resource(resource)
|
| 922 |
return resource
|
|
@@ -983,9 +988,10 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 983 |
|
| 984 |
Args:
|
| 985 |
name_or_fn: Either a function (when used as @prompt), a string name, or None
|
|
|
|
| 986 |
description: Optional description of what the prompt does
|
| 987 |
tags: Optional set of tags for categorizing the prompt
|
| 988 |
-
|
| 989 |
|
| 990 |
Example:
|
| 991 |
@server.prompt
|
|
|
|
| 667 |
|
| 668 |
Args:
|
| 669 |
name_or_fn: Either a function (when used as @tool), a string name, or None
|
| 670 |
+
name: Optional name for the tool (keyword-only, alternative to name_or_fn)
|
| 671 |
description: Optional description of what the tool does
|
| 672 |
tags: Optional set of tags for categorizing the tool
|
| 673 |
+
annotations: Optional annotations about the tool's behavior (e.g. {"is_async": True})
|
| 674 |
exclude_args: Optional list of argument names to exclude from the tool schema
|
| 675 |
+
enabled: Optional boolean to enable or disable the tool
|
| 676 |
|
| 677 |
Example:
|
| 678 |
@server.tool
|
|
|
|
| 821 |
description: str | None = None,
|
| 822 |
mime_type: str | None = None,
|
| 823 |
tags: set[str] | None = None,
|
| 824 |
+
enabled: bool | None = None,
|
| 825 |
) -> Callable[[AnyFunction], Resource | ResourceTemplate]:
|
| 826 |
"""Decorator to register a function as a resource.
|
| 827 |
|
|
|
|
| 844 |
description: Optional description of the resource
|
| 845 |
mime_type: Optional MIME type for the resource
|
| 846 |
tags: Optional set of tags for categorizing the resource
|
| 847 |
+
enabled: Optional boolean to enable or disable the resource
|
| 848 |
|
| 849 |
Example:
|
| 850 |
@server.resource("resource://my-resource")
|
|
|
|
| 909 |
description=description,
|
| 910 |
mime_type=mime_type,
|
| 911 |
tags=tags,
|
| 912 |
+
enabled=enabled,
|
| 913 |
)
|
| 914 |
self.add_template(template)
|
| 915 |
return template
|
|
|
|
| 921 |
description=description,
|
| 922 |
mime_type=mime_type,
|
| 923 |
tags=tags,
|
| 924 |
+
enabled=enabled,
|
| 925 |
)
|
| 926 |
self.add_resource(resource)
|
| 927 |
return resource
|
|
|
|
| 988 |
|
| 989 |
Args:
|
| 990 |
name_or_fn: Either a function (when used as @prompt), a string name, or None
|
| 991 |
+
name: Optional name for the prompt (keyword-only, alternative to name_or_fn)
|
| 992 |
description: Optional description of what the prompt does
|
| 993 |
tags: Optional set of tags for categorizing the prompt
|
| 994 |
+
enabled: Optional boolean to enable or disable the prompt
|
| 995 |
|
| 996 |
Example:
|
| 997 |
@server.prompt
|
src/fastmcp/utilities/components.py
CHANGED
|
@@ -44,7 +44,7 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
| 44 |
return self.model_dump() == other.model_dump()
|
| 45 |
|
| 46 |
def __repr__(self) -> str:
|
| 47 |
-
return f"{self.__class__.__name__}(name={self.name!r}, description={self.description!r}, tags={self.tags})"
|
| 48 |
|
| 49 |
def enable(self) -> None:
|
| 50 |
"""Enable the component."""
|
|
|
|
| 44 |
return self.model_dump() == other.model_dump()
|
| 45 |
|
| 46 |
def __repr__(self) -> str:
|
| 47 |
+
return f"{self.__class__.__name__}(name={self.name!r}, description={self.description!r}, tags={self.tags}, enabled={self.enabled})"
|
| 48 |
|
| 49 |
def enable(self) -> None:
|
| 50 |
"""Enable the component."""
|
tests/server/test_server_interactions.py
CHANGED
|
@@ -732,6 +732,9 @@ class TestToolEnabled:
|
|
| 732 |
tools = await client.list_tools()
|
| 733 |
assert len(tools) == 0
|
| 734 |
|
|
|
|
|
|
|
|
|
|
| 735 |
async def test_tool_toggle_enabled(self):
|
| 736 |
mcp = FastMCP()
|
| 737 |
|
|
@@ -758,6 +761,9 @@ class TestToolEnabled:
|
|
| 758 |
tools = await client.list_tools()
|
| 759 |
assert len(tools) == 0
|
| 760 |
|
|
|
|
|
|
|
|
|
|
| 761 |
async def test_get_tool_and_disable(self):
|
| 762 |
mcp = FastMCP()
|
| 763 |
|
|
@@ -774,6 +780,9 @@ class TestToolEnabled:
|
|
| 774 |
result = await client.list_tools()
|
| 775 |
assert len(result) == 0
|
| 776 |
|
|
|
|
|
|
|
|
|
|
| 777 |
async def test_cant_call_disabled_tool(self):
|
| 778 |
mcp = FastMCP()
|
| 779 |
|
|
@@ -870,6 +879,102 @@ class TestResourceContext:
|
|
| 870 |
assert result[0].text == "1" # type: ignore[attr-defined]
|
| 871 |
|
| 872 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 873 |
class TestResourceTemplates:
|
| 874 |
async def test_resource_with_params_not_in_uri(self):
|
| 875 |
"""Test that a resource with function parameters raises an error if the URI
|
|
@@ -1121,6 +1226,99 @@ class TestResourceTemplateContext:
|
|
| 1121 |
assert result[0].text.startswith("Resource template: test 1") # type: ignore[attr-defined]
|
| 1122 |
|
| 1123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1124 |
class TestPrompts:
|
| 1125 |
"""Test prompt functionality in FastMCP server."""
|
| 1126 |
|
|
@@ -1340,6 +1538,9 @@ class TestPromptEnabled:
|
|
| 1340 |
prompts = await client.list_prompts()
|
| 1341 |
assert len(prompts) == 0
|
| 1342 |
|
|
|
|
|
|
|
|
|
|
| 1343 |
async def test_prompt_toggle_enabled(self):
|
| 1344 |
mcp = FastMCP()
|
| 1345 |
|
|
@@ -1366,6 +1567,9 @@ class TestPromptEnabled:
|
|
| 1366 |
prompts = await client.list_prompts()
|
| 1367 |
assert len(prompts) == 0
|
| 1368 |
|
|
|
|
|
|
|
|
|
|
| 1369 |
async def test_get_prompt_and_disable(self):
|
| 1370 |
mcp = FastMCP()
|
| 1371 |
|
|
@@ -1382,6 +1586,9 @@ class TestPromptEnabled:
|
|
| 1382 |
result = await client.list_prompts()
|
| 1383 |
assert len(result) == 0
|
| 1384 |
|
|
|
|
|
|
|
|
|
|
| 1385 |
async def test_cant_get_disabled_prompt(self):
|
| 1386 |
mcp = FastMCP()
|
| 1387 |
|
|
|
|
| 732 |
tools = await client.list_tools()
|
| 733 |
assert len(tools) == 0
|
| 734 |
|
| 735 |
+
with pytest.raises(ToolError, match="Unknown tool"):
|
| 736 |
+
await client.call_tool("sample_tool", {"x": 5})
|
| 737 |
+
|
| 738 |
async def test_tool_toggle_enabled(self):
|
| 739 |
mcp = FastMCP()
|
| 740 |
|
|
|
|
| 761 |
tools = await client.list_tools()
|
| 762 |
assert len(tools) == 0
|
| 763 |
|
| 764 |
+
with pytest.raises(ToolError, match="Unknown tool"):
|
| 765 |
+
await client.call_tool("sample_tool", {"x": 5})
|
| 766 |
+
|
| 767 |
async def test_get_tool_and_disable(self):
|
| 768 |
mcp = FastMCP()
|
| 769 |
|
|
|
|
| 780 |
result = await client.list_tools()
|
| 781 |
assert len(result) == 0
|
| 782 |
|
| 783 |
+
with pytest.raises(ToolError, match="Unknown tool"):
|
| 784 |
+
await client.call_tool("sample_tool", {"x": 5})
|
| 785 |
+
|
| 786 |
async def test_cant_call_disabled_tool(self):
|
| 787 |
mcp = FastMCP()
|
| 788 |
|
|
|
|
| 879 |
assert result[0].text == "1" # type: ignore[attr-defined]
|
| 880 |
|
| 881 |
|
| 882 |
+
class TestResourceEnabled:
|
| 883 |
+
async def test_toggle_enabled(self):
|
| 884 |
+
mcp = FastMCP()
|
| 885 |
+
|
| 886 |
+
@mcp.resource("resource://data")
|
| 887 |
+
def sample_resource() -> str:
|
| 888 |
+
return "Hello, world!"
|
| 889 |
+
|
| 890 |
+
assert sample_resource.enabled
|
| 891 |
+
|
| 892 |
+
resource = await mcp.get_resource("resource://data")
|
| 893 |
+
assert resource.enabled
|
| 894 |
+
|
| 895 |
+
resource.disable()
|
| 896 |
+
|
| 897 |
+
assert not resource.enabled
|
| 898 |
+
assert not sample_resource.enabled
|
| 899 |
+
|
| 900 |
+
resource.enable()
|
| 901 |
+
assert resource.enabled
|
| 902 |
+
assert sample_resource.enabled
|
| 903 |
+
|
| 904 |
+
async def test_resource_disabled_in_decorator(self):
|
| 905 |
+
mcp = FastMCP()
|
| 906 |
+
|
| 907 |
+
@mcp.resource("resource://data", enabled=False)
|
| 908 |
+
def sample_resource() -> str:
|
| 909 |
+
return "Hello, world!"
|
| 910 |
+
|
| 911 |
+
async with Client(mcp) as client:
|
| 912 |
+
resources = await client.list_resources()
|
| 913 |
+
assert len(resources) == 0
|
| 914 |
+
|
| 915 |
+
with pytest.raises(McpError, match="Unknown resource"):
|
| 916 |
+
await client.read_resource(AnyUrl("resource://data"))
|
| 917 |
+
|
| 918 |
+
async def test_resource_toggle_enabled(self):
|
| 919 |
+
mcp = FastMCP()
|
| 920 |
+
|
| 921 |
+
@mcp.resource("resource://data", enabled=False)
|
| 922 |
+
def sample_resource() -> str:
|
| 923 |
+
return "Hello, world!"
|
| 924 |
+
|
| 925 |
+
sample_resource.enable()
|
| 926 |
+
|
| 927 |
+
async with Client(mcp) as client:
|
| 928 |
+
resources = await client.list_resources()
|
| 929 |
+
assert len(resources) == 1
|
| 930 |
+
|
| 931 |
+
async def test_resource_toggle_disabled(self):
|
| 932 |
+
mcp = FastMCP()
|
| 933 |
+
|
| 934 |
+
@mcp.resource("resource://data")
|
| 935 |
+
def sample_resource() -> str:
|
| 936 |
+
return "Hello, world!"
|
| 937 |
+
|
| 938 |
+
sample_resource.disable()
|
| 939 |
+
|
| 940 |
+
async with Client(mcp) as client:
|
| 941 |
+
resources = await client.list_resources()
|
| 942 |
+
assert len(resources) == 0
|
| 943 |
+
|
| 944 |
+
with pytest.raises(McpError, match="Unknown resource"):
|
| 945 |
+
await client.read_resource(AnyUrl("resource://data"))
|
| 946 |
+
|
| 947 |
+
async def test_get_resource_and_disable(self):
|
| 948 |
+
mcp = FastMCP()
|
| 949 |
+
|
| 950 |
+
@mcp.resource("resource://data")
|
| 951 |
+
def sample_resource() -> str:
|
| 952 |
+
return "Hello, world!"
|
| 953 |
+
|
| 954 |
+
resource = await mcp.get_resource("resource://data")
|
| 955 |
+
assert resource.enabled
|
| 956 |
+
|
| 957 |
+
sample_resource.disable()
|
| 958 |
+
|
| 959 |
+
async with Client(mcp) as client:
|
| 960 |
+
result = await client.list_resources()
|
| 961 |
+
assert len(result) == 0
|
| 962 |
+
|
| 963 |
+
with pytest.raises(McpError, match="Unknown resource"):
|
| 964 |
+
await client.read_resource(AnyUrl("resource://data"))
|
| 965 |
+
|
| 966 |
+
async def test_cant_read_disabled_resource(self):
|
| 967 |
+
mcp = FastMCP()
|
| 968 |
+
|
| 969 |
+
@mcp.resource("resource://data", enabled=False)
|
| 970 |
+
def sample_resource() -> str:
|
| 971 |
+
return "Hello, world!"
|
| 972 |
+
|
| 973 |
+
with pytest.raises(McpError, match="Unknown resource"):
|
| 974 |
+
async with Client(mcp) as client:
|
| 975 |
+
await client.read_resource(AnyUrl("resource://data"))
|
| 976 |
+
|
| 977 |
+
|
| 978 |
class TestResourceTemplates:
|
| 979 |
async def test_resource_with_params_not_in_uri(self):
|
| 980 |
"""Test that a resource with function parameters raises an error if the URI
|
|
|
|
| 1226 |
assert result[0].text.startswith("Resource template: test 1") # type: ignore[attr-defined]
|
| 1227 |
|
| 1228 |
|
| 1229 |
+
class TestResourceTemplateEnabled:
|
| 1230 |
+
async def test_toggle_enabled(self):
|
| 1231 |
+
mcp = FastMCP()
|
| 1232 |
+
|
| 1233 |
+
@mcp.resource("resource://{param}")
|
| 1234 |
+
def sample_template(param: str) -> str:
|
| 1235 |
+
return f"Template: {param}"
|
| 1236 |
+
|
| 1237 |
+
assert sample_template.enabled
|
| 1238 |
+
|
| 1239 |
+
template = await mcp.get_resource_template("resource://{param}")
|
| 1240 |
+
assert template.enabled
|
| 1241 |
+
|
| 1242 |
+
template.disable()
|
| 1243 |
+
|
| 1244 |
+
assert not template.enabled
|
| 1245 |
+
assert not sample_template.enabled
|
| 1246 |
+
|
| 1247 |
+
template.enable()
|
| 1248 |
+
assert template.enabled
|
| 1249 |
+
assert sample_template.enabled
|
| 1250 |
+
|
| 1251 |
+
async def test_template_disabled_in_decorator(self):
|
| 1252 |
+
mcp = FastMCP()
|
| 1253 |
+
|
| 1254 |
+
@mcp.resource("resource://{param}", enabled=False)
|
| 1255 |
+
def sample_template(param: str) -> str:
|
| 1256 |
+
return f"Template: {param}"
|
| 1257 |
+
|
| 1258 |
+
async with Client(mcp) as client:
|
| 1259 |
+
templates = await client.list_resource_templates()
|
| 1260 |
+
assert len(templates) == 0
|
| 1261 |
+
|
| 1262 |
+
with pytest.raises(McpError, match="Unknown resource"):
|
| 1263 |
+
await client.read_resource(AnyUrl("resource://test"))
|
| 1264 |
+
|
| 1265 |
+
async def test_template_toggle_enabled(self):
|
| 1266 |
+
mcp = FastMCP()
|
| 1267 |
+
|
| 1268 |
+
@mcp.resource("resource://{param}", enabled=False)
|
| 1269 |
+
def sample_template(param: str) -> str:
|
| 1270 |
+
return f"Template: {param}"
|
| 1271 |
+
|
| 1272 |
+
sample_template.enable()
|
| 1273 |
+
|
| 1274 |
+
async with Client(mcp) as client:
|
| 1275 |
+
templates = await client.list_resource_templates()
|
| 1276 |
+
assert len(templates) == 1
|
| 1277 |
+
|
| 1278 |
+
async def test_template_toggle_disabled(self):
|
| 1279 |
+
mcp = FastMCP()
|
| 1280 |
+
|
| 1281 |
+
@mcp.resource("resource://{param}")
|
| 1282 |
+
def sample_template(param: str) -> str:
|
| 1283 |
+
return f"Template: {param}"
|
| 1284 |
+
|
| 1285 |
+
sample_template.disable()
|
| 1286 |
+
|
| 1287 |
+
async with Client(mcp) as client:
|
| 1288 |
+
templates = await client.list_resource_templates()
|
| 1289 |
+
assert len(templates) == 0
|
| 1290 |
+
|
| 1291 |
+
async def test_get_template_and_disable(self):
|
| 1292 |
+
mcp = FastMCP()
|
| 1293 |
+
|
| 1294 |
+
@mcp.resource("resource://{param}")
|
| 1295 |
+
def sample_template(param: str) -> str:
|
| 1296 |
+
return f"Template: {param}"
|
| 1297 |
+
|
| 1298 |
+
template = await mcp.get_resource_template("resource://{param}")
|
| 1299 |
+
assert template.enabled
|
| 1300 |
+
|
| 1301 |
+
sample_template.disable()
|
| 1302 |
+
|
| 1303 |
+
async with Client(mcp) as client:
|
| 1304 |
+
result = await client.list_resource_templates()
|
| 1305 |
+
assert len(result) == 0
|
| 1306 |
+
|
| 1307 |
+
with pytest.raises(McpError, match="Unknown resource"):
|
| 1308 |
+
await client.read_resource(AnyUrl("resource://test"))
|
| 1309 |
+
|
| 1310 |
+
async def test_cant_read_disabled_template(self):
|
| 1311 |
+
mcp = FastMCP()
|
| 1312 |
+
|
| 1313 |
+
@mcp.resource("resource://{param}", enabled=False)
|
| 1314 |
+
def sample_template(param: str) -> str:
|
| 1315 |
+
return f"Template: {param}"
|
| 1316 |
+
|
| 1317 |
+
with pytest.raises(McpError, match="Unknown resource"):
|
| 1318 |
+
async with Client(mcp) as client:
|
| 1319 |
+
await client.read_resource(AnyUrl("resource://test"))
|
| 1320 |
+
|
| 1321 |
+
|
| 1322 |
class TestPrompts:
|
| 1323 |
"""Test prompt functionality in FastMCP server."""
|
| 1324 |
|
|
|
|
| 1538 |
prompts = await client.list_prompts()
|
| 1539 |
assert len(prompts) == 0
|
| 1540 |
|
| 1541 |
+
with pytest.raises(McpError, match="Unknown prompt"):
|
| 1542 |
+
await client.get_prompt("sample_prompt")
|
| 1543 |
+
|
| 1544 |
async def test_prompt_toggle_enabled(self):
|
| 1545 |
mcp = FastMCP()
|
| 1546 |
|
|
|
|
| 1567 |
prompts = await client.list_prompts()
|
| 1568 |
assert len(prompts) == 0
|
| 1569 |
|
| 1570 |
+
with pytest.raises(McpError, match="Unknown prompt"):
|
| 1571 |
+
await client.get_prompt("sample_prompt")
|
| 1572 |
+
|
| 1573 |
async def test_get_prompt_and_disable(self):
|
| 1574 |
mcp = FastMCP()
|
| 1575 |
|
|
|
|
| 1586 |
result = await client.list_prompts()
|
| 1587 |
assert len(result) == 0
|
| 1588 |
|
| 1589 |
+
with pytest.raises(McpError, match="Unknown prompt"):
|
| 1590 |
+
await client.get_prompt("sample_prompt")
|
| 1591 |
+
|
| 1592 |
async def test_cant_get_disabled_prompt(self):
|
| 1593 |
mcp = FastMCP()
|
| 1594 |
|