aiMCPGate
MCP gateway/proxy: multiplexes tool calls across upstream MCP servers into one aggregated, namespaced catalog and logs every call. Local, single-user, $0/month by default.
README
aiMCPGate
Русская версия — README_RU.md.
A gateway / proxy for MCP servers (Model Context Protocol) written in Go.
It presents itself to an MCP client (Claude Code, Cursor, etc.) as one
MCP server, while under the hood it multiplexes calls across several
upstream MCP servers, aggregates their tool catalogs into one, and
logs every call. (Resource aggregation across upstreams is not implemented
yet — resources/list returns an empty catalog; see internal/transport/dispatch.go.)
Status: MVP complete (Stages 0–6). Phase 1 — multiplexing stdio upstreams behind a stdio endpoint with a call log; Phase 2 — HTTP/SSE client-facing transport, HTTP upstreams, a CLI log viewer (
mcp-gate logs); release pipeline (goreleaser, cross-compiled for linux/darwin/windows × amd64/arm64, no CGO).
Releases
Cross-platform binaries are built via goreleaser
(.goreleaser.yaml): linux/darwin/windows × amd64/arm64, no CGO,
the version is baked in via -ldflags -X main.version=..., checksums land in
SHA256SUMS. Local dry run: goreleaser release --snapshot --clean.
Install from MCP registry
Besides the raw release binaries, the gateway ships as an OCI image on GitHub Container Registry and as an npm wrapper package — the two formats MCP registries install from.
Docker:
docker run --rm -i -v $(pwd)/config.yaml:/config.yaml ghcr.io/akomyagin/aimcpgate serve
-i is mandatory: the gateway talks MCP over stdio, so the client must keep
stdin open (without it the container sees EOF and exits immediately). The
image has no config of its own, so mount yours — the example above mounts it
onto the default path /config.yaml; any other path works with serve -c.
To reproduce a registry sandbox check (Glama.ai etc.) without any real upstream, use the demo config baked into the image — this exact command is what a sandbox should run:
docker run --rm -i ghcr.io/akomyagin/aimcpgate serve -c /demo.config.yaml
npx (downloads the prebuilt binary for your platform on first install and verifies its SHA256 checksum):
npx aimcpgate serve -c ./config.yaml
Image policy: the OCI image contains only the mcp-gate binary — no
runtimes for stdio upstreams (no node/npx, python, shells). If your config
launches stdio upstream servers, extend the image yourself and install what
they need; HTTP upstreams work out of the box (CA certificates are included).
Demo config: demo.config.yaml and the hidden
__demo-echo subcommand exist only so registry sandboxes (Glama.ai) can
introspect the gateway without any real upstream — never use them in a real
deployment.
Why
An active MCP user typically has several servers configured (filesystem,
GitHub, search, custom ones), each one duplicated in every client's own
config. aiMCPGate gives you:
- One entry point — a single MCP endpoint instead of N entries in the client config.
- One catalog — every upstream server's tools merged together (namespaced
as
<upstream>__<tool>so names never collide). - A call log — which upstream, which tool, when, success/failure. This is the value added on top of "just a proxy".
Solo pet project: the priority is learning Go (concurrency, os/exec,
JSON-RPC 2.0, the stdio and HTTP/SSE transports). Cost — $0/month by
default (a local process), no telemetry.
How it works (short version)
MCP client ──stdio/HTTP──▶ aiMCPGate ──JSON-RPC──▶ upstream A (stdio)
│ ├─────────▶ upstream B (stdio)
call log └─────────▶ upstream C (http, Phase 2)
MVP (two phases)
- Phase 1 — multiplexing 2+ stdio upstreams behind one stdio endpoint (the same transport Claude Code speaks) plus basic logging.
- Phase 2 — HTTP/SSE transport, HTTP upstream servers, a log viewer (CLI/web), optionally an access policy.
Build
export PATH="$HOME/sdk/go/bin:$PATH" # if go isn't already on PATH
go build ./...
go vet ./...
go test -race ./...
go run ./cmd version
Usage
# stdio mode (the client launches the gateway as a subprocess):
mcp-gate serve --config ./config.yaml
# http mode (transport: http in the config) — endpoint at http://<listen_addr>/mcp:
mcp-gate serve --config ./config-http.yaml
# check every enabled upstream once (launch → handshake → tools/list) and print
# a per-upstream OK/FAIL table; exit code is non-zero if any upstream failed
# (scriptable for CI/cron), no auto-restart, no call logging — one pass then exit:
mcp-gate doctor --config ./config.yaml
# view the call log (last 50 records; filter by upstream/tool/status):
mcp-gate logs --file ./logs/calls.jsonl --tail 50
mcp-gate logs --config ./config.yaml --upstream github --status err
# generate a random auth token (for the HTTP transport) and see how to wire it in:
mcp-gate token --generate
# print the auth token currently set in the config:
mcp-gate token --config ./config-http.yaml
# print ready-to-paste MCP client config snippets (Claude Code / Cursor); requires
# transport: http in the config, and includes the Bearer header when auth_token is set:
mcp-gate client-config --config ./config-http.yaml
# print a SKILL.md teaching an agent how to use the aggregated catalog
# (built-in text by default; overridable via skill_file in the config):
mcp-gate skill > .claude/skills/mcp-gate/SKILL.md
All commands except token --generate and skill (which falls back to a built-in
guide) load the config: pass --config, or drop a config.yaml next to the
binary (see Configuration below).
Reloading config (SIGHUP)
The gateway reloads its configuration live on SIGHUP — no restart, no
dropped client connection. Edit config.yaml and send the signal:
kill -HUP $(pgrep -f 'mcp-gate serve')
On reload the gateway diffs the new config against the running upstreams and
applies the minimum change: newly added upstreams are launched, removed (or
enabled: false) ones are shut down, upstreams whose launch fields
(command/args/url/env/headers) changed are relaunched, and upstreams
where only the tool allow/deny/rename filter changed are re-projected
without any restart. Unchanged upstreams keep running untouched. A bad edit
(invalid YAML, failed validation) is logged and ignored — the currently running
config stays live, so a typo never takes the gateway down.
Behavioural note: since the gateway installs a SIGHUP handler, SIGHUP no longer terminates the process the way the OS default would. To stop the gateway use Ctrl-C, SIGINT, or SIGTERM. SIGHUP is Unix-only; on Windows it does not exist and reload is unavailable (the process serves the config it started with until restarted).
Configuration
Without --config, the gateway looks for config.yaml next to its own
binary (e.g. if mcp-gate is installed at /etc/gate/, it looks for
/etc/gate/config.yaml — regardless of the working directory it was launched
from). If that file doesn't exist and --config wasn't passed either, it
errors explicitly instead of starting an empty gateway. Relative paths inside
the config (log_file, skill_file) resolve against the config file's own
directory, not the current working directory.
Note: the "next to the binary" lookup uses the path of the running executable. Under
go run ./cmd ...that executable is a throwaway build in a temp directory, so the default lookup will not find yourconfig.yaml— pass--configexplicitly when usinggo run, or run a built binary.
Full example with every field — config.example.yaml.
The set of upstream servers is declared in YAML; secrets (tokens) go through
env/.env (${VAR} expansion at load time), never committed in the config.
Each upstream sets exactly one of command (stdio subprocess) or url
(HTTP server, Streamable HTTP) — the connection kind is inferred automatically.
transport: stdio # stdio (Phase 1) | http (Phase 2)
listen_addr: "127.0.0.1:28080" # only used for transport: http; loopback by default
# auth_token: ${AIMCPGATE_TOKEN} # required if you widen listen_addr past loopback
log_file: ./logs/calls.jsonl
upstreams:
- name: filesystem # stdio upstream
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user"]
enabled: true
- name: github
command: github-mcp-server
env:
GITHUB_TOKEN: ${GITHUB_TOKEN} # from the environment, not hardcoded
enabled: true
- name: remote # http upstream (Phase 2)
url: https://mcp.example.com/mcp
headers:
Authorization: "Bearer ${REMOTE_MCP_TOKEN}" # secret, never logged
enabled: true
License
MIT — see LICENSE.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.