Skip to main content

MCP Gateway

PyPI version Python License

A standalone CLI gateway that aggregates multiple MCP (Model Context Protocol) servers behind a single HTTP/SSE endpoint with Code Mode — reducing LLM input token usage by up to 92% when using multiple MCP servers. v0.7.0 GA adds a local-first Dashboard (Python htpy + python-htmx + Tailwind vendoreado, sin Node).

Features

  • Multi-Server Aggregation — Connect to multiple MCP servers (HTTP, SSE, Stdio, Streamable HTTP) and expose them through a single endpoint
  • Code Mode — 4 meta-tools that let LLMs discover and use tools dynamically without loading all schemas upfront
  • Dashboard (v0.7.0) — Local-first UI en http://127.0.0.1:8080/dashboard para listar/agregar/inspeccionar/enable-disable/remover/refrescar servers. SSR con htpy, mutaciones htmx, Tailwind vendoreado (<100KB), sin package.json ni build Node. Registry única fuente, masking *** obligatorio.
  • OAuth 2.0 Support — Built-in OAuth flow with dynamic client registration (RFC 7591) and token storage
  • Hermetic Sandbox — Starlark-based sandbox for safe code execution
  • MCP Protocol Compliant — Works with Claude Desktop, Cursor, and any MCP-compatible client

Installation

pip install mcp-gway

Or with mise:

mise install
uv sync --all-groups  # installs dev group with pre-commit

Quick Start

OpenCode Format (Primary)

OpenCode schema — remote / local with transport auto-detection. This is the recommended path.

# Remote — auto-detects transport (streamable-http → sse → http)
mcp-gway add youtube --type remote --url http://localhost:3001/mcp

# Remote with headers
mcp-gway add supabase --type remote --url https://mcp.supabase.com/mcp --header "Authorization=Bearer TOKEN"

# Remote with pre-registered OAuth
mcp-gway add supabase --type remote --url https://mcp.supabase.com/mcp --oauth-client-id ID --oauth-client-secret SECRET --oauth-scope "openid profile"

# Remote with timeout and enable toggle
mcp-gway add api --type remote --url https://api.example.com/mcp --timeout 10000 --enabled
mcp-gway add api --type remote --url https://api.example.com/mcp --timeout 10000 --no-enabled

# Local
mcp-gway add filesystem --type local --command "npx -y @anthropic/mcp-filesystem"
mcp-gway add tools --type local --command "python -m my_mcp_server" --env MY_VAR=value --cwd /path/to/workdir
mcp-gway add tools --type local --command "npx -y my-mcp" --env KEY=VALUE --env OTHER=123 --cwd /tmp/workdir

# List and serve (local-first)
mcp-gway list
mcp-gway serve --port 8080              # bindea 127.0.0.1 por defecto
mcp-gway serve --host 127.0.0.1 --port 8080
open http://127.0.0.1:8080/dashboard

Deprecated Format (still works)

Old --type http|stdio|sse|streamable-http syntax is kept for backward compat and internally mapped to remote/local. Prefer remote/local for new configs.

# Equivalent old syntax — prefer remote/local above
mcp-gway add youtube --type http --url http://localhost:3001/mcp
mcp-gway add filesystem --type stdio --command npx --args '["-y", "@anthropic/mcp-filesystem"]'
mcp-gway add supabase --type streamable-http --url https://mcp.supabase.com/mcp
mcp-gway add legacy --type sse --url https://example.com/sse

Dashboard — Local-First SSR (htpy)

Stack: htpy + python-htmx + TailwindCSS vendoreado en src/mcp_gway/dashboard/static/ (<100KB + <20KB). Sin Node, sin package.json, sin build. Todo HTML tipado en Python; Registry es única fuente (dashboard nunca toca FS directo).

Un solo proceso Gateway(registry, host) monta dashboard embebido: GET /dashboard (SSR) + GET /api/servers (JSON) sobre el mismo Starlette que sirve /mcp y /health.

Routes

