Spots

Build an MCP Client for AI Agents: Config, Auth, Transport

Short answer: building an MCP-compatible client for AI agents is 20% protocol and 80% edge-case handling. Each client (Cursor, Claude Desktop, Windsurf, OpenClaw, Hermes) wants a slightly different JSON shape for the same Streamable HTTP server; the only way to keep one server working across all of them is to generate that shape per platform, pin the single upstream URL in one place, forward exactly two headers deliberately, normalise non-object JSON-RPC params before validation, and stay stateless for clients that never send initialize. mcp client is searched about 1,600 times a month; almost none of those readers need the specification - they need the config that does not 401.

One server, one upstream string. mcpStreamableUpstreamUrl trims a

One server, one upstream string. mcpStreamableUpstreamUrl trims a trailing slash and returns <root>/mcp/; every client config and every proxy hop resolves the same value, so a base-URL change is one edit instead of five pasted files.

The config shape is per client, not per

The config shape is per client, not per protocol. type versus transport, url versus serverUrl, mcpServers versus mcp.servers - four differences that each cost an afternoon of "the server is fine, the paste is wrong".

Two headers are load-bearing. buildMcpAuthHeaders emits the API

Two headers are load-bearing. buildMcpAuthHeaders emits the API key and the agent platform header, because a gateway that cannot attribute the caller cannot audit it.

A proxy must not silently drop identity. forwardMcpRequestHeaders

A proxy must not silently drop identity. forwardMcpRequestHeaders carries Authorization, Content-Type, the platform header and the trace id, and defaults the platform instead of failing the request.

Normalise before you validate. Cursor sends "params": []

Normalise before you validate. Cursor sends "params": [] for tools/list and notifications/initialized; pydantic rejects a list where it expects an object, so normalize_jsonrpc_body rewrites non-object params to an empty object and logs the method.

Compatibility can be a patch, not a fork

Compatibility can be a patch, not a fork. apply_mcp_session_compat monkey-patches the session layer once, idempotently, so clients that skip the handshake still get a stateless session.

Do this next: open your own client config

Do this next: open your own client config, find which of the four shape differences your setup assumes, and move the URL and the headers into functions before the next client is added. The short version for whoever owns the integration

If you are evaluating this for a product

If you are evaluating this for a product decision, three sentences are enough. An MCP-compatible client does not fail because the protocol is hard; it fails because every desktop client wants a different JSON file, and the differences are invisible until a user pastes the wrong one. The durable fix is to treat the client configuration as generated output: one function per platform that emits the file, one function that builds the auth headers, and one place that decides what the upstream URL is. Everything else in this article is the edge cases that generation exposes - the ones that show up as 401s, empty tool lists and mystery sessions.

For a gateway, the second-order benefit is bigger

For a gateway, the second-order benefit is bigger than the first. Once every client arrives through generated configuration, the platform header travels with each request, and per-agent audit - which platform, which key, which trace - stops being a guess. Why client compatibility is the real MCP problem

News

Build an MCP Client for AI Agents: Config, Auth, Transport

Short answer: building an MCP-compatible client for AI agents is 20% protocol and 80% edge-case handling.

@spots #dev
Source: Dev.to
See more like this