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
-
Create the MCP config file
mkdir -p mcpCreate
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 }}" -
Set the secret
muxi secrets set BRAVE_API_KEY -
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. |
tools
| object | - | Filter the agent-visible tool surface advertised by this MCP server. See Tool Filtering. |
Tool Filtering (whitelist / blacklist)
Large MCP servers (Microsoft 365, Google Workspace, ms365-assistant, large internal catalogs) often expose 30+ tools, of which any given agent only needs a handful. Filtering trims the surface advertised to the LLM at registration time, so the planning prompt shrinks and the model is less likely to pick a wrong-but-plausible tool.
# mcp/ms365-excel.afs
schema: "1.0.0"
id: ms365-excel
type: command
command: npx
args: ["-y", "@softeria/ms-365-mcp-server"]
auth:
type: env
ACCESS_TOKEN: "${{ user.credentials.MS365 }}"
tools:
whitelist:
- "list-excel-files"
- "read-excel-*"
- "update-excel-*"
Schema
| Field | Type | Description |
|---|---|---|
tools.whitelist
| string[] | If set, only these tool names are exposed. fnmatch-style globs (*, ?) supported.
|
tools.blacklist
| string[] | If set, every tool except these is exposed. Same glob syntax. |
whitelist and blacklist are mutually exclusive. Setting both at the same time fails formation validation at load time.
How it's applied
Upstream MCP tools/list response
↓
Filter (whitelist or blacklist, fnmatch globs)
↓
Agent-visible tool registry (this is what the LLM ever sees)
The filter runs on every MCP tools/list refresh, so dynamic tool catalogs stay filtered after upstream redeploys. Filtered-out tools never appear in the planning prompt or in the agent's allowed-tool list — they are invisible end-to-end.
Common patterns
| Goal | Pattern |
|---|---|
| Read-only access to a domain | whitelist: ["list-*", "read-*", "search-*"]
|
| Block destructive operations | blacklist: ["delete-*", "drop-*", "purge-*"]
|
| Single-tool agent | whitelist: ["search-web"]
|
| Per-domain split of a large catalog | One MCP file per domain (excel/word/email/calendar), each with its own whitelist
|
Per-domain split for large catalogs
For MCPs with >20 tools, the recommended pattern is to define one MCP .afs file per domain, each scoped to the appropriate agent, instead of giving every agent the full catalog. Example: ms365-excel.afs (whitelisted to Excel tools) → ms365-excel-agent.afs; ms365-email.afs → ms365-email-agent.afs. The full upstream MCP server still runs once per agent, but each agent's tool surface is a curated slice. See the multi-agent guide for a worked example.
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:
- Discovery: On formation startup, the runtime connects to each MCP server, discovers its tools, then disconnects.
- First tool call: The runtime reconnects and keeps the connection alive.
- Subsequent calls: Reuse the existing connection. Each call resets the idle timer.
- Idle timeout: A background reaper closes connections that have been idle longer than
connection_ttl. - 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
- Secrets - Manage tool credentials
- Agents - Assign tools to agents
- Add Tools Guide - Step-by-step tutorial