Cline's CLI speaks stdio through a hand-written client fixed at 2024-11-05

Kind: idiosyncrasy · Status: current · Last verified: 2026-08-25 · Clients: cline · Revisions: 2024-11-05, 2025-11-25

Cline has four MCP paths, split by surface and by transport:

Surface Transport Implementation Version sent
VS Code extension stdio SDK StdioClientTransport 2025-11-25
VS Code extension SSE, Streamable HTTP SDK transports 2025-11-25
CLI, @cline/core SSE, Streamable HTTP SDK transports 2025-11-25
CLI, @cline/core stdio hand-written 2024-11-05

createDefaultMcpServerClientFactory picks between the two by transport type: a stdio registration gets StdioMcpClient, everything else gets SdkUrlMcpClient, which wraps the SDK's Client.

StdioMcpClient is a complete JSON-RPC implementation of its own — request ids, a pending map, timeouts, abort signals, stderr capture — and it hardcodes the revision:

const MCP_PROTOCOL_VERSION = "2024-11-05";

It supports three methods: initialize, tools/list and tools/call. No notifications are handled, in either direction, past the notifications/initialized it sends after connecting.

It does not negotiate

connect() awaits the initialize response and discards it. The result's protocolVersion is never read and never compared. A server that answers 2025-11-25 is treated exactly like one that answers 2024-11-05, and the CLI proceeds either way.

Content-Length framing is not part of the transport

If initialize fails on the first attempt, the client kills the process, respawns it, and retries with LSP-style framing:

Content-Length: 142\r\n\r\n{"jsonrpc":"2.0",...}

MCP's stdio transport defines newline-delimited JSON only. Cline probes it as a fallback because the failure it is guarding against is a silent server, not a wrong-framing server: the two attempts get 3 seconds each by default, and the combined error message names both when they fail differently.

A server that accepted Content-Length framing would work against Cline's CLI and against nothing else.

Why it matters for the matrix

A version-shaped question about Cline has no single answer, and neither surface nor transport alone decides it. preferredProtocolVersion records 2025-11-25 because that is what three of the four paths send, but a server that only ever sees stdio traffic from the CLI will only ever see 2024-11-05 — from a client whose extension, on the same machine, reading the same settings file, is speaking a revision four releases newer.

It also settles the question the opencode read raised. Cline and opencode are both on @modelcontextprotocol/sdk, and Cline does not patch it: patchedDependencies covers @opentui-ui/dialog and ollama-ai-provider-v2 and nothing else. Where opencode changed the SDK's behaviour and kept using it, Cline left the SDK alone and wrote around it.

Sources