|
1 | 1 | import pytest |
2 | 2 | from pydantic import AnyUrl |
3 | 3 |
|
| 4 | +from mcp.server.fastmcp import FastMCP |
4 | 5 | from mcp.server.fastmcp.resources import FunctionResource, Resource |
| 6 | +from mcp.types import Annotations |
5 | 7 |
|
6 | 8 |
|
7 | 9 | class TestResourceValidation: |
@@ -99,3 +101,95 @@ class ConcreteResource(Resource): |
99 | 101 |
|
100 | 102 | with pytest.raises(TypeError, match="abstract method"): |
101 | 103 | ConcreteResource(uri=AnyUrl("test://test"), name="test") # type: ignore |
| 104 | + |
| 105 | + |
| 106 | +class TestResourceAnnotations: |
| 107 | + """Test annotations on resources.""" |
| 108 | + |
| 109 | + def test_resource_with_annotations(self): |
| 110 | + """Test creating a resource with annotations.""" |
| 111 | + |
| 112 | + def get_data() -> str: |
| 113 | + return "data" |
| 114 | + |
| 115 | + annotations = Annotations(audience=["user"], priority=0.8) |
| 116 | + |
| 117 | + resource = FunctionResource.from_function(fn=get_data, uri="resource://test", annotations=annotations) |
| 118 | + |
| 119 | + assert resource.annotations is not None |
| 120 | + assert resource.annotations.audience == ["user"] |
| 121 | + assert resource.annotations.priority == 0.8 |
| 122 | + |
| 123 | + def test_resource_without_annotations(self): |
| 124 | + """Test that annotations are optional.""" |
| 125 | + |
| 126 | + def get_data() -> str: |
| 127 | + return "data" |
| 128 | + |
| 129 | + resource = FunctionResource.from_function(fn=get_data, uri="resource://test") |
| 130 | + |
| 131 | + assert resource.annotations is None |
| 132 | + |
| 133 | + @pytest.mark.anyio |
| 134 | + async def test_resource_annotations_in_fastmcp(self): |
| 135 | + """Test resource annotations via FastMCP decorator.""" |
| 136 | + |
| 137 | + mcp = FastMCP() |
| 138 | + |
| 139 | + @mcp.resource("resource://annotated", annotations=Annotations(audience=["assistant"], priority=0.5)) |
| 140 | + def get_annotated() -> str: |
| 141 | + """An annotated resource.""" |
| 142 | + return "annotated data" |
| 143 | + |
| 144 | + resources = await mcp.list_resources() |
| 145 | + assert len(resources) == 1 |
| 146 | + assert resources[0].annotations is not None |
| 147 | + assert resources[0].annotations.audience == ["assistant"] |
| 148 | + assert resources[0].annotations.priority == 0.5 |
| 149 | + |
| 150 | + @pytest.mark.anyio |
| 151 | + async def test_resource_annotations_with_both_audiences(self): |
| 152 | + """Test resource with both user and assistant audience.""" |
| 153 | + |
| 154 | + mcp = FastMCP() |
| 155 | + |
| 156 | + @mcp.resource("resource://both", annotations=Annotations(audience=["user", "assistant"], priority=1.0)) |
| 157 | + def get_both() -> str: |
| 158 | + return "for everyone" |
| 159 | + |
| 160 | + resources = await mcp.list_resources() |
| 161 | + assert resources[0].annotations is not None |
| 162 | + assert resources[0].annotations.audience == ["user", "assistant"] |
| 163 | + assert resources[0].annotations.priority == 1.0 |
| 164 | + |
| 165 | + |
| 166 | +class TestAnnotationsValidation: |
| 167 | + """Test validation of annotation values.""" |
| 168 | + |
| 169 | + def test_priority_validation(self): |
| 170 | + """Test that priority is validated to be between 0.0 and 1.0.""" |
| 171 | + |
| 172 | + # Valid priorities |
| 173 | + Annotations(priority=0.0) |
| 174 | + Annotations(priority=0.5) |
| 175 | + Annotations(priority=1.0) |
| 176 | + |
| 177 | + # Invalid priorities should raise validation error |
| 178 | + with pytest.raises(Exception): # Pydantic validation error |
| 179 | + Annotations(priority=-0.1) |
| 180 | + |
| 181 | + with pytest.raises(Exception): |
| 182 | + Annotations(priority=1.1) |
| 183 | + |
| 184 | + def test_audience_validation(self): |
| 185 | + """Test that audience only accepts valid roles.""" |
| 186 | + |
| 187 | + # Valid audiences |
| 188 | + Annotations(audience=["user"]) |
| 189 | + Annotations(audience=["assistant"]) |
| 190 | + Annotations(audience=["user", "assistant"]) |
| 191 | + Annotations(audience=[]) |
| 192 | + |
| 193 | + # Invalid roles should raise validation error |
| 194 | + with pytest.raises(Exception): # Pydantic validation error |
| 195 | + Annotations(audience=["invalid_role"]) # type: ignore |
0 commit comments