Method Path Response Nota
GET /dashboard HTML htpy.layout (max-w-6xl mx-auto + Tailwind + htmx.min.js) banner ámbar si host != loopback
GET /dashboard/servers Fragmento <tbody id="server-table-body"> hx-get polling
GET /dashboard/servers/{name} Drawer server_drawer con firmas tools (truncado >50KB)
GET /dashboard/close Vacía drawer
GET /static/tailwind.css CSS vendoreado sin CDN
GET /static/htmx.min.js htmx vendoreado
GET /api/servers 200 [{name,type,enabled,tool_count,url|command,timeout}] secrets ***
GET /api/servers/{name} 200 {config,pyi_content,truncated} secrets ***
POST /api/servers 201 + tools/list discovery (timeout + streamable-http→sse→http); 409 si existe tools=[] + toast si falla
PATCH /api/servers/{name} {"enabled":bool} → badge disabled/healthy/unreachable vía Registry.patch_enabled
DELETE /api/servers/{name} 204 (idempotente, borra *.json+*.pyi+tokens/)
POST /api/servers/{name}/refresh 202 {status:"refreshing"} background no bloqueante 409 si disabled
POST /api/servers/{name}/reveal 200 {headers|oauth|environment} solo 127.0.0.1 POST, rate-limit 5/min, 403 si no loopback

Content negotiation: HX-Request: true → text/html fragment (swap); sin header → application/json. CSP default-src 'self' en todas las respuestas.

curl Examples

# Serve local-first
mcp-gway serve --port 8080 &
curl -s http://127.0.0.1:8080/dashboard | head -n 20        # 200 HTML htpy
curl -s http://127.0.0.1:8080/api/servers | jq                # secrets masked ***

# Add remote (JSON)
curl -X POST http://127.0.0.1:8080/api/servers \
  -H 'Content-Type: application/json' \
  -d '{"name":"gh","type":"remote","url":"https://example.com/mcp"}'  # 201 {name,tool_count}

# Add local (form, htmx)
curl -X POST http://127.0.0.1:8080/api/servers \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'name=echo_srv&type=local&command=echo+hi&cwd=/tmp'

# Fragment htmx (polling tabla)
curl -H "HX-Request: true" http://127.0.0.1:8080/dashboard/servers          # <tbody>

# Inspect + reveal
curl -s http://127.0.0.1:8080/api/servers/gh | jq               # masked
curl -X POST http://127.0.0.1:8080/api/servers/gh/reveal \
  -H 'Content-Type: application/json' -d '{"field":"headers"}' | jq  # solo loopback POST

# Toggle / refresh / delete
curl -X PATCH http://127.0.0.1:8080/api/servers/gh \
  -H 'Content-Type: application/json' -d '{"enabled":false}' | jq
curl -X POST http://127.0.0.1:8080/api/servers/gh/refresh | jq   # 202 background, health <50ms
curl -X DELETE http://127.0.0.1:8080/api/servers/gh              # 204

HTMX Examples

<!-- Add: form SSR + hx-post swap tabla -->
<form hx-post="/api/servers" hx-target="#server-table-body" hx-swap="outerHTML" hx-indicator="#add-spinner">
  <input name="name" required /><select name="type"><option>remote</option><option>local</option></select>
  <input name="url" /><input name="command" /><button>Add</button>
</form>

<!-- Inspect: click fila abre drawer -->
<tr hx-get="/dashboard/servers/gh" hx-target="#drawer" hx-swap="innerHTML"><td>gh</td></tr>

<!-- Toggle / Refresh / Delete con confirm -->
<button hx-patch="/api/servers/gh" hx-vals='{"enabled":false}' hx-target="#drawer">Disable</button>
<button hx-post="/api/servers/gh/refresh" hx-target="#toast">Refresh</button>
<button hx-delete="/api/servers/gh" hx-confirm="Delete gh?" hx-target="#server-table-body" hx-swap="outerHTML">Delete</button>

Local-First Security

# Default seguro — solo loopback
mcp-gway serve --port 8080            # bindea 127.0.0.1

# Exponer en 0.0.0.0 requiere opt-in explícito
MCP_GWAY_ALLOW_REMOTE=1 mcp-gway serve --host 0.0.0.0 --port 8080
# └─ log warning "dashboard exposed on non-loopback"
# └─ header X-Warning: exposed + banner ámbar en UI + botón Reveal deshabilitado

# Sin opt-in → error controlado
mcp-gway serve --host 0.0.0.0
# Error: binding to non-loopback host '0.0.0.0' requires MCP_GWAY_ALLOW_REMOTE=1
# exit 2
  • Masking *** obligatorio: GET /api/servers, GET /api/servers/{name}, GET /dashboard nunca exponen headers/oauth.clientSecret/environment reales.
  • Reveal solo POST /api/servers/{name}/reveal desde 127.0.0.1, rate-limit 5/min, audit log sin valor, 403 si no loopback, 405 si GET.

Connect from Claude Desktop

Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "gateway": {
      "url": "http://localhost:8080/mcp"
    }
  }
}

