Agents & MCP/

One MCP server, four clients

Pointing Cursor, a raw SDK client, LangChain, and LangGraph at one MCP server to test "write once, call from any agent" — and why transport choice, structured I/O, and not being an SSRF proxy are the real work.


title: "One MCP server, four clients" series: "Agents & MCP" date: "2026-03-24" summary: "Pointing Cursor, a raw SDK client, LangChain, and LangGraph at one MCP server to test "write once, call from any agent" — and why transport choice, structured I/O, and not being an SSRF proxy are the real work."

When the Model Context Protocol showed up, the pitch was "write your tool once and any agent can call it." That's a nice sentence. I didn't believe it until I'd tried to break it, so a few months after the spec landed I built one small MCP server and pointed four very different clients at the same endpoint to see whether "any agent" was real or marketing.

The four:

  • Cursor, the IDE, configured to talk to the server directly.
  • A raw SDK client — no framework, just the protocol, listing tools and calling them by hand.
  • A LangChain agent that loaded the server's tools as LangChain tools.
  • A LangGraph agent — a small ReAct-style graph over gpt-4o that streamed its tool calls as it went.

Same server. Same tools. Four consumers that share almost no code and were written by different teams with different ideas about what an "agent" is. All four discovered the tools, called them, and got structured results back. The promise held. That sounds anticlimactic, but it's the whole point: I didn't write four integrations. I wrote one server and four thin clients, and the protocol did the adapting.

What actually varied between clients wasn't whether tool-calling worked — it was the texture around it. The raw SDK made the request/response cycle explicit and obvious. LangChain hid it behind its tool abstraction. LangGraph made the control flow visible: you watch the graph go node → tool → back → node, and streaming the intermediate steps (astream with values) turned the agent from a black box into something I could actually debug. If you want to understand what an agent is doing, the framework that shows you the loop beats the one that hides it.

Pushing past text

Most MCP tutorials stop at tools that take a string and return a string or a small JSON blob. I wanted to know what happened at the edges, so I built a tool that moves real files — encode a binary as base64, ship it through the tool call over SSE, decode it on the other side, and route it by MIME type (a zip goes one way, an image another, source text another). It round-tripped actual binaries — archives, images, source files — not just toy payloads.

Two things came out of that:

  • Transport matters more than the tutorials admit. SSE (and now Streamable HTTP) behaves very differently from stdio for anything chunked or long-running. stdio is dead simple for a local desktop client; HTTP-style transports are what you want the moment the tool is remote, slow, or streaming. Picking the transport is a real design decision, not a config detail.
  • Tool I/O design is the actual work. The protocol will happily carry whatever you put in the result, but you have to decide the shape — how a binary is encoded, how errors come back as structured data instead of a stack trace, how you keep a result from blowing past a context window. The MCP part is easy. Designing tools an agent can use well is the part that takes taste.

Where it went next

That lab was the cheap version — a server built to prove the model to myself. The expensive version is doing it properly for something real, which is what I did with geocheck: an AI-visibility checker that ships as a CLI and an MCP server exposing three tools (check_visibility, list_visibility_states, explain_metric) over Streamable HTTP. This time the boring production concerns were the point — outbound calls restricted to a fixed allowlist so the server can't be tricked into fetching arbitrary URLs (most public MCP servers still get this wrong), a required token before it will bind to anything non-local, and hard caps on how much work a single call can request.

The takeaway

"Write once, call from any agent" turned out to be true, and that's a bigger deal than it sounds — it means the unit of reuse for agent tooling is finally the tool, not the integration. But the protocol working is table stakes. The parts that separate a toy MCP server from one you'd actually deploy are the unglamorous ones: transport choice, structured I/O, and refusing to be an open SSRF proxy. Build the toy to convince yourself it works. Then build the real one like anything else you put on the internet.