|
| 1 | +# Connect to a real host |
| 2 | + |
| 3 | +A **host** is the application your server ends up inside: Claude Desktop, Claude Code, an IDE. The host is what the user talks to. Inside it, an MCP **client** launches your server as a child process and speaks to it over that process's stdin and stdout. |
| 4 | + |
| 5 | +Which means connecting to a host is one act: you tell it **the command that starts your server**. Everything on this page — two CLI commands, three JSON files — is a different place to put that same command. |
| 6 | + |
| 7 | +## One server, every host |
| 8 | + |
| 9 | +```python title="server.py" hl_lines="3 33-34" |
| 10 | +--8<-- "docs_src/real_host/tutorial001.py" |
| 11 | +``` |
| 12 | + |
| 13 | +Two tools and a resource, one file. Two things about that file matter to every host below: |
| 14 | + |
| 15 | +* `run()` is under `if __name__ == "__main__":`. Everything below **imports** this file rather than executing it, so an unguarded `run()` would start a server the moment anything loaded the module. |
| 16 | +* The server object is a module-level global named `mcp`. That's the name `mcp run` looks for (`server` and `app` also work). Call it something else and you name it explicitly: `mcp run server.py:bookshop`. |
| 17 | + |
| 18 | +That is the last line of Python on this page. From here down it is all host configuration. |
| 19 | + |
| 20 | +## The launch command |
| 21 | + |
| 22 | +Every host below gets the same command: |
| 23 | + |
| 24 | +```bash |
| 25 | +uv run --with "mcp[cli]==2.0.0b1" mcp run /absolute/path/to/server.py |
| 26 | +``` |
| 27 | + |
| 28 | +One command for all of them because `uv run --with` resolves the pinned SDK into a fresh environment on the spot: it works from any directory, needs no project and no virtual environment to activate, and always gets the exact `mcp` version these docs describe. That matters here more than anywhere else, because a host launches your server from *its* working directory with a near-empty environment — not from your shell. |
| 29 | + |
| 30 | +It is also the command `mcp install` writes into Claude Desktop's config for you (below), so what you type by hand and what the tool generates agree. |
| 31 | + |
| 32 | +!!! warning "The version pin is not optional" |
| 33 | + v2 of this SDK is in beta, and installers never select a pre-release unless you name one. An |
| 34 | + unpinned `--with "mcp[cli]"` gives you the latest **v1.x**, which these docs do not describe. |
| 35 | + Use the exact pin from **[Installation](installation.md)**. |
| 36 | + |
| 37 | +!!! tip "If a host can't find `uv`" |
| 38 | + A host spawns your server with a minimal `PATH`, and `uv` may not be on it. Replace the bare |
| 39 | + `uv` with the absolute path from `which uv` (macOS/Linux) or `where uv` (Windows). That is |
| 40 | + exactly what `mcp install` writes. |
| 41 | + |
| 42 | +## Claude Desktop |
| 43 | + |
| 44 | +The one host the SDK can configure for you: |
| 45 | + |
| 46 | +```bash |
| 47 | +uv run mcp install server.py |
| 48 | +``` |
| 49 | + |
| 50 | +That's it. `mcp install` imports the file to read the server's name, finds Claude Desktop's config file, and writes the launch command into it — converting your path to an absolute one on the way, so you don't have to. |
| 51 | + |
| 52 | +There is nothing to be mystified by. This is the entry it writes: |
| 53 | + |
| 54 | +```json |
| 55 | +{ |
| 56 | + "mcpServers": { |
| 57 | + "Bookshop": { |
| 58 | + "command": "/absolute/path/to/uv", |
| 59 | + "args": [ |
| 60 | + "run", |
| 61 | + "--frozen", |
| 62 | + "--with", |
| 63 | + "mcp[cli]==2.0.0b1", |
| 64 | + "mcp", |
| 65 | + "run", |
| 66 | + "/absolute/path/to/server.py" |
| 67 | + ] |
| 68 | + } |
| 69 | + } |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +That's the launch command from the section above with two additions: the absolute path to `uv`, and `--frozen` so `uv` never rewrites a lockfile it happens to be near. It lands in `claude_desktop_config.json`, which lives at: |
| 74 | + |
| 75 | +* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` |
| 76 | +* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` |
| 77 | + |
| 78 | +You can write that file by hand. `mcp install` exists so you don't make the two classic mistakes (a relative path, a missing version pin) while doing it. |
| 79 | + |
| 80 | +Fully quit Claude Desktop — not just its window — and reopen it. |
| 81 | + |
| 82 | +!!! warning |
| 83 | + `mcp install` fails with `Claude app not found` if Claude Desktop's config *directory* doesn't |
| 84 | + exist yet. Install Claude Desktop and run it once — that's what creates the directory. |
| 85 | + |
| 86 | +!!! tip |
| 87 | + Claude Desktop starts your server in its own process, so your shell's environment variables are |
| 88 | + not there. `uv run mcp install server.py -v API_KEY=abc123` (or `-f .env`) records them in the |
| 89 | + entry's `env` field. `--name` overrides the entry name; it defaults to the server's `name`. |
| 90 | + |
| 91 | +## Claude Code |
| 92 | + |
| 93 | +There is no file to edit. Register the server with the `claude` CLI; everything after `--` is the launch command. |
| 94 | + |
| 95 | +```bash |
| 96 | +claude mcp add bookshop -- uv run --with "mcp[cli]==2.0.0b1" mcp run /absolute/path/to/server.py |
| 97 | +``` |
| 98 | + |
| 99 | +Run `/mcp` inside a Claude Code session to confirm `bookshop` is connected and its tools are listed. |
| 100 | + |
| 101 | +## Cursor |
| 102 | + |
| 103 | +Create `.cursor/mcp.json` in your project root. |
| 104 | + |
| 105 | +```json |
| 106 | +{ |
| 107 | + "mcpServers": { |
| 108 | + "bookshop": { |
| 109 | + "command": "uv", |
| 110 | + "args": ["run", "--with", "mcp[cli]==2.0.0b1", "mcp", "run", "/absolute/path/to/server.py"] |
| 111 | + } |
| 112 | + } |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +The same `command` plus `args`, under the same `mcpServers` key Claude Desktop uses. The server appears in Cursor's MCP settings with both tools listed. |
| 117 | + |
| 118 | +## VS Code |
| 119 | + |
| 120 | +Create `.vscode/mcp.json` in your project root. |
| 121 | + |
| 122 | +```json |
| 123 | +{ |
| 124 | + "servers": { |
| 125 | + "bookshop": { |
| 126 | + "type": "stdio", |
| 127 | + "command": "uv", |
| 128 | + "args": ["run", "--with", "mcp[cli]==2.0.0b1", "mcp", "run", "/absolute/path/to/server.py"] |
| 129 | + } |
| 130 | + } |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +Two differences from Cursor's file, and they are the only two: the wrapper key is `servers`, not `mcpServers`, and each entry declares its `type`. Confirm the trust prompt, then **MCP: List Servers** in the Command Palette shows `bookshop` running. |
| 135 | + |
| 136 | +!!! note |
| 137 | + You need VS Code 1.99 or later with the **GitHub Copilot** extension signed in (Copilot Free is |
| 138 | + enough), and Copilot Chat must be in **Agent** mode — the only mode that calls tools. |
| 139 | + |
| 140 | +## It doesn't show up |
| 141 | + |
| 142 | +Before you touch any host config, run the launch command yourself: |
| 143 | + |
| 144 | +```bash |
| 145 | +uv run --with "mcp[cli]==2.0.0b1" mcp run /absolute/path/to/server.py |
| 146 | +``` |
| 147 | + |
| 148 | +Nothing prints, and it doesn't return. That silence is correct: a stdio server is waiting for a host to speak first on stdin (`Ctrl-C` to stop it). A traceback or an immediate exit is the real bug, and now you can read it instead of guessing at it through a host. |
| 149 | + |
| 150 | +Once that command sits and waits, what's left is almost always one of three things: |
| 151 | + |
| 152 | +* **A relative path.** The host launches your server from *its* working directory, not the one you registered from. `server.py` where `/absolute/path/to/server.py` is needed is the single most common failure. If the host can't find `uv` either, that path has to be absolute too. |
| 153 | +* **The host is still running its old config.** Hosts read their config at launch. Claude Desktop in particular has to be *fully quit* — not just its window closed — and reopened before an edit to `claude_desktop_config.json` takes effect. |
| 154 | +* **Something reached stdout.** On stdio, stdout *is* the protocol. One stray `print()` and the host reads a corrupt message and drops the connection. Log with the `logging` module — it writes to stderr. **[Logging](../handlers/logging.md)** has the whole story. |
| 155 | + |
| 156 | +Claude Desktop keeps a log per server: `mcp-server-<NAME>.log` is your server's stderr, next to `mcp.log` for connections, under `~/Library/Logs/Claude` on macOS and `%APPDATA%\Claude\logs` on Windows. |
| 157 | + |
| 158 | +For anything past those three, **[Troubleshooting](../troubleshooting.md)** is the page. |
| 159 | + |
| 160 | +## Recap |
| 161 | + |
| 162 | +* A **host** (Claude Desktop, an IDE) runs an MCP client that launches your server as a child process over stdio. Connecting means giving it one launch command. |
| 163 | +* That command is `uv run --with "mcp[cli]==2.0.0b1" mcp run /absolute/path/to/server.py`: version-pinned, no venv to activate, works from any directory. The pin is mandatory while v2 is in beta. |
| 164 | +* **Claude Desktop** is the one host `mcp install` configures for you. It writes that same command — plus the absolute path to `uv` — into `claude_desktop_config.json`, so you never have to. |
| 165 | +* **Claude Code** is `claude mcp add bookshop -- <launch command>`. **Cursor** is `.cursor/mcp.json` under `mcpServers`. **VS Code** is `.vscode/mcp.json` under `servers`, each entry with a `type`. |
| 166 | +* Absolute paths everywhere, restart the host after editing its config, and never let anything but the SDK write to stdout. |
| 167 | + |
| 168 | +Every host on this page connected to the same file, with the same command. What that file can *expose* is the rest of these docs: **[Tools](../servers/tools.md)**, **[Resources](../servers/resources.md)**, and every transport besides stdio in **[Running your server](../run/index.md)**. |
0 commit comments