ADR 0244 sorts the mesh's concepts into ten domains (ADR 0006's contexts carry over as domains), keeps machine and node as distinct words, and makes the glossary the authority with every retired word on its replacement's Not: line. To-be 49 draws the domains; the glossary is reorganised by them, its two contradictions removed and the missing words added. words.py now fails on a retired word in running prose and on a word defined twice, so the rule is enforced rather than believed; the 63 documents it failed on are reworded here, and research 034 is kept as the record of the words it studied.
12 KiB
layer, status, code, updated, decisions
| layer | status | code | updated | decisions | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| to-be | designed |
|
2026-10-03 |
|
34 — The mesh MCP server
The mesh's tools, on the machine a person sits at, served by a module the mesh assigned there. An agent reaches them over MCP on the machine's loopback; a person reaches the same endpoint. Nothing is installed by hand, nothing is configured with an address, and the mesh knows the surface exists because it put it there (ADR 0152).
Amended 2026-10-02 by ADR 0175. What this document describes stays true in substance and changes in form: the console becomes the serving mode of the node tools runtime, a host-side process the host supervises rather than a container, which also serves every assigned module's tools from their bundles. The module is renamed
node-tools. 37 — The operator's machine §3 is where it now lives.
1. What it is
A module, mesh-console, in the catalogue. Its image is the tool runner's own — the client that
already speaks the bus as a command line and as an MCP server — started in a mode that reads the
module's credential and listens on loopback. It has no state, no provision, no seat. What it needs is
the bus, which it gets the way every module does: a credential the mesh minted for <node>.mesh-console,
sealed to the machine, delivered as the module's own secret.
2026-10-02: it gains one provision, at node scope — the MCP endpoint on loopback, serving the port the machine gave it — so that a module whose software must be told where the mesh MCP server is requires that and is coupled to an endpoint rather than to a module's name (ADR 0027). The first consumer is the operator's agent, 36 — The operator's agent on a machine §6; a machine without the mesh MCP server refuses such a module by name. Nothing else above changes: no seat, no state, no tools of its own.
Its manifest says three things nothing else in the catalogue says together:
invokes: ["*"]— it calls every tool on the mesh, and the bus grants exactly that publish side;listenson a portfrom: machine— the filter opens nothing for it, because loopback is not outside;- no
emits, noconsumes, notools— it answers nothing on the bus and nobody can address it there.
2. What it serves, and to whom
One endpoint, POST /mcp on the machine's loopback, speaking MCP over HTTP: initialize,
tools/list, tools/call. An agent on the machine is pointed at it once — the address is the machine's
own and never changes — and sees every tool the mesh can say it has. A person at a terminal uses the
same endpoint through the mesh client, or through anything that can make an HTTP request; the client
needs no credential, because the mesh MCP server holds it.
The endpoint is the machine's login. It binds 127.0.0.1 and nothing else. Whoever can connect is
on the machine, and whoever is on the machine is the account that owns the mesh there
(ADR 0034,
ADR 0144). There is no
token, no login page and no second identity, on purpose: a credential a person had to carry to reach
their own machine's mesh MCP server would be the arrangement this replaces, moved one hop.
3. How it knows what the mesh can do
Design 33 §5 splits discovery in two: a role's tools are read from the mesh's records, a module's own are asked of the module. The mesh MCP server builds the second half now and reads the first when it exists.
Every tool runner answers tools. The runtime that serves a module's tools also serves one verb of
its own under that module's name, mesh.mod.<module>.tool.tools, answering the module's tool names,
descriptions and argument schemas — the definitions from the code that answers them, and from nowhere
else. A module may not name a tool of its own tools; the runtime refuses the collision at load.
The mesh MCP server asks the catalogue which modules the mesh holds, then asks each. catalog_modules
answers the roster; one tools request per module, in parallel, answers the list. The bus refuses at
once a request nothing serves, so a module that is not running costs nothing and is named in the answer
as not answering, rather than silently absent — silence and success must never look alike. The list is
kept for a short while and refreshed, so an agent asking on every turn does not fan out on every turn.
Every module tool takes the machine to ask (2026-10-01, ADR 0159):
an optional node the mesh MCP server lists on each one, puts into the subject and never hands to the module,
for a module that runs on several machines; without it whichever instance answers first does, and the
mesh MCP server appends answered by to every answer. A seat's verb takes none; the seat's scope
decides. Later the same day (ADR 0160): the mesh MCP server composes no
subject at all; each tool's subject comes with the listing, and a stateful module on two machines is
listed once per machine because the mesh issued it no plain subject.
A tool that was not listed can still be called. Listing is discovery; calling is the grant. An agent
that knows a tool's name asks for it by <module>.<tool> and the module answers or the bus says why not.
What is missing from the list, and until when. A role's tools and the mesh's own verbs — status,
push, assign — are the mesh-controller seat's under ADR 0132 and are not served yet; their three
prerequisites are listed in that record. When the seat serves them, the mesh MCP server lists them beside the
modules' own, and the person stops opening a shell for the mesh's own questions. Until then the mesh MCP server
says so in its handshake.
3a. Found by address, not announced whole (2026-10-03)
By ADR 0195;
this section governs where it and §2–§3 disagree. The mesh MCP server announces five tools —
mesh_overview, mesh_machine, mesh_search, mesh_describe, mesh_call — and every tool the mesh
answers is reached through them by its address: <seat>.<verb> for a seat held once for the mesh,
<node>/<seat>.<verb> for one held per machine, <node>/<module>.<tool> for an assignment, and
<module>.<tool> as well for a module whose instances are interchangeable. A module that is not
interchangeable is called with its machine or refused with the machines it runs on. Each discovery
verb asks the mesh when it is called, so nothing is kept for a session's length; the flat catalogue
stays reachable through the mesh client and a setting, unannounced.
What exists is what announced itself (ADR 0197):
every runtime answers the NATS services protocol's $SRV.INFO with what it serves, and the mesh MCP server
gathers one request's answers; the controller's records, read as JSON, say which assignments with
tools should have answered.
Found 2026-10-03, measuring for that record: §3's statement that a stateful module on two machines is
listed once per machine does not hold on the live mesh MCP server — postgres and mssql are listed once, node
optional, answered by whichever instance replies. The address replaces that statement rather than
repairing it.
4. Where it runs
On whichever machines an operator sits at, by assignment. It is not on the control node by default and does not need to be: it reaches the bus like any module, from anywhere in the mesh. A machine that is not a node cannot have it, which is the right refusal — the mesh reaches what it declares, and a workstation that wants the mesh MCP server joins first.
The person's credential and the mesh client (design 25 §7) remain the path
for a machine that is not a node, and the path to a mesh not yet far enough along to assign anything.
5. Removing it
Unassigning the mesh MCP server from a machine revokes its bus account at the next composition and stops the container; nothing is left on the machine that could still connect. An agent pointed at the loopback address gets a refused connection, which is the truthful answer.
How it is checked
| Check | Defends |
|---|---|
a module invoking one tool may publish that subject and no other tool's; * may publish every one; neither may publish an event or subscribe what it did not consume |
ADR 0152, the grant |
a module registering two tools answers three names to tools, with schemas; a module naming its own tools is refused at load |
ADR 0152, discovery |
| against a real bus: two modules up, a third held and not running — the mesh MCP server lists the two and names the third as not answering | ADR 0152, silence is not success |
| a call through the mesh MCP server's endpoint reaches a module over the bus and the answer is the module's own, unshaped | ADR 0035, a surface decides nothing |
on the live mesh: the mesh MCP server assigned to a workstation answers tools/list on loopback and a call to the forge returns repositories |
the exit of work-order step 3 |
| the composed filter for a machine carrying the mesh MCP server opens no port for it | ADR 0144 |
What shipped, 2026-09-30
Everything above, the same day: mesh-controller PR 164 (invokes, module check), mesh-tools PR 20
(mesh serve, the tools verb), mesh-catalog PR 181 (mesh-console). Verified on the live mesh: the
mesh MCP server assigned to a workstation answered tools/list on its loopback with 62 tools from the modules
whose runtimes had been rebuilt to answer tools, named 36 modules as not answering (modules that serve
no tools, and modules whose new runtime the mesh records rather than rolls out), and a tools/call of
the forge's gitea_list_repos returned repositories. An agent on that machine reaches it as an HTTP
MCP server and reports it connected. The mesh assigned the declared port unchanged, which is what a
machine with nothing else on it does; the mesh MCP server binds whatever it is given.
Two things shipped bent, both stated in 00-as-is/13-the-console.md:
the person's client through the mesh MCP server (--console) exists and was exercised in the test suite, not
on the live mesh; and a module registered from the catalogue by hand recorded its source as a URL rather
than as a path on the git seat, because --self takes the forge path form — the rebuild-on-merge still
matched it by URL.
What this does not settle
- Narrowing a mesh MCP server's grant per assignment. ADR 0046 makes it a setting; nothing reads one yet.
- The mesh's own verbs on the bus. Design 33's third family; this document only says where they appear once they exist.
- A person's identity behind the mesh MCP server. The mesh sees the mesh MCP server's account; design 15 keeps the question open.
References
- ADR 0152 — the decision
- 33 — The tools the mesh answers — what the mesh MCP server lists
- 25 — The bus on NATS §7 — the person's client this makes a module of
- issue 147 — the symptom