AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Mcp Aggregator

mcp-domdomegg-mcp-aggregator Β· by domdomegg

🌐 Combine multiple MCP servers behind a single endpoint with one login

β€” No reviews yet
0 installs
40 views
0.0% view→install

Install

$ agentstack add mcp-domdomegg-mcp-aggregator

βœ“ scanned Β· βœ“ verified, works with Claude Code, Cursor, and more.

Security review

βœ“ Passed

No issues found. Passed automated security review. Β· v0.1.0 How review works β†’

  • βœ“ Prompt-injection patterns
  • βœ“ Secret / credential exfiltration
  • βœ“ Dangerous shell & filesystem operations
  • βœ“ Untrusted network calls
  • βœ“ Known-malicious package signatures

What it can access

  • βœ“ Network access No
  • βœ“ Filesystem access No
  • βœ“ Shell / process execution No
  • βœ“ Environment & secrets No
  • βœ“ Dynamic code execution No

From automated source analysis of v0.1.0. β€œUsed” means the capability is present in the source β€” more access means more to trust, not that it’s unsafe.

View the full security report β†’

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-domdomegg-mcp-aggregator)

Reliability & compatibility

βœ“ Security review passed
0 installs to date
β€” no reviews yet
● 3mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work β†’
Are you the author of Mcp Aggregator? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

mcp-aggregator

> Save your and your team's time configuring MCP servers in every client β€” set up a single endpoint that combines all your MCPs behind one login.

If you run several remote MCP servers (e.g. Gmail, Google Calendar, Airtable via mcp-auth-wrapper) and multiple MCP clients (e.g. Claude.ai, Claude Code, VS Code), every client needs to be configured with each server separately. Add a new server? Update every client. Want your team to use the same set of tools? Configure each person's machine.

mcp-aggregator solves this: you configure your upstream servers once, then point all your MCP clients at one URL. When a user connects, they log in via your existing identity provider (Google Workspace, Microsoft Entra ID, Okta, Auth0, Keycloak, etc.). The aggregator then presents them with every tool from every upstream, namespaced and ready to use (e.g. gmail__send_email, calendar__create_event). If an upstream requires its own OAuth (like a Google API), the user is prompted to authorize it on first use β€” the aggregator stores and refreshes those tokens automatically from then on.

graph LR
    A["MCP Client(Claude, Cursor, etc.)"] -->|single URL, single login| G["mcp-aggregator"]
    G --> U1["Gmail MCP"]
    G --> U2["Google Calendar MCP"]
    G --> U3["Airtable MCP"]
    G --> U4["..."]

Under the hood, mcp-aggregator exposes a streamable HTTP endpoint with OAuth 2.1 authentication. Tools from all upstreams are namespaced and served through a single /mcp endpoint. Per-user upstream OAuth tokens are stored in SQLite. All auth state is stateless (encrypted sealed tokens), so there's no session database to manage.

Usage

Set MCP_AGGREGATOR_CONFIG to a JSON config object and run:

MCP_AGGREGATOR_CONFIG='{
  "auth": {"issuer": "https://auth.example.com"},
  "upstreams": [
    {"name": "gmail", "url": "https://gmail-mcp.example.com/mcp"},
    {"name": "calendar", "url": "https://calendar-mcp.example.com/mcp"}
  ]
}' npx -y mcp-aggregator

This starts an HTTP MCP server on localhost:3000. When a user connects, they'll be redirected to your login provider. After logging in, they can use tools from all configured upstreams. If an upstream requires its own OAuth, the user is prompted to authorize on first use.

Other configuration methods

The env var can also point to a file path:

MCP_AGGREGATOR_CONFIG=/path/to/config.json npx -y mcp-aggregator

Or create mcp-aggregator.config.json in the working directory β€” it's picked up automatically:

npx -y mcp-aggregator

Running with Docker

