The Inference Gateway CLI supports direct integration with MCP (Model Context Protocol) servers, allowing you to extend the LLM's capabilities with custom tools from external services.
MCP servers must speak MCP
2026-07-28.inferuses the stateless 2026-07-28 revision only: there is noinitializehandshake, no session, and no fallback to older revisions. A server on an earlier revision is shown as unavailable with an error naming the versions it supports. Current SDKs serve 2026-07-28: TypeScript SDK v2 (createMcpHandler), Gogo-sdkv1.7+ withStreamableHTTPOptions{Stateless: true}, and Pythonmcp2.x. The Inference Gateway's own/mcpendpoint speaks it too, so one entry can expose every server behind the gateway (see Example 4).
- Overview
- Quick Start
- Configuration
- Tool Discovery
- Liveness Probes
- Tool Execution
- Examples
- Auto-Starting MCP Servers
- Troubleshooting
- Security Considerations
- MCP vs A2A
- Advanced Topics
Model Context Protocol (MCP) is a standardized protocol for connecting AI models to external tools and data sources. It enables:
- Stateless tool execution: Each request is independent - no handshake, no session
- Streamable HTTP transport: Each request is one HTTP POST, answered with JSON or a per-request SSE stream
- Dynamic tool discovery: Tools are discovered at runtime
- Schema-based validation: JSON Schema for tool parameters
┌──────────────────────────────────────────────────────────────────┐
│ Inference CLI │
│ │
│ ┌────────────────┐ ┌─────────────────┐ │
│ │ MCP │ │ Tool Registry │ │
│ │ Supervisor │──register──▶ │ │ │
│ │ │ tools │ • Bash │ │
│ │ • Discovery │ │ • Read │ │
│ │ • Execution │ │ • MCP_* │ │
│ └────────┬───────┘ └─────────────────┘ │
│ │ │
└───────────┼──────────────────────────────────────────────────────┘
│
│ Streamable HTTP, MCP 2026-07-28 (stateless)
│
├────────────────────┬────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ MCP Server │ │ MCP Server │ │ MCP Server │
│ (Filesystem) │ │ (Database) │ │ (Custom API) │
│ │ │ │ │ │
│ Tools: │ │ Tools: │ │ Tools: │
│ • read_file │ │ • query │ │ • fetch_data │
│ • write_file │ │ • list_tables │ │ • process │
│ • list_dir │ │ • describe │ │ • transform │
└─────────────────┘ └─────────────────┘ └─────────────────┘
- Direct connections: CLI connects directly to MCP servers, or to the gateway's
/mcpas a single entry - Stateless design: No handshake or session; every request carries its protocol version
- Auto-start servers: Automatically start and manage MCP servers in OCI/Docker containers
- Automatic port assignment: No need to manually configure ports for auto-started servers
- Per-server configuration: Enable/disable servers independently
- Tool filtering: Include/exclude specific tools per server
- Concurrent discovery: Servers are queried in parallel
- Resilient: Failed servers don't prevent CLI startup
- Mode-aware: MCP tools automatically excluded from Plan mode
cd your-project
infer initThis seeds ~/.infer/mcp.yaml with example configuration. Manage MCP servers with infer mcp ...,
which writes to that userspace file unless --project sends the change to ./.infer/mcp.yaml instead.
Edit ~/.infer/mcp.yaml:
enabled: true
connection_timeout: 30
discovery_timeout: 30
servers:
- name: "filesystem"
host: "localhost"
port: 3000
path: "/mcp"
enabled: true
description: "File system operations"Configure the server to start automatically when the CLI launches:
servers:
- name: "demo-server"
enabled: true
run: true # Auto-start in container
oci: "mcp-demo-server:latest"
description: "Demo MCP server"The CLI will automatically:
- Pull the OCI image if needed
- Start the container in the background
- Assign an available port (starting from 3000)
- Configure healthchecks
- Connect to the server
Run the included demo MCP server manually:
cd examples/mcp
docker compose up -dThen configure the server URL:
servers:
- name: "demo-server"
host: "localhost"
port: 3000
path: "/mcp"
enabled: trueThe demo server provides four example tools: get_time, calculate, list_files, and get_env.
infer chatType /help to see available tools. MCP tools appear as MCP_<server>_<tool>.
Outside of chat, infer mcp status dials each enabled server once and reports the same
connected/total (tools) counts the status bar shows; infer mcp status <server> probes one
server and --format json prints a machine-readable report (total_servers, connected_servers,
total_tools, per-server connected, tools, error).
Located in ~/.infer/mcp.yaml (or ./.infer/mcp.yaml for a project override):
| Setting | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false |
Global MCP enable/disable toggle |
connection_timeout |
integer | 30 |
Default connection timeout (seconds) |
discovery_timeout |
integer | 30 |
Tool discovery timeout (seconds) |
liveness_probe_enabled |
boolean | true |
Enable health monitoring |
liveness_probe_interval |
integer | 10 |
Health check interval (seconds) |
max_retries |
integer | 10 |
Maximum retry attempts before marking server as permanently failed |
servers |
array | [] |
List of MCP server configurations |
Each server in the servers array supports:
| Field | Required | Type | Description |
|---|---|---|---|
name |
✅ | string | Unique server identifier |
enabled |
✅ | boolean | Enable/disable this server |
timeout |
❌ | integer | Override global timeout |
description |
❌ | string | Human-readable description |
include_tools |
❌ | array | Include specific tools |
exclude_tools |
❌ | array | Exclude specific tools |
run |
❌ | boolean | Auto-start server in OCI container (default: false) |
host |
❌ | string | Container host (default: localhost) |
scheme |
❌ | string | URL scheme (default: http) |
port |
❌ | integer | Simple port mapping (auto-assigned if omitted) |
ports |
❌ | array | Advanced Docker-compose style port mappings |
path |
❌ | string | HTTP path (default: /mcp) |
oci |
❌* | string | OCI/Docker image (*required if run=true) |
args |
❌ | array | Container startup arguments |
env |
❌ | object | Environment variables for container |
volumes |
❌ | array | Docker volume mounts |
startup_timeout |
❌ | integer | Container startup timeout in seconds (default: 30) |
health_cmd |
❌ | string | Custom Docker healthcheck command |
Manual servers (
run: false): sethost/port/pathto point at the running server's MCP endpoint. The URL is built as{scheme}://{host}:{port}{path}(defaults:http,localhost,/mcp).
When include_tools is specified, only these tools are exposed:
servers:
- name: "database"
host: "localhost"
port: 3001
path: "/mcp"
enabled: true
include_tools:
- "query"
- "describe_table"
- "list_tables"When exclude_tools is specified, these tools are hidden:
servers:
- name: "filesystem"
host: "localhost"
port: 3000
path: "/mcp"
enabled: true
exclude_tools:
- "delete_file" # Exclude dangerous operations
- "format_disk"- If
include_toolsis set, it takes precedence (strict allowed list) - If only
exclude_toolsis set, all tools except excluded ones are available - If neither is set, all tools from the server are available
All configuration values support environment variable expansion:
servers:
- name: "filesystem"
host: "${MCP_FILESYSTEM_HOST}"
port: 3000
path: "/mcp"
enabled: trueOverride via environment:
export INFER_MCP_ENABLED=true
export INFER_MCP_CONNECTION_TIMEOUT=60
export MCP_FILESYSTEM_HOST=localhost- CLI startup: The MCP supervisor starts
- Concurrent discovery: Each enabled server is queried in parallel
- Protocol check:
server/discovermust list2026-07-28; a server that doesn't is marked unavailable - Tool listing:
tools/list, following pagination cursors - Tool registration: Discovered tools are registered in the tool registry
- Filtering applied: Include/exclude rules are enforced
- Naming: Tools are prefixed with
MCP_<server>_<tool>
Configure discovery timeout to prevent slow servers from delaying startup:
discovery_timeout: 30 # secondsIf a server fails during discovery:
- A warning is logged
- Other servers continue normally
- CLI starts successfully
- Failed server's tools are not available
Failures are logged at debug level per retry attempt, with an error once retries are exhausted:
{"level":"debug","msg":"mCP server health check failed (tool discovery failed)","server":"filesystem","error":"connection refused"}
{"level":"error","msg":"mCP server permanently failed after max retries","server":"filesystem","error":"connection refused"}
Successful discovery is logged with server and toolCount fields:
{"level":"info","msg":"mCP server tools discovered successfully","server":"database","toolCount":5}
The CLI includes health monitoring for MCP servers to detect disconnections and display real-time connection status in the UI.
- Background Monitoring: Goroutines periodically send
server/discoverto each enabled MCP server - Status Updates: Connection changes trigger UI updates via event channels
- Real-time Display: Status bar shows "MCP: X/Y" (connected/total)
- Auto-reconnection: When server reconnects, tools become available immediately
liveness_probe_enabled: true # Enable health monitoring
liveness_probe_interval: 10 # Seconds between health checks
max_retries: 10 # Maximum retry attempts before permanent failureThe status bar displays current MCP server health:
MCP: 0/1 # 0 connected, 1 total (server down)
MCP: 1/1 # 1 connected, 1 total (server healthy)
MCP: 2/3 # 2 connected, 3 total (1 server down)
infer mcp status reports the same counts from outside a chat session.
A run: true server is normally started when a session begins and stopped when it ends. To keep
it running across sessions (for example when the desktop app starts one infer headless process
per message), start it detached:
infer mcp start # every enabled run: true server
infer mcp start demo # one server
infer mcp stop demoDetached containers are named inference-mcp-<server>-shared. A session that finds one running
connects to it instead of starting its own copy and leaves it running on exit.
- Initial State: Shows total servers, all marked disconnected
- First Connect: When the server answers
server/discoverandtools/list, status updates to connected - Disconnection: A failed probe marks the server as disconnected
- Reconnection: A successful probe after failure marks the server as connected
- Event-Driven: UI updates only when status actually changes (no polling)
To disable health monitoring:
liveness_probe_enabled: falseWith probes disabled, the MCP status will not appear in the status bar. Servers are still checked during initial tool discovery at startup.
- LLM requests tool:
MCP_filesystem_read_file - Tool lookup: Registry finds MCP tool wrapper
- Server call: One
tools/callPOST carrying the protocol version (no session) - Result formatting: Content blocks flattened to text for LLM/UI
Per-server timeout configuration:
servers:
- name: "slow-server"
host: "slow-service"
port: 8080
path: "/mcp"
timeout: 120 # Override global timeoutTimeout precedence:
- Server-specific
timeout - Global
connection_timeout - Default: 30 seconds
Common errors and handling:
| Error | Behavior |
|---|---|
| Server unreachable | Tool execution fails, error returned to LLM |
| Timeout exceeded | Connection closed, timeout error returned |
| Invalid arguments | Validation error before server call |
| Server error | Error response passed to LLM |
Tool result with isError |
Execution fails, the tool's message is passed to LLM |
Tool asks for input (input_required) |
Execution fails; elicitation and sampling are not supported |
# .infer/mcp.yaml
enabled: true
connection_timeout: 30
discovery_timeout: 30
servers:
- name: "filesystem"
host: "localhost"
port: 3000
path: "/mcp"
enabled: true
description: "Sandboxed file system operations"
exclude_tools:
- "delete_file"
- "delete_directory"Available tools:
MCP_filesystem_read_fileMCP_filesystem_write_fileMCP_filesystem_list_directoryMCP_filesystem_create_directoryMCP_filesystem_get_file_info
enabled: true
servers:
# Filesystem access
- name: "filesystem"
host: "localhost"
port: 3000
path: "/mcp"
enabled: true
timeout: 60
# Database queries
- name: "postgres"
host: "localhost"
port: 3001
path: "/mcp"
enabled: true
include_tools:
- "query"
- "describe_table"
# External API (disabled)
- name: "weather-api"
host: "localhost"
port: 3002
path: "/mcp"
enabled: false# .infer/mcp.yaml
enabled: ${MCP_ENABLED:-false}
connection_timeout: ${MCP_TIMEOUT:-30}
servers:
- name: "production-db"
scheme: "https"
host: "${PROD_DB_MCP_HOST}"
path: "/mcp"
enabled: ${PROD_DB_ENABLED:-false}# .env
MCP_ENABLED=true
MCP_TIMEOUT=60
PROD_DB_MCP_HOST=mcp.production.example.com
PROD_DB_ENABLED=trueThe Inference Gateway serves every server in its MCP_SERVERS behind one MCP 2026-07-28 endpoint, POST /mcp.
Point one entry at it instead of configuring each server in infer:
# gateway environment
MCP_ENABLED=true
MCP_EXPOSE=true
MCP_SERVERS=http://filesystem:3000/mcp,http://database:3000/mcp# .infer/mcp.yaml
enabled: true
servers:
- name: "gateway"
host: "localhost"
port: 8080
path: "/mcp"
enabled: trueThe gateway namespaces its tools as mcp_<alias>_<tool>, so they surface in infer as
MCP_gateway_mcp_<alias>_<tool>. Server entries cannot send an Authorization header yet, so this needs a gateway
without auth enabled on /mcp.
Check 1: Is MCP enabled?
enabled: true # Must be trueCheck 2: Are servers enabled?
servers:
- name: "my-server"
enabled: true # Must be trueCheck 3: Check CLI logs
There is no --log-level flag; debug logging is enabled with the INFER_LOGGING_DEBUG=true
environment variable or the global -v/--verbose flag. Logs are written to
~/.infer/logs/app-<date>.log, not to the terminal:
INFER_LOGGING_DEBUG=true infer chat
grep "tools discovered successfully" ~/.infer/logs/app-*.logSuccessful discovery is logged as:
{"level":"info","msg":"mCP server tools discovered successfully","server":"filesystem","toolCount":8}
Error: connection refused
Causes:
- MCP server not running
- Wrong URL/port
- Firewall blocking connection
Solutions:
- Verify the server answers:
infer mcp status <server> - Check URL in config
- Check network connectivity
Error: MCP server must speak protocol version 2026-07-28 ...
The server is on an earlier MCP revision (it expects an initialize handshake or a session). The error names the
versions it supports when it says. Upgrade the server to an SDK that serves 2026-07-28 (see the note at the top);
Go servers on go-sdk also need StreamableHTTPOptions{Stateless: true}.
Error: context deadline exceeded
Causes:
- Server response too slow
- Network latency
- Timeout too short
Solutions:
-
Increase timeout:
servers: - name: "slow-server" timeout: 120 # Increase from default 30
-
Check server performance
-
Verify network connection
Issue: Excluded tools still appear
Check: Verify exact tool names:
infer mcp status <server> --format jsonThe JSON report lists each server's tools. You can also check the discovery log - debug logging goes to
~/.infer/logs/app-<date>.log (see Check 3 above):
grep "tools discovered successfully" ~/.infer/logs/app-*.logFix: Match exact tool names:
exclude_tools:
- "delete_file" # Exact match requiredThis is a bug - MCP tools should be automatically filtered in Plan mode. Please file an issue.
Always use exclude_tools to block dangerous operations:
servers:
- name: "filesystem"
exclude_tools:
- "delete_file"
- "delete_directory"
- "format_disk"
- "execute_command"- Use HTTPS for production MCP servers
- Configure firewall rules
- Use VPN for remote servers
- Limit server access with authentication (if supported by MCP server)
Set reasonable timeouts to prevent hanging:
connection_timeout: 30
discovery_timeout: 30MCP tool wrappers validate arguments before sending to server. Invalid arguments are rejected before network calls.
Run MCP servers in sandboxed environments:
- Docker containers
- Virtual machines
- Restricted user accounts
- Stateless operations: Read data, query APIs, simple transformations
- Fast execution: Operations complete in seconds
- Direct tool calls: Single request/response
- External services: Databases, APIs, file systems
- Long-running tasks: Operations taking minutes to hours
- Complex workflows: Multi-step processes
- Background processing: Async task execution
- Agent delegation: Specialized AI agents for specific domains
| Feature | MCP | A2A |
|---|---|---|
| Connection | Stateless Streamable HTTP | Persistent |
| Duration | Seconds | Minutes to hours |
| Use case | Tool execution | Task delegation |
| Polling | N/A | Background monitoring |
| Mode availability | Standard, Auto-Accept | All modes |
The CLI can automatically start and manage MCP servers as OCI/Docker containers. This provides:
- Zero-configuration startup: Servers start automatically when CLI launches
- Automatic port assignment: No need to manually configure ports
- Container lifecycle management: Start, stop, and health monitoring
- Background execution: Non-blocking startup, CLI ready immediately
- Automatic healthchecks: Container healthchecks with an MCP
server/discoverrequest
Add a server with auto-start:
infer mcp add my-server \
--description="My MCP server" \
--run \
--oci=my-mcp-server:latestThis automatically:
- Creates server configuration with
run: true - Assigns the next available port, starting at 3001
- Configures container with defaults (localhost, http, /mcp path)
- Adds a container healthcheck using an MCP
server/discoverrequest
Auto-start servers use component-based URL configuration instead of a single URL field:
servers:
- name: "my-server"
enabled: true
run: true # Enable auto-start
oci: "my-mcp-server:latest" # OCI/Docker image
host: localhost # Default: localhost
scheme: http # Default: http
port: 3000 # Auto-assigned if omitted
path: /mcp # Default: /mcp
startup_timeout: 60 # Default: 30 secondsThe URL is constructed as: {scheme}://{host}:{port}{path}
Automatic (recommended):
When adding servers without specifying --port, the CLI automatically:
- Finds the highest port currently used by MCP servers
- Assigns
basePort + 1(starting from 3000)
infer mcp add server-1 --run --oci=image:latest # Gets port 3000
infer mcp add server-2 --run --oci=image:latest # Gets port 3001
infer mcp add server-3 --run --oci=image:latest # Gets port 3002Manual:
Specify a custom port:
infer mcp add my-server --run --oci=image:latest --port=8080Advanced port mappings:
For complex scenarios, use ports array (Docker-compose style):
servers:
- name: "multi-port-server"
run: true
oci: "my-server:latest"
ports:
- "3000:8080" # Host:Container
- "3001:8081"Environment variables:
servers:
- name: "api-server"
run: true
oci: "api-mcp:latest"
env:
API_KEY: "${MY_API_KEY}" # Supports variable expansion
LOG_LEVEL: "debug"
DATABASE_URL: "postgres://..."Volume mounts:
servers:
- name: "filesystem-server"
run: true
oci: "fs-mcp:latest"
volumes:
- "/host/path:/container/path"
- "/data:/mnt/data:ro" # Read-onlyStartup arguments:
servers:
- name: "custom-server"
run: true
oci: "custom-mcp:latest"
args:
- "--verbose"
- "--config=/etc/config.yaml"Custom healthcheck:
servers:
- name: "api-server"
run: true
oci: "api-mcp:latest"
health_cmd: 'sh -c "curl -f http://localhost:8080/health || exit 1"'Default healthcheck (MCP server/discover):
sh -c 'curl -fsS -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" -H "Mcp-Method: server/discover" \
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"server/discover\",\"params\":{\"_meta\":{\"io.modelcontextprotocol/protocolVersion\":\"2026-07-28\",\"io.modelcontextprotocol/clientCapabilities\":{}}}}"\
> /dev/null || exit 1'Container naming: inference-mcp-{server-name}-{session-id}, or
inference-mcp-{server-name}-shared for a detached infer mcp start container
Network: All containers join the infer-network Docker network
Restart policy: none. The CLI passes no --restart flag, so the container follows the Docker
daemon default when it restarts. Detached infer mcp start containers are the ones meant to outlive
the session.
Startup behavior:
- CLI checks if container already running (reuses if exists)
- Pulls image if not cached locally
- Starts container in background goroutine
- Waits for healthcheck to pass (with timeout)
- Logs success or failure (non-fatal)
Shutdown: Containers are stopped and removed when CLI exits
Add server with auto-start:
infer mcp add <name> [flags]
--run # Enable auto-start
--oci <image> # OCI/Docker image (required if --run)
--port <port> # Optional: specific port
--startup-timeout <sec> # Optional: startup timeout (default: 60)
--description <text> # Optional: description
--enabled # Optional: enable immediately (default: true)Examples:
# Minimal - automatic port assignment
infer mcp add demo --run --oci=mcp-demo:latest
# With custom port
infer mcp add api --run --oci=api-mcp:latest --port=8080
# With startup timeout
infer mcp add slow --run --oci=slow-mcp:latest --startup-timeout=120
# Complete configuration
infer mcp add custom \
--run \
--oci=custom-mcp:latest \
--port=3000 \
--startup-timeout=60 \
--description="Custom MCP server" \
--enabledManaging servers:
# List servers
infer mcp list
# Add a manual server alongside an auto-started one
infer mcp add filesystem http://localhost:3000/sse
infer mcp add demo --run --oci=mcp-demo-server:latest --port=3000
# Change an existing server
infer mcp update filesystem --url=http://localhost:3002/sse
infer mcp update filesystem --timeout=60 --include=read_file,list_directory
# Remove server (stops container if running)
infer mcp remove <name>
# Enable or disable a server, or all of MCP
infer mcp enable <name>
infer mcp disable <name>
infer mcp enable-global
infer mcp disable-global
# Probe each enabled server and report its state and tool count
infer mcp status
infer mcp status filesystem --format jsonEvery write lands in ~/.infer/mcp.yaml unless --project is passed, which writes to
./.infer/mcp.yaml instead. infer init seeds the userspace file.
update flags: --url, --description, --enabled, --timeout <seconds> (-1 leaves it
unchanged, 0 falls back to the global timeout), --include <tools> and --exclude <tools>
(an empty value leaves the filters unchanged).
add flags: --description, --enabled, --timeout <seconds>, --include <tools>,
--exclude <tools> and the container set --run, --oci <image>, --port <port>,
--startup-timeout <seconds>.
A run: true server can override the image's startup with the entrypoint and command keys in
mcp.yaml, alongside args, env and volumes.
Container won't start:
Check container logs:
docker logs inference-mcp-<name>Check if port is already in use:
lsof -i :<port>Healthcheck failing:
Verify the server's healthcheck endpoint:
curl -v http://localhost:<port>/healthTest the MCP endpoint (it must list 2026-07-28 in supportedVersions):
curl -X POST http://localhost:<port>/mcp \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" -H "Mcp-Method: server/discover" \
-d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'Image not found:
Build or pull the image manually:
docker pull <image>
# or
docker build -t <image> .Startup timeout:
Increase timeout for slow-starting servers:
servers:
- name: "slow-server"
startup_timeout: 120 # 2 minutesCreate a custom MCP server with the official Go SDK. The stateless Streamable HTTP handler serves MCP 2026-07-28
(a complete version lives in examples/mcp/mcp-server):
server := mcp.NewServer(&mcp.Implementation{Name: "my-custom-server", Version: "1.0.0"}, nil)
mcp.AddTool(server, &mcp.Tool{Name: "my_tool", Description: "My custom tool"},
func(_ context.Context, _ *mcp.CallToolRequest, in struct {
Input string `json:"input"`
}) (*mcp.CallToolResult, any, error) {
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: "Processed: " + in.Input}}}, nil, nil
})
// 2026-07-28 requests are only served in stateless mode.
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return server },
&mcp.StreamableHTTPOptions{Stateless: true})
http.Handle("/mcp", handler)
log.Fatal(http.ListenAndServe(":3000", nil))Monitor MCP tool usage:
# Count MCP tool calls
cat ~/.infer/logs/*.log | grep "MCP_" | wc -l
# Failed MCP calls
cat ~/.infer/logs/*.log | grep "MCP.*failed"For issues or questions: