# Build a minimal MCP server with the Python SDK's MCPServer, @mcp.tool and stdio

> A minimal Python MCP server creates an MCPServer, decorates one function with @mcp.tool(), calls mcp.run() to serve over stdio, and is tested with the MCP Inspector or a JSON-RPC client.

## Answer

Install the `mcp` package, create `MCPServer("Demo")` from `mcp.server`, decorate a typed function with `@mcp.tool()`, and call `mcp.run()`, which serves over stdio by default. Test it with `uv run mcp dev server.py`, which opens the MCP Inspector. This page describes **`mcp` 2.2.0 on Python 3.11**, the version we ran. The SDK README says v2 supports MCP specification **2026-07-28**, and that `mcp>=1.28,<2` keeps you on the older v1 line.

## Details

### 1. Install

```sh
uv add "mcp[cli]"      # or: pip install "mcp[cli]"
```

The `cli` extra provides the `mcp` command used for `mcp dev`. Plain `mcp` is enough for a server that you run with `python`. PyPI lists Python 3.10 or later as required.

### 2. Write the server (`server.py`)

```python
from mcp.server import MCPServer

mcp = MCPServer("Demo")


@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b


if __name__ == "__main__":
    mcp.run()
```

The README and first-steps guide show the class and decorator. `MCPServer.run(transport='stdio', ...)` also accepts `'sse'` and `'streamable-http'`, so a bare `mcp.run()` means stdio. The docs run a stdio server with `python server.py`.

### 3. Test it

- **Inspector:** `uv run mcp dev server.py` starts the server and the MCP Inspector together.
- **Any client:** write JSON-RPC lines to the process's stdin and read stdout.

### Verification

We ran the file above in a fresh virtualenv with `mcp` 2.2.0 and sent `initialize`, `notifications/initialized`, `tools/list` and `tools/call` as newline-delimited JSON. Real replies, trimmed to the interesting part:

```text
initialize  -> "protocolVersion":"2025-11-25","serverInfo":{"name":"Demo","version":""}
tools/list  -> {"description":"Add two numbers.","inputSchema":{"properties":{"a":{"title":"A","type":"integer"},"b":{"title":"B","type":"integer"}},"required":["a","b"],"type":"object","title":"addArguments"},"name":"add","outputSchema":{"properties":{"result":{"title":"Result","type":"integer"}},"required":["result"],"type":"object","title":"addOutput"}}
tools/call {"a":2,"b":3} -> {"content":[{"text":"5","type":"text"}],"isError":false,"structuredContent":{"result":5}}
tools/call {"a":"x","b":3} -> {"content":[{"text":"Error executing tool add: 1 validation error for addArguments\na\n  Input should be a valid integer, ...","type":"text"}],"isError":true}
```

Our `initialize` asked for 2025-11-25 and the server answered with it. We did not test a 2026-07-28 client.

### How the schema is built

| You write | Client sees |
|---|---|
| Function name `add` | Tool `name` |
| Docstring `"""Add two numbers."""` | Tool `description` |
| Type hints `a: int, b: int` | `inputSchema` (both required) |
| Return type `-> int` | `outputSchema` and `structuredContent`, plus a text block |

### Pitfall: writing to stdout

On stdio, stdout carries the protocol. The SDK docs say that while serving it diverts output that is *flushed* to stdout, such as a flushed `print()`, to stderr. They also say buffered output or prints made before serving starts can still reach the wire, and that `logging`, which writes each record to stderr, is the right tool. Log with `logging` or `print(..., file=sys.stderr)`, and do not rely on the diversion.

## See also

- [[mcp-server-typescript-minimal]]
- [[mcp-transports-stdio-vs-http]]
- [[mcp-connection-errors]]
- [[mcp-tools-resources-prompts]]

## Sources

- [MCP Python SDK — README](https://github.com/modelcontextprotocol/python-sdk)
- [MCP Python SDK — First steps](https://py.sdk.modelcontextprotocol.io/get-started/first-steps/)
- [MCP Python SDK — Running your server](https://py.sdk.modelcontextprotocol.io/run/)
- [mcp on PyPI](https://pypi.org/project/mcp/)

## Sources

- [MCP Python SDK — README](https://github.com/modelcontextprotocol/python-sdk)
- [MCP Python SDK — First steps](https://py.sdk.modelcontextprotocol.io/get-started/first-steps/)
- [MCP Python SDK — Running your server](https://py.sdk.modelcontextprotocol.io/run/)
- [mcp on PyPI](https://pypi.org/project/mcp/)