PanDA Gateway

PanDA Gateway

A thin, stateless MCP routing layer that exposes a single Streamable HTTP endpoint and routes tool calls to upstream servers (e.g., Bamboo MCP, PanDA MCP) based on namespace prefixes, enabling unified access and tool catalog management.

Category
访问服务器

README

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).

Installation

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

Requires Python ≥ 3.11.

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. From Python:

    import anyio
    from mcp.client.session import ClientSession
    from mcp.client.streamable_http import streamablehttp_client
    
    async def main():
        async with streamablehttp_client("http://127.0.0.1:8090/mcp") as (r, w, _):
            async with ClientSession(r, w) as session:
                await session.initialize()
                tools = await session.list_tools()
                print([t.name for t in tools.tools])
                result = await session.call_tool(
                    "bamboo.bamboo_answer", {"prompt": "How many jobs failed today?"}
                )
                print(result.content[0].text)
    
    anyio.run(main)
    

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
# development alternative: panda-gateway --config gateway.toml --stdio

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.

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.

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 The tool catalog below for how and why.
  • GET /healthz (unauthenticated) returns per-upstream status JSON — machine-readable groundwork for the Phase 2 dashboard.

The tool catalog

The catalog is the gateway's answer to two different questions, and it is important to keep them apart:

  1. "Which tools exist right now?" — answered exactly, from a cache.
  2. "Which tools are relevant to what I'm trying to do?" — answered approximately, from a semantic index.

Routing is involved in neither: a tools/call is dispatched purely by its namespace prefix (bamboo.… → Bamboo MCP), a single dict lookup. The ChromaDB index never decides where a call goes.

How the catalog is built and kept fresh

On startup and on every reconnect, the gateway calls tools/list on each upstream and caches the result per namespace. The cache is then refreshed by the periodic tools/list health probe (default every 12 min per upstream) and immediately whenever an upstream sends a tools/list_changed notification. Downstream tools/list requests are served from this cache with the namespace prefix applied — the gateway never fans a listing out to the upstreams on request, so a slow or flapping upstream can never stall a listing. Staleness is bounded by the probe interval, and in practice by the list_changed path, since well-behaved MCP servers announce tool changes.

Availability is part of the answer: only namespaces whose upstream is currently UP contribute tools. If PanDA MCP is down, panda.* tools simply disappear from the listing while bamboo.* keeps working — clients see a smaller catalog rather than errors, and operators see the gap.

Each probe result is fingerprinted (SHA-256 over every tool's name, description, and input schema). If the fingerprint is unchanged from the previous probe — the overwhelmingly common case — the probe costs one RPC and a hash comparison, and nothing downstream happens.

Why a ChromaDB index

The merged catalog will grow: Bamboo MCP plus PanDA MCP already contribute dozens of tools, and Rucio MCP and CRIC MCP will add more. An LLM-driven client (the Monitor's assistant, or Bamboo's planner) that receives the full catalog on every request pays for it twice — in context-window tokens and in tool-selection accuracy, which measurably degrades as the tool list grows. The standard MCP tools/list has no way to say "only the tools relevant to this prompt".

Rather than extending the protocol (a custom prompt parameter on tools/list would tie every client to gateway-specific behaviour), the gateway keeps the wire format plain MCP and exposes the narrowing capability as an ordinary tool: gateway.search_tools. Under the hood, every tool is embedded as a small document — "<namespace>.<name>: <description>" — into a ChromaDB collection with cosine similarity. A query embeds the caller's natural-language description of what they want ("kill a stuck task", "why did my jobs fail on that site") and returns the nearest tool names with their descriptions and distances. Embeddings capture meaning rather than keywords, which is what makes this robust: "terminate a job" finds panda.kill_task even though no word matches.

A typical client flow, entirely in standard MCP:

tools/call gateway.search_tools {"query": "kill a stuck task", "limit": 5}
   → [{"name": "panda.kill_task", "description": ..., "distance": 0.31}, …]
tools/call panda.kill_task {"task_id": 12345}

Clients that don't care (few tools, no LLM in the loop) ignore the search tool and use tools/list as usual.

Index lifecycle

The index is a derived, disposable cache — the upstream servers remain the sole source of truth. It is rebuilt per namespace only when a probe's fingerprint actually changed, so the steady state does no embedding work at all. By default it lives in memory and is rebuilt on startup; set [rag] persist_dir to keep it across restarts. Deleting a persisted index is always safe.

Embedding uses ChromaDB's built-in model (all-MiniLM-L6-v2, ~80 MB, downloaded to ~/.cache/chroma/ on first indexing). Indexing failures — for example the model download being blocked on an offline host — are logged, retried on the next probe, and never affect tools/list, routing, or upstream health; the gateway degrades to an empty search result rather than a broken catalog. To run without the index entirely, set [rag] enabled = false (which also hides gateway.search_tools).

Development

python -m pytest tests/    # 47 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.

Attribution

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

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选