docker run -e 'MCP_AGGREGATOR_CONFIG={"auth":{"issuer":"...","clientId":"..."},"upstreams":[...]}' -p 3000:3000 ghcr.io/domdomegg/mcp-aggregator

Running on Kubernetes

The Docker image runs as USER node (uid 1000). If you use a PersistentVolumeClaim for SQLite storage, the volume mount will be owned by root by default, so the node user can't write to it. Fix this by setting fsGroup: 1000 in the pod's security context:

apiVersion: v1
kind: Pod
spec:
  securityContext:
    fsGroup: 1000
  containers:
    - name: mcp-aggregator
      image: ghcr.io/domdomegg/mcp-aggregator
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: mcp-aggregator-data

This ensures Kubernetes sets the group ownership of the mounted volume to gid 1000, allowing the node user to read and write the SQLite database.

Config

Only auth.issuer and upstreams are required. Everything else has sensible defaults.

| Field | Required | Description | |-------|----------|-------------| | auth.issuer | Yes | Your login provider's URL. Must support OpenID Connect discovery. | | auth.clientId | No | Client ID registered with your login provider. Defaults to "mcp-aggregator". | | auth.clientSecret | No | Client secret. Omit for public clients. | | auth.scopes | No | Scopes to request during login. Defaults to ["openid"]. | | auth.userClaim | No | Which field from the login token identifies the user. Defaults to "sub". | | upstreams[].name | Yes | Namespace prefix for tools (e.g. "gmail"). | | upstreams[].displayName | No | Human-readable name used to prefix tool titles, e.g. "Gmail" shows tools as "Gmail: List drafts". Defaults to the upstream's advertised server title, then name. | | upstreams[].url | Yes | Streamable HTTP endpoint URL. | | upstreams[].clientId | No | Pre-registered OAuth client ID. If set, Dynamic Client Registration is skipped. Use for upstreams that don't support DCR (e.g. Slack). | | upstreams[].clientSecret | No | Pre-registered OAuth client secret, used alongside clientId for confidential clients. | | storage | No | Where to store per-user upstream tokens: "memory" (default) or a SQLite file path. | | port | No | Port to listen on. Defaults to 3000. | | host | No | Host to bind to. Defaults to "0.0.0.0". | | issuerUrl | No | Public URL of this server. Required when behind a reverse proxy. | | secret | No | Signing key for tokens. Random if not set. Set a fixed value to survive restarts. | | discoveryTimeout | No | Timeout for upstream discovery/connect in ms. Defaults to 5000. | | toolTimeout | No | Timeout for upstream tool calls in ms. Defaults to 60000. |

A full example:

{
  "auth": {
    "issuer": "https://keycloak.example.com/realms/myrealm",
    "clientId": "mcp-aggregator",
    "clientSecret": "optional-secret",
    "scopes": ["openid"],
    "userClaim": "sub"
  },
  "upstreams": [
    { "name": "gmail", "url": "https://gmail-mcp.example.com/mcp" },
    { "name": "calendar", "url": "https://calendar-mcp.example.com/mcp" }
  ],
  "storage": "/data/aggregator.sqlite",
  "port": 3000,
  "host": "0.0.0.0",
  "issuerUrl": "https://mcp.example.com",
  "secret": "some-persistent-secret",
  "discoveryTimeout": 5000,
  "toolTimeout": 60000
}

Login provider examples

Google Workspace

{
  "auth": {
    "issuer": "https://accounts.google.com",
    "clientId": "...",
    "clientSecret": "..."
  },
  "upstreams": [
    {"name": "gmail", "url": "https://gmail-mcp.example.com/mcp"}
  ]
}

Create OAuth 2.0 credentials in the Google Cloud Console. Choose "Web application", add https:///callback as an authorized redirect URI. To restrict access to your organization, configure the OAuth consent screen as "Internal".

Microsoft Entra ID

{
  "auth": {
    "issuer": "https://login.microsoftonline.com//v2.0",
    "clientId": "...",
    "clientSecret": "..."
  },
  "upstreams": [
    {"name": "gmail", "url": "https://gmail-mcp.example.com/mcp"}
  ]
}

