Skip to content

MCP: support remote servers over Streamable HTTP - #83

Merged
glenjbarber merged 1 commit into
mainfrom
claude/relaxed-knuth-1frpkt
Oct 13, 2026
Merged

glenjbarber merged 1 commit into
mainfrom
claude/relaxed-knuth-1frpkt

Conversation

@glenjbarber

Copy link
Copy Markdown
Owner

Fixes #62.

What

internal/mcp previously only reached a configured server by launching it as a local subprocess over stdio. This adds the transport the MCP specification currently recommends for new integrations, Streamable HTTP (spec 2025-03-26), while keeping local stdio support unchanged. The older HTTP+SSE transport is legacy and is not implemented, per the issue's own direction not to make it the default.

Design

Split the transport out behind a small interface (call/notify/Close) so client's own protocol methods (initialize, listResources, readResource) are written once and work unchanged against either kind of server:

  • The existing stdio transport moved into its own file (stdio_transport.go) with no behavior change — its own pre-existing tests pass unmodified.
  • A new httpTransport (http_transport.go) implements the remote side:
    • POSTs one JSON-RPC message per call to the server's configured URL, and reads back either a single application/json response body or a single-event text/event-stream response — a server may legally answer either way, and both are handled.
    • Tracks the Mcp-Session-Id a server assigns on initialize and sends it back on every later request in the same session.
    • Best-effort session termination (DELETE) on Close, matching the spec's SHOULD without making a remote server's non-cooperation a local failure.
    • Deliberately does not open a standalone server-initiated SSE stream (a bare GET to the same URL) — this client does not act on an unsolicited server notification over stdio either (stdioTransport's own readLoop already skips a line with no numeric id), so there is nothing this narrows that today's stdio behavior does not already narrow the same way.

Config

A server now names command (local) or url + optional headers (remote), never both and never neither — refused by name at configuration read time, the same way every other misconfiguration in this block already is.

cmd/orcli/ask.go's newMCPManager passes URL/Headers through to mcp.Server, the one place this package's own dependency on internal/mcp is named.

orcli.json.example and README.md document the remote shape side by side with the existing local one.

Confirming the current state before finalizing (per the issue's own ask)

  • Confirmed the existing Manager/client structure: a single transport abstraction was the natural seam, since initialize/listResources/readResource were already written purely in terms of call/notify and never touched the stdio-specific fields directly.
  • Confirmed against the current MCP specification (2025-03-26): Streamable HTTP's session header name (Mcp-Session-Id), the dual JSON/SSE response shapes for a POST, and the DELETE-to-terminate convention.
  • No silent remote writes: this client only ever calls resources/list/resources/read, both read-only, over either transport — the same restriction already in place for local servers, unchanged by this PR. State-changing MCP calls are out of scope here and would go through orcli's existing permission handling if ever added.
  • Existing resource calls and schemas are unchanged for local servers; this is a purely additive change for remote ones.

Testing

New internal/mcp/http_transport_test.go covers: JSON response mode, SSE response mode (including multi-line SSE data: framing), session-ID propagation across calls, header forwarding, non-2xx status handling, a JSON-RPC error object, Close's best-effort DELETE (only when a session was actually assigned), and an end-to-end Manager-level test proving a remote Server works through listResources/readResource exactly like a local one. internal/config gets new tests for the remote shape and the mutual-exclusion refusals. cmd/orcli gets a test proving the adapter forwards the new fields.

make test passes clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Nxr4mW9rMNzn9kThjdJpZt


Generated by Claude Code

internal/mcp previously only reached a configured server by launching
it as a local subprocess over stdio. This adds the current
recommended remote transport, Streamable HTTP (MCP spec 2025-03-26),
while keeping local stdio support unchanged.

Split the transport out behind a small interface (call/notify/Close)
so client's own protocol methods (initialize, listResources,
readResource) are written once and work unchanged against either
kind of server - the existing stdio transport moved into its own file
(stdio_transport.go) with no behavior change (its own tests pass
unmodified), and a new httpTransport (http_transport.go) implements
the remote side:

- POSTs one JSON-RPC message per call to the server's configured URL,
  and reads back either a single `application/json` response body or
  a single-event `text/event-stream` response - a server may legally
  answer either way, and both are handled.
- Tracks the Mcp-Session-Id a server assigns on initialize and sends
  it back on every later request in the same session.
- Best-effort session termination (DELETE) on Close, matching the
  spec's SHOULD without making a remote server's non-cooperation a
  local failure.
- Deliberately does not open a standalone server-initiated SSE stream
  (a bare GET to the same URL) - this client does not act on an
  unsolicited server notification over stdio either, so there is
  nothing this narrows that today's stdio behavior does not already
  narrow the same way.

Config (internal/config/mcp.go): a server now names `command` (local)
or `url` + optional `headers` (remote), never both and never neither
- refused by name at configuration read time, the same way every
  other misconfiguration in this block already is.

cmd/orcli/ask.go's newMCPManager passes URL/Headers through to
mcp.Server, the one place this package's own dependency on
internal/mcp is named.

orcli.json.example and README.md document the remote shape
side by side with the existing local one.

`make test` passes clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nxr4mW9rMNzn9kThjdJpZt
@glenjbarber
glenjbarber marked this pull request as ready for review October 13, 2026 20:01
@glenjbarber
glenjbarber merged commit 559edec into main Oct 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP: support remote servers over Streamable HTTP

2 participants