MCP tools (outbound client)
Mount remote MCP servers as ordinary Starling tools. stdio subprocess, streamable HTTP, replay-safe.
This page is the outbound MCP client - agents calling external MCP servers. For the inbound server that lets AI assistants query your recorded event log, see MCP server.
tool/mcp adapts Model Context Protocol
servers onto tool.Tool. The core runtime stays MCP-agnostic.
Three transports
import (
"os/exec"
mcptool "github.com/jerkeyray/starling/tool/mcp"
)
// 1. stdio subprocess server.
client, err := mcptool.NewCommand(ctx,
exec.Command("uvx", "mcp-server-filesystem", "/tmp"),
mcptool.WithToolNamePrefix("fs_"),
)
// 2. streamable HTTP server.
client, err = mcptool.NewHTTP(ctx, "https://mcp.example.com/sse", nil)
// 3. any custom mcp.Transport.
client, err = mcptool.New(ctx, transport)Each constructor lists the server's tools at connect time and caches them.
Call client.Tools(ctx) to retrieve []tool.Tool for use with Agent,
or client.RefreshTools(ctx) to re-list.
Wire into an agent
client, err := mcptool.NewCommand(ctx,
exec.Command("uvx", "mcp-server-filesystem", "/tmp"),
mcptool.WithToolNamePrefix("fs_"),
mcptool.WithCallTimeout(10*time.Second),
)
if err != nil {
panic(err)
}
defer client.Close()
mcpTools, err := client.Tools(ctx)
if err != nil {
panic(err)
}
a := &starling.Agent{
Provider: prov,
Tools: append(localTools, mcpTools...),
Log: log,
Config: starling.Config{Model: "gpt-4o-mini", MaxTurns: 8},
}Options
| Option | Purpose |
|---|---|
WithClientInfo(name, version) | Override the client identity sent on initialize. |
WithToolNamePrefix(p) | Namespace remote tools: useful when mounting multiple servers. |
WithIncludeTools(...) | Restrict to the named remote tools. |
WithExcludeTools(...) | Drop the named remote tools. |
WithCallTimeout(d) | Per-call deadline. Zero leaves cancellation to the caller's ctx. |
WithMaxOutputBytes(n) | Cap the JSON-encoded result. Defaults to 1 MiB. |
WithTextOnly(true) | Reject non-text content rather than forwarding it. |
WithTransientErrorClassifier(fn) | Classify transport errors as tool.ErrTransient for retries. |
Replay safety
Each MCP tool call goes through step.SideEffect keyed on
mcp/<remote-name>. The first live invocation contacts the server,
records the result as a SideEffectRecorded event, and returns. Replay
reads the recorded value out of the log and never re-contacts the server.
The same applies under (*Agent).Resume. An orphaned MCP call from a
crashed run is reissued under a fresh CallID; the new live invocation is
recorded as its own SideEffect.
This means your recorded runs are portable. You can replay them on a machine that has no network access to the MCP server.
Tool errors vs transport errors
The MCP protocol distinguishes two failure modes; Starling preserves both.
Server returned IsError: true. The remote tool ran and decided the
call failed (bad input, business-rule rejection). Execute returns a
typed *mcptool.ToolError carrying the server's content. Use errors.As
to inspect.
Transport or protocol error. Connection refused, timeout, malformed
JSON-RPC, etc. Execute returns the underlying error wrapped with the
tool name. If WithTransientErrorClassifier reports the error retryable,
it's also wrapped with tool.ErrTransient so the caller's
step.ToolCall{Idempotent: true, MaxAttempts: N} retries kick in.
What's intentionally not adapted
tool/mcp ships with a deliberately narrow surface:
- MCP resources: file-tree-style reads; not yet wired.
- MCP prompts: server-provided prompt templates; deferred.
- MCP sampling: server asking the client to run a model; deferred.
Tools cover the common ADK-parity case. If you hit a wall on resources or prompts, file an issue with the use case before building it locally.