The bus withholds a marked subject from a runner that does not honour the mark again, as ADR 0286 has it; marks follow G's catalog half; a recording, a stuck gate, an absent router and a start before the marks are read each have a stated answer, so nothing runs unasked and nothing fails silently. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
19 KiB
layer, status, code, updated, decisions
| layer | status | code | updated | decisions | |||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| to-be | in-progress |
|
2026-11-11 |
|
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 catalog. 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 catalog 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 catalog 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 catalog
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.
3b. Waiting on the bus
By ADR 0367, as ADR 0670 and ADR 0671 change it. Beside the five discovery tools of §3a and mesh_runtimes, the mesh MCP
server offers a seventh, mesh_wait: a session names one or more events, an optional filter of body fields and a
timeout (four minutes when absent, ten at most), and is answered the first matching event or told it timed out
(ADR 0367 §1). A session calls it in a loop from a background command (ADR 0367 §3), and after every answer or time-out
reads the state once from the verb that owns it: an event only wakes it (ADR 0367 §4).
flowchart LR
E[a seat's, a module's or a module stream's event<br/>mesh.seat.<seat>.event.… / mesh.mod.<module>.event.… / mesh.stream.<module>.event.…] -->|heard for the call| TR[the tool runner's account]
G[seat mesh-grants] -->|grants the three event subjects| TR
M[the catalog half, seat mesh-catalog] -->|the marked events and restricted streams| TR
TR -->|a marked wait only on the operator's warrant| OC[seat operator-channel]
TR -->|mesh_wait answers it by name| S[a session]
S -->|then reads the state| O[the verb that owns it]
The tool runner hears every event of the mesh, and no list says which (ADR 0670 §1, §2). The grants part (seat
mesh-grants) grants the account of the runtime carrying the tool runner a plain subscription on the event subjects
of every seat and every module, and on nothing else of theirs: no accept, proof, call, work queue or bucket, and no
publish. mesh_wait subscribes when called and closes the subscription when it answers, so the account keeps no
durable consumer and no queue of its own (kept from ADR 0367 §2), and any event name is accepted: a new event is
heard from its first publication, with no change to a manifest. The tool runner's manifest names no events, the
manifest validator has no rule about them, and no membership lists them. A name whose emitter no seat and no module
bears is refused, so a misspelt wait does not run for ever.
Who hears. Whoever may call the mesh MCP server: any process on the machine, through its loopback endpoint (§2), which is the session there. The tool runner's standing grant of every event is accepted under ADR 0670 §1: what a session hears through it, it may hear.
Module streams too, mail included (ADR 0671 §1, §2). The tool runner also hears the events kept in a module's own
stream (ADR 0363 §2), the seat holders' events published there among them, a restricted stream such as mail's
included. A restricted stream's grant value decides who consumes it, never who hears it, so the tool runner's hearing
needs no consumes and no entry in that value, and keeps no consumer of it.
A marked event is heard only on the operator's warrant (the operator, 2026-11-11). The approval mark (ADR 0286) covers a module's events and streams as well as its tools and
verbs: a module may mark one of its events in needs-approval, and a restricted stream is marked whole. The tool
runner reads the marks from the catalog half on the bus (seat mesh-catalog), and a mesh_wait on a marked event, or on any subject
of a marked stream, runs only on the operator's warrant for that one wait, asked on the seat operator-channel with
the exact names it waits on and its bound, and checked before the tool runner subscribes; without one it is refused
and says so. The bus grants the subjects, and the tool runner's own gate holds the mark, as the operator chose; the
gate is design 46's milestone 7, built in design 47's milestone G and delivered in one group with G and milestone
every-event, so the subjects and the gate reach the machines together. This departs from ADR 0286 §1 and §8 (tools and
verbs only, held by the bus) and from ADR 0671 §1 and §3 (every module stream heard with no exception) by the
operator's answer. An unmarked event is heard with no ask.
What that makes audible, and what still bounds it. Every session now hears the operator channel's decided of
every asker and the intake bench's events. So no event of a seat or a module carries a secret: no credential, token or
code the mesh itself issues or keeps, and no message marked secret, which the code review asks of every change that
adds an event or a field (ADR 0670 §4 as ADR 0671 §3 narrows it). A mail's own content is the operator's mail and is
heard as it is, a sign-in link or a code someone sent by mail among it (ADR 0671 §3). And a heard event never answers anything: the operator's answer to an ask is read only
with claude_code_answer, from the router's record and held to the question's digest (ADR 0670 §5, ADR 0332 §5,
§6), and an asker acts only on a warrant from its own decided subject through its own consumer (ADR 0259 §6).
What is built for this, line by line, is milestone every-event's part,
34-milestones/every-event.md.
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 |
on a real bus, mesh_wait answers the first matching event within a second of its publication, and after every answer a session reads the owner's state |
ADR 0367 §1, §4 |
live: a watcher's mesh_wait on the status view's delivered, an event no manifest names, wakes on it with no rereading on a timer |
ADR 0670 §2, milestone every-event |
a composition test: the tool runner's runtime account is granted exactly mesh.seat.*.event.>, mesh.mod.*.event.> and mesh.stream.*.event.> beside what its carried modules declare, with no publish on another's events, no durable consumer for them and no wider subject; on a real bus it is refused a publish on an event subject, and another module's account a subscription on an event it does not consume |
ADR 0670 §1, §3; ADR 0671 §1 |
| on a real bus, the tool runner's account hears a restricted stream's event with no entry in its grant value, still after the value changes, and is refused a durable consumer and an acknowledgement on it | ADR 0671 §1, §2 |
| the tool runner's gate refuses a marked wait with no warrant and accepts one with a warrant bound to its exact names, filter and timeout; on the test bus a restricted stream's event is heard only after the warrant | the operator, 2026-11-11; ADR 0286 |
the manifest validator, as the checker runs it, refuses a manifest that carries waits as an unknown field, and accepts the tool runner's without it |
ADR 0670 §2 |
| the milestone's prepare lists every event with its body's fields, and its code review confirms none carries a secret before the grant is delivered; every later change adding an event or a field is asked the same in its review | ADR 0670 §4 |
nothing takes an operator's answer from a decided event's body; claude_code_answer is its one reader |
ADR 0670 §5 |
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 catalog 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.
Milestones
| Milestone | What it builds | State |
|---|---|---|
| the mesh MCP server | §1 to §5: the module, its loopback endpoint, its discovery (ADR 0152) | delivered 2026-09-30 (above) |
| found by address | §3a: five announced tools and addresses (ADR 0195, ADR 0197) | delivered; as-is 13 |
| waiting on the bus | §3b's mesh_wait on a list of five families in the tool runner's manifest (ADR 0367) |
delivered (issue 745); as-is 13 |
| every-event | §3b as ADR 0670 and ADR 0671 change it: the tool runner hears every event, module streams included, a marked one on the operator's warrant, and the list goes; one delivery group of mesh-sdk, mesh-delivery, mesh-tools and mesh-catalog, with hq | designed; its part is 34-milestones/every-event.md |
What this does not settle
- The mesh's own verbs on the bus. Design 33's third family: these now answer directly, through
the
mesh-controllerseat, under ADR 0154 — settled, not open. As-is 13 records it live. - Narrowing the tool runner's grant per assignment. ADR 0046 makes it a setting; nothing reads one yet. Owned by design 58 milestone 6 (issue 883): an identity asked for each assignment is one of that milestone's parts.
- A person's identity behind the mesh MCP server. The mesh still sees only the tool runner's own account, never the caller's; design 15 raised the question and leaves it open. Owned by the same milestone (design 58 milestone 6, issue 883): its outcome is that every person, agent and module becomes an identity with a role, and a call carries which one made it.
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