Skip to main content

MCP Gateway

MCP Gateway

Summary: The MCP Gateway bridges Model Context Protocol (MCP) servers with SignalWire AI agents, enabling your agents to use any MCP-compatible tool through a managed gateway service.

What is MCP?

The Model Context Protocol (MCP) is an open standard for connecting AI systems to external tools and data sources. MCP servers expose "tools" (functions) that AI models can call—similar to SWAIG functions but using a standardized protocol.

The MCP Gateway acts as a bridge: it runs MCP servers and exposes their tools as SWAIG functions that SignalWire agents can call. This lets you leverage the growing ecosystem of MCP tools without modifying your agent code.

Architecture Overview

┌────────────────────────────────────────────────────────────────┐
│ MCP Gateway Flow │
├────────────────────────────────────────────────────────────────┤
│ │
│ SignalWire Agent MCP Gateway MCP Server │
│ │ │ │ │
│ │───(1) Add Skill ────>│ │ │
│ │<──(2) Query Tools ───│ │ │
│ │ │──(3) List Tools ────>│ │
│ │ │<─(4) Tool List ──────│ │
│ │ │ │ │
│ │───(5) SWAIG Call ───>│ │ │
│ │ │──(6) Spawn Session ─>│ │
│ │ │──(7) Call MCP Tool ─>│ │
│ │ │<─(8) MCP Response ───│ │
│ │<──(9) SWAIG Response─│ │ │
│ │ │ │ │
│ │───(10) Call Hangup ─>│ │ │
│ │ │──(11) Close Session->│ │
│ │
└────────────────────────────────────────────────────────────────┘

When to Use MCP Gateway

Good use cases:

  • Integrating existing MCP tools without modification
  • Using community MCP servers (database connectors, APIs, etc.)
  • Isolating tool execution in sandboxed processes
  • Managing multiple tool services from one gateway
  • Session-based tools that maintain state across calls

Consider alternatives when:

  • You need simple, stateless functions (use SWAIG directly)
  • You're building custom tools from scratch (SWAIG is simpler)
  • Low latency is critical (gateway adds network hop)
  • You don't need MCP ecosystem compatibility

Components

The MCP Gateway consists of:

ComponentPurpose
Gateway ServiceHTTP server that manages MCP servers and sessions
MCP ManagerSpawns and communicates with MCP server processes
Session ManagerTracks per-call sessions with automatic cleanup
mcp_gateway SkillSignalWire skill that connects agents to the gateway

Installation

The MCP Gateway is included in the SignalWire Agents SDK. Install with the gateway dependencies:

pip install "signalwire-agents[mcp-gateway]"

Once installed, the mcp-gateway CLI command is available:

mcp-gateway --help

Setting Up the Gateway

1. Configuration

Create a configuration file for the gateway:

{
"server": {
"host": "0.0.0.0",
"port": 8080,
"auth_user": "admin",
"auth_password": "your-secure-password"
},
"services": {
"todo": {
"command": ["python3", "./todo_mcp.py"],
"description": "Todo list management",
"enabled": true,
"sandbox": {
"enabled": true,
"resource_limits": true
}
},
"calculator": {
"command": ["node", "./calc_mcp.js"],
"description": "Mathematical calculations",
"enabled": true
}
},
"session": {
"default_timeout": 300,
"max_sessions_per_service": 100,
"cleanup_interval": 60
}
}

Configuration supports environment variable substitution:

{
"server": {
"auth_password": "${MCP_AUTH_PASSWORD|changeme}"
}
}

2. Start the Gateway

# Using the installed CLI command
mcp-gateway -c config.json

# Or with Docker (in the mcp_gateway directory)
cd mcp_gateway
./mcp-docker.sh start

The gateway starts on the configured port (default 8080).

3. Connect Your Agent

from signalwire_agents import AgentBase

class MCPAgent(AgentBase):
def __init__(self):
super().__init__(name="mcp-agent")
self.add_language("English", "en-US", "rime.spore")

