Skip to content
Forward Deployed

Forward Deployed
Lessons and contents
MCP Applications & RAG over MCPLesson 3 of 9
9 lessons
Learn

MCP servers with Claude Desktop

Source
From the RAG & Agents Engineering course
By
Sourav Banerjee
Last updated
9 Nov 2026

Report an issue

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 --directory sets the working directory. Every course entry ships with an /ABSOLUTE/PATH/TO/… placeholder to replace. The 40.4 server writes to Path(".") / "jobs"; uv --directory makes that resolve inside the project, where it would otherwise land wherever the host starts the process.
  • Secrets go in env, per server. notionApi gets NOTION_TOKEN and nothing else does. excel sets a paging limit, EXCEL_MCP_PAGING_CELLS_LIMIT, to 4000 cells.
  • Some entries fetch code at launch. @playwright/mcp@latest takes whatever version is current, and the sqlite entry installs from a Git repository each time uvx needs 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.

Diagram: Host MCP client; Server process; Host logDiagram: Host MCP client; Server process; Host log

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 logged Failed to parse JSONRPC message from server with 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 with uv --directory, give each server only its own secrets in env, and pin versions instead of @latest.
  • A remote server reaches a stdio host through a bridge such as mcp-remote. Keep --allow-http to 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.

Rate your confidence and keep notes on the module page.