Commands

Command Description
mcp-gway add --type remote|local Add an MCP server and generate .pyi stub (OpenCode format, primary)
mcp-gway remove Remove an MCP server
mcp-gway update Update tools for a server
mcp-gway list List all connected servers
mcp-gway inspect Show tool signatures for a server
mcp-gway refresh [<name>] [--auth] [--oauth-port <port>] Refresh connection and re-discover tools
mcp-gway serve [--host 127.0.0.1] [--port <port>] Start gateway (MCP + Dashboard). Default 127.0.0.1; 0.0.0.0 necesita MCP_GWAY_ALLOW_REMOTE=1

Backward compat: mcp-gway add --type http|stdio|sse|streamable-http still works (deprecated). http/sse/streamable-http → remote (with resolved_transport cached), stdio → local. Use remote/local going forward.

Options for add (OpenCode) — 12+ flags grouped by scope:

Option Description
--type remote|local Server type (primary)
--url <url> URL for remote
--header "KEY=VALUE" HTTP header for remote (repeatable)
--command "<cmd>" Command for local (e.g. "npx -y my-mcp")
--env KEY=VALUE Environment variable for local (repeatable)
--cwd <path> Working directory for local
--oauth-client-id ID Pre-registered OAuth client ID
--oauth-client-secret SECRET Pre-registered OAuth client secret
--oauth-scope SCOPE OAuth scope
--oauth-port <port> Local port for OAuth callback (default 8989)
--timeout <ms> Connection timeout in ms (default 5000)
--enabled / --no-enabled Enable/disable without removal (default enabled)
--tools <list> Comma-separated tool filter (default * = all)
--args <json> JSON array of extra args — deprecated compat, used with stdio/local
--docs-url <url> Deprecated — accepted for compat but not persisted (legacy)

Code Mode

When connected, the gateway exposes 4 meta-tools:

Tool Description
listToolFiles List all available .pyi stub files
readToolFile Read function signatures from a stub
getToolDocs Get detailed documentation for a tool
executeToolCode Execute code in a sandboxed Starlark interpreter

OAuth Authentication

For servers requiring OAuth (e.g., Supabase):

# Trigger OAuth flow
mcp-gway refresh supabase --auth

# Or store token manually
mkdir -p ~/.config/mcp-gway/tokens
echo '{"access_token": "YOUR_TOKEN"}' > ~/.config/mcp-gway/tokens/supabase.json

Development

# Install dependencies
uv sync --all-groups  # installs dev group with pre-commit
uv run pre-commit install  # once per clone — hooks already configured in .pre-commit-config.yaml

# Run checks
uv run pre-commit run --all-files  # ruff + ruff-format + hygiene (trailing-whitespace, end-of-file-fixer, check-yaml, check-added-large-files)
uv run pytest -v  # 181 tests v0.7.0 — incluye test_dashboard_views + test_dashboard_api
uv run ruff check src/ tests/
uv run ruff format --check src/ tests/

# Verification Dashboard (sin Node, sin build)
uv run pytest tests/test_dashboard_views.py tests/test_dashboard_api.py -v  # masking, HX-Request, reveal, refresh, local gating
curl -s http://127.0.0.1:8080/dashboard | grep -q '<table' && echo "dashboard ok"
curl -s http://127.0.0.1:8080/api/servers | jq 'map(select(.headers))'       # *** masked
curl -H "HX-Request: true" http://127.0.0.1:8080/dashboard/servers | head   # fragment tbody

# Local-first check
mcp-gway serve --host 0.0.0.0 2>&1 | grep -q "requires MCP_GWAY_ALLOW_REMOTE" && echo "gate ok"
MCP_GWAY_ALLOW_REMOTE=1 mcp-gway serve --host 0.0.0.0 --port 8081 & curl -s -D - http://127.0.0.1:8081/dashboard | grep -qi X-Warning

Pre-commit is already in place (.pre-commit-config.yaml — ruff v0.16.4, ruff-format, trailing-whitespace, end-of-file-fixer, check-yaml, check-added-large-files).

Architecture

