docs: stop presenting the in-memory client as the way to connect (#3443)
This commit is contained in:
parent
0c9136841f
commit
7bb486a10f
2 changed files with 803 additions and 598 deletions
|
|
@ -12,7 +12,7 @@ Three words you'll see on every page from here on:
|
|||
* A **client** lives inside the host and speaks MCP. The host runs one client per server it's connected to.
|
||||
* A **server** is what you build with this SDK. It exposes things to clients. It never talks to the model directly.
|
||||
|
||||
You write the server. Hosts are someone else's product. The SDK also gives you a `Client`. You'll use it to test your servers, and it shows up later on this page.
|
||||
You write the server. Hosts are someone else's product. The SDK also gives you a `Client`, the same class a host would use to reach a server by URL or launch it as a subprocess. It shows up later on this page, and it is also how you'll test your servers.
|
||||
|
||||
## The three primitives
|
||||
|
||||
|
|
@ -78,22 +78,20 @@ You saw three tabs in the Inspector. How did it know there were three?
|
|||
|
||||
When a client connects, the server declares its **capabilities**: which families of requests it will answer. The client uses that declaration to decide what to even ask for. You never wrote it; `MCPServer` declares it for you.
|
||||
|
||||
Look at it yourself. The SDK's `Client` accepts the server object directly and connects to it **in memory** (no subprocess, no port):
|
||||
Look at it yourself. Leave `server.py` running over HTTP in one terminal:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
from mcp import Client
|
||||
and point a client at it from another:
|
||||
|
||||
from server import mcp
|
||||
```python title="client.py" hl_lines="7-8"
|
||||
--8<-- "docs_src/first_steps/tutorial001_client.py"
|
||||
```
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
print(client.server_capabilities.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```console
|
||||
python client.py
|
||||
```
|
||||
|
||||
```text
|
||||
|
|
@ -113,8 +111,9 @@ That dictionary is your server's declared **capabilities**. It's the first thing
|
|||
Notice what isn't there. `completions` (argument autocomplete for resource templates and prompts) needs a handler you write, this server doesn't have one, so the capability is absent and a well-behaved client won't ask. That's the rule for everything optional: register the thing and the capability appears; **[Completions](../servers/completions.md)** proves it.
|
||||
|
||||
!!! info
|
||||
`Client(mcp)` is the same in-memory client every example in these docs is tested with, and
|
||||
it's how you'll test yours. It gets a whole page: **[Testing](testing.md)**.
|
||||
That `client.py` is a complete MCP client, and **[The Client](../client/index.md)** is its page.
|
||||
In a test you skip the terminal and the port and hand `Client` the server object itself,
|
||||
`Client(mcp)`. That gets a whole page too: **[Testing](testing.md)**.
|
||||
|
||||
## What you did not write
|
||||
|
||||
|
|
@ -123,7 +122,7 @@ Look back over this page. You wrote three small Python functions. You did **not*
|
|||
* A JSON Schema. `a: int, b: int` *is* the schema for `add`.
|
||||
* A request handler. `tools/list`, `resources/read`, `prompts/get`: all served for you.
|
||||
* A capability declaration. `MCPServer` made it for you.
|
||||
* A line of protocol. The version negotiation, the JSON-RPC framing, the capability exchange: all of it happened inside `mcp dev` and `Client(mcp)`, and you never saw it.
|
||||
* A line of protocol. The version negotiation, the JSON-RPC framing, the capability exchange: all of it happened inside `mcp dev` and `client.py`, and you never saw it.
|
||||
|
||||
That ratio is the whole point of the SDK.
|
||||
|
||||
|
|
@ -134,6 +133,6 @@ That ratio is the whole point of the SDK.
|
|||
* One decorator per primitive: `@mcp.tool()`, `@mcp.resource(uri)`, `@mcp.prompt()`. Name, description, and schema come from the function.
|
||||
* A URI with a `{param}` makes a resource **template**, listed separately from concrete resources.
|
||||
* The server's **capabilities** are declared for you, and a client only asks for what a server declares.
|
||||
* `Client(mcp)` connects to the server object in memory: your test harness from day one.
|
||||
* `Client("http://localhost:8000/mcp")` talks to your running server. Hand it the server object instead, `Client(mcp)`, and it is your test harness from day one.
|
||||
|
||||
Next up is **[Connect to a real host](real-host.md)**: this server inside Claude Desktop or an IDE, for real. Then **[Testing](testing.md)**: one page, one in-memory client, and you're never guessing whether it works. After that, each primitive gets its own page, starting with the one the model drives: **[Tools](../servers/tools.md)**.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Testing
|
||||
|
||||
The Python SDK ships a `Client` class with an **in-memory transport**: pass it your server object and it connects to it directly.
|
||||
The SDK's `Client` class, the same one that connects to a URL or launches a subprocess, also connects **in memory**: pass it your server object and it talks to it directly.
|
||||
|
||||
No subprocess. No port. No transport at all. It's the same idea as FastAPI's `TestClient`.
|
||||
No subprocess. No port. Nothing on a wire. It's the same idea as FastAPI's `TestClient`.
|
||||
|
||||
## Basic usage
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ instead of the sanitised one.
|
|||
|
||||
Leave it on in tests. It has no meaning in production code.
|
||||
|
||||
## In-process by default
|
||||
## Era-neutral by default
|
||||
|
||||
!!! note
|
||||
`Client(mcp)` connects in-process and is **era-neutral** by default: it probes the server and
|
||||
|
|
|
|||
Loading…
Reference in a new issue