MCP Gateway

View as Markdown

MCP Gateway

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.
MCP Gateway Flow

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:

1{
2 "server": {
3 "host": "0.0.0.0",
4 "port": 8080,
5 "auth_user": "admin",
6 "auth_password": "your-secure-password"
7 },
8 "services": {
9 "todo": {
10 "command": ["python3", "./todo_mcp.py"],
11 "description": "Todo list management",
12 "enabled": true,
13 "sandbox": {
14 "enabled": true,
15 "resource_limits": true
16 }
17 },
18 "calculator": {
19 "command": ["node", "./calc_mcp.js"],
20 "description": "Mathematical calculations",
21 "enabled": true
22 }
23 },
24 "session": {
25 "default_timeout": 300,
26 "max_sessions_per_service": 100,
27 "cleanup_interval": 60
28 }
29}

Configuration supports environment variable substitution:

1{
2 "server": {
3 "auth_password": "${MCP_AUTH_PASSWORD|changeme}"
4 }
5}

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

1from signalwire_agents import AgentBase
2
3class MCPAgent(AgentBase):
4 def __init__(self):
5 super().__init__(name="mcp-agent")
6 self.add_language("English", "en-US", "rime.spore")
7
8 # Connect to MCP Gateway
9 self.add_skill("mcp_gateway", {
10 "gateway_url": "http://localhost:8080",
11 "auth_user": "admin",
12 "auth_password": "your-secure-password",
13 "services": [
14 {"name": "todo", "tools": "*"}, # All tools
15 {"name": "calculator", "tools": ["add", "multiply"]} # Specific tools
16 ]
17 })
18
19 self.prompt_add_section(
20 "Role",
21 "You are an assistant with access to a todo list and calculator."
22 )
23
24if __name__ == "__main__":
25 agent = MCPAgent()
26 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:

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

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.

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

Security Features

Authentication

The gateway supports two authentication methods:

1{
2 "server": {
3 "auth_user": "admin",
4 "auth_password": "secure-password",
5 "auth_token": "optional-bearer-token"
6 }
7}

Sandbox Isolation

MCP processes run in sandboxed environments:

1{
2 "services": {
3 "untrusted_tool": {
4 "command": ["python3", "tool.py"],
5 "sandbox": {
6 "enabled": true,
7 "resource_limits": true,
8 "restricted_env": true
9 }
10 }
11 }
12}

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:

1{
2 "rate_limiting": {
3 "default_limits": ["200 per day", "50 per hour"],
4 "tools_limit": "30 per minute",
5 "call_limit": "10 per minute"
6 }
7}

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:

1#!/usr/bin/env python3
2# greeter_mcp.py - Simple MCP server that the gateway can spawn
3"""Simple MCP server example"""
4import json
5import sys
6
7def handle_request(request):
8 method = request.get("method")
9 req_id = request.get("id")
10
11 if method == "initialize":
12 return {
13 "jsonrpc": "2.0",
14 "id": req_id,
15 "result": {
16 "protocolVersion": "2024-11-05",
17 "serverInfo": {"name": "example", "version": "1.0.0"},
18 "capabilities": {"tools": {}}
19 }
20 }
21
22 elif method == "tools/list":
23 return {
24 "jsonrpc": "2.0",
25 "id": req_id,
26 "result": {
27 "tools": [
28 {
29 "name": "greet",
30 "description": "Greet someone by name",
31 "inputSchema": {
32 "type": "object",
33 "properties": {
34 "name": {"type": "string", "description": "Name to greet"}
35 },
36 "required": ["name"]
37 }
38 }
39 ]
40 }
41 }
42
43 elif method == "tools/call":
44 tool_name = request["params"]["name"]
45 args = request["params"].get("arguments", {})
46
47 if tool_name == "greet":
48 name = args.get("name", "World")
49 return {
50 "jsonrpc": "2.0",
51 "id": req_id,
52 "result": {
53 "content": [{"type": "text", "text": f"Hello, {name}!"}]
54 }
55 }
56
57 return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": "Method not found"}}
58
59def main():
60 for line in sys.stdin:
61 request = json.loads(line)
62 response = handle_request(request)
63 print(json.dumps(response), flush=True)
64
65if __name__ == "__main__":
66 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

1#!/usr/bin/env python3
2# greeter_agent.py - Agent with MCP Gateway integration
3"""Agent with MCP Gateway integration using the greeter MCP server"""
4from signalwire_agents import AgentBase
5
6class GreeterAgent(AgentBase):
7 def __init__(self):
8 super().__init__(name="greeter-agent")
9 self.add_language("English", "en-US", "rime.spore")
10
11 # Connect to MCP Gateway
12 self.add_skill("mcp_gateway", {
13 "gateway_url": "http://localhost:8080",
14 "auth_user": "admin",
15 "auth_password": "secure-password",
16 "services": [
17 {"name": "greeter", "tools": "*"}
18 ]
19 })
20
21 self.prompt_add_section(
22 "Role",
23 "You are a friendly assistant that can greet people by name."
24 )
25
26 self.prompt_add_section(
27 "Guidelines",
28 bullets=[
29 "When users want to greet someone, use the mcp_greeter_greet function",
30 "Always be friendly and helpful",
31 "The greet function requires a name parameter"
32 ]
33 )
34
35if __name__ == "__main__":
36 agent = GreeterAgent()
37 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