┌──────────────────────────────────────────────────────────────────────┐
│                        MCP Gateway v0.7.0 GA                         │
├──────────────────────────────────────────────────────────────────────┤
│  CLI (click)              │  Gateway (Starlette + uvicorn, CSP)      │
│  - add remote/local       │  - POST /mcp (JSON-RPC)                  │
│  - remove/inspect/list    │  - GET  /mcp (SSE endpoint event)        │
│  - refresh --auth         │  - POST /mcp/messages?session_id=...     │
│  - serve --host 127.0.0.1 │  - GET  /health                          │
│  (local-first default)    │                                          │
├───────────────────────────┼──────────────────────────────────────────┤
│  Dashboard (htpy+htmx, vendoreado)                                    │
│  SSR: GET /dashboard, /dashboard/servers, /dashboard/servers/{name}   │
│  API: GET/POST /api/servers, PATCH/DELETE /api/servers/{name},        │
│       POST /api/servers/{name}/refresh (202 background), /reveal      │
│  Static: /static/tailwind.css (<100KB) + /static/htmx.min.js (<20KB) │
│  Content-negotiation HX-Request + masking *** + rate-limit reveal      │
├──────────────────────────────────────────────────────────────────────┤
│  Code Mode (4 meta-tools)      │  Starlark Sandbox                    │
│  - listToolFiles               │  - Hermetic execution                │
│  - readToolFile                │  - Server injection                  │
│  - getToolDocs                 │                                      │
│  - executeToolCode             │                                      │
├──────────────────────────────────────────────────────────────────────┤
│  Registry (única fuente)       │  OAuth2 (RFC 7591, reutilizado)      │
│  - servers/*.pyi = signatures  │  - Dynamic registration              │
│  - servers/*.json = config     │  - PKCE + FileTokenStorage           │
│  - last-write-wins, atómico    │  - tokens/ no expuesto vía API       │
└──────────────────────────────────────────────────────────────────────┘
         │                │                │
    ┌────┴────┐      ┌────┴────┐      ┌────┴────┐
    │ Server1 │      │ Server2 │      │ Server3 │
    │ (remote)│      │ (local) │      │ (remote)│
    └─────────┘      └─────────┘      └─────────┘
  • Sin Node en runtime ni CI: Tailwind + htmx vendoreados commit, ruff único linter, uv_build backend.
  • Release híbrido (ADR-007): push tags v* → uv build + pypi-publish (GA v0.7.0 tag manual) + workflow_run Tests completed → python-semantic-release@v9 para fix/perf patches auto. concurrency: release, fetch-depth:0, [tool.semantic_release] sync pyproject.toml + __init__.py.

License

MIT

Metadata

Release files for mcp-gway 1.3.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-gway 1.3.5
File Size Uploaded
mcp_gway-1.3.5.tar.gz 155.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-gway 1.3.5
File Interpreter ABI Platform
mcp_gway-1.3.5-py3-none-any.whl Python 3 none any Details

Total release size: 320.5 kB

Release files / mcp_gway-1.3.5.tar.gz

Download URL mcp_gway-1.3.5.tar.gz
Size 155.3 kB
Tags Source
SHA-256 checksum
How to use checksums
2e6472b11fa3f09d8d6f2a3941d57984164d4da51cb80167f171a7314b83717b
BLAKE2b-256 checksum
How to use checksums
e0f65a2d40bc4ea3dc7108e70c63e3df69ab7c71b42c4bc05e1711903d61c898
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

Release files / mcp_gway-1.3.5-py3-none-any.whl

Download URL mcp_gway-1.3.5-py3-none-any.whl
Size 165.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
eddd52febed199bd9ceb8eb24ee6d2f14df5c3fa17a07718e820f9a7a4056b66
BLAKE2b-256 checksum
How to use checksums
31bc00190231db60b2ec11716d4afcc3f0affec8e376fc1f41eedd741d55523a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

Release history Release notifications | RSS feed

4.5.7

2 release files

4.5.6

2 release files

4.5.5

2 release files

4.5.4

2 release files

4.5.3

2 release files

4.5.2

2 release files

4.5.1

2 release files

4.5.0

2 release files

4.4.0

2 release files

4.3.0

2 release files

4.2.0

2 release files

4.1.0

2 release files

4.0.0

2 release files

3.2.0

2 release files

3.1.0

2 release files

3.0.1

2 release files

3.0.0

2 release files

2.11.3

2 release files

2.11.2

2 release files

2.5.0

2 release files

2.4.0

2 release files

2.3.0

2 release files

2.2.1

2 release files

2.2.0

2 release files

2.1.2

2 release files

2.1.1

2 release files

2.1.0

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.6.2

2 release files

1.6.1

2 release files

1.6.0

2 release files

1.5.1

2 release files

1.5.0

2 release files

1.4.2

2 release files

1.4.1

2 release files

1.4.0

2 release files

This release

1.3.5 This release

2 release files

1.3.4

2 release files

1.3.3

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page