MCP client¶
Purpose¶
Connect Golem agents to the Model Context Protocol ecosystem: the
mcp package speaks MCP to a server over stdio or streamable HTTP,
discovers its tools, and returns them as ordinary tool.Tool values
any agent can register.
When to use¶
Reusing tool servers the ecosystem already ships — package-runner
based tools and local daemons over stdio, hosted services over
streamable HTTP — instead of re-implementing their capabilities as
application tools. Not for capabilities that are one tool.New away:
a typed Go tool with the process's own packages is simpler than a
subprocess or remote service speaking JSON-RPC.
How it works¶
mcp.NewStdio(mcp.StdioConfig{Command, Args, Dir, Env}) starts the
server as a subprocess and owns its lifecycle — Close terminates it.
mcp.NewHTTP(mcp.HTTPConfig{URL, HTTPClient, Header}) reaches a
remote endpoint: every message is one POST, answered as a JSON body or
a text/event-stream whose events arrive one by one; the session header
a server assigns at initialize is captured and sent automatically, and
extra headers (such as Authorization) ride along on every request.
The server-to-client GET stream is not opened — pushes are not needed
for tool use.
mcp.NewClient(transport) wraps any Transport; Initialize performs
the MCP handshake (announcing this client, recording the server's
identity in Server()), and must run before anything else.
mcp.AsTools[Deps](ctx, client) snapshots the server's tool list and
returns each as a tool.Tool[Deps] carrying the server's name,
description, and input schema verbatim — the server's schema is the
model-facing contract. Executing a bridged tool sends the model's raw
arguments to tools/call:
- Text content comes back as the tool result, items joined by newlines; non-text content renders as placeholder lines.
- A result flagged
isErrorrejects as a correctable*model.ModelRetry: the model sees the server's explanation and may retry with fixed arguments under the agent's tool retry budget. - Protocol failures surface as the typed
*mcp.ProtocolError; transport and cancellation errors preserve their cause — both fail the run at the tool stage, never as correctable rejections.
The client is synchronous and serialized — one in-flight request at a
time, no goroutines — and honors the caller's context on every
operation. While a call waits, a server-initiated ping is answered
inline, any other server-initiated request gets a method-not-found
reply, and notifications are dropped.
Example¶
Run examples/mcp-client — offline: the example binary doubles as a
minimal MCP stdio server and spawns itself. Run examples/mcp-http
for the streamable-HTTP transport against a local endpoint.
transport, _ := mcp.NewStdio(mcp.StdioConfig{Command: "npx", Args: []string{"-y", "some-mcp-server"}})
// or remotely:
// transport, _ := mcp.NewHTTP(mcp.HTTPConfig{URL: "https://example.com/mcp"})
client := mcp.NewClient(transport)
defer client.Close()
_ = client.Initialize(ctx)
tools, _ := mcp.AsTools[struct{}](ctx, client)
agent, _ := golem.New[struct{}, string](model, decoder,
golem.WithTools[struct{}, string](tools...))
API surface¶
mcp.NewStdio(mcp.StdioConfig) (*StdioTransport, error)—StdioConfig{Command, Args, Dir, Env}mcp.NewHTTP(mcp.HTTPConfig) (*HTTPTransport, error)—HTTPConfig{URL, HTTPClient, Header}mcp.Transport— theSend/Read/Closeboundary other transports implementmcp.NewClient(mcp.Transport) *Client(*Client).Initialize(ctx)/(*Client).Server()/(*Client).Close()(*Client).ListTools(ctx) ([]ToolInfo, error)—ToolInfo{Name, Description, InputSchema}(*Client).CallTool(ctx, name, arguments) (CallResult, error)—CallResult{Content, IsError},CallResult.Text()mcp.AsTools[Deps](ctx, *Client) ([]tool.Tool[Deps], error)mcp.ProtocolError{Code, Message, Data},mcp.HTTPStatusError{StatusCode, URL}
Gotchas¶
- The server subprocess inherits the process environment;
Enventries are appended after it. Passing secrets to a server is the application's decision. - Concurrent tool calls serialize at the client — parallel tool groups run one server call at a time.
AsToolsis a point-in-time snapshot; a server that changes its tools dynamically is re-bridged by calling it again, not by push notification.- Only text content is carried; images and resources render as placeholders.
- Over HTTP, the default client has no wall-clock timeout so event
streams are bounded only by the caller's context; supply
HTTPClientfor a hard bound. A server that drops the session answers 404, surfacing as*mcp.HTTPStatusError— re-initialize a fresh client to recover. - On platforms where pipe reads cannot take deadlines (Windows), cancelling an in-flight stdio call is honored when the transport closes, not immediately.
- The package depends only on the standard library,
model, andtool— never on the root package or a provider. - Decisions live in
docs/adr/0016-mcp-client-package.md.