Skip to main content
chainforge supports the Model Context Protocol out of the box. Tools are discovered automatically on first Run and become indistinguishable from built-in tools inside the agent loop. Tool names are namespaced as servername__toolname (e.g. fs__read_file) to avoid collisions across servers.

Streamable HTTP

The modern MCP transport used by Cursor, Claude Code, and hosted MCP services.
import "github.com/lioarce01/chainforge/pkg/mcp"

chainforge.WithMCPServer(
    mcp.HTTP("https://docs.langchain.com/mcp").WithName("langchain"),
)

Stdio

Launches a subprocess and communicates over stdin/stdout. Requires the command to be installed locally.
chainforge.WithMCPServer(
    mcp.Stdio("npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp").WithName("fs"),
)

Multiple servers

chainforge.WithMCPServers(
    mcp.HTTP("https://docs.langchain.com/mcp").WithName("langchain"),
    mcp.Stdio("npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp").WithName("fs"),
)
All servers connect in parallel on the first Run call.

Environment variables (Stdio)

Pass secrets to the subprocess without leaking them into Args:
mcp.Stdio("my-server").
    WithName("myserver").
    WithEnv("API_KEY=" + os.Getenv("MY_API_KEY"))

Pre-warming connections

By default MCP servers connect lazily on the first Run call. Call WarmMCP to pre-connect before serving traffic — useful in HTTP handlers where you want predictable first-request latency:
agent, err := chainforge.NewAgent(
    chainforge.WithMCPServer(mcp.HTTP(os.Getenv("MCP_URL")).WithName("remote")),
    // ...
)
if err != nil { panic(err) }
defer agent.Close()

// Pre-connect all MCP servers during startup.
if err := agent.WarmMCP(ctx); err != nil {
    log.Fatal("MCP pre-connect failed:", err)
}

// Subsequent Run calls have no connection overhead.
result, err := agent.Run(ctx, "session-1", userMessage)
WarmMCP is idempotent — calling it multiple times (or mixing with Run) is safe. Returns nil immediately if no MCP servers are configured.

Cleanup

Always defer Close to terminate subprocesses and release HTTP connections:
agent, err := chainforge.NewAgent(...)
defer agent.Close()

Error handling & reconnection

ScenarioBehavior
Server fails to connectError is held; retry by calling ReconnectMCP
Tool call returns an errorNon-fatal — error string fed to LLM as tool result
Context cancelled during connectPropagates immediately
Multiple concurrent Run callsInternal mutex guarantees connect runs exactly once
If a transient network failure prevents the initial connection, use ReconnectMCP to reset the connection state and retry — without needing to create a new Agent:
// Initial run fails due to a network blip.
result, err := agent.Run(ctx, "session-1", "hello")
if err != nil {
    // Re-attempt the connection explicitly.
    if reconnErr := agent.ReconnectMCP(ctx); reconnErr != nil {
        log.Fatal("reconnect failed:", reconnErr)
    }
    // Retry the run — MCP servers are now connected.
    result, err = agent.Run(ctx, "session-1", "hello")
}
ReconnectMCP is safe to call from multiple goroutines. Once it returns nil all subsequent Run and RunStream calls proceed without re-connecting.

Full example

package main

import (
    "context"
    "fmt"
    "os"

    chainforge "github.com/lioarce01/chainforge"
    "github.com/lioarce01/chainforge/pkg/mcp"
    "github.com/lioarce01/chainforge/pkg/providers/openai"
)

func main() {
    provider := openai.NewWithBaseURL(
        os.Getenv("OPENROUTER_API_KEY"),
        "https://openrouter.ai/api/v1",
        "openrouter",
    )

    agent, err := chainforge.NewAgent(
        chainforge.WithProvider(provider),
        chainforge.WithModel(os.Getenv("MODEL")),
        chainforge.WithSystemPrompt("You are a helpful assistant."),
        chainforge.WithMCPServer(mcp.HTTP(os.Getenv("MCP_URL")).WithName("remote")),
        chainforge.WithMaxIterations(5),
    )
    if err != nil {
        panic(err)
    }
    defer agent.Close()

    result, err := agent.Run(context.Background(), "session-1", "What is LangChain?")
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}