Skip to main content
Glama
PalNilsson

PanDA Gateway

by PalNilsson

PanDA Gateway

A thin, stateless MCP routing layer for the PanDA ecosystem. The gateway sits between the PanDA Monitor (or any MCP client) and upstream MCP servers such as Bamboo MCP and PanDA MCP, exposing a single MCP endpoint (Streamable HTTP) and routing each tools/call to the correct upstream based on a namespace prefix in the tool name.

The gateway carries no LLM logic, no planning, no synthesis — those remain in Bamboo MCP.

PanDA Monitor (MCP client)
        │  MCP / Streamable HTTP  (Bearer token)
        ▼
┌─────────────────────────────────────────────┐
│              PanDA Gateway                  │
│  GatewayServer · UpstreamRegistry · Router  │
└─────────────────────────────────────────────┘
        │           │           │          │
   Bamboo MCP   PanDA MCP   Rucio MCP   CRIC MCP
   bamboo.*     panda.*     (future)    (future)

Developed under DOE REDWOOD WBS 2.4.3 (Bamboo MCP / Agentic PanDA).

Deeper reference docs live in docs/: architecture.md (components and design decisions), catalog-and-search.md (how the tool catalog and gateway.search_tools work), security-and-resilience.md (trust model, hardening, and failure recovery — read this before any non-local deployment), and examples.md (example prompts, how to try them, and common first-run errors).

Installation

pip install -e .                    # runtime
pip install -e ".[dev]"             # + tests, linting, type checking
pip install -e ".[observability]"   # + OpenTelemetry tracing

Requires Python ≥ 3.11.

Related MCP server: Master MCP Server

Quick start (minimal: Bamboo MCP only, no tokens)

