nas-reach-mcp
Reads the live Docker runtime (containers, networks, attachments and published ports) over the Docker API socket to answer vantage-relative reachability questions. Provides tools to detect the caller's own vantage point, list networks and the containers that bridge them, resolve which address a service answers at from the host, a named network, or a specific container, describe a container's networks, IPs and published ports, and probe whether a service actually responds right now.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@nas-reach-mcpwhere can I reach litellm:4000 from the ai_proxy network?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
nas-reach-mcp
What is the address of service X, from vantage point Y — and is it up?
A read-only MCP server that answers reachability questions about a Docker host. It reads the live Docker runtime (containers, networks, attachments and published ports) and reports the correct address for the vantage point you name — or, just as importantly, explains precisely why there isn't one.
It is built for a homelab where the same service is reachable at several different addresses depending on where you are standing, and where guessing wrongly is the normal outcome.
Why this exists: a port is absolute, an address is relative
litellm:4000 and nas:4000 can name the same gateway, and neither is
the address. Docker's embedded DNS resolves container names on a
user-defined network, but the host's own hostname does not resolve inside
that network — so:
from the host, the address is
nas:4000;from a container on the
ai_proxynetwork, it islitellm:4000.
Reachability is therefore a graph query, not a lookup: a container is reachable from a network if and only if it is attached to that network, and multi-homed containers act as bridges between networks. Getting this wrong returns a plausible-looking URL that simply doesn't work — which is exactly the failure this tool exists to prevent.
Related MCP server: hermes-fleet-mcp
The six tools
Tool | Question it answers |
| Where am I standing? (this process's own vantage, detected) |
| What networks exist, who is on them, and which containers bridge them? |
| What is reachable from here, and at what address? |
| Where does this name answer from there — or why not? |
| Does it actually answer, right now? |
| What is this thing attached to? (networks, IPs, published ports) |
The vantage model
from_vantage is required on resolve_service and check_reachable and is
never defaulted — a default is how you get a confidently wrong address.
(list_services is the one exception: "everything from where I am" is a
complete question on its own.)
Value | Means |
| the Docker host itself, or any |
| a container attached to that network |
| through a specific container (i.e. its networks) |
| this process's own vantage, detected |
A bare word (ai_proxy) is read as a network name.
Answers are specific, not empty
resolve_service always answers with a reason. Returning nothing is a failure
mode, so a negative result names what it checked and points at where the
service is reachable:
resolved— a candidate address exists for that vantagehost_only— runsnetwork_mode: host; no name on any network, so it is reachable by host port or not at allnot_attached— exists and has networks, but not this one (the name will not resolve there; a bridge IP may still answer via host routing)unresolvable_name— no such container (DSM packages and host processes are invisible to the Docker API)unknown_port— attached and nameable, but no port is visible to the APIambiguous— two containers share the name; guessing would be wrongunknown_network/unknown_container— the vantage itself doesn't exist
check_reachable never collapses failures into an opaque 000. curl reports
one status code for several different diseases, so they are separated:
unresolvable_name (the name never resolved), refused (port actively
refused), timeout (filtered), unreachable (no route), or ok. A successful
probe also reports the HTTP status, Server header and latency, and — on an MCP
path — serverInfo from a single initialize handshake, because an HTTP 200
from the wrong service is worse than a 404.
Nothing is cached: every call re-reads runtime state, and every response is
stamped verified_at.
The full tool contract lives in docs/tools.md.
Deployment
The server is designed to be deployed to the Docker host itself, because it needs the host's view of the runtime:
Network mode:
host— so thehostvantage is genuinely the host and host-network containers are visible. The trade-off: this process has no name on any Docker network, so it can only resolve host names and ports./var/run/docker.sockmounted read-only (note: a read-only mount does not make the socket read-only — it is root-equivalent either way).Port
3010—mcpowraps the stdio server as Streamable HTTP, served at the root (/openapi.json,/docs).Registered in MCPHub as slug
nas-reach(typeopenapi, groupops) and surfaced on the Homepage dashboard.
See deploy/README.md for the full runbook
(Buildkite → GHCR → Watchtower → Docker host, plus rollback).
docker compose up -dConfiguration
Variable | Default | Purpose |
|
| The host name to use when composing host-vantage addresses |
|
| Docker API socket path |
| (unset) | Override self-container detection when hostname matching fails |
The
MCPHUB_*variables in the generated.env.example/ compose file are genproj scaffolding and are not read by this code.
Known limitation
Because it runs network_mode: host, check_reachable probes from the
host vantage. It confirms a host-vantage answer directly; for a network
vantage it probes the container's bridge IP (which is what actually answers)
while reporting the name-based address the caller would use. The
confirms_requested_vantage flag is derived from that evidence, not asserted:
confirmed_by says which probe established it (by-name or by-ip), and when
a probe answers but is not evidence for the requested vantage, confirms_note
says why instead of leaving a bare false. Truly probing from inside a
container vantage requires running inside that vantage (e.g. an ephemeral
container on the target network), which this read-only tool does not do.
Relationship to nas-port-mcp
This is a sibling to nas-port-mcp. nas-port-mcp answers which host port
maps to which container; nas-reach-mcp answers the harder, vantage-relative
question — what address works from where, and does it answer. The two
complement each other.
Development
python3 -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"Run the checks (the exact sequence CI runs):
ruff check src tests
pytest -qCI runs on Buildkite (see .buildkite/); the pipeline's
docker_publish step builds the linux/amd64 image and pushes it to
ghcr.io/nickbrett1/nas-reach-mcp.
Doppler
This project uses Doppler for secrets from the shared common project
(config dev) — no per-repo Doppler project is created. First use:
doppler setup --project common --config devThe Doppler CLI is installed in the devcontainer; auth is persisted via the
host ~/.doppler bind-mount.
Env-var precedence (read this if doppler run hits the wrong project)
Doppler resolves its target as environment variables > doppler.yaml >
~/.doppler scoped config. If your shell — or the session that launched the
devcontainer (e.g. an agent runtime) — exports DOPPLER_PROJECT /
DOPPLER_CONFIG / DOPPLER_ENVIRONMENT, those silently override this repo's
doppler.yaml. To force the correct context manually:
unset DOPPLER_PROJECT DOPPLER_CONFIG DOPPLER_ENVIRONMENT
doppler setup --no-interactive --project common --config devThe container's agent
This devcontainer brings up its own a2a-goose agent, registered in the hub as
nas-reach-mcp-dev — one agent per repo, so a restart reclaims the same entry
instead of adding a second one. Turns are billed through the LiteLLM proxy
configured in Doppler (LITELLM_BASE_URL).
scripts/agent-dev.sh start # write secrets + config, fetch the launcher, run it
scripts/agent-dev.sh status # running or not, the card URL, the log tail
scripts/agent-dev.sh stop # SIGTERM, wait for a clean deregister, confirm gonestart runs from the devcontainer's post-start hook, so the agent is normally
already up when you arrive. It fails open: with no network on a first start it
prints why it did not start and leaves the project usable. Secrets come from
Doppler into ~/.config/a2a-goose/env (mode 0600) and never into the image or
containerEnv.
Generated by genproj
This project was scaffolded with genproj; the application code above is hand-written.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server for OnceAsk, the AI-native current-address layer for people and agents.
MCP compatibility and transport verification server.
MCP compatibility and transport verification server.
MCP compatibility and transport verification server.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP server that gives any LLM client the ability to list, inspect, start, stop, and monitor Docker containers on the host machine.1-
- FlicenseNot gradedqualityCmaintenanceMCP server that routes Docker operations across a fleet of Docker hosts via the Scotty API.-
- AlicenseNot gradedqualityBmaintenanceMCP server that wraps Docker Hub v2 API to enable querying public Docker Hub data (e.g., repositories, tags) with no authentication required.54 npmMIT
- FlicenseBqualityDmaintenanceMCP server for natural-language control of local Docker, covering containers, images, volumes, networks, and Compose stacks, plus security scanning and diagnostics.34-