Skip to Content
DocsMCPServer specs + registry

Server specs + registry

MCPServerSpec.stdio(...)

Launches an MCP server as a subprocess and talks to it over stdio.

from loomflow.mcp import MCPServerSpec spec = MCPServerSpec.stdio( name="git", # registry id command="uvx", # the binary to launch args=["mcp-server-git", "--repo", "/Users/me/code/myrepo"], env={"GIT_AUTHOR_NAME": "agent"}, # optional extra env )

The name becomes the prefix for tool-name disambiguation (git.commit etc.).

MCPServerSpec.http(...)

Talks to a hosted MCP server over Streamable HTTP transport.

spec = MCPServerSpec.http( name="hosted", url="https://example.com/mcp/", headers={ "Authorization": "Bearer sk-...", "X-Tenant-Id": "acme", }, )

The framework keeps a single connection open per server for the lifetime of the registry; reconnects on transient failures.

MCPRegistry. Combine multiple servers

from loomflow import Agent from loomflow.mcp import MCPRegistry, MCPServerSpec registry = MCPRegistry([ MCPServerSpec.stdio("git", "uvx", ["mcp-server-git", "--repo", "."]), MCPServerSpec.stdio("fs", "uvx", ["mcp-server-filesystem", "--root", "."]), MCPServerSpec.http("hosted", url="https://example.com/mcp/"), ]) agent = Agent("...", model="claude-opus-4-7", tools=registry)

MCPRegistry implements the ToolHost protocol. Pass it directly to Agent(tools=...).

Tool-name conflict resolution

If two servers expose a tool with the same simple name:

  • Both get qualified, git.status and fs.status.
  • The agent sees both qualified names in the tool list.
  • Either qualified or bare form is accepted at call time; the registry strips the prefix before forwarding.

If only one server has the name, it stays bare:

git.commit # only git ships commit status, fs.status, git.status # both ship status; both qualified

Listing tools at runtime

tools = await registry.list_tools() for t in tools: print(t.name, t.description)

list_tools is a coroutine. The registry queries each server’s tools/list endpoint on first call and caches the result.

Fault isolation + unavailable

One dead server must not make every other server unusable. The registry connects all clients in parallel and isolates per-server failures at every stage:

  • Connect failures are logged, not raised — the healthy servers’ tools register normally.
  • Listing failures skip that server’s tools from the index; its name is recorded in registry.unavailable and the pull is retried on the next refresh().
  • Call-time transport failures (broken pipe, server restart, connection drop) trigger a reconnect-and-retry — once, and only for errors that look like connection failures. A mid-execution error may mean the server already performed a side effect, so it is surfaced instead of silently re-run.
await registry.connect() if registry.unavailable: log.warning("degraded MCP servers: %s", registry.unavailable)

listChanged → automatic refresh

MCP servers can announce that their tool list changed (notifications/tools/list_changed) — a server that gains or loses capabilities mid-run. The registry handles this automatically: the notification flags the server stale, and the next registry operation (list, call) re-pulls that server only and rebuilds the index. No polling, no restart, no re-listing of healthy siblings.

To observe changes, subscribe to the diff stream:

async for event in registry.watch(): print(event.kind, event.tool, event.server) # "added" / "removed" / "updated"

watch() registers eagerly (no events lost between subscribe and iterate) and each subscriber has a bounded buffer — a slow consumer drops events rather than blocking refreshes.

Lazy initialization

MCPRegistry connects to its servers lazily. The subprocess launches and the HTTP connection opens on the first agent.run. This keeps Agent(...) synchronous and avoids paying for unused servers in tests.

Cleanup

For long-lived processes, hold the registry across runs and let Python’s garbage collector clean up. For short-lived processes (scripts), the framework’s atexit handler closes stdio subprocesses and HTTP connections cleanly.

Errors

ErrorCause
MCPErrorConnect-phase failure: SDK not installed, bad spec, unsupported transport, unknown server / resource / prompt.
ToolResult(ok=False)A tool call failed (unknown tool, server-side isError, transport failure after the one reconnect retry). The error text reaches the model as the tool result; the run continues.

Server failures never crash a run: a server that can’t list tools is simply absent from the index (and named in unavailable), and a failing tool call surfaces to the model as an error result it can react to.

Multiple instances of the same server. Two stdio specs with the same command but different name and different --repo (etc.) run as two independent subprocesses. Useful for “git for repo A” and “git for repo B” in the same agent.

Last updated on