|
10 | 10 | from mcp_types import ( |
11 | 11 | INTERNAL_ERROR, |
12 | 12 | INVALID_PARAMS, |
| 13 | + MISSING_REQUIRED_CLIENT_CAPABILITY, |
13 | 14 | AudioContent, |
14 | 15 | BlobResourceContents, |
15 | 16 | CallToolResult, |
@@ -2104,6 +2105,69 @@ async def ask(topic: str, ctx: Context) -> str | InputRequiredResult: |
2104 | 2105 | assert result is sentinel |
2105 | 2106 |
|
2106 | 2107 |
|
| 2108 | +async def test_prompt_raising_mcp_error_surfaces_code_and_data_to_client(): |
| 2109 | + """A handler-raised MCPError keeps its code and data through the prompt pipeline — |
| 2110 | + the same parity tools/call has, needed for self-service capability rejection.""" |
| 2111 | + mcp = MCPServer() |
| 2112 | + |
| 2113 | + @mcp.prompt() |
| 2114 | + async def briefing(ctx: Context) -> str: |
| 2115 | + raise MCPError( |
| 2116 | + code=MISSING_REQUIRED_CLIENT_CAPABILITY, |
| 2117 | + message="needs elicitation", |
| 2118 | + data={"requiredCapabilities": ["elicitation"]}, |
| 2119 | + ) |
| 2120 | + |
| 2121 | + async with Client(mcp) as client: |
| 2122 | + with pytest.raises(MCPError) as exc: |
| 2123 | + await client.get_prompt("briefing") |
| 2124 | + assert exc.value.error.code == MISSING_REQUIRED_CLIENT_CAPABILITY |
| 2125 | + assert exc.value.error.message == "needs elicitation" |
| 2126 | + assert exc.value.error.data == {"requiredCapabilities": ["elicitation"]} |
| 2127 | + |
| 2128 | + |
| 2129 | +async def test_resource_template_raising_mcp_error_surfaces_code_and_data_to_client(): |
| 2130 | + """A handler-raised MCPError keeps its code and data through the resource template |
| 2131 | + pipeline instead of being wrapped into a generic ResourceError.""" |
| 2132 | + mcp = MCPServer() |
| 2133 | + |
| 2134 | + @mcp.resource("ask://{topic}") |
| 2135 | + async def ask(topic: str, ctx: Context) -> str: |
| 2136 | + raise MCPError( |
| 2137 | + code=MISSING_REQUIRED_CLIENT_CAPABILITY, |
| 2138 | + message="needs elicitation", |
| 2139 | + data={"requiredCapabilities": ["elicitation"]}, |
| 2140 | + ) |
| 2141 | + |
| 2142 | + async with Client(mcp) as client: |
| 2143 | + with pytest.raises(MCPError) as exc: |
| 2144 | + await client.read_resource("ask://databases") |
| 2145 | + assert exc.value.error.code == MISSING_REQUIRED_CLIENT_CAPABILITY |
| 2146 | + assert exc.value.error.message == "needs elicitation" |
| 2147 | + assert exc.value.error.data == {"requiredCapabilities": ["elicitation"]} |
| 2148 | + |
| 2149 | + |
| 2150 | +async def test_static_resource_raising_mcp_error_surfaces_code_and_data_to_client(): |
| 2151 | + """A handler-raised MCPError keeps its code and data through the static resource |
| 2152 | + read path too — parity with the template path above.""" |
| 2153 | + mcp = MCPServer() |
| 2154 | + |
| 2155 | + @mcp.resource("static://thing") |
| 2156 | + def thing() -> str: |
| 2157 | + raise MCPError( |
| 2158 | + code=MISSING_REQUIRED_CLIENT_CAPABILITY, |
| 2159 | + message="needs elicitation", |
| 2160 | + data={"requiredCapabilities": ["elicitation"]}, |
| 2161 | + ) |
| 2162 | + |
| 2163 | + async with Client(mcp) as client: |
| 2164 | + with pytest.raises(MCPError) as exc: |
| 2165 | + await client.read_resource("static://thing") |
| 2166 | + assert exc.value.error.code == MISSING_REQUIRED_CLIENT_CAPABILITY |
| 2167 | + assert exc.value.error.message == "needs elicitation" |
| 2168 | + assert exc.value.error.data == {"requiredCapabilities": ["elicitation"]} |
| 2169 | + |
| 2170 | + |
2107 | 2171 | async def test_context_exposes_client_capabilities_from_connection(): |
2108 | 2172 | mcp = MCPServer() |
2109 | 2173 | seen: list[ClientCapabilities | None] = [] |
|
0 commit comments