This is the smallest working setup: one Bamboo MCP upstream, no authentication anywhere. Use it for local development and first integration tests.

  1. Start your Bamboo MCP server (assumed below at http://localhost:8000/mcp).

  2. Use the provided gateway.minimal.toml (edit the url if Bamboo runs elsewhere):

    [gateway]
    host = "127.0.0.1"
    port = 8090
    auth_disabled = true     # no inbound token; local development only
    separator = "."
    
    [rag]
    enabled = true
    
    [[upstreams]]
    namespace = "bamboo"
    url = "http://localhost:8000/mcp"
    # no bearer_token_env / token_file -> unauthenticated upstream connection
  3. Run the gateway:

    panda-gateway --config gateway.minimal.toml
    # equivalently: python -m panda_gateway --config gateway.minimal.toml
  4. Verify:

    curl http://127.0.0.1:8090/healthz
    # -> {"service": "panda-gateway", "status": "ok", ...,
    #     "upstreams": [{"namespace": "bamboo", "state": "up", "tools": N, ...}]}

    The MCP endpoint is http://127.0.0.1:8090/mcp (Streamable HTTP). Point any MCP client at it; Bamboo's tools appear as bamboo.<tool>, e.g. bamboo.bamboo_answer. scripts/verify_gateway.py is a small smoke test that connects, lists tools, and calls one — run it against this setup with:

    python scripts/verify_gateway.py
    # or, against a different address/token:
    python scripts/verify_gateway.py http://127.0.0.1:8090/mcp --token "$PANDA_GATEWAY_TOKEN"
  5. Try it through an LLM. verify_gateway.py proves the routing works, but calling a hardcoded tool name isn't the experience an actual user will have. Claude Desktop is a quick way to see the real one — an LLM reading the gateway's catalog and picking a tool on its own from a plain-English question. Add this to its local MCP server config (this runs the gateway as a subprocess Claude Desktop manages directly, instead of the standalone server from step 3) and restart it:

    {
      "mcpServers": {
        "panda-gateway": {
          "command": "panda-gateway",
          "args": ["--config", "/absolute/path/to/gateway.minimal.toml", "--stdio"]
        }
      }
    }

    Then just ask it something in a new chat — e.g. "How many jobs failed today?" — and watch it pick bamboo.bamboo_answer on its own, with no tool name typed anywhere. See docs/examples.md for the production version of this config (environment variables, absolute paths, and other gotchas that show up once real upstream auth is involved) and more prompts to try.

auth_disabled = true logs a prominent warning at startup; never use it beyond localhost or a trusted network.

Running in production

export PANDA_GATEWAY_TOKEN=...   # inbound token (required)
export BAMBOO_TOKEN=...          # per-upstream tokens as configured
panda-gateway --config gateway.toml

If --config is omitted, the path is read from PANDA_GATEWAY_CONFIG. Clients must then send Authorization: Bearer $PANDA_GATEWAY_TOKEN; only GET /healthz stays unauthenticated.

Deployment modes

  • HTTP (production): the form above — uvicorn serving Streamable HTTP at /mcp, Bearer auth required (unless auth_disabled = true).

  • stdio (development): panda-gateway --config gateway.toml --stdio runs the gateway as a local subprocess instead of a network service, speaking MCP directly over stdin/stdout — there's no HTTP layer in this mode, so inbound auth (PANDA_GATEWAY_TOKEN) doesn't apply at all. This is the mode local MCP clients that spawn their own subprocess (e.g. Claude Desktop's mcpServers config) expect. See docs/examples.md for a concrete walkthrough, including an environment-variable gotcha that comes with subprocess-launched clients.

Configuration

See gateway.example.toml for a complete annotated example. Minimal form:

[gateway]
host = "0.0.0.0"
port = 8090
bearer_token_env = "PANDA_GATEWAY_TOKEN"
separator = "."          # namespace separator in tool names

[[upstreams]]
namespace = "bamboo"
url = "https://aipanda033.cern.ch:8000/mcp"
bearer_token_env = "BAMBOO_TOKEN"
tls_verify = true
ca_bundle_env = "SSL_CERT_FILE"

[[upstreams]]
namespace = "panda"
url = "https://panda-mcp.cern.ch/mcp"
token_file = "~/.panda_id_token"   # OIDC token, re-read on every reconnect
use_sse = false                    # set true if PanDA MCP serves SSE only

Each upstream authenticates with either bearer_token_env (token from an environment variable) or token_file — or neither, for open endpoints. token_file understands the JSON token cache written by get-panda-token (the id_token field is used) as well as plain-text token files, and is re-read on every reconnect so externally renewed tokens apply automatically. Following Bamboo's panda_mcp_session.py, the token is sent as both Authorization and X-Auth-Token, and an optional origin = "<vo>" is sent as the Origin header.

Note on the separator: the handover convention is bamboo.* / panda.*, but some MCP clients validate tool names against ^[a-zA-Z0-9_-]+$ and reject dots. If the Monitor's client stack does, set separator = "__" — routing is separator-agnostic.

Two more top-level tables exist beyond what's shown above: [backoff] (reconnect tuning) and [transport_security] (inbound Host/Origin validation, off by default — see docs/security-and-resilience.md before enabling it or deploying anywhere non-local). Per-upstream, allow_redirects and the init_timeout/ping_timeout/probe_timeout deadlines are also documented there and in gateway.example.toml; the defaults are sensible for most deployments and rarely need changing.

Behaviour

  • Routing is a single dict lookup on the namespace prefix. Unknown namespaces return JSON-RPC -32602; a configured but unavailable upstream returns -32603 naming the upstream, so operators can see which capability is missing.

  • Degraded service is visible: tools of a down upstream are absent from tools/list; other namespaces keep working.

  • Health checks are two-tier: a liveness ping every 45 s and a tools/list probe every 12 min per upstream (both configurable). An upstream notifications/tools/list_changed triggers an immediate probe. Failures cause reconnection with exponential backoff and jitter.

  • Tool catalog is served from a probe-refreshed cache, with a ChromaDB semantic index exposed via the gateway.search_tools tool — see docs/catalog-and-search.md for how the catalog is built, why the semantic index exists, and how both stay consistent with actual upstream availability.

  • GET /healthz (unauthenticated) returns per-upstream status JSON — machine-readable groundwork for the Phase 2 dashboard.

  • Resilience: crash isolation between upstreams, bounded health-check deadlines, and immediate reconnection on a router-observed failure — see docs/security-and-resilience.md for the full detail, including the trust model this gateway is built for.

Development

python -m pytest tests/    # 122 tests
flake8 panda_gateway tests
pyright

Tests run entirely in-process (fake upstream MCP servers over memory streams, deterministic embeddings) — no network and no model downloads.

scripts/verify_gateway.py is a small end-to-end smoke test against a running gateway (see the quick start above) — not part of the pytest suite, since it needs a live process and an upstream to talk to.

Attribution

Session-lifecycle, health-check, retry, and observability patterns are adapted from IBM ContextForge (mcp-contextforge-gateway, Apache-2.0). See NOTICE.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Hosted MCP server for LLM cost estimation, model comparison, and budget-aware routing.

  • MCP Hub: AI service discovery, per-user OAuth, and multi-service workflow orchestration

  • Single entry point for the GOSCE portfolio: routes orchestrators to verified agents by capability, w

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/PalNilsson/panda-gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server