chore: import upstream snapshot with attribution
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 12:10:27 +08:00
commit 49b9bb6724
992 changed files with 161690 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
# roots
> **Deprecated** in the 2026-07-28 protocol (SEP-2577); functional through the
> deprecation window. Migration: accept directory paths as ordinary tool
> parameters or resource URIs instead of relying on `roots/list`.
> TODO(maxisbey): revisit before beta.
The client passes a `list_roots_callback` returning the filesystem locations it
is willing to expose; a server tool calls `ctx.session.list_roots()` mid-request
and the client's callback answers it. Passing the callback is what makes the
client advertise the `roots` capability — there is no separate flag.
## Run it
```bash
# stdio (default — the client spawns the server as a subprocess)
uv run python -m stories.roots.client
# HTTP — the client self-hosts the server on a free port, runs, then tears it down
uv run python -m stories.roots.client --http --legacy
# same, against the lowlevel-API server variant
uv run python -m stories.roots.client --http --legacy --server server_lowlevel
```
## What to look at
- `client.py` `main` — the
`Client(target, mode=mode, list_roots_callback=list_roots)` construction is
the whole client-side story: the callback is wired in as a constructor
argument, and that alone advertises the capability.
- `client.py` `list_roots` — the callback takes a `ClientRequestContext` and
returns `ListRootsResult`.
- `server.py``await ctx.session.list_roots()` inside the tool body: a
server→client request that blocks until the callback answers.
- `server_lowlevel.py` — the same call from `ServerRequestContext.session`,
with the `CallToolResult` built by hand.
## Caveats
- **Legacy-era only.** `roots/list` is a server-initiated request with no
2026-07-28 wire carrier, so this story runs with `era = "legacy"` and the
harness pins the handshake path.
- `ctx.session.list_roots()` is `@deprecated`; the
`# pyright: ignore[reportDeprecated]` is deliberate. The non-deprecated
replacement is to accept directory paths as ordinary tool parameters (see the
banner above) — there is no successor server→client call.
- `ctx.session.*` is the interim 2-hop path; a later release will shorten it.
- `notifications/roots/list_changed` is intentionally not shown — removed in
2026-07-28 (SEP-2575) and deprecated on the legacy path.
## Spec
[Roots — client features](https://modelcontextprotocol.io/specification/2025-11-25/client/roots)
## See also
`legacy_elicitation/`, `sampling/` — sibling stories that exercise the same
legacy server→client request shape.
View File
+31
View File
@@ -0,0 +1,31 @@
"""Expose two filesystem roots and verify the server's tool can read them back."""
from mcp_types import ListRootsResult, Root, TextContent
from pydantic import FileUrl
from mcp.client import Client, ClientRequestContext
from stories._harness import Target, run_client
async def list_roots(context: ClientRequestContext) -> ListRootsResult:
return ListRootsResult(
roots=[
Root(uri=FileUrl("file:///workspace/project"), name="project"),
Root(uri=FileUrl("file:///workspace/scratch")),
]
)
async def main(target: Target, *, mode: str = "auto") -> None:
async with Client(target, mode=mode, list_roots_callback=list_roots) as client:
result = await client.call_tool("show_roots", {})
assert not result.is_error, result
assert isinstance(result.content[0], TextContent)
assert result.content[0].text == ("file:///workspace/project (project)\nfile:///workspace/scratch (unnamed)"), (
result.content[0].text
)
if __name__ == "__main__":
run_client(main)
+19
View File
@@ -0,0 +1,19 @@
"""Roots primitive: a tool asks the client which filesystem roots it may use."""
from mcp.server.mcpserver import Context, MCPServer
from stories._hosting import run_server_from_args
def build_server() -> MCPServer:
mcp = MCPServer("roots-example")
@mcp.tool(description="Return the filesystem roots the client has exposed.")
async def show_roots(ctx: Context) -> str:
result = await ctx.session.list_roots() # pyright: ignore[reportDeprecated]
return "\n".join(f"{root.uri} ({root.name or 'unnamed'})" for root in result.roots)
return mcp
if __name__ == "__main__":
run_server_from_args(build_server)
+36
View File
@@ -0,0 +1,36 @@
"""Roots primitive (lowlevel API): the same server→client round-trip, hand-built."""
from typing import Any
import mcp_types as types
from mcp.server.context import ServerRequestContext
from mcp.server.lowlevel import Server
from stories._hosting import run_server_from_args
def build_server() -> Server[Any]:
async def list_tools(
ctx: ServerRequestContext[Any], params: types.PaginatedRequestParams | None
) -> types.ListToolsResult:
return types.ListToolsResult(
tools=[
types.Tool(
name="show_roots",
description="Return the filesystem roots the client has exposed.",
input_schema={"type": "object"},
),
]
)
async def call_tool(ctx: ServerRequestContext[Any], params: types.CallToolRequestParams) -> types.CallToolResult:
assert params.name == "show_roots"
result = await ctx.session.list_roots() # pyright: ignore[reportDeprecated]
lines = [f"{root.uri} ({root.name or 'unnamed'})" for root in result.roots]
return types.CallToolResult(content=[types.TextContent(text="\n".join(lines))])
return Server("roots-example", on_list_tools=list_tools, on_call_tool=call_tool)
if __name__ == "__main__":
run_server_from_args(build_server)