Skip to content

MCP Integration

RobotLab supports the Model Context Protocol (MCP) for connecting to external tool servers.

What is MCP?

MCP is a protocol that allows LLM applications to connect to external servers that provide tools, resources, and context. This enables:

  • Reusable tool servers across applications
  • Separation of tool logic from AI logic
  • Dynamic tool discovery

Configuring MCP Servers

At Robot Level

Use the mcp: parameter on RobotLab.build to connect a robot to MCP servers:

robot = RobotLab.build(
  name: "coder",
  template: :developer,
  mcp: [
    {
      name: "filesystem",
      transport: {
        type: "stdio",
        command: "mcp-server-filesystem",
        args: ["--root", "/home/user/projects"]
      }
    },
    {
      name: "github",
      transport: {
        type: "stdio",
        command: "mcp-server-github",
        env: { "GITHUB_TOKEN" => ENV["GITHUB_TOKEN"] }
      }
    }
  ]
)

In Template Front Matter

MCP servers can be declared directly in a template's YAML front matter, making the template fully self-contained:

prompts/github_assistant.md
---
description: GitHub assistant with MCP tool access
mcp:
  - name: github
    transport: stdio
    command: npx
    args: ["-y", "@modelcontextprotocol/server-github"]
---
You are a helpful GitHub assistant with access to GitHub tools via MCP.
# MCP config comes from the template — no mcp: parameter needed
robot = RobotLab.build(template: :github_assistant)

Constructor mcp: overrides frontmatter mcp: when provided.

Hierarchical Configuration

The mcp: parameter supports three modes:

Value Behavior
:none No MCP servers (default)
:inherit Inherit from network or global config
[...] Explicit array of server configurations
# Inherit from network/config
robot = RobotLab.build(
  name: "reader",
  system_prompt: "You help read files.",
  mcp: :inherit
)

# Disable MCP explicitly
robot = RobotLab.build(
  name: "calculator",
  system_prompt: "You do math.",
  mcp: :none
)

Resolution Order

MCP configuration resolves through a hierarchy: runtime > robot build > network > global config. Each level can override the previous:

Global (RobotLab.config.mcp)
  -> Network (task mcp: [...])
    -> Robot (mcp: :inherit | :none | [...])
      -> Runtime (robot.run("msg", mcp: [...]))

Timeout Configuration

All transports support a configurable request timeout. The default is 15 seconds. Set a custom timeout at the server level:

robot = RobotLab.build(
  name: "patient_bot",
  system_prompt: "You help with slow operations.",
  mcp: [
    {
      name: "heavy_server",
      transport: { type: "stdio", command: "heavy-mcp-server" },
      timeout: 60  # seconds
    }
  ]
)

Values >= 1000 are auto-converted from milliseconds to seconds. The minimum timeout is 1 second.

Transport Types

Stdio Transport

Communicate via stdin/stdout with a subprocess:

{
  name: "server_name",
  transport: {
    type: "stdio",
    command: "mcp-server-command",
    args: ["--option", "value"],
    env: { "API_KEY" => ENV["API_KEY"] }
  }
}

WebSocket Transport

Connect via WebSocket:

{
  name: "remote_server",
  transport: {
    type: "websocket",
    url: "ws://localhost:8080/mcp"
  }
}

Dependency Required

WebSocket transport requires the async-websocket gem.

SSE Transport

Server-Sent Events transport:

{
  name: "sse_server",
  transport: {
    type: "sse",
    url: "http://localhost:8080/sse"
  }
}

HTTP Transport

Streamable HTTP transport with session support:

{
  name: "http_server",
  transport: {
    type: "streamable_http",
    url: "https://api.example.com/mcp",
    session_id: "optional_session_id",
    auth_provider: -> { "Bearer #{fetch_token}" }
  }
}

Using MCP Tools

Once configured, MCP tools are automatically discovered and made available to the robot. The robot connects to MCP servers on its first run call and discovers tools dynamically:

robot = RobotLab.build(
  name: "helper",
  system_prompt: <<~PROMPT
    You can help users with GitHub tasks.
    Use available tools to search repositories, create issues, etc.
  PROMPT,
  mcp: [
    { name: "github", transport: { type: "stdio", command: "mcp-server-github" } }
  ]
)

# MCP tools are automatically available
result = robot.run("Find repositories about machine learning")
puts result.last_text_content
    You can help users with GitHub tasks.
    Use available tools to search repositories, create issues, etc.
  PROMPT,
  mcp: [
    { name: "github", transport: { type: "stdio", command: "mcp-server-github" } }
  ]
)

# MCP tools are automatically available
result = robot.run("Find repositories about machine learning")
puts result.last_text_content

Filtering MCP Tools

Use the tools: parameter to restrict which tools (including MCP-discovered tools) are available to a robot:

robot = RobotLab.build(
  name: "reader",
  system_prompt: "You help read and search files.",
  mcp: [
    { name: "filesystem", transport: { type: "stdio", command: "mcp-server-fs" } }
  ],
  tools: %w[read_file search_files list_directory]  # Only allow specific tools
)

MCP in Networks

When running robots in a network, use per-task MCP configuration:

