Tools (MCP)

Give agents superpowers with MCP servers

Tools let agents interact with the world - search the web, read files, query databases, call APIs. MUXI uses the Model Context Protocol (MCP) for tool integration.

Add Your First Tool

  1. Create the MCP config file

    mkdir -p mcp

    Create mcp/web-search.afs:

    schema: "1.0.0"
    id: web-search
    description: Brave web search
    type: command
    command: npx
    args: ["-y", "@modelcontextprotocol/server-brave-search"]
    auth:
      type: env
      BRAVE_API_KEY: "${{ secrets.BRAVE_API_KEY }}"
  2. Set the secret

    muxi secrets set BRAVE_API_KEY
  3. Test it

    All agents in the formation automatically have access to MCP servers defined in mcp/*.afs.

    muxi dev
    # Then: "Search for recent AI news"

MCP Server Types

Command-based (Local)

Runs a local process:

# mcp/local-tool.afs
schema: "1.0.0"
id: local-tool
type: command
command: npx
args: ["-y", "@modelcontextprotocol/server-brave-search"]
timeout_seconds: 30
auth:
  type: env
  API_KEY: "${{ secrets.API_KEY }}"

HTTP-based (Remote)

Calls a remote MCP server:

# mcp/remote-tool.afs
schema: "1.0.0"
id: remote-tool
type: http
endpoint: "https://api.example.com/mcp"
timeout_seconds: 30
auth:
  type: bearer
  token: "${{ secrets.API_TOKEN }}"

MCP File Fields

Required Fields

Field Type Description
schema string Schema version ("1.0.0")
id string Unique identifier
description string What this MCP server does
type enum command (stdio) or http

Command Type Fields

Field Type Description
command string Command to execute (npx, python, node)
args array Command arguments
env object Environment variables for the command
cwd string Working directory for the command

HTTP Type Fields

Field Type Description
endpoint string HTTP endpoint URL
protocol enum http or https
method enum GET, POST, PUT, DELETE (default: POST)
headers object Additional HTTP headers

Common Optional Fields

Field Type Default Description
timeout_seconds int - Request timeout
retry_attempts int - Number of retries (0-10)
connection_ttl int - Per-server idle TTL override (seconds). See Connection Lifecycle.
parameters object - Default tool arguments injected into every call. See Default Parameters.

Capabilities

capabilities:
  tools: ["list_repos", "create_issue"]  # Tool names provided
  streaming: false                        # Supports streaming?
  async: false                            # Supports async?

Health Check (HTTP only)

health_check:
  enabled: true
  endpoint: "/health"
  interval_seconds: 60
  timeout_seconds: 5

Rate Limiting

rate_limiting:
  requests_per_minute: 60
  requests_per_hour: 1000
  burst_limit: 10

Metadata

metadata:
  version: "1.0.0"
  author: "Your Team"
  homepage: "https://example.com"
  documentation: "https://docs.example.com"
  tags: ["search", "web"]

Popular MCP Servers

Server What It Does Type
@modelcontextprotocol/server-brave-search Web search command
@modelcontextprotocol/server-filesystem Read/write files command
@modelcontextprotocol/server-github GitHub API command
@modelcontextprotocol/server-postgres PostgreSQL command
@modelcontextprotocol/server-sqlite SQLite command

Configuration Examples

Web Search

# mcp/web-search.afs
schema: "1.0.0"
id: web-search
type: command
command: npx
args: ["-y", "@modelcontextprotocol/server-brave-search"]
auth:
  type: env
  BRAVE_API_KEY: "${{ secrets.BRAVE_API_KEY }}"

File System

# mcp/filesystem.afs
schema: "1.0.0"
id: filesystem
type: command
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "./documents", "./projects"]
timeout_seconds: 30

Always restrict file access to specific directories in the args.

Database

# mcp/postgres.afs
schema: "1.0.0"
id: database
type: command
command: npx
args: ["-y", "@modelcontextprotocol/server-postgres"]
auth:
  type: env
  DATABASE_URL: "${{ secrets.DATABASE_URL }}"
# mcp/sqlite.afs
schema: "1.0.0"
id: database
type: command
command: npx
args: ["-y", "@modelcontextprotocol/server-sqlite", "--db", "./data/app.db"]

GitHub

# mcp/github.afs
schema: "1.0.0"
id: github
type: command
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
auth:
  type: env
  GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

Formation-Level MCP Settings

Configure global MCP behavior in formation.afs:

mcp:
  connection_ttl: 300             # Idle connection TTL in seconds (default: 300)
  default_timeout_seconds: 30
  max_tool_iterations: 10
  max_tool_calls: 50
  max_repeated_errors: 3

Connection Lifecycle

MCP connections follow a lazy-persistent lifecycle:

  1. Discovery: On formation startup, the runtime connects to each MCP server, discovers its tools, then disconnects.
  2. First tool call: The runtime reconnects and keeps the connection alive.
  3. Subsequent calls: Reuse the existing connection. Each call resets the idle timer.
  4. Idle timeout: A background reaper closes connections that have been idle longer than connection_ttl.
  5. Next call after timeout: The runtime reconnects transparently.

A frequently-used server stays connected indefinitely. A rarely-used server closes after the TTL and reconnects on demand.

Set connection_ttl: 0 to disable keep-alive and use ephemeral connections (connect/execute/disconnect per call).

Per-server override

Override the global TTL for individual servers:

# mcp/ms365.afs
schema: "1.0.0"
id: ms365
type: http
endpoint: "https://ms365.example.com/mcp"
connection_ttl: 600    # Keep this server alive longer (10 minutes)
# mcp/one-off-tool.afs
schema: "1.0.0"
id: one-off-tool
type: http
endpoint: "https://tool.example.com/mcp"
connection_ttl: 0      # Always disconnect after each call

Default Parameters

MCP servers support an optional parameters field: a flat key-value map injected into every tool call on that server. Use this for infrastructure constants that should never be left to LLM inference -- org-level drive IDs, tenant IDs, project keys, etc.

# mcp/ms365.afs
schema: "1.0.0"
id: ms365-mcp
type: http
endpoint: "https://mcp.example.com/ms365"
parameters:
  driveId: "${{ secrets.ORG_DRIVE_ID }}"
  siteId: "contoso.sharepoint.com,guid1,guid2"

How it works:

  • Parameters are injected at execution time, before the LLM planner runs
  • If a tool call already provides a value for the same key, the explicit value wins
  • Values support ${{ secrets.X }} and ${{ user.credentials.X }} interpolation
  • Must be a flat map with scalar values (strings, numbers, booleans) -- no nested objects

When to use:

  • Fixed org-wide identifiers (drive IDs, site IDs, tenant IDs)
  • API version strings
  • Any value that is constant across all tool calls for a given server

Move constants out of agent prompts. If your agent's system message contains a hardcoded ID just so the LLM can pass it to tools, put it in parameters instead. The LLM never needs to see or propagate infrastructure config.

Works on both formation-level MCP servers (mcp/*.afs) and agent-level MCP servers (mcp_servers in agent .afs files).

Agent-Specific Tools

Prefer per-agent tools. Defining tools per-agent improves Overlord's agent selection and each agent's tool selection. Use global MCP servers only for tools ALL agents need.

Formation-level MCP servers (in mcp/*.afs, declared in mcp.servers) are available to all agents. Agents can reference them by string ID, or define agent-private tools inline:

# agents/researcher.afs
schema: "1.0.0"
id: researcher
name: Researcher
description: Research topics

system_message: |
  You are a research specialist.
  Your job is to gather accurate information...

mcp_servers:
  - web-search              # Reference formation-level MCP by ID
# agents/developer.afs
schema: "1.0.0"
id: developer
name: Developer
description: Code assistant

system_message: |
  You are a software developer.
  Your job is to write, review, and debug code...

mcp_servers:
  - filesystem              # Reference formation-level MCP by ID
  - database                # Reference formation-level MCP by ID

Authentication Types

Environment Variables

auth:
  type: env
  API_KEY: "${{ secrets.API_KEY }}"
  DEBUG: "true"

Bearer Token

auth:
  type: bearer
  token: "${{ secrets.TOKEN }}"

Basic Auth

auth:
  type: basic
  username: "${{ secrets.USERNAME }}"
  password: "${{ secrets.PASSWORD }}"

API Key Header

auth:
  type: api_key
  header: "X-API-Key"
  key: "${{ secrets.API_KEY }}"

Troubleshooting

Tool Not Available

Verify the server is installed:

npx @modelcontextprotocol/server-brave-search --version
Connection Failed

Check server logs:

muxi logs --mcp

Common issues:

  • Invalid API key
  • Network connectivity
  • Server not running

Next Steps