|
13 | 13 | from mcp.server.context import ServerRequestContext |
14 | 14 | from mcp.server.experimental.request_context import Experimental |
15 | 15 | from mcp.server.mcpserver import Context, MCPServer |
16 | | -from mcp.server.mcpserver.exceptions import ToolError |
| 16 | +from mcp.server.mcpserver.exceptions import PromptError, ResourceError, ToolError |
17 | 17 | from mcp.server.mcpserver.prompts.base import Message, UserMessage |
18 | 18 | from mcp.server.mcpserver.resources import FileResource, FunctionResource |
19 | 19 | from mcp.server.mcpserver.utilities.types import Audio, Image |
@@ -785,6 +785,69 @@ def get_data() -> str: # pragma: no cover |
785 | 785 | assert resource.name == "test_get_data" |
786 | 786 | assert resource.mime_type == "text/plain" |
787 | 787 |
|
| 788 | + async def test_remove_resource(self): |
| 789 | + """Test removing a resource from the server.""" |
| 790 | + mcp = MCPServer() |
| 791 | + |
| 792 | + @mcp.resource("resource://test") |
| 793 | + def get_data() -> str: # pragma: no cover |
| 794 | + return "Hello" |
| 795 | + |
| 796 | + assert len(mcp._resource_manager.list_resources()) == 1 |
| 797 | + |
| 798 | + mcp.remove_resource("resource://test") |
| 799 | + |
| 800 | + assert len(mcp._resource_manager.list_resources()) == 0 |
| 801 | + |
| 802 | + async def test_remove_nonexistent_resource(self): |
| 803 | + """Test that removing a non-existent resource raises ResourceError.""" |
| 804 | + mcp = MCPServer() |
| 805 | + |
| 806 | + with pytest.raises(ResourceError, match="Unknown resource: resource://nonexistent"): |
| 807 | + mcp.remove_resource("resource://nonexistent") |
| 808 | + |
| 809 | + async def test_remove_resource_and_list(self): |
| 810 | + """Test that a removed resource doesn't appear in list_resources.""" |
| 811 | + mcp = MCPServer() |
| 812 | + |
| 813 | + @mcp.resource("resource://first") |
| 814 | + def first() -> str: # pragma: no cover |
| 815 | + return "first" |
| 816 | + |
| 817 | + @mcp.resource("resource://second") |
| 818 | + def second() -> str: # pragma: no cover |
| 819 | + return "second" |
| 820 | + |
| 821 | + async with Client(mcp) as client: |
| 822 | + resources = await client.list_resources() |
| 823 | + assert len(resources.resources) == 2 |
| 824 | + |
| 825 | + mcp.remove_resource("resource://first") |
| 826 | + |
| 827 | + async with Client(mcp) as client: |
| 828 | + resources = await client.list_resources() |
| 829 | + assert len(resources.resources) == 1 |
| 830 | + assert resources.resources[0].uri == "resource://second" |
| 831 | + |
| 832 | + async def test_remove_resource_and_read(self): |
| 833 | + """Test that reading a removed resource fails appropriately.""" |
| 834 | + mcp = MCPServer() |
| 835 | + |
| 836 | + @mcp.resource("resource://test") |
| 837 | + def get_data() -> str: # pragma: no cover |
| 838 | + return "Hello" |
| 839 | + |
| 840 | + async with Client(mcp) as client: |
| 841 | + result = await client.read_resource("resource://test") |
| 842 | + assert isinstance(result.contents[0], TextResourceContents) |
| 843 | + assert result.contents[0].text == "Hello" |
| 844 | + |
| 845 | + mcp.remove_resource("resource://test") |
| 846 | + |
| 847 | + async with Client(mcp) as client: |
| 848 | + with pytest.raises(MCPError, match="Unknown resource"): |
| 849 | + await client.read_resource("resource://test") |
| 850 | + |
788 | 851 |
|
789 | 852 | class TestServerResourceTemplates: |
790 | 853 | async def test_resource_with_params(self): |
@@ -920,6 +983,50 @@ def get_csv(user: str) -> str: |
920 | 983 | ) |
921 | 984 | ) |
922 | 985 |
|
| 986 | + async def test_remove_resource_template(self): |
| 987 | + """Test removing a resource template from the server.""" |
| 988 | + mcp = MCPServer() |
| 989 | + |
| 990 | + @mcp.resource("resource://{name}/data") |
| 991 | + def get_data(name: str) -> str: # pragma: no cover |
| 992 | + return f"Data for {name}" |
| 993 | + |
| 994 | + assert len(mcp._resource_manager._templates) == 1 |
| 995 | + |
| 996 | + mcp.remove_resource_template("resource://{name}/data") |
| 997 | + |
| 998 | + assert len(mcp._resource_manager._templates) == 0 |
| 999 | + |
| 1000 | + async def test_remove_nonexistent_resource_template(self): |
| 1001 | + """Test that removing a non-existent template raises ResourceError.""" |
| 1002 | + mcp = MCPServer() |
| 1003 | + |
| 1004 | + with pytest.raises(ResourceError, match="Unknown resource template: resource://\\{name\\}/data"): |
| 1005 | + mcp.remove_resource_template("resource://{name}/data") |
| 1006 | + |
| 1007 | + async def test_remove_resource_template_and_list(self): |
| 1008 | + """Test that a removed template doesn't appear in list_resource_templates.""" |
| 1009 | + mcp = MCPServer() |
| 1010 | + |
| 1011 | + @mcp.resource("resource://{name}/first") |
| 1012 | + def first(name: str) -> str: # pragma: no cover |
| 1013 | + return f"first {name}" |
| 1014 | + |
| 1015 | + @mcp.resource("resource://{name}/second") |
| 1016 | + def second(name: str) -> str: # pragma: no cover |
| 1017 | + return f"second {name}" |
| 1018 | + |
| 1019 | + async with Client(mcp) as client: |
| 1020 | + templates = await client.list_resource_templates() |
| 1021 | + assert len(templates.resource_templates) == 2 |
| 1022 | + |
| 1023 | + mcp.remove_resource_template("resource://{name}/first") |
| 1024 | + |
| 1025 | + async with Client(mcp) as client: |
| 1026 | + templates = await client.list_resource_templates() |
| 1027 | + assert len(templates.resource_templates) == 1 |
| 1028 | + assert templates.resource_templates[0].uri_template == "resource://{name}/second" |
| 1029 | + |
923 | 1030 |
|
924 | 1031 | class TestServerResourceMetadata: |
925 | 1032 | """Test MCPServer @resource decorator meta parameter for list operations. |
@@ -1418,6 +1525,68 @@ def prompt_fn(name: str) -> str: ... # pragma: no branch |
1418 | 1525 | with pytest.raises(MCPError, match="Missing required arguments"): |
1419 | 1526 | await client.get_prompt("prompt_fn") |
1420 | 1527 |
|
| 1528 | + async def test_remove_prompt(self): |
| 1529 | + """Test removing a prompt from the server.""" |
| 1530 | + mcp = MCPServer() |
| 1531 | + |
| 1532 | + @mcp.prompt() |
| 1533 | + def fn() -> str: # pragma: no cover |
| 1534 | + return "Hello" |
| 1535 | + |
| 1536 | + assert len(mcp._prompt_manager.list_prompts()) == 1 |
| 1537 | + |
| 1538 | + mcp.remove_prompt("fn") |
| 1539 | + |
| 1540 | + assert len(mcp._prompt_manager.list_prompts()) == 0 |
| 1541 | + |
| 1542 | + async def test_remove_nonexistent_prompt(self): |
| 1543 | + """Test that removing a non-existent prompt raises PromptError.""" |
| 1544 | + mcp = MCPServer() |
| 1545 | + |
| 1546 | + with pytest.raises(PromptError, match="Unknown prompt: nonexistent"): |
| 1547 | + mcp.remove_prompt("nonexistent") |
| 1548 | + |
| 1549 | + async def test_remove_prompt_and_list(self): |
| 1550 | + """Test that a removed prompt doesn't appear in list_prompts.""" |
| 1551 | + mcp = MCPServer() |
| 1552 | + |
| 1553 | + @mcp.prompt() |
| 1554 | + def first() -> str: # pragma: no cover |
| 1555 | + return "first" |
| 1556 | + |
| 1557 | + @mcp.prompt() |
| 1558 | + def second() -> str: # pragma: no cover |
| 1559 | + return "second" |
| 1560 | + |
| 1561 | + async with Client(mcp) as client: |
| 1562 | + prompts = await client.list_prompts() |
| 1563 | + assert len(prompts.prompts) == 2 |
| 1564 | + |
| 1565 | + mcp.remove_prompt("first") |
| 1566 | + |
| 1567 | + async with Client(mcp) as client: |
| 1568 | + prompts = await client.list_prompts() |
| 1569 | + assert len(prompts.prompts) == 1 |
| 1570 | + assert prompts.prompts[0].name == "second" |
| 1571 | + |
| 1572 | + async def test_remove_prompt_and_get(self): |
| 1573 | + """Test that getting a removed prompt fails appropriately.""" |
| 1574 | + mcp = MCPServer() |
| 1575 | + |
| 1576 | + @mcp.prompt() |
| 1577 | + def fn() -> str: # pragma: no cover |
| 1578 | + return "Hello" |
| 1579 | + |
| 1580 | + async with Client(mcp) as client: |
| 1581 | + result = await client.get_prompt("fn") |
| 1582 | + assert result.messages[0].content == TextContent(text="Hello") |
| 1583 | + |
| 1584 | + mcp.remove_prompt("fn") |
| 1585 | + |
| 1586 | + async with Client(mcp) as client: |
| 1587 | + with pytest.raises(MCPError, match="Unknown prompt"): |
| 1588 | + await client.get_prompt("fn") |
| 1589 | + |
1421 | 1590 |
|
1422 | 1591 | async def test_completion_decorator() -> None: |
1423 | 1592 | """Test that the completion decorator registers a working handler.""" |
|
0 commit comments