# Connect to MCP Gateway
self.add_skill("mcp_gateway", {
"gateway_url": "http://localhost:8080",
"auth_user": "admin",
"auth_password": "your-secure-password",
"services": [
{"name": "todo", "tools": "*"}, # All tools
{"name": "calculator", "tools": ["add", "multiply"]} # Specific tools
]
})

self.prompt_add_section(
"Role",
"You are an assistant with access to a todo list and calculator."
)

if __name__ == "__main__":
agent = MCPAgent()
agent.run()

Skill Configuration

The mcp_gateway skill accepts these parameters:

ParameterTypeDescriptionDefault
gateway_urlstringGateway service URLRequired
auth_userstringBasic auth usernameNone
auth_passwordstringBasic auth passwordNone
auth_tokenstringBearer token (alternative to basic auth)None
servicesarrayServices and tools to enableAll services
session_timeoutintegerSession timeout in seconds300
tool_prefixstringPrefix for SWAIG function names"mcp_"
retry_attemptsintegerConnection retry attempts3
request_timeoutintegerRequest timeout in seconds30
verify_sslbooleanVerify SSL certificatestrue

Service Configuration

Each service in the services array specifies:

{
"name": "service_name", # Service name from gateway config
"tools": "*" # All tools, or list: ["tool1", "tool2"]
}

Tools are exposed as SWAIG functions with names like mcp_{service}_{tool}.

Gateway API

The gateway exposes these REST endpoints:

EndpointMethodPurpose
/healthGETHealth check (no auth)
/servicesGETList available services
/services/{name}/toolsGETList tools for a service
/services/{name}/callPOSTCall a tool
/sessionsGETList active sessions
/sessions/{id}DELETEClose a session

Example API Calls

# List services
curl -u admin:password http://localhost:8080/services

# Get tools for a service
curl -u admin:password http://localhost:8080/services/todo/tools

# Call a tool
curl -u admin:password -X POST http://localhost:8080/services/todo/call \
-H "Content-Type: application/json" \
-d '{
"tool": "add_todo",
"arguments": {"text": "Buy groceries"},
"session_id": "call-123",
"timeout": 300
}'

Session Management

Sessions are tied to SignalWire call IDs:

  1. First tool call: Gateway creates new MCP process and session
  2. Subsequent calls: Same session reused (process stays alive)
  3. Call ends: Hangup hook closes session and terminates process

This enables stateful tools—a todo list MCP can maintain items across multiple tool calls within the same phone call.

# Session persists across multiple tool calls in same call
# Call 1: "Add milk to my list" → mcp_todo_add_todo(text="milk")
# Call 2: "What's on my list?" → mcp_todo_list_todos() → Returns "milk"
# Call 3: "Add eggs" → mcp_todo_add_todo(text="eggs")
# Call 4: "Read my list" → mcp_todo_list_todos() → Returns "milk, eggs"

Security Features

Authentication

The gateway supports two authentication methods:

{
"server": {
"auth_user": "admin",
"auth_password": "secure-password",
"auth_token": "optional-bearer-token"
}
}

Sandbox Isolation

MCP processes run in sandboxed environments:

{
"services": {
"untrusted_tool": {
"command": ["python3", "tool.py"],
"sandbox": {
"enabled": true,
"resource_limits": true,
"restricted_env": true
}
}
}
}

Sandbox levels:

LevelSettingsUse Case
Highenabled: true, resource_limits: true, restricted_env: trueUntrusted tools
Mediumenabled: true, resource_limits: true, restricted_env: falseTools needing env vars
Noneenabled: falseTrusted internal tools

Resource limits (when enabled):

  • CPU: 300 seconds
  • Memory: 512 MB
  • Processes: 10
  • File size: 10 MB

Rate Limiting

Configure rate limits per endpoint:

{
"rate_limiting": {
"default_limits": ["200 per day", "50 per hour"],
"tools_limit": "30 per minute",
"call_limit": "10 per minute"
}
}

