docs: lead the README client example with a URL, not the server object - #3315
Conversation
The sentence pointed at a localhost URL and called it a "remote server", which reads oddly since nothing earlier in the README starts a server on that port. Present the URL as an example and describe what actually changes: the same client code talks to a server over HTTP. Closes #3313 No-Verification-Needed: doc-only wording change in README.md
The "client in 10 lines" example imported the server object and connected to it in-process, then offered a URL as the variation. In-process is a testing technique, so this inverts it: serve the file you just wrote with `mcp run server.py --transport streamable-http`, connect to it by URL, and mention stdio and the in-memory test path in the closing sentence. Also fix the `mcp run --transport` help text, which still said "stdio or sse" although streamable-http is accepted and is what the README now uses. Closes #3313
No-Verification-Needed: doc-only wording change in README.md
There was a problem hiding this comment.
Additional findings (outside current diff — PR may have been updated during review):
-
🟡
src/mcp/cli/cli.py— [quality] pre-existing:mcp run --helpstill shows a stale docstring claiming a "Module approach: server.py - runs the module directly, expecting a server.run() call" that does not exist, right next to the option help this PR fixes for the same stalenessExtended reasoning...
The PR's stated goal is making
mcp run --helpaccurate (it fixes the --transport line at src/mcp/cli/cli.py:314), but the command docstring shown in the same help output (lines 318-327) still misdescribes behavior: withserver.pyand no:object, run() never "runs the module directly, expecting a server.run() call" — it always imports the file, searches for a module-levelmcp/server/app, and callsserver.run(**kwargs)itself (lines 328-348). docs/run/index.md:113-122 documents the opposite of the docstring: "mcp runimports the file ... and callsrun()on it ... Yourif __name__ == \"__main__\":block never executes here". Concrete cost: users following the new README command who checkmcp run --helpread a description of a code path that does not exist, the same class of stale-help defect this PR was opened to fix; a user who writes a server.py that only calls run() inside main() gets "No server object found" despite the help saying their file shape is supported.Verification: nit (pre-existing docstring, but the diff edits the same help output). The claim is factually true. The
run()docstring shown inmcp run --help(src/mcp/cli/cli.py:320-322) still says: "1. Module approach: server.py - runs the module directly, expecting a server.run() call." No such mode exists. Both file_spec forms take the identical path: src/mcp/cli/cli.py:328 `file, server_object = _pars
| ```python | ||
| import asyncio | ||
|
|
||
| from mcp import Client | ||
|
|
||
| from server import mcp | ||
|
|
||
|
|
||
| async def main() -> None: | ||
| async with Client(mcp) as client: | ||
| async with Client("http://localhost:8000/mcp") as client: | ||
| result = await client.call_tool("add", {"a": 1, "b": 2}) | ||
| print(result.structured_content) # {'result': 3} | ||
|
|
||
|
|
||
| asyncio.run(main()) | ||
| ``` |
There was a problem hiding this comment.
🟡 [quality] nit: the rewritten README client example is an inline, untracked code block instead of using the repo's snippet-source mechanism (a docs_src/ module wrapped in <!-- snippet-source ... --> markers), which the server block directly above (lines 53-73, sourcing docs_src/index/tutorial001.py) already uses and which the pre-commit hook scripts/update_readme_snippets.py --check enforces; near-identical client code already lives in docs_src/client_transports/tutorial002.py.
Extended reasoning...
Concrete cost: the landing-page client example is the only code block in the README exempt from the repo's drift-prevention tooling — it is neither synced by the 'Check README snippets are up to date' pre-commit hook nor executed by the tests/docs_src suite that runs docs_src tutorial modules. When Client's constructor, call_tool signature, or structured_content shape next changes, the server block above will be caught and updated automatically while this rewritten client block silently goes stale, breaking the first client a reader copies. Fix at the right depth: add e.g. docs_src/index/tutorial002.py (mirroring docs_src/client_transports/tutorial002.py) with a matching tests/docs_src test and wrap the block in snippet-source markers. Note the inline pattern predates this PR (the old block was also inline), but this PR rewrites the block wholesale, so adopting the existing mechanism was the natural moment.
Verification: nit — README.md:97-110's rewritten client example is an inline code block with no <!-- snippet-source --> markers, while the server block directly above (README.md:53, <!-- snippet-source docs_src/index/tutorial001.py -->) uses the repo's snippet mechanism enforced by the pre-commit hook at .pre-commit-config.yaml:57-62 (readme-snippets, scripts/update_readme_snippets.py --check); the scri
Reworks the README's "A client in 10 lines" so the first client a reader sees connects the way a real program does: serve the
server.pyfrom the section above withuv run mcp run server.py --transport streamable-http, thenClient("http://localhost:8000/mcp"). The closing sentence mentions stdio and custom transports and links to the Clients docs; in-memory is left to the Testing page.Also fixes the
mcp run --transporthelp text, which still said "stdio or sse" even thoughstreamable-httpis accepted (and is what the README now tells you to run).Motivation and Context
Started as #3313 (calling a localhost URL a "remote server" read oddly). Looking at it more closely, the bigger problem was that the example imported the server object and connected in-process, then offered the URL as the variation. In-process is a testing convenience, not how anyone connects to a real server, so the landing example shouldn't lead with it.
Closes #3313
How Has This Been Tested?
Ran the README verbatim: extracted the server block, the
mcp run ... --transport streamable-httpline and the client block into a scratch directory, started the server with that exact command (listens on127.0.0.1:8000/mcp), ran the client, got{'result': 3}. Also checkedlocalhostvs127.0.0.1, a second run against the same server, and thatmcp run --helpshows the new text.pre-commit run --files README.md src/mcp/cli/cli.pypasses.Breaking Changes
None.
Types of changes
Checklist
Additional context
The docs site has the same lean in more places (the Clients section leads with in-memory, several "what
Clientaccepts" lists put the server object first). That's a broader docs pass and is deliberately not in this PR.AI Disclaimer