Skip to main content
Glama
Jemplayer82

mcp-aggregator

by Jemplayer82

protocol transport dependencies image license


[ the problem ]

You've got a dozen MCP servers running — memory, search, Home Assistant, RAG, Schwab, whatever — each one its own container, its own port, its own token. Every time you want a new AI client (a fresh Claude Code session, some other agent, a script) to have the full toolset, you're hand-copying a dozen mcpServers entries into its config.

Related MCP server: MCP Proxy Server

[ what it is ]

A single MCP server that connects out to every backend you list, and re-exposes all of their tools through one streamable-HTTP endpoint. Point any MCP-capable client at one URL and one token, and it gets every tool from every backend — namespaced so nothing collides.

  • Tool list_vms on backend proxmox shows up as proxmox__list_vms

  • One backend being down, slow, or misconfigured never breaks the others

  • Tool lists refresh on an interval, so backends can add/remove tools without a restart

  • A REST side-channel (/api/servers, /api/servers/:id/tools) exposes live health + per-backend tool lists for anything that wants to build on top of this (a catalog UI, a dashboard) without depending on MCP framing

IMPORTANT

This is the source of truth for "what MCP servers exist." Anything that wants a live catalog — a store UI, a dashboard — should read/api/servers from here, not the other way around.

[ quick start ]

$ git clone https://github.com/jemplayer82/mcp-aggregator && cd mcp-aggregator
$ cp config.example.json config.json     # edit with your real backend URLs
$ cp .env.example .env                    # fill in tokens for backends that need them
$ docker compose up -d
$ docker compose logs mcp-aggregator      # shows the auto-generated aggregator token
$ curl -sf http://localhost:3117/healthz
# → {"ok":true}

Point any MCP client at http://your-host:3117/mcp with the printed bearer token, and it has every enabled backend's tools.

[ config.json ]

{
  "servers": [
    { "id": "memory", "name": "Memory", "url": "http://192.168.7.50:3100/mcp" },
    {
      "id": "switchboard",
      "name": "Switchboard",
      "url": "http://192.168.7.50:3108/mcp",
      "authEnv": "SWITCHBOARD_TOKEN"
    }
  ]
}

field

required

notes

id

yes

lowercase alphanumeric + hyphens only — no underscores, since __ is the tool-namespace separator

name

no

display name, defaults to id

url

yes

the backend's streamable-HTTP MCP endpoint (its /mcp path)

enabled

no

set false to keep an entry in the file without connecting to it

authEnv

no

name of an env var holding the backend's token — the actual token lives in .env, never in config.json

authHeader

no

header name to send the token in, default Authorization

authPrefix

no

prefix before the token value, default "Bearer " — set "" for headers like x-api-key

protocolVersionOverride

no

pin the MCP-Protocol-Version header sent on requests after initialize — works around backends (usually older supergateway builds) whose outer HTTP layer validates that header against a version list older than what the wrapped server declared at initialize. See ollama in config.example.json.

config.json and .env are both gitignored. Only config.example.json and .env.example are tracked.

[ endpoints ]

All endpoints except /healthz require Authorization: Bearer <AGGREGATOR_MCP_TOKEN>.

endpoint

method

what

/healthz

GET

liveness, unauthenticated

/mcp

GET/POST

the aggregated MCP server — streamable-HTTP, stateless per request

/api/servers

GET

JSON status of every configured backend: connected, tool count, last error

/api/servers/:id/tools

GET

raw (unnamespaced) tool list for one backend

[ running more than one instance ]

The aggregator is generic — nothing hardcodes it to one backend list or one host. Run a separate instance per "trust domain" or physical host by giving each its own config.json, port, and token:

  • config.example.json / docker-compose.yaml — the mcp-shared backends (memory, github, rag, gsd-browser, gsd-cloud, home-assistant, schwab, ollama, codebase-index, switchboard, searxng, sequential-thinking). Deployed on the Web Server at http://192.168.7.50:3119 (port 3117 was taken by mcp-truenas by the time this deployed — check for collisions before reusing the default).

  • config.billy.example.json / docker-compose.billy.yaml — Billy/Openclaw's internal infra tools (portainer, proxmox, homelable, unifi, ssh, bash-billy), port 3118. Deployed at http://192.168.1.19:3118. These backends run as a separate ad-hoc docker-compose project directly on that host (/home/landon/mcp-shared/docker-compose.yml), not through this repo's Portainer stack — the aggregator just needs their published ports reachable.

Both instances currently run as standalone docker compose projects directly on their host (/home/landon/mcp-aggregator and /home/landon/mcp-aggregator-billy respectively), not as Portainer-tracked stacks — Portainer's update_stack API path only supports edge stacks, and create_regular_stack needs a compose-file-on-disk workflow this repo doesn't use yet.

Known backend issues (as of first deploy): ollama (Web Server instance) fails with an MCP protocol-version mismatch — the ollama-mcp server via supergateway doesn't like whatever version the aggregator's client negotiates. ssh (Billy instance) 404s on POST /mcp — that supergateway instance likely isn't listening on the streamable-HTTP path the aggregator expects. Both backends fail independently without affecting the rest.

Copy whichever config.*.example.json fits, rename to config.json, adjust docker-compose*.yaml's CONFIG_PATH/volume mount and port if running side by side, and deploy independently.

[ how this differs from mcp-switchboard ]

Switchboard is a message bus between agents. This is a tool aggregator — it doesn't relay messages, it fans out MCP tool calls to other MCP servers and hands back the results. They're complementary and typically deployed side by side in mcp-shared.

[ license ]

GNU Affero General Public License v3.0. See CONTRIBUTING.md for how to contribute.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Self-hosted MCP proxy and aggregation platform. Register multiple upstream MCP servers and expose them through a single unified endpoint with namespace routing, multi-transport support (HTTP/SSE, stdio, OpenAPI→MCP), per-tool overrides, and a web admin UI.
    17
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Aggregates multiple backend MCP servers into a single unified interface with optional web management UI for tool control and configuration.
    60 npm
    193
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Aggregates multiple MCP servers into a single HTTP endpoint with tool namespacing, dashboard, and REST API for management.
    13 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Aggregates multiple upstream MCP servers (stdio, streamable-http, sse, websocket) behind a single streamable-HTTP endpoint with declarative configuration, namespacing, filtering, and auto-reload.
    MIT