Writing MCP Servers

MCP servers communicate via JSON-RPC 2.0 over stdin/stdout. The gateway spawns these as child processes and communicates with them via stdin/stdout. Here's a minimal example:

#!/usr/bin/env python3
# greeter_mcp.py - Simple MCP server that the gateway can spawn
"""Simple MCP server example"""
import json
import sys

def handle_request(request):
method = request.get("method")
req_id = request.get("id")

if method == "initialize":
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {
"protocolVersion": "2024-11-05",
"serverInfo": {"name": "example", "version": "1.0.0"},
"capabilities": {"tools": {}}
}
}

elif method == "tools/list":
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {
"tools": [
{
"name": "greet",
"description": "Greet someone by name",
"inputSchema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Name to greet"}
},
"required": ["name"]
}
}
]
}
}

elif method == "tools/call":
tool_name = request["params"]["name"]
args = request["params"].get("arguments", {})

if tool_name == "greet":
name = args.get("name", "World")
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {
"content": [{"type": "text", "text": f"Hello, {name}!"}]
}
}

return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": "Method not found"}}

def main():
for line in sys.stdin:
request = json.loads(line)
response = handle_request(request)
print(json.dumps(response), flush=True)

if __name__ == "__main__":
main()

Testing

Test with swaig-test

# List available tools (including MCP tools)
swaig-test greeter_agent.py --list-tools

# Execute the greet tool
swaig-test greeter_agent.py --call-id test-session --exec mcp_greeter_greet --name "World"

# Generate SWML
swaig-test greeter_agent.py --dump-swml

Test Gateway Directly

# Health check (no auth required)
curl http://localhost:8080/health

# List services
curl -u admin:secure-password http://localhost:8080/services

# Get tools for the greeter service
curl -u admin:secure-password http://localhost:8080/services/greeter/tools

# Call the greet tool
curl -u admin:secure-password -X POST http://localhost:8080/services/greeter/call \
-H "Content-Type: application/json" \
-d '{"tool": "greet", "session_id": "test", "arguments": {"name": "World"}}'

# List active sessions
curl -u admin:secure-password http://localhost:8080/sessions

Docker Deployment

The gateway includes Docker support:

cd mcp_gateway

# Build and start
./mcp-docker.sh build
./mcp-docker.sh start

# Or use docker-compose
docker-compose up -d

# View logs
./mcp-docker.sh logs -f

# Stop
./mcp-docker.sh stop

Complete Example

#!/usr/bin/env python3
# greeter_agent.py - Agent with MCP Gateway integration
"""Agent with MCP Gateway integration using the greeter MCP server"""
from signalwire_agents import AgentBase

class GreeterAgent(AgentBase):
def __init__(self):
super().__init__(name="greeter-agent")
self.add_language("English", "en-US", "rime.spore")

# Connect to MCP Gateway
self.add_skill("mcp_gateway", {
"gateway_url": "http://localhost:8080",
"auth_user": "admin",
"auth_password": "secure-password",
"services": [
{"name": "greeter", "tools": "*"}
]
})

self.prompt_add_section(
"Role",
"You are a friendly assistant that can greet people by name."
)

self.prompt_add_section(
"Guidelines",
bullets=[
"When users want to greet someone, use the mcp_greeter_greet function",
"Always be friendly and helpful",
"The greet function requires a name parameter"
]
)

if __name__ == "__main__":
agent = GreeterAgent()
agent.run()

Troubleshooting

IssueSolution
"Connection refused"Verify gateway is running and URL is correct
"401 Unauthorized"Check auth credentials match gateway config
"Service not found"Verify service name and that it's enabled
"Tool not found"Check tool exists with /services/{name}/tools
"Session timeout"Increase session_timeout or default_timeout
Tools not appearingVerify services config includes the service

See Also

TopicReference
Built-in SkillsBuilt-in Skills
SWAIG FunctionsDefining Functions
Testingswaig-test CLI