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
+51
View File
@@ -0,0 +1,51 @@
# custom-methods
Register and call a vendor-prefixed JSON-RPC method that is not part of the
MCP spec. The server uses the low-level `Server.add_request_handler` (there is
no `MCPServer` surface for this, so `server.py` is lowlevel-native and there is
no `server_lowlevel.py` sibling); the client drops to `client.session` to send
it.
## Run it
```bash
# stdio (default — the client spawns the server as a subprocess)
uv run python -m stories.custom_methods.client
# HTTP — the client self-hosts the server on a free port, runs, then tears it down
uv run python -m stories.custom_methods.client --http
```
## What to look at
- `client.py` `main` — the body opens with `Client(target, mode=mode)`. The
vendor request rides whichever protocol era `mode` selects; nothing else in
the story changes between eras.
- `server.py` `SearchParams` — subclasses `types.RequestParams` so `_meta`
(and on a 2026-07-28 connection, the reserved `io.modelcontextprotocol/*`
envelope keys) parse uniformly without extra code.
- `server.py` `add_request_handler("acme/search", SearchParams, search)` — the
method string is the wire `method`; use a vendor prefix so it can never
collide with a future spec method.
- `client.py` `client.session.send_request(...)``Client` only exposes spec
verbs, so vendor methods go through the underlying `ClientSession`.
`send_request` accepts any `types.Request` subclass.
## Caveats
- The TypeScript SDK's equivalent example also shows a custom server→client
**notification** (`acme/searchProgress`). The Python client can observe
vendor notifications via `NotificationBinding` (see
`docs/advanced/extensions.md`). That half is omitted here because the
lowlevel server has no surface for emitting vendor notifications yet.
## Spec
[Requests — basic protocol](https://modelcontextprotocol.io/specification/2025-11-25/basic#requests)
(JSON-RPC request shape; vendor method names live outside the spec's reserved
set).
## See also
`serve_one/` (the per-exchange driver that runs registered handlers),
`middleware/` (wrapping every registered handler, including vendor methods).
+37
View File
@@ -0,0 +1,37 @@
"""Send a vendor-prefixed request via the `client.session` escape hatch."""
from typing import Literal
import mcp_types as types
from mcp.client import Client
from stories._harness import Target, run_client
class SearchParams(types.RequestParams):
query: str
limit: int = 10
class SearchRequest(types.Request[SearchParams, Literal["acme/search"]]):
method: Literal["acme/search"] = "acme/search"
params: SearchParams
class SearchResult(types.Result):
items: list[str]
async def main(target: Target, *, mode: str = "auto") -> None:
async with Client(target, mode=mode) as client:
# `Client` only exposes spec-defined verbs, so vendor methods have to drop one
# layer to `client.session` today — there is no `Client`-level API for them
# yet, and whether `.session` stays public is undecided. `send_request`
# accepts any `Request` subclass.
request = SearchRequest(params=SearchParams(query="mcp", limit=3))
result = await client.session.send_request(request, SearchResult)
assert result.items == ["mcp-0", "mcp-1", "mcp-2"], result
if __name__ == "__main__":
run_client(main)
+39
View File
@@ -0,0 +1,39 @@
"""Register a vendor-prefixed JSON-RPC method on the low-level Server.
`MCPServer` has no public surface for arbitrary method registration, so this
story's `server.py` is lowlevel-native (no `server_lowlevel.py` sibling).
"""
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
class SearchParams(types.RequestParams):
"""Subclass `RequestParams` so `_meta` (and the 2026 envelope keys) parse uniformly."""
query: str
limit: int = 10
class SearchResult(types.Result):
items: list[str]
def build_server() -> Server[Any]:
server = Server("custom-methods-example")
async def search(ctx: ServerRequestContext[Any], params: SearchParams) -> SearchResult:
items = [f"{params.query}-{i}" for i in range(params.limit)]
return SearchResult(items=items)
server.add_request_handler("acme/search", SearchParams, search)
return server
if __name__ == "__main__":
run_server_from_args(build_server)