Adding MCP tool servers
Agents act through tools, and tools come from MCP servers that the Gateway runs for them — Grafana, Atlassian, a web-fetch tool, a database client, anything that speaks MCP over stdio. This page is the how-to for adding one on any platform.
How it works
Section titled “How it works”You declare each server under mcp_servers: in the Gateway’s config.yaml. At
startup the Gateway spawns each one as a subprocess, does the MCP handshake, and
caches its tools. An agent then lists the server by name in tools: in its
agent.yaml. The full config reference is in Gateway
configuration.
mcp_servers: time: command: uvx args: ["mcp-server-time"] grafana: command: npx args: ["-y", "@grafana/mcp-grafana"] env: GRAFANA_URL: ${GRAFANA_URL} GRAFANA_TOKEN: ${GRAFANA_TOKEN}Adding or changing a server needs a Gateway restart. POST /reload re-reads
agents and skills, but it never re-reads config.yaml and never starts new
server subprocesses.
Which servers can run here — and the one rule to remember
Section titled “Which servers can run here — and the one rule to remember”The Gateway runs MCP servers as subprocesses inside its own container. So:
The Gateway can run any MCP server that speaks stdio and can be launched as a command inside the Gateway container. Whatever that command needs — a runtime, a package, a system library, a binary — has to be in the container. If it isn’t in our image, build your own image from ours and add it.
That’s the whole model. Our image is a base, not a limit: the stock image
covers the common case (npx and uvx servers), and for anything else you
FROM it and install what your server needs (how, with
examples).
Two kinds of server can’t run this way, whatever you add to the image:
- Remote servers reached over HTTP/SSE. The config has no URL option — only
command,argsandenv— and the Gateway speaks stdio to what it spawns. Use a server that can be launched locally, or put a stdio bridge in the image that launches as a command. - Servers you can only run as a container (
docker run -i …). There’s no Docker inside the Gateway container. Install the server’s own runtime and files into your image instead, or run it where it can be reached as above.
What the Gateway image already includes
Section titled “What the Gateway image already includes”The published vectorstep-gateway image is built to run npx and uvx
servers without extra setup:
| Included | Version (0.1.12) |
|---|---|
Node.js, npm, npx |
Node 22 |
uv, uvx |
0.12 |
| Python | 3.11 |
curl |
yes |
Not included: git, docker, Go, Java, Deno, Bun. A server that needs one of
these — for example uvx --from git+https://…, a server launched with docker run, or a Go or Java binary — won’t run in the stock image. Build your own
image that adds it (see Pre-installing servers).
On native Linux (no container) the Gateway binary bundles neither Node nor
Python, so install Node.js and/or uv on the host yourself — but only if you
configure npx or uvx servers. A server that fails to spawn is logged and
skipped; every other server and agent keeps working.
Adding a server, step by step
Section titled “Adding a server, step by step”1. Edit the Gateway’s config
Section titled “1. Edit the Gateway’s config”| Platform | Config file |
|---|---|
| Docker Compose | ~/.vectorstep/config/gateway.yaml |
| Native Linux | /etc/vectorstep/vectorstep-gateway/config.yaml |
| Kubernetes | the config.yaml key of the vectorstep-gateway-config ConfigMap (kubectl -n vectorstep edit configmap vectorstep-gateway-config) |
Add the server under mcp_servers:. (The shipped Kubernetes config starts with
mcp_servers: {} — replace that line.)
2. Give it its secrets
Section titled “2. Give it its secrets”A server often needs a token or URL. Never put a secret in config.yaml;
reference an environment variable and supply the value through your platform’s
secret mechanism:
| Platform | Where the secret goes |
|---|---|
| Docker Compose | ~/.vectorstep/.env — it’s forwarded into the Gateway container |
| Native Linux | /etc/vectorstep/vectorstep-gateway/env |
| Kubernetes | the vectorstep-gateway-secrets Secret, which the Deployment already loads as environment variables: kubectl -n vectorstep patch secret vectorstep-gateway-secrets -p '{"stringData":{"GRAFANA_TOKEN":"…"}}' |
Then reference it in the server’s own env: block, as in the example above.
This is the only way a secret reaches the server. Each spawned server gets a
deliberately minimal environment — just PATH, HOME, LANG and whatever its
own env: block lists, with ${VAR} filled in from the Gateway’s environment.
It does not inherit the Gateway’s other variables, so one server can’t see
your LLM provider keys, the Gateway’s tokens, or another server’s credentials.
(We checked: with GRAFANA_TOKEN, ANTHROPIC_API_KEY and an unrelated secret
all set on the Gateway, a server declaring only GRAFANA_TOKEN received HOME,
LANG, PATH and that one value.)
The corollary: if a server needs a variable and you only set it on the Gateway,
the server won’t see it. Declare it in that server’s env:.
3. Restart the Gateway
Section titled “3. Restart the Gateway”| Platform | Command |
|---|---|
| Docker Compose | docker compose restart gateway |
| Native Linux | sudo systemctl restart vectorstep-gateway |
| Kubernetes | kubectl -n vectorstep rollout restart deployment/vectorstep-gateway |
4. Check it’s running
Section titled “4. Check it’s running”Each of these needs a Gateway token (the invoke or admin token works for the
read routes). On Kubernetes, port-forward first
(kubectl -n vectorstep port-forward svc/vectorstep-gateway 18780:18780).
curl -s -H "Authorization: Bearer $TOKEN" localhost:18780/mcp/servers# {"time":{"running":true,"pid":7,"restart_count":0}, ...}
curl -s -H "Authorization: Bearer $TOKEN" localhost:18780/mcp/tools# {"time":[{"name":"get_current_time", ...}, ...]}GET /mcp/serversshows each server’srunningstate.GET /mcp/toolslists the tools each server exposed — an empty list for a server means it failed to start.GET /healthreports"status": "degraded"while any configured server is down.- The
vectorstep_gateway_mcp_servers_runningmetric is 1 per running server (see Operations). - With the Gateway MCP, the
list_mcp_serversandlist_mcp_toolstools show the same thing. - Failures are in the Gateway log:
Failed to start MCP server '…': ….
5. Give an agent the tools
Section titled “5. Give an agent the tools”List the server’s name (the key under mcp_servers:, not the package) in the
agent’s tools::
name: sre-triagemodel: anthropic/claude-sonnet-4-6max_tokens: 8192tools: - grafanaThe agent is validated against your configuration, so a typo is caught up front
rather than at run time — tools: [nope] is rejected with “tools reference
unconfigured MCP server ‘nope’”. To restrict which of a server’s tools an agent
may actually call, see Tool policy.
The first start of an npx server can fail
Section titled “The first start of an npx server can fail”npx -y <package> downloads the package the first time it runs. The Gateway
gives a server 10 seconds to complete its startup handshake, and that limit
isn’t configurable. If the download takes longer than that, the server
fails with:
ERROR Failed to start MCP server 'memory': MCP request 'initialize' timed out after 10.0sand /health shows degraded. Whether this happens depends on your network:
on our test cluster, with empty caches, an npx server started within about four
seconds of the first attempt, while on a slower connection the same server timed
out. A server whose first start fails is not
retried until the Gateway restarts (one that crashes after starting is
restarted automatically — watch restart_count in /mcp/servers). The download
did complete and is cached, so restarting the Gateway once fixes it: in our test the same server started within two
seconds on the second start. uvx servers are usually quicker, but the same can
happen on a slow network.
Where that cache lives matters:
- Kubernetes: the shipped Deployment sets
HOMEto the Gateway’s PVC (/data/home), so the cache survives restarts and a restart is all it takes. - Docker Compose: the Gateway container’s
HOMEisn’t on the persistent/datavolume, so the cache lives in the container itself and is lost whenever the container is recreated (for example, upgrading the image) — the next start is cold again.
The way to avoid this entirely, and the one we recommend for anything beyond experimenting, is to install the servers into the image.
Pre-installing servers in your own image
Section titled “Pre-installing servers in your own image”This is also how you run a server that needs anything the stock image lacks.
Build a small image on top of the Gateway’s and install your servers and their
prerequisites into it — it’s the same FROM + RUN you’d write for any
container. Nothing is downloaded at start-up, so servers come up immediately — no cold-start
timeout, no dependence on npm or PyPI being reachable (which also makes this the
answer for air-gapped or egress-restricted clusters), and the versions are
pinned and reviewable.
FROM ghcr.io/bantex01/vectorstep-gateway:0.1.12
USER root# npm packages install globally; uv tools go to /opt/uv-tools with their# launchers on PATH. Pin exact versions.RUN npm install -g @modelcontextprotocol/[email protected] \ && UV_TOOL_DIR=/opt/uv-tools UV_TOOL_BIN_DIR=/usr/local/bin \ uv tool install mcp-server-time==2026.8.18# Anything else the server needs (git, a Go binary, …) goes here too.USER vectorstepThen refer to the servers by their installed command names — no npx, no uvx:
mcp_servers: time: command: mcp-server-time memory: command: mcp-server-memoryBuild and push it to a registry your cluster can pull from, and point the
Gateway at your image: set image: on the Gateway Deployment (add an
imagePullSecrets entry for a private registry), or image: on the gateway
service in your compose file. Keep your image’s base tag in step with the
service — the service and Gateway must run matching versions (see Image tags
and versions).
We tested this image on an empty volume with a read-only root filesystem and no network at all: both servers initialised within the same second.
Recipes for common prerequisites
Section titled “Recipes for common prerequisites”Always switch to root to install, and back to vectorstep (the non-root user
the Gateway runs as) at the end.
| Your server needs… | Add to your Dockerfile |
|---|---|
An OS package (git, a client library, a CLI the server shells out to) — the base is Debian |
RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/* |
| An npm package | RUN npm install -g <package>@<version> — then command: is the package’s bin name |
| A Python package/CLI | RUN UV_TOOL_DIR=/opt/uv-tools UV_TOOL_BIN_DIR=/usr/local/bin uv tool install <package>==<version> |
| A prebuilt binary (Go, Rust, …) | COPY --chmod=755 my-mcp-server /usr/local/bin/ — build it for the image’s architecture (amd64 or arm64; the published image is both) |
| A different runtime (Java, Ruby, …) | install it with apt-get, or copy it in, then command: points at the server’s launcher |
We tested the OS-package, npm and Python recipes: the git image ran as the
vectorstep user under a read-only root filesystem, and the npm and Python ones
are the example above. Pin versions so the image is reproducible, rebuild it
when you upgrade the Gateway, and keep it on the same version as the service.
Platform notes
Section titled “Platform notes”Kubernetes
Section titled “Kubernetes”The shipped Gateway Deployment runs with readOnlyRootFilesystem: true, a
non-root user and dropped capabilities, and is set up so npx/uvx servers work
under those restrictions: HOME and the package caches point at the PVC. We ran
an npx and a uvx server on a cluster with these exact settings: with empty
caches both started on the first attempt, the caches landed on the PVC (about
100 MB for these two) and survived a restart so the next start was instant, and a
secret supplied through the Gateway’s Secret reached only the server that declared
it. Two things to plan for:
- Egress. Without a pre-built image, the first start needs to reach the
package registries (
registry.npmjs.orgfornpx;pypi.organdfiles.pythonhosted.orgforuvx). If you apply egress NetworkPolicies, allow those or use a pre-installed image. - Changes are a restart, not a hot reload. Edit the ConfigMap, then
kubectl rollout restart. (See Changing config on Kubernetes for how agent and pipeline changes — which do hot-reload — are delivered.)
Docker Compose
Section titled “Docker Compose”Edit ~/.vectorstep/config/gateway.yaml and ~/.vectorstep/.env, then
docker compose restart gateway. A plain docker compose up -d does not
restart a container whose compose definition hasn’t changed, so use restart.
For a pre-built image, replace the gateway service’s image: in your compose
file.
Native Linux
Section titled “Native Linux”Install Node.js and/or uv on the host for any npx/uvx server (see
Linux install), edit
/etc/vectorstep/vectorstep-gateway/config.yaml and the env file beside it,
then sudo systemctl restart vectorstep-gateway. Check
journalctl -u vectorstep-gateway for spawn errors.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause |
|---|---|
Failed to start MCP server '…': … 'initialize' timed out after 10.0s |
A cold npx/uvx download exceeded the 10-second start limit. Restart once (cache is warm), or pre-install |
/health shows degraded |
At least one configured server isn’t running — check /mcp/servers and the log |
/mcp/tools lists a server with no tools |
That server failed to start |
| Server starts but its calls fail with auth errors | It didn’t get its secret — declare the variable in the server’s own env: block; the Gateway’s other variables aren’t passed through |
tools reference unconfigured MCP server '…' when saving an agent |
The name in tools: isn’t a key under mcp_servers: |
command not found / spawn error for npx or uvx |
Native Linux: Node.js / uv isn’t installed on the host. Container: the server needs a runtime the image lacks (git, docker, Go, Java…) — build a derived image |
| Config change had no effect | MCP servers are read at startup — restart the Gateway; POST /reload doesn’t re-read config.yaml |
| Server worked, then stopped working after an image upgrade (Docker Compose) | The package cache was in the old container; the new one starts cold — restart once, or pre-install |
Related
Section titled “Related”- Gateway configuration →
mcp_servers— the field reference. - Tool policy — restricting which tools an agent can call.
- Creating agents — the
tools:field. - Build your first agent — a worked example adding
fetchandfilesystemservers (Docker Compose).