network = RobotLab.create_network(name: "dev_pipeline") do
  task :planner, planner_robot, depends_on: :none
  task :coder, coder_robot,
       mcp: [
         { name: "filesystem", transport: { type: "stdio", command: "mcp-server-fs" } }
       ],
       depends_on: [:planner]
  task :reviewer, reviewer_robot, depends_on: [:coder]
end

Common MCP Servers

Filesystem

{
  name: "filesystem",
  transport: {
    type: "stdio",
    command: "mcp-server-filesystem",
    args: ["--root", "/path/to/files"]
  }
}

Tools: read_file, write_file, list_directory, search_files

GitHub

{
  name: "github",
  transport: {
    type: "stdio",
    command: "mcp-server-github",
    env: { "GITHUB_TOKEN" => ENV["GITHUB_TOKEN"] }
  }
}

Tools: search_repositories, create_issue, get_file_contents, etc.

Database

{
  name: "postgres",
  transport: {
    type: "stdio",
    command: "mcp-server-postgres",
    env: { "DATABASE_URL" => ENV["DATABASE_URL"] }
  }
}

Tools: query, list_tables, describe_table

MCP Server and Client Objects

For programmatic access, you can work with MCP objects directly:

# Server configuration
server = RobotLab::MCP::Server.new(
  name: "my_server",
  transport: {
    type: "stdio",
    command: "my-mcp-server"
  }
)

# Client connection
client = RobotLab::MCP::Client.new(server)
client.connect

client.connected?           # => true
client.list_tools           # => Array of tool definitions
client.call_tool("search", { query: "ruby" })
client.list_resources       # => Array of resource definitions
client.disconnect

Connection Resilience

Eager Connection

By default, MCP connections are lazy — established on the first run() call. Use connect_mcp! to connect early:

robot = RobotLab.build(
  name: "assistant",
  system_prompt: "You help with tasks.",
  mcp: [
    { name: "github", transport: { type: "stdio", command: "mcp-server-github" } },
    { name: "filesystem", transport: { type: "stdio", command: "mcp-server-fs" } }
  ]
)

robot.connect_mcp!

# Check which servers failed
if robot.failed_mcp_server_names.any?
  puts "Failed to connect: #{robot.failed_mcp_server_names.join(', ')}"
end

Automatic Retry

Failed MCP servers are automatically retried on subsequent run() calls. If a server was down when the robot first connected, it will be retried transparently:

robot.run("First message")       # github connects, filesystem fails
# ... filesystem comes back up ...
robot.run("Second message")      # filesystem retried and connects

Injecting External MCP Clients

Host applications that manage MCP connections externally can inject pre-connected clients into a robot:

robot.inject_mcp!(clients: my_clients, tools: my_tools)

This skips the normal connection process and marks the robot as MCP-initialized.

Error Handling

Connection Errors

begin
  result = robot.run("Search for repos")
rescue RobotLab::MCPError => e
  puts "MCP Error: #{e.message}"
end

MCP connection failures are logged as warnings but do not raise errors by default. The robot will continue without MCP tools if a server is unreachable. One failing server does not prevent other servers from connecting.

Timeout Errors

Stdio transports wrap all blocking I/O with a configurable timeout. If a server does not respond within the timeout period, an MCPError is raised with a descriptive message:

# Server that takes too long will raise:
# RobotLab::MCPError: MCP server 'heavy-server' did not respond within 15s

Disconnecting

Robots can be manually disconnected from MCP servers:

robot.disconnect  # Disconnect all MCP clients

Patterns

Development vs Production

mcp_config = if Rails.env.development?
  [{ name: "local_fs", transport: { type: "stdio", command: "mcp-fs", args: ["--root", "."] } }]
else
  [{ name: "s3", transport: { type: "stdio", command: "mcp-s3" } }]
end

robot = RobotLab.build(
  name: "file_handler",
  system_prompt: "You manage files.",
  mcp: mcp_config
)

Dynamic Server Selection

def mcp_servers_for_user(user)
  servers = []
  servers << github_server if user.github_connected?
  servers << slack_server if user.slack_connected?
  servers
end

robot = RobotLab.build(
  name: "assistant",
  system_prompt: "You help the user with connected services.",
  mcp: mcp_servers_for_user(current_user)
)

Best Practices

1. Use Environment Variables for Credentials

{
  name: "github",
  transport: {
    type: "stdio",
    command: "mcp-server-github",
    env: {
      "GITHUB_TOKEN" => ENV["GITHUB_TOKEN"],
      "GITHUB_ORG" => ENV["GITHUB_ORG"]
    }
  }
}

2. Limit Tool Access

Restrict which MCP tools are available to a robot using the tools: parameter:

robot = RobotLab.build(
  name: "reader",
  system_prompt: "You read and search files.",
  mcp: [{ name: "fs", transport: { type: "stdio", command: "mcp-fs" } }],
  tools: %w[read_file search_files]  # No write access
)

3. Use Appropriate Transports

Transport Best For
stdio Local servers, CLI tools
websocket Persistent connections, bidirectional
sse Server push, event streams
streamable_http Remote APIs, session-based

Next Steps