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, and it sets
confirms_requested_vantage: false rather than implying success. Truly
confirming from a container vantage requires running the probe inside that
vantage (e.g. an ephemeral container on the target network).
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.
Diagnoses whether an MCP server is reachable, by live HTTP probe.
Experimental MCP server for current empirical verification of explicit public HTTPS endpoint claims.
MCP server for network documentation, generated by doc2mcp.
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.257 npmMIT
- FlicenseBqualityCmaintenanceMCP server for natural-language control of local Docker, covering containers, images, volumes, networks, and Compose stacks, plus security scanning and diagnostics.34-