MCP Integration Guide

Abbyfile agents integrate with MCP-compatible runtimes (Claude Code, Codex, Gemini CLI) through the Model Context Protocol (MCP). The serve-mcp subcommand starts an MCP-over-stdio server that exposes the agent’s tools, prompts, and memory.

What serve-mcp Exposes

When you run ./my-agent serve-mcp, the MCP server registers:

Tools

Every tool registered via WithTools() plus the automatically registered memory tools (if memory is enabled). Additionally, a get_instructions tool is always registered for backward compatibility.

Example tool listing for an agent with tools: Read, Write and memory enabled:

  • read_file, write_file – builtin tools
  • memory_read, memory_write, memory_list, memory_delete – memory tools
  • get_instructions – returns the system prompt

Server Instructions

The system prompt is set as the MCP server’s instructions field during the initialization handshake. MCP clients that support server instructions receive the prompt automatically.

If a model hint is configured (via the agent definition or a config override), a ## Model Preference section is appended to the instructions, e.g.:

## Model Preference

This agent was designed for model: claude-opus-4-6

This is informational — the runtime decides which model to use.

Resources (when memory is enabled)

  • memory://<name>/ – JSON array of all memory keys
  • memory://<name>/{key} – content of a specific memory key

Prompts

  • system – returns the agent’s system prompt as a prompt message
  • memory-context (when memory is enabled) – returns memory state; accepts an optional key argument to return a specific key’s content

Runtime Config Files

abby build and abby install auto-generate MCP config for detected runtimes. Use --runtime to target a specific runtime.

Runtime Local Config Global Config Format
Claude Code .mcp.json ~/.claude/mcp.json JSON {"mcpServers": {...}}
Codex .codex/config.toml ~/.codex/config.toml TOML [mcp_servers.name]
Gemini CLI .gemini/settings.json ~/.gemini/settings.json JSON {"mcpServers": {...}}

.mcp.json Configuration (Claude Code)

Claude Code discovers MCP servers through .mcp.json in the project root.

Production: Built Binary

{
  "mcpServers": {
    "my-agent": {
      "command": "./my-agent",
      "args": ["serve-mcp"]
    }
  }
}

Development: go run

Use go run so code changes take effect without rebuilding:

{
  "mcpServers": {
    "my-agent": {
      "type": "stdio",
      "command": "go",
      "args": ["run", "./cmd/my-agent", "serve-mcp"]
    }
  }
}

abby build auto-generates .mcp.json with entries like:

{
  "mcpServers": {
    "my-agent": {
      "command": "/path/to/build/my-agent",
      "args": ["serve-mcp"]
    }
  }
}

Multiple Agents

Register multiple agents in the same .mcp.json:

{
  "mcpServers": {
    "go-pro": {
      "command": "./build/go-pro",
      "args": ["serve-mcp"]
    },
    "tool-eng": {
      "command": "./build/tool-eng",
      "args": ["serve-mcp"]
    }
  }
}

Each agent runs as a separate MCP server process. Claude Code manages the lifecycle.

The MCP Bridge

The pkg/mcp package translates between Abbyfile’s internal types and the MCP protocol. It uses the official Go MCP SDK (github.com/modelcontextprotocol/go-sdk).

The bridge:

  1. Creates an MCP Server with the agent’s name and version
  2. Registers each tool from the tools.Registry as an MCP tool
  3. Maps tools.Annotations to MCP ToolAnnotations
  4. Wires tool calls through the tools.Executor
  5. Registers memory resources and prompt templates (if applicable)
  6. Runs on a StdioTransport (production) or an in-memory transport (testing)

From pkg/mcp/bridge.go:

func (b *Bridge) Serve(ctx context.Context) error {
    return b.ServeTransport(ctx, &gomcp.StdioTransport{})
}

func (b *Bridge) ServeTransport(ctx context.Context, transport gomcp.Transport) error {
    instructions, _ := b.cfg.Loader.Load()
    server := gomcp.NewServer(&gomcp.Implementation{
        Name:    b.cfg.Name,
        Version: b.cfg.Version,
    }, &gomcp.ServerOptions{
        Instructions: instructions,
    })
    // ... register tools, resources, prompts ...
    return server.Run(ctx, transport)
}

Testing with In-Memory Transports

For unit tests, use gomcp.NewInMemoryTransports() to create a client-server pair without stdio:

func TestMyBridge(t *testing.T) {
    registry := tools.NewRegistry()
    registry.Register(tools.BuiltinTool(
        "echo", "Echo input", schema,
        func(input map[string]any) (string, error) {
            msg, _ := input["message"].(string)
            return "echo: " + msg, nil
        },
    ))

    bridge := mcp.NewBridge(mcp.BridgeConfig{
        Name:     "test-agent",
        Version:  "v0.1.0",
        Registry: registry,
        Executor: tools.NewExecutor(30*time.Second, nil),
        Loader:   loader,
    })

    serverTransport, clientTransport := gomcp.NewInMemoryTransports()

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    go func() {
        bridge.ServeTransport(ctx, serverTransport)
    }()

    client := gomcp.NewClient(&gomcp.Implementation{
        Name: "test-client", Version: "v0.1.0",
    }, nil)
    session, err := client.Connect(ctx, clientTransport, nil)
    // ... use session.ListTools(), session.CallTool(), etc.
}

See pkg/mcp/bridge_test.go for comprehensive examples including tool calls, error handling, annotations, memory resources, and prompt templates.

Integration Testing with CommandTransport

For end-to-end tests against a built binary, use gomcp.CommandTransport:

func TestServeMCP_Integration(t *testing.T) {
    cmd := exec.CommandContext(ctx, binaryPath, "serve-mcp")
    client := gomcp.NewClient(&gomcp.Implementation{
        Name: "integration-test", Version: "v0.1.0",
    }, nil)

    session, err := client.Connect(ctx, &gomcp.CommandTransport{Command: cmd}, nil)
    // ... exercise the MCP API ...
}

This starts the actual binary, connects to it via stdio, and exercises the full MCP protocol. See internal/integration/agent_test.go for the pattern.

Debugging

Agent binaries log to stderr via slog. When running under Claude Code, these logs are separate from the MCP protocol (which uses stdout).

To see logs during development:

./my-agent serve-mcp 2>agent.log

Or set a custom logger:

agent.WithLogger(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))),

Plugin Alternative

Instead of wiring .mcp.json manually, you can generate a Claude Code plugin directory with abby build --plugin. The plugin wraps the binary with its own .mcp.json and adds skills:

abby build --plugin
claude --plugin-dir ./build/my-agent.claude-plugin/

The plugin’s .mcp.json uses a relative path (./my-agent), making the directory self-contained and portable. See the Plugins Guide.

Compatibility

Abbyfile uses the standard MCP protocol. All three supported runtimes (Claude Code, Codex, Gemini CLI) connect via MCP-over-stdio, and any other MCP client can connect to an agent’s serve-mcp server. The binary is a generic MCP server that happens to be built with Abbyfile.

The --runtime flag on build, install, and uninstall controls which runtime configs are generated. The auto default detects installed runtimes. Use all to target all three.


Abbyfile is an open-source project licensed under MIT.

This site uses Just the Docs, a documentation theme for Jekyll.