MCP servers with Claude Desktop
- Source
- From the RAG & Agents Engineering course
- By
- Sourav Banerjee
- Last updated
- 9 Nov 2026
Claude Desktop
Larkspur adds its handbook server to a desktop host. You will inspect launch paths and working directories, then test whether a startup banner corrupts the stdio channel used for MCP messages.
Step 1: Read the host launcher configuration
Claude Desktop is an MCP host: it owns the model, the chat window and one client per server. You give it servers through claude_desktop_config.json, a map under mcpServers from a name to the command that starts the server. The project's config is the course's whole MCP toolbox in one file, thirteen entries:
| Launcher | Entries | What it starts |
|---|---|---|
uv |
5 | The course's own servers: weather, math_server, job_search (40.4), chroma_db_rag (40.5), research_server (40.6) |
npx |
5 | Published Node packages: playwright, airbnb, notionApi, filesystem, and remote-research-server through mcp-remote |
uvx |
2 | Published Python packages: ppt, and sqlite straight from a Git URL |
cmd |
1 | excel, as cmd /c npx --yes @negokaz/excel-mcp-server, which only runs on Windows |
A Larkspur entry for the handbook server looks like the course's own:
"larkspur-handbook": {
"command": "uv",
"args": ["--directory", "/ABSOLUTE/PATH/TO/larkspur-handbook", "run", "server.py"]
}
Step 2: Resolve paths, environment and work directories
What the file teaches beyond its syntax:
- Paths are absolute, and
--directorysets the working directory. Every course entry ships with an/ABSOLUTE/PATH/TO/…placeholder to replace. The 40.4 server writes toPath(".") / "jobs";uv --directorymakes that resolve inside the project, where it would otherwise land wherever the host starts the process. - Secrets go in
env, per server.notionApigetsNOTION_TOKENand nothing else does.excelsets a paging limit,EXCEL_MCP_PAGING_CELLS_LIMIT, to 4000 cells. - Some entries fetch code at launch.
@playwright/mcp@latesttakes whatever version is current, and thesqliteentry installs from a Git repository each timeuvxneeds it. Pin versions for anything you depend on. - A remote server needs a bridge. The config reaches the EC2 server from lesson 7 of T12.3 through
npx mcp-remote http://<EC2 address>:8000/mcp --allow-http, a local stdio process that forwards to the remote URL. mcp-remote 0.15.2's README says--allow-http"should only be used in secure private networks where traffic cannot be intercepted"; here it points at a public address.
Step 3: Count the tool surface the host exposes
Thirteen servers also means thirteen sets of tool definitions on offer to the model; T12.1 measures what connected servers cost in tokens.
Stdout belongs to the protocol
Step 1: Keep diagnostic text off the wire
Over stdio, the host writes requests to the server's stdin and reads responses from its stdout, one JSON-RPC message per line. Anything else the server writes to stdout arrives in the same stream.
Step 2: Observe a stray stdout line
The 06 server breaks this rule. Its main() prints a banner before mcp.run(transport="stdio"), and ingest_pdf prints progress lines while it works. A probe with mcp 1.30.0's stdio client and a server that prints the same way:
- Output unbuffered (
PYTHONUNBUFFERED=1): every stray line reached the client, which loggedFailed to parse JSONRPC message from serverwith the offending text (the banner's'LangChain Chroma Vector DB Server'among them), dropped the line and carried on. The tool results still arrived. - Default buffering: Python block-buffers stdout when it is a pipe, so the prints sat in the buffer and nothing reached the client during the short session. The bug was there and did not show.
Step 3: Send diagnostic logs to stderr
The Python client is forgiving; another host may not be. Send logs to stderr (print(..., file=sys.stderr) or the logging module, whose default handler writes there), and keep stdout for the protocol.
Try it yourself
Launch a stdio server with an ordinary print before it starts, first with normal buffering and then unbuffered output. Check the recorded parse error rather than interpreting a successful connection as clean protocol output. Move the diagnostic to stderr and keep stdout reserved for JSON-RPC.
Practice notebook
Run the complete implementation from the practice material after checking the trace or calculation above. Use its stated environment for live model or server calls; recorded model choices and timings are observations, not guaranteed outputs.
Continue learning
Run the notebooks
The code for this lesson is in these notebooks. Colab runs one in your browser: install the packages its first cell imports and set the API keys it reads. To run every notebook as written, clone the repository and use its uv environment.
| Notebook | GitHub | Colab |
|---|---|---|
| MCP servers with claude desktop (optional) | Open on GitHub | Run locally |
Key Takeaways
- In
claude_desktop_config.json, use absolute paths withuv --directory, give each server only its own secrets inenv, and pin versions instead of@latest. - A remote server reaches a stdio host through a bridge such as
mcp-remote. Keep--allow-httpto private networks where traffic cannot be intercepted. - Over stdio, stdout carries only JSON-RPC. Send logs to stderr, because buffering can hide stray prints in testing that break a stricter host.
Check yourself
1. How does the Claude Desktop config reach the remote EC2 server, and what does its --allow-http flag risk?
Through npx mcp-remote http://<EC2 address>:8000/mcp --allow-http, a local stdio process that forwards to the remote URL. mcp-remote 0.15.2's README says --allow-http should only be used in secure private networks where traffic cannot be intercepted; here it points at a public address.
2. The 40.4 server writes to a jobs folder relative to its working directory. Why does its Claude Desktop entry pass uv an absolute directory?
uv --directory sets the working directory, so Path(".") / "jobs" resolves inside the project; without it, the folder lands wherever the host starts the process. Every course entry ships with an /ABSOLUTE/PATH/TO/… placeholder you must replace.
3. The 06 server prints a banner to stdout, yet a short test session shows no parse errors. Is the server safe to ship?
No. Python block-buffers stdout when it is a pipe, so the prints sat in the buffer and never reached the client: the bug was there and did not show. Unbuffered, every stray line reached the client, which logged Failed to parse JSONRPC message from server. Send logs to stderr and keep stdout for the protocol, because another host may be less forgiving.