Register an application in the Azure portal. Add https:///callback as a redirect URI under "Web". Create a client secret under "Certificates & secrets". Replace `` with your directory (tenant) ID.

Okta

{
  "auth": {
    "issuer": "https://your-org.okta.com",
    "clientId": "...",
    "clientSecret": "..."
  },
  "upstreams": [
    {"name": "gmail", "url": "https://gmail-mcp.example.com/mcp"}
  ]
}

Create a Web Application in Okta. Set the sign-in redirect URI to https:///callback. The issuer URL is your Okta org URL (or a custom authorization server URL if you use one).

Keycloak

{
  "auth": {
    "issuer": "https://keycloak.example.com/realms/myrealm",
    "clientSecret": "..."
  },
  "upstreams": [
    {"name": "gmail", "url": "https://gmail-mcp.example.com/mcp"}
  ]
}

Create an OpenID Connect client in your Keycloak realm with client ID mcp-aggregator (or set auth.clientId to match). Set the redirect URI to https:///callback. Users are identified by sub (Keycloak user ID) by default. Set auth.userClaim to preferred_username to match by username instead.

Auth0

{
  "auth": {
    "issuer": "https://your-tenant.auth0.com",
    "clientId": "...",
    "clientSecret": "..."
  },
  "upstreams": [
    {"name": "gmail", "url": "https://gmail-mcp.example.com/mcp"}
  ]
}

Create a Regular Web Application in Auth0. Add https:///callback as an allowed callback URL. Set auth.clientId to the Auth0 application's client ID. The sub claim in Auth0 is typically prefixed with the connection type (e.g. auth0|abc123).

Authentik

{
  "auth": {
    "issuer": "https://authentik.example.com/application/o/myapp/",
    "clientSecret": "...",
    "userClaim": "preferred_username"
  },
  "upstreams": [
    {"name": "gmail", "url": "https://gmail-mcp.example.com/mcp"}
  ]
}

Create an OAuth2/OpenID Provider in Authentik with client ID mcp-aggregator (or set auth.clientId to match). Set the redirect URI to https:///callback.

Home Assistant (via hass-oidc-provider)

Home Assistant doesn't natively support OpenID Connect. Use hass-oidc-provider to bridge the gap β€” it runs alongside Home Assistant and adds the missing pieces.

{
  "auth": {
    "issuer": "https://hass-oidc-provider.example.com"
  },
  "upstreams": [
    {"name": "gmail", "url": "https://gmail-mcp.example.com/mcp"}
  ]
}

Point auth.issuer at your hass-oidc-provider instance (not Home Assistant directly). The sub claim is the Home Assistant user ID. No clientId or clientSecret needed.

Advanced: scaling and persistence

All auth state (tokens, sessions, in-flight logins) is stateless β€” tokens are self-contained encrypted blobs and each request gets a fresh transport. Nothing is stored server-side except per-user upstream tokens (in storage).

To survive restarts, set secret to a fixed value and use a SQLite file for storage.

To run multiple instances behind a load balancer, set secret to the same value across instances and point storage at a shared SQLite file.

Meta-tools

The gateway exposes two built-in tools:

  • gateway__status β€” Shows all upstream servers and their authentication status. Returns auth URLs for servers that need per-user authentication.
  • gateway__unauth β€” Removes stored authentication for an upstream server, allowing re-authentication.

Contributing

Pull requests are welcomed on GitHub! To get started:

  1. Install Git and Node.js
  2. Clone the repository
  3. Install dependencies with npm install
  4. Run npm run test to run tests
  5. Build with npm run build

Releases

Versions follow the semantic versioning spec.

To release:

  1. Use npm version to bump the version
  2. Run git push --follow-tags to push with tags
  3. Wait for GitHub Actions to publish to the NPM registry and GHCR (Docker).

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source β€” we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.