14 KiB
mcp-supergateway
A local toolbox for AI coding assistants, running in Docker.
AI coding tools — opencode, crush, VS Code's Cline, Roo Code, Kilo Code — can all use extra abilities (fetch a web page, search the web, read library docs, search a codebase) through a standard plug system called MCP. Every ability is a small server program that normally has to be installed and run separately for each tool.
This repo runs those servers once per machine, in Docker, and gives each one a simple local web address. Every AI tool on your computer then shares the same services — no Node.js, no Python, nothing to install on the host.
The idea in one picture
your AI tools this docker-compose stack
┌─────────────┐
│ opencode │───┐
│ crush │ │ http://localhost:8000/fetch/mcp ┌────────────────────┐
│ Cline │ ├───►http://localhost:8000/context7/mcp ──► │ mcp-gateway │
│ Roo / Kilo │ │ http://localhost:8000/brave-search/mcp │ (shared tools) │
└─────────────┘ │ └────────────────────┘
│ http://localhost:8001/supergateway/mcp
└───►http://localhost:8001/yazi/mcp ──► ┌────────────────────┐
http://localhost:8001/<project>/mcp │ mcp-codebase-index│
│ (one endpoint per │
│ code project) │
└────────────────────┘
Two containers do all the work:
mcp-gateway(port 8000) — always-useful helpers, shared by every tool: web fetch, library docs via context7, web search via brave (needs a free API key).mcp-codebase-index(port 8001) — a private search index for each of your code projects, built with open-codebase-index. It lets the AI find things in a large codebase quickly (keyword search, "where is this defined", "who calls this", and — with the bundled ollama embeddings — "find this by meaning") instead of reading files one by one. Every project gets its own address:http://localhost:8001/<project>/mcp.
Quick start (once per machine)
git clone <this repo> && cd supergateway
cp .env.example .env # put your BRAVE_API_KEY in it (https://brave.com/search/api/)
./add-project.sh . # registers this repo as the first indexed project and starts everything
./add-project.sh . builds the Docker image, starts both containers, indexes this
repository, and writes a few small config files into it (see below). Check that it worked:
docker compose ps # both containers should be "Up"
curl http://localhost:8000/healthz # -> ok
curl http://localhost:8001/healthz # -> ok
Indexing your own projects — the one command you'll actually use
From this repo, run:
./add-project.sh ~/src/git/my-project
That single command:
- remembers the project in
projects.env(the list of indexed repositories), - gives it its own address:
http://localhost:8001/my-project/mcp, - builds its search index (stored inside the project at
.opencode/index/— disposable, rebuilt anytime, gitignored), - writes three tiny config files into the project, each pointing at that address:
opencode.json— opencode reads this automatically when started in that directory.crush.json— crush reads this automatically when started in that directory.roo/mcp.json— Roo Code / Cline / Kilo Code workspace config (commit them — teammates who run this stack get the same behavior for free),
- adds
.opencode/to the project's.gitignore.
Why this is nice: you never re-configure your tools when switching projects. Open a project → the AI tool automatically finds that project's index. Work on three projects at the same time — each has its own address and its own index, nothing interferes.
Rules of the game:
- the project must be a git repository (the index is branch-aware and needs git),
- it must live under
PROJECTS_ROOT(default~/src, set in.env— the script configures this for you). Projects elsewhere need one extra volume mount, see Troubleshooting.
To stop indexing a project: ./add-project.sh --remove ~/src/git/my-project (index data
and the small config files are left in the project; delete them if you want).
For "how does repo X use repo Y" questions you can also attach neighboring repositories
to the current project as knowledge bases (the add_knowledge_base tool) — they get
searched alongside the project's own code.
Pointing your tools at the stack (once per tool)
The shared tools (port 8000) are configured once, globally. Per-project indexes (port
8001) need no global configuration at all — each project carries its own config files,
written by add-project.sh.
opencode
~/.config/opencode/opencode.jsonc:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"fetch": { "type": "remote", "url": "http://localhost:8000/fetch/mcp", "enabled": true },
"context7": { "type": "remote", "url": "http://localhost:8000/context7/mcp", "enabled": true },
"brave-search": { "type": "remote", "url": "http://localhost:8000/brave-search/mcp", "enabled": true }
}
}
Verify from any indexed project directory: opencode mcp list → four servers connected
(three above + codebase-index from the project's own opencode.json). If opencode was
already running there, run opencode reload first and give it a few seconds.
crush
~/.config/crush/crush.json:
{
"$schema": "https://charm.land/crush.json",
"mcp": {
"fetch": { "type": "http", "url": "http://localhost:8000/fetch/mcp" },
"context7": { "type": "http", "url": "http://localhost:8000/context7/mcp" },
"brave-search": { "type": "http", "url": "http://localhost:8000/brave-search/mcp" }
}
}
crush picks up each project's .crush.json automatically (restart crush in that
directory).
Cline / Roo Code / Kilo Code (VS Code)
Each indexed project already contains a workspace .roo/mcp.json (written by
add-project.sh), so opening the project in Roo Code just works. To also get the shared
tools everywhere, add them once to your plugin's global settings file:
| Plugin | Global config file (Linux) |
|---|---|
| Cline | ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json |
| Roo Code | ~/.config/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json |
| Kilo Code | ~/.config/Code/User/globalStorage/kilocode.kilo-code/settings/mcp_settings.json |
{
"mcpServers": {
"fetch": { "type": "streamableHttp", "url": "http://localhost:8000/fetch/mcp", "disabled": false },
"context7": { "type": "streamableHttp", "url": "http://localhost:8000/context7/mcp", "disabled": false },
"brave-search": { "type": "streamableHttp", "url": "http://localhost:8000/brave-search/mcp", "disabled": false }
}
}
What the code index actually searches
The index runs in hybrid mode: keyword (BM25) search, symbol definitions, call
graphs, branch awareness — plus semantic search ("find code by meaning") via local
ollama embeddings. The ollama container ships with this stack
but stays dormant (it lives behind the embeddings profile), so everything already
works fully offline without it: while ollama is off, semantic ranking just falls back
to keyword ranking.
Enable semantic search (once per machine):
docker compose --profile embeddings up -d # start the ollama container
docker compose exec ollama ollama pull nomic-embed-text # one-time, ~274 MB, kept in a volume
# build vectors for your projects (safe to repeat):
docker compose exec mcp-codebase-index ocbi index --project ~/src/git/my-project \
--config /etc/codebase-index/config.json --force
Projects registered later with ./add-project.sh pick embeddings up automatically.
Check a project's mode: docker compose exec mcp-codebase-index ocbi status --project ~/src/git/my-project --config /etc/codebase-index/config.json — semantic search is
live when it reports Provider: ollama and the index shows embedded chunks. (You can
also just ask the AI to run index_status / codebase_search; semantic hits carry
similarity scores near 1.0.)
Gotchas:
- Plain
docker compose up -d/down/psdo not manage the ollama container. Pass--profile embeddingswhenever a command should include it;docker compose --profile embeddings downstops everything. - If indexing logs
INDEXING WARNING: N chunks failed to embed, ollama was unreachable or too slow (CPU). Start it, make sure the model is pulled, re-run the index — failed chunks are simply rebuilt on the next run. Search keeps working keyword-only meanwhile. - Batch sizes for the index container are pre-tuned small (
OLLAMA_MAX_BATCH_TOKENS/OLLAMA_MAX_BATCH_ITEMSenv indocker-compose.yml) so CPU-only ollama stays under the embedding API's 30s timeout. Raise them if you have a GPU. - Embeddings don't have to be local: any OpenAI-compatible endpoint works. Switch
embeddingProviderto"custom"incodebase-index.config.jsonand setcustomProvider: { "baseUrl", "model", "apiKey" }. The chat model provider (z.ai, OpenRouter, cortecs, …) is configured in your AI tool and is completely independent of this stack — the index never talks to it.
Everyday commands
docker compose ps # status
docker compose --profile embeddings ps # status, including ollama
docker compose logs -f mcp # shared-tools gateway logs
docker compose logs -f mcp-codebase-index # index gateway logs (per-project lines prefixed)
docker compose restart mcp mcp-codebase-index # after editing supergateway.config.jsonc / codebase-index.config.json
docker compose --profile embeddings down # stop everything incl. ollama (index data survives, it lives in the projects)
Adding or removing shared tools (port 8000)
Edit supergateway.config.jsonc — each entry becomes
http://localhost:8000/<name>/mcp. Any stdio MCP server works:
"memory": {
"stdio": "npx -y @modelcontextprotocol/server-memory"
}
Then docker compose restart mcp. Servers started via npx need outbound network from
the container on first use.
How it works under the hood (curious readers)
- One image (built from
Dockerfile,node:24-slim) runs both services: supergateway v4 plus the bundled MCP servers, and open-codebase-index. - Both gateways speak Streamable HTTP (the current MCP transport) with sessions, so any modern client can connect.
- The index gateway mounts your
PROJECTS_ROOTinto the container at the same path it has on the host — so every file path the index reports is a real path you (and your AI tool) can open. - Each project's index server process is started per client session and dies when the client disconnects; the index data on disk persists, so reconnects are incremental. Registering 20 projects does not mean 20 idle processes.
- Index data is stored per project in
.opencode/index/— the same layout the native open-codebase-index uses — and is safe to delete at any time (it gets rebuilt).
Troubleshooting
curl http://localhost:8000/healthz # -> ok (shared tools gateway)
curl http://localhost:8001/healthz # -> ok (index gateway)
docker compose ps # both services "Up"
-
opencode mcp listsays "No MCP servers configured" right afteropencode reload— the background service reconnects asynchronously; wait 2–3 seconds and run it again. -
Project lives outside
PROJECTS_ROOT— create adocker-compose.override.ymlnext todocker-compose.ymlin this repo, mounting the extra root at its own path:services: mcp-codebase-index: volumes: - /opt/code:/opt/codethen run
docker compose up -d mcp-codebase-indexand register the project with./add-project.sh /opt/code/whatever. -
Docker build fails with
Temporary failure resolving ...— the default docker bridge has no internet access on that host. AddBUILD_NETWORK=hostto.envand rebuild. Note that recentdocker composeversions delegate builds to buildx bake, which refusesnetwork: hostbuilds with "Pass--allow=network.host..." unless the host really needs it — on machines with working bridge networking leaveBUILD_NETWORKunset (or set it todefault). If it still fails, check your firewall. -
Indexing logs
INDEXING WARNING: N chunks failed to embed(no semantic scores) — the ollama embeddings container isn't running or the model isn't pulled yet. Fix withdocker compose --profile embeddings up -d+docker compose exec ollama ollama pull nomic-embed-text, then re-run the index. Search still works meanwhile, just keyword-ranked (see "What the code index actually searches"). -
brave-searchtools fail — missing/invalidBRAVE_API_KEYin.env. -
Index seems stale — re-run the project's index:
./add-project.sh ~/src/git/my-project(safe to repeat), or ask the AI to runindex_codebase. -
Index data owned by root / permission errors — the index container runs as uid 1000. If your user has a different uid, set
user: "<uid>:<gid>"on themcp-codebase-indexservice indocker-compose.yml.