MCP Gateway
MCPGateway is an HTTP/HTTPS service that bridges Model Context Protocol (MCP) servers to SignalWire SWAIG functions. It manages sessions, handles auth, and translates between MCP's JSON-RPC tool-call protocol and SignalWire's SWAIG webhook protocol.
Use the gateway when you want to expose tools from one or more MCP servers — Anthropic's reference tooling, an org-wide MCP federation, or custom-built MCP services — as SWAIG functions an AI agent can call mid-conversation.
Two integration shapes
| Shape | Use when |
|---|---|
| Standalone MCP Gateway service | You have multiple MCP servers, multiple agents, and want one bridge. |
| AgentBase.add_mcp_server() | One agent, one or two MCP servers, no shared infra. |
Standalone gateway
from signalwire.mcp_gateway import MCPGateway
gateway = MCPGateway("config.json")
gateway.run()
The gateway loads config, spawns each configured MCP server as a subprocess, exposes their tools at authenticated HTTP routes, and translates SWAIG webhook calls into MCP JSON-RPC requests.
config.json shape
{
"services": {
"todo": {
"command": ["python3", "todo_mcp.py"],
"description": "Todo list management",
"enabled": true
},
"github": {
"command": ["node", "github-mcp.js"],
"description": "GitHub repo operations",
"enabled": true,
"env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
}
},
"auth": {
"basic": { "username": "signalwire", "password": "${MCP_GATEWAY_PASSWORD}" }
},
"ssl": {
"enabled": true,
"cert": "/etc/ssl/cert.pem",
"key": "/etc/ssl/key.pem"
}
}
${VAR} placeholders are env-var-substituted at load time.
Auth options
basic— HTTP Basic auth with username/password.bearer— Bearer token (configured underauth.bearer).api_key— Header-based API key (auth.api_key).
All three can be enabled simultaneously. Each request just needs to satisfy any one.
Component classes
MCPManager
Spawns, tracks, and shuts down MCP server subprocesses. The gateway owns one MCPManager.
from signalwire.mcp_gateway import MCPManager
config = {
"services": {
"todo": {"command": ["python3", "todo_mcp.py"],
"description": "Todo list", "enabled": True}
}
}
manager = MCPManager(config)
client = manager.create_client("todo")
tools = client.get_tools()
result = client.call_tool("add_todo", {"text": "Buy groceries"})
client.stop()
manager.shutdown()
MCPClient
Manages a single MCP server subprocess: start, JSON-RPC init, call tools, stop with cleanup. Normally created by MCPManager.create_client(), not directly.
Key methods:
| Method | Purpose |
|---|---|
| start() | Spawn process, init MCP session, fetch tool list. Returns True on success. |
| stop() | Graceful JSON-RPC shutdown → SIGTERM → SIGKILL fallback. Cleans up sandbox. |
| call_tool(name, args) | Invoke a tool, return result dict. |
| call_method(method, params) | Generic JSON-RPC call (e.g., "tools/list"). 30s timeout. |
| get_tools() | Return cached tool definitions. |
SessionManager
Tracks MCP session lifecycle. Sessions auto-expire after a configurable idle timeout. The gateway uses sessions to multiplex multiple concurrent SWAIG callers against the same MCP server subprocess.
Agent-side: AgentBase.add_mcp_server()
If you only have one agent talking to one MCP server, skip the standalone gateway:
from signalwire import AgentBase
agent = AgentBase(name="assistant")
agent.add_mcp_server(
name="todo",
command=["python3", "todo_mcp.py"],
env={"TODO_DB": "/var/data/todos.db"},
)
# Tools from the MCP server are now auto-registered as SWAIG functions.
The agent spawns the MCP subprocess at boot, registers every MCP tool as a SWAIG function under the namespace mcp_{server_name}_{tool_name}, and tears down the subprocess when the agent stops.
For agents deployed serverless, prefer the standalone gateway — serverless cold starts can't host long-lived MCP subprocesses.
Sandbox config
Each MCP service runs in a sandboxed working directory. Defaults are safe (no host filesystem access, ephemeral tmp). Override per-service if a tool needs persistent state:
{
"services": {
"filebrowser": {
"command": ["python3", "fb_mcp.py"],
"enabled": true,
"sandbox_config": {
"working_directory": "/var/data/sandbox",
"ephemeral": false
}
}
}
}
End-to-end SWAIG call flow
- SignalWire AI agent decides to invoke a function (e.g.,
mcp_todo_add_todo). - SignalWire POSTs the SWAIG webhook to the gateway URL.
- Gateway authenticates the request, finds the session, locates the MCP client.
- Gateway translates the SWAIG payload into an MCP
tools/callJSON-RPC request. - MCP server executes the tool and returns a result.
- Gateway formats the result as a SWAIG response and returns it to SignalWire.
- AI speaks the result to the caller.
CLI
The package ships with a mcp-gateway CLI for running the gateway in production:
mcp-gateway --config config.json --port 8080
mcp-gateway --config config.json --ssl-cert cert.pem --ssl-key key.pem
Anti-patterns
- Running the gateway and the agent in the same process — gateway needs persistent subprocesses; serverless agents can't host that.
- Using
MCPClientdirectly instead of viaMCPManager— you lose session tracking and shutdown ordering. - No auth on a public-facing gateway — anyone on the internet can run your MCP tools.
- Setting
sandbox_config.ephemeral: falsefor stateless tools — leaks files between calls. - Embedding secrets directly in
config.json— use${ENV_VAR}substitution.