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
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:
@@ -0,0 +1,139 @@
|
||||
# First steps
|
||||
|
||||
The **[landing page](../index.md)** moves fast: write a server, run it, call a tool.
|
||||
|
||||
This page takes it slowly, with all three things a server can expose, and a name for everything along the way.
|
||||
|
||||
## Host, client, and server
|
||||
|
||||
Three words you'll see on every page from here on:
|
||||
|
||||
* A **host** is the LLM application: Claude, an IDE, an agent runtime. It's the thing the user is talking to.
|
||||
* 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.
|
||||
|
||||
## The three primitives
|
||||
|
||||
A server exposes exactly three kinds of thing. What separates them is **who decides to use them**:
|
||||
|
||||
| Primitive | Controlled by | What it is | Example |
|
||||
|---------------|-----------------|-----------------------------------------------------|------------------------------------|
|
||||
| **Tools** | The model | A function the model calls to take an action | An API call, a database write |
|
||||
| **Resources** | The application | Data the host loads into the model's context | A file's contents, an API response |
|
||||
| **Prompts** | The user | A reusable message template the user invokes by name | A slash command, a menu entry |
|
||||
|
||||
"Controlled by" is the whole point of the split. A tool runs because the **model** decided to call it. A resource is attached because the **application** decided the model needed it. A prompt runs because the **user** picked it.
|
||||
|
||||
!!! info
|
||||
If you've built a web API you already have most of the intuition: a **resource** is a `GET`
|
||||
(it loads data and changes nothing) and a **tool** is a `POST` (it does work and may have
|
||||
side effects). A **prompt** has no HTTP analogue; it's closer to a saved query the user runs
|
||||
by name.
|
||||
|
||||
## One server, all three
|
||||
|
||||
```python title="server.py" hl_lines="6 12 18"
|
||||
--8<-- "docs_src/first_steps/tutorial001.py"
|
||||
```
|
||||
|
||||
Three plain functions, three decorators. Each decorator is the entire registration:
|
||||
|
||||
* `@mcp.tool()` makes `add` a **tool**.
|
||||
* `@mcp.resource("greeting://{name}")` makes `greeting` a **resource template**: the `{name}` in the URI is the function's parameter.
|
||||
* `@mcp.prompt()` makes `summarize` a **prompt**. The string it returns becomes a user message.
|
||||
|
||||
Everything else (the name, the description, the argument schema) the SDK reads from the function itself: its name, its docstring, its type hints. You never declared any of it separately.
|
||||
|
||||
!!! tip
|
||||
The two halves of the SDK have two import paths: `from mcp import Client` and
|
||||
`from mcp.server import MCPServer`. There is no `from mcp import MCPServer`.
|
||||
|
||||
### Try it
|
||||
|
||||
Run it with the MCP Inspector:
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
Open the URL it prints. The Inspector has one tab per primitive; walk through them in order.
|
||||
|
||||
**Tools.** One entry: `add`, described as *Add two numbers.* The form has a required integer field for `a` and another for `b`. Fill them in, call it, and the result is `3`. The Inspector built that form from `a: int, b: int`. So does every other client.
|
||||
|
||||
**Resources.** The *Resources* list is empty. `greeting` is under **Resource Templates**, because `greeting://{name}` has a parameter: there is no single resource to list until someone supplies a `name`. Give it `World` and read it:
|
||||
|
||||
```text
|
||||
Hello, World!
|
||||
```
|
||||
|
||||
**Prompts.** One entry: `summarize`, with a single required `text` argument. Get it with some text and you receive one message with `role: user` and your rendered string as the content. That's all a prompt is: a function that builds messages.
|
||||
|
||||
The Inspector ran your server over **stdio**, one of the transports an MCP server can speak. You don't pick one yet; **[Running your server](../run/index.md)** is the page for that.
|
||||
|
||||
## Capabilities
|
||||
|
||||
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):
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from mcp import Client
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
print(client.server_capabilities.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
```text
|
||||
{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}}
|
||||
```
|
||||
|
||||
That dictionary is your server's declared **capabilities**. It's the first thing every connecting client learns:
|
||||
|
||||
| Capability | The client may now call |
|
||||
|-------------|------------------------------------------------------------|
|
||||
| `tools` | `tools/list`, `tools/call` |
|
||||
| `resources` | `resources/list`, `resources/templates/list`, `resources/read` |
|
||||
| `prompts` | `prompts/list`, `prompts/get` |
|
||||
|
||||
`MCPServer` serves all three primitives, so all three are always declared.
|
||||
|
||||
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)**.
|
||||
|
||||
## What you did not write
|
||||
|
||||
Look back over this page. You wrote three small Python functions. You did **not** write:
|
||||
|
||||
* 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.
|
||||
|
||||
That ratio is the whole point of the SDK.
|
||||
|
||||
## Recap
|
||||
|
||||
* A **host** is the LLM app, a **client** is its MCP-speaking half, a **server** is what you build.
|
||||
* Tools are **model**-controlled, resources are **application**-controlled, prompts are **user**-controlled.
|
||||
* 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.
|
||||
|
||||
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)**.
|
||||
Reference in New Issue
Block a user