Repository navigation
Expand file tree
/
Copy pathtest_extensions_capability.py
More file actions
134 lines (100 loc) · 5.93 KB
/
Copy pathtest_extensions_capability.py
File metadata and controls
134 lines (100 loc) · 5.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
"""Tests for the SEP-2133 extensions capability negotiation plumbing.
The extension-map negotiation is independent of any concrete extension (Apps,
Tasks): the lowlevel `Server` advertises `self.extensions` under
`ServerCapabilities.extensions`, a client mirrors its own support under
`ClientCapabilities.extensions`, and `Connection.check_capability` resolves the
server-side query. These tests pin that plumbing end-to-end and at the unit
level. Per-extension contribution wiring lives in `test_extension.py`; this file
covers only the capability advertisement and negotiation.
"""
import mcp_types as types
import pytest
from inline_snapshot import snapshot
from mcp.client import advertise
from mcp.client.client import Client
from mcp.server import Server, ServerRequestContext
from mcp.server.extension import Extension
from mcp.server.mcpserver import MCPServer
pytestmark = pytest.mark.anyio
_EXTENSION_ID = "com.example/x"
_OTHER_EXTENSION_ID = "com.example/other"
class _Extension(Extension):
identifier = _EXTENSION_ID
def settings(self) -> dict[str, object]:
return {"k": 1}
def test_get_capabilities_omits_extensions_when_none_registered() -> None:
"""SDK-defined: a lowlevel `Server` with an empty `extensions` map advertises
`ServerCapabilities.extensions` as `None`, not an empty map."""
server = Server("bare")
assert server.get_capabilities().extensions is None
def test_get_capabilities_advertises_populated_self_extensions() -> None:
"""SDK-defined: `get_capabilities` reads `self.extensions` (the map higher
layers populate) and advertises it under `ServerCapabilities.extensions`."""
server = Server("with-ext")
settings = {"k": 1}
server.extensions = {_EXTENSION_ID: settings}
assert server.get_capabilities().extensions == {_EXTENSION_ID: settings}
async def test_modern_connection_carries_the_advertised_extensions_map() -> None:
"""SDK-defined: over a modern (`server/discover`) connection the client reads
the server's advertised extension map from `server_capabilities`."""
server = MCPServer("host", extensions=[_Extension()])
async with Client(server, mode="auto") as client:
assert client.server_capabilities.extensions == snapshot({"com.example/x": {"k": 1}})
async def test_legacy_handshake_carries_the_advertised_extensions_map() -> None:
"""SDK-defined: over a legacy (`initialize`) connection the client also reads
the server's advertised extension map from `server_capabilities`."""
server = MCPServer("host", extensions=[_Extension()])
async with Client(server, mode="legacy") as client:
assert client.server_capabilities.extensions == snapshot({"com.example/x": {"k": 1}})
async def test_server_accepts_capability_for_client_advertised_extension() -> None:
"""SDK-defined: a client advertising `extensions={id: ...}` makes the
server-side `check_client_capability` return True when queried for that id.
Observed inside a tool handler."""
queried = types.ClientCapabilities(extensions={_EXTENSION_ID: {}})
supported: list[bool] = []
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> types.CallToolResult:
assert params.name == "probe"
supported.append(ctx.session.check_client_capability(queried))
return types.CallToolResult(content=[])
async def list_tools(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListToolsResult:
return types.ListToolsResult(tools=[types.Tool(name="probe", input_schema={"type": "object"})])
server = Server("checker", on_call_tool=call_tool, on_list_tools=list_tools)
async with Client(server, extensions=[advertise(_EXTENSION_ID, {"mimeTypes": ["text/html"]})]) as client:
await client.call_tool("probe", {})
assert supported == [True]
async def test_server_rejects_capability_for_undeclared_extension() -> None:
"""SDK-defined: when the client advertises one extension, a server query for a
*different* identifier returns False - presence, not value, is the check."""
queried = types.ClientCapabilities(extensions={_OTHER_EXTENSION_ID: {}})
supported: list[bool] = []
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> types.CallToolResult:
assert params.name == "probe"
supported.append(ctx.session.check_client_capability(queried))
return types.CallToolResult(content=[])
async def list_tools(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListToolsResult:
return types.ListToolsResult(tools=[types.Tool(name="probe", input_schema={"type": "object"})])
server = Server("checker", on_call_tool=call_tool, on_list_tools=list_tools)
async with Client(server, extensions=[advertise(_EXTENSION_ID, {"mimeTypes": ["text/html"]})]) as client:
await client.call_tool("probe", {})
assert supported == [False]
async def test_server_rejects_capability_when_client_advertises_no_extensions() -> None:
"""SDK-defined: a client that declares no extensions makes any server
`check_client_capability` query for an extension return False."""
queried = types.ClientCapabilities(extensions={_EXTENSION_ID: {}})
supported: list[bool] = []
async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> types.CallToolResult:
assert params.name == "probe"
supported.append(ctx.session.check_client_capability(queried))
return types.CallToolResult(content=[])
async def list_tools(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListToolsResult:
return types.ListToolsResult(tools=[types.Tool(name="probe", input_schema={"type": "object"})])
server = Server("checker", on_call_tool=call_tool, on_list_tools=list_tools)
async with Client(server) as client:
await client.call_tool("probe", {})
assert supported == [False]