Repository navigation
MCP: support remote servers over Streamable HTTP - #83
Merged
Merged
Conversation
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
marked this pull request as ready for review
October 13, 2026 20:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #62.
What
internal/mcppreviously 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) soclient's own protocol methods (initialize,listResources,readResource) are written once and work unchanged against either kind of server:stdio_transport.go) with no behavior change — its own pre-existing tests pass unmodified.httpTransport(http_transport.go) implements the remote side:application/jsonresponse body or a single-eventtext/event-streamresponse — a server may legally answer either way, and both are handled.Mcp-Session-Ida server assigns oninitializeand sends it back on every later request in the same session.DELETE) onClose, matching the spec's SHOULD without making a remote server's non-cooperation a local failure.stdioTransport's ownreadLoopalready 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) orurl+ optionalheaders(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'snewMCPManagerpassesURL/Headersthrough tomcp.Server, the one place this package's own dependency oninternal/mcpis named.orcli.json.exampleandREADME.mddocument the remote shape side by side with the existing local one.Confirming the current state before finalizing (per the issue's own ask)
Manager/clientstructure: a singletransportabstraction was the natural seam, sinceinitialize/listResources/readResourcewere already written purely in terms ofcall/notifyand never touched the stdio-specific fields directly.Mcp-Session-Id), the dual JSON/SSE response shapes for a POST, and theDELETE-to-terminate convention.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.Testing
New
internal/mcp/http_transport_test.gocovers: JSON response mode, SSE response mode (including multi-line SSEdata: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-endManager-level test proving a remoteServerworks throughlistResources/readResourceexactly like a local one.internal/configgets new tests for the remote shape and the mutual-exclusion refusals.cmd/orcligets a test proving the adapter forwards the new fields.make testpasses clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01Nxr4mW9rMNzn9kThjdJpZt
Generated by Claude Code