Skip to content

Integrate mcp-gateway (awmg) with awf firewall using MCP streamable-http protocol #7429

@Mossaka

Description

@Mossaka

Summary

Enable MCP servers running on the host to be accessible by agents running inside the awf (aw-firewall) container through the mcp-gateway, using the standard MCP streamable-http protocol.

Current State

mcp-gateway (awmg) currently:

  • Exposes custom HTTP endpoints: /health, /servers, /mcp/{server} (POST)
  • Uses simple JSON-RPC over HTTP, NOT the standard MCP streamable-http protocol
  • Only supports command/stdio transport for backend MCP servers
  • Has config rewriting that generates http://host.docker.internal:PORT/mcp/{server} URLs

awf (firewall) currently:

  • Runs agents in isolated containers on dedicated network (172.30.0.0/24)
  • Routes HTTP traffic through Squid proxy with domain whitelisting
  • Allows localhost traffic (stdio MCP servers work automatically)
  • Supports host.docker.internal for accessing host services

The Problem

The current gateway's /mcp/{server} endpoint is a custom JSON POST API, not a proper MCP streamable-http endpoint. An MCP client using StreamableClientTransport (which is the standard way to connect via HTTP) won't be able to communicate with it correctly.

Proposed Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        HOST MACHINE                              │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │              mcp-gateway (awmg)                           │   │
│  │                                                           │   │
│  │   HTTP Server (:8080)                                     │   │
│  │   └── /mcp  ← StreamableHTTPHandler (NEW)                │   │
│  │       └── routes to appropriate backend MCP server        │   │
│  │                                                           │   │
│  │   Backend MCP Sessions (stdio):                           │   │
│  │   ├── gh-aw mcp-server                                    │   │
│  │   ├── filesystem server                                   │   │
│  │   └── other MCP servers...                                │   │
│  └──────────────────────────────────────────────────────────┘   │
│                           ▲                                      │
│                           │ host.docker.internal:8080            │
│  ┌────────────────────────┼─────────────────────────────────┐   │
│  │        awf container   │                                  │   │
│  │                        │                                  │   │
│  │   Agent (Copilot/Claude/Codex)                            │   │
│  │   └── MCP Client                                          │   │
│  │       └── StreamableClientTransport                       │   │
│  │           Endpoint: http://host.docker.internal:8080/mcp  │   │
│  │                                                           │   │
│  └───────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

Required Changes

1. Gateway: Add StreamableHTTPHandler endpoint

The gateway needs to expose a proper MCP streamable-http endpoint. The go-sdk already has StreamableHTTPHandler available (used in mcp_server.go:762-794):

handler := mcp.NewStreamableHTTPHandler(func(req *http.Request) *mcp.Server {
    // Route based on request path or header to appropriate backend
    return server
}, &mcp.StreamableHTTPOptions{
    SessionTimeout: 2 * time.Hour,
})

The gateway would need to:

  1. Create a virtual "aggregated" MCP server that combines tools from all backends
  2. Expose this via StreamableHTTPHandler on /mcp
  3. When tools are called, route to the appropriate backend MCP session

2. Tool Aggregation Strategy

Two approaches to consider:

Option A - Namespace prefixing: Tools from different servers get prefixed (e.g., gh-aw:list_files, filesystem:read_file)

Option B - Flat namespace: All tools exposed without prefix, conflicts resolved by priority

3. Agent Configuration

The agent inside awf would use a single MCP config entry:

{
  "mcpServers": {
    "gateway": {
      "url": "http://host.docker.internal:8080/mcp"
    }
  }
}

4. Firewall Whitelist

host.docker.internal needs to be whitelisted in the Squid proxy configuration for the awf container.

Benefits

  1. Security: MCP servers run on host with full access; agent runs in restricted container
  2. Simplicity: Agent sees single MCP endpoint with all tools aggregated
  3. Standard Protocol: Uses proper MCP streamable-http, not custom API
  4. Existing Patterns: Both StreamableClientTransport and StreamableHTTPHandler are already used in the codebase

Implementation Scope

Main work in pkg/cli/mcp_gateway_command.go:

  1. Create an aggregated mcp.Server that combines tools from all backend sessions
  2. Replace current HTTP handlers with StreamableHTTPHandler
  3. Implement tool routing logic to dispatch calls to appropriate backends

References

  • Existing StreamableHTTPHandler usage: pkg/cli/mcp_server.go:762-794
  • Existing StreamableClientTransport usage: pkg/cli/mcp_inspect_mcp.go:238-276
  • Current gateway implementation: pkg/cli/mcp_gateway_command.go
  • Gateway spec: specs/mcp-gateway.md

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions