9.8 KiB
layer, status, code, updated, decisions
| layer | status | code | updated | decisions | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| as-is | implemented |
|
2026-10-26 |
|
The mesh MCP server, as it runs
The mesh-console container no longer exists. Read 2026-10-17 from mesh-tools (package
node-tools), mesh-host and mesh-controller on their forges' main, and from the live mesh
through mesh_overview, mesh_machine, mesh_runtimes and mesh_describe. Checked: the
catalog (mesh-catalog) carries no module named mesh-console any more — the name survives only
in the glossary's retired-words list and in a test fixture — and none of the four nodes answers a
module of that name. How the tool runner reaches a node, its count of modules and tools and the
controller's verbs were read again on 2026-10-26, after the delivery of release 6 (ADR 0539).
What it is now
The tool runner is one of the mesh's core components, named node-tools beside
mesh-controller, mesh-host (the node-engine) and nats (the bus) — the mesh's own repositories'
check treats the four the same way, as the core whose health the mesh itself defines rather than a
module's own. Since release 6 it reaches each node the way every module does: the controller composes
and signs its composition for that node, the assigner writes the node's want of it, and the
node-engine's node agent applies it and writes its have. What stays particular to it is the account
named <node>.node-tools on the bus (BusNodeTools in mesh-controller) and the node-engine's
witness and lease code that puts back a tool runner that does not come up (ComponentNodeTools in
mesh-host, ADR 0360 §9).
It still carries a manifest, node-tools/module.json in mesh-tools, and that manifest is
where its grants live: invokes names *, so its account may call every tool on the mesh;
waits names the five event families mesh_wait may subscribe to (the knowledge base's reviews,
mesh-delivery's transitions, the forge's status, the controller's checked event, and the operator
channel's decision for the agent module) — the bus refuses a subscription outside this list
(ADR 0367 §2); and it
provides one provision, the MCP endpoint, at node scope, and listens on the port it serves from,
opened only to the machine itself. Its code lives under node-tools/ in the mesh-tools
repository (internal/console, internal/announce, internal/bus).
One runtime per node, run by the node-engine as a sibling process, never a container
(ADR 0175). It holds the node's one bus credential and may call every tool on the mesh; its account
publishes nothing else. It binds 127.0.0.1:<port> and refuses to bind anything else, said before
binding rather than after (node-tools/internal/console/http.go, citing ADR 0152): whoever is on
the machine owns the mesh there, and nobody else may reach this port. An agent or a person on the
node is pointed at http://127.0.0.1:<port>/mcp once; the serverInfo.name the handshake answers
is mesh.
What it answers
initialize, notifications/initialized, ping, tools/list, tools/call, over HTTP, one JSON
body per request, no session and no event stream (node-tools/internal/console/mcp.go).
It announces seven tools, not five (ADR 0195, extended by ADR 0367): mesh_overview,
mesh_machine, mesh_search, mesh_describe, mesh_call, mesh_runtimes and mesh_wait. The
handshake's instructions text names all seven and the four address forms. mesh_overview is
where the mesh-wide count of modules and tools is read live — a number copied into this document
would drift; asked on 2026-10-26 it answered 115 modules and 714 tools across the whole mesh, with
two module instances (route-proxy on two of the nodes) named as not answering rather than silently
absent.
Addresses, exactly as the handshake states them: <seat>.<verb> for a seat held once for the
mesh (mesh-controller.status); <node>/<seat>.<verb> for a seat every machine holds
(<node>/node-packet-filter.rules); <node>/<module>.<tool> for a module assigned to one machine;
and <module>.<tool> also for a module whose instances are interchangeable. A tool not listed can
still be called by address — listing is discovery, calling is the grant — and a module that is not
interchangeable is refused with the machines it runs on rather than guessed at.
What exists is what announced itself (ADR 0197). Every node's tool runner answers the NATS services
protocol's $SRV.PING, $SRV.INFO and $SRV.STATS, one service per process, naming in each
endpoint's metadata which module, seat, scope and machine serves it. mesh_overview and
mesh_machine gather one request's answers: at least 750ms so a runtime that only just answered
$SRV.PING is still waited for, up to 5s for one that said it is there but has not yet sent its
full announcement — a fixed shorter window lost the largest runtime's listing once a live
measurement found how slow its round trip over the bus could be. An announcement unchanged since
last asked (by its digest) is not re-fetched, which is why mesh_runtimes can show a runtime
answering in under 100ms for hundreds of kilobytes of discovery.
mesh_wait
The seventh tool, added by ADR 0367, lets a session wait on the bus for an event instead of
polling: a call names one or more granted event families and an optional filter, and either answers
the first match or says it timed out. The tool runner's own bus account hears only the families its
manifest's waits list names; an event it hears is never taken as the state — a session reads the
state from the verb that owns it once woken. It is meant to be called in a loop from a background
command, not inline, because one call blocks until it answers or times out.
The mesh's own verbs
These already answer directly, not as a stated future. Beside every assigned module's tools,
the tool runner asks the mesh-controller seat for its own verbs the same way (ADR 0154): measured
live on 2026-10-26, the mesh-controller runtime is one instance, mesh-wide, and it answers 65 verbs for
that one seat (status, assign, modules, artifacts and the rest), of which 20 are refused by
name with what replaces them: the delivery verbs release 6 removed (plans, plan, push,
deliver among them) and the build verbs of release 4 (build, queue among them). The mesh-controller
module answers none of its own — it serves no module tools, only its seat's, exactly as design 33
described while it was still only designed. The 56-to-93 seat-verb counts each node's tool runner
answers (below) are a different thing: the tool runner serves the verbs of whichever node-scoped
seats are held on that node (node-backup, node-build-go, node-service-manager among them),
on behalf of the modules holding them, not the mesh-controller seat's — that one is served by the
mesh-controller runtime itself, a separate process, and the tool runner only asks it and relays what
it answers.
Where it runs
Every node, not a per-operator assignment. Measured live: all four of the mesh's machines
answer a node-tools runtime, each with its own module and tool count (between 294 and 312 tools,
56 to 93 seat verbs, in this reading). This is a fact that shipped past the design it came from: the
to-be document still describes it as assigned to "whichever machines an operator sits at"; what
actually runs is the opposite — every node the mesh holds carries one, because it is core
infrastructure the mesh itself depends on (discovery, the controller's own verbs, every module's
tools), not a convenience assigned to a workstation. There is accordingly no assign or unassign verb
for it: removing it from a node is not an operation the controller or the catalog exposes.
What this repository's prior text got wrong
The document this replaces described mesh-console as a catalog module served by a container and
assigned only to workstations. That module and its container are gone; the manifest is not — it
moved to node-tools/module.json, still declares invokes: ["*"], and gained waits and the
mcp-endpoint provision, described above. What also moved is which language builds it: the
manifest's build.artifacts names a Go binary built from cmd/node-tools, and that is what a node
runs. The repository still carries the earlier TypeScript sources (node-tools/src/mesh.ts,
http.ts, runtime.ts, announce.ts, broker-nats.ts, client.ts, launch.ts, main.ts,
mcp.ts) on main, but the manifest does not build from them. The quirks the prior text recorded
from the mesh-console era (a module registered by hand recording its source as a URL; a
record-policy module staying listed as not-answering until its rebuilt runtime is pushed) are not
re-checked here: they were about the mesh-console container's registration path, which no longer
exists.
How it is checked
mesh_overviewandmesh_machine <node>: the live tool and seat-verb counts, and which modules are named not-answering.mesh_runtimes: which runtimes answered discovery, how long they took, how large their announcement was, and whether it was shortened to fit the bus — read this when an address is said to be missing or on another machine.mesh_describe <address>for one tool's schema; a call throughmesh_callreaches the module or seat over the bus and the answer is its own, unshaped.- The code:
mesh-tools(node-tools/internal/console,internal/announce,internal/bus),mesh-hostandmesh-controller's core-component handling ofnode-tools(witness, lease, bus-user naming). Their unit tests hold the loopback-only refusal, the seven-tool handshake, the discovery gather window andmesh_wait's event-then-read-state rule. knowledge-base.getwithkind: decisionand number 152, 175, 195, 197, 367 or 154.