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 registered when eager_instructions is false (the default); see Context Budget.

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 (non-eager agents only)

Server Instructions

The handshake (server/discover for MCP 2026-07-28, or initialize for legacy clients on 2025-11-25/2025-06-18) always sets the MCP server’s instructions field, but what it carries depends on eager_instructions. When eager_instructions: true, it carries the full system prompt. By default (eager_instructions: false), it carries a short stub instead, telling the model to call the get_instructions tool to load the full prompt on demand. See Context Budget for the full behavior and how to change it.

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

## Model Preference

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

In eager mode this hint is sent directly in the handshake, as part of the full prompt. In the default (non-eager) mode, the handshake stub doesn’t carry it — it’s included in the text returned by the get_instructions tool instead.

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

Protocol Versions

Agent binaries built with abbyfile v0.10.0+ speak MCP 2026-07-28 (stateless: server/discover plus per-request _meta) and still accept legacy initialize clients on 2025-11-25 and 2025-06-18. The client picks the version; no configuration is needed.

  • Server instructions are returned by server/discover (modern) or initialize (legacy).
  • tools/list, prompts/list and resource listings are cacheable for one hour (ttlMs: 3600000, cacheScope: public). Memory reads are never cached (ttlMs: 0, cacheScope: private).
  • Tools that declare an outputSchema return structuredContent, plus a JSON text copy. Their output must be one non-null JSON value within the tool’s max_output_bytes; structured output is never truncated.
  • Tool names must match ^[A-Za-z0-9_.-]{1,128}$.

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_CONFIG_DIR/.claude.json if set, else ~/.claude.json JSON {"mcpServers": {...}}
Codex .codex/config.toml $CODEX_HOME/config.toml if set, else ~/.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": {
      "type": "stdio",
      "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": {
      "type": "stdio",
      "command": "/path/to/project/build/my-agent",
      "args": [
        "serve-mcp"
      ],
      "timeout": 40000
    }
  }
}

timeout is in milliseconds: the agent’s largest tool limit (30s by default, or sandbox.maxCommandTimeout when it exposes run_command) plus 10s. Any --env KEY=VALUE values appear under env. abby only rewrites the keys it owns, so keys you add by hand are kept, and other entries are untouched.

Multiple Agents

Register multiple agents in the same .mcp.json:

{
  "mcpServers": {
    "go-pro": {
      "type": "stdio",
      "command": "./build/go-pro",
      "args": [
        "serve-mcp"
      ]
    },
    "tool-eng": {
      "type": "stdio",
      "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.