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.
178 lines
12 KiB
Markdown
178 lines
12 KiB
Markdown
---
|
|
layer: to-be
|
|
status: designed
|
|
code: [mesh-catalog, mesh-tools, mesh-controller]
|
|
updated: 2026-10-03
|
|
decisions:
|
|
- 02-DECISIONS/0197-every-tool-announces-itself-on-the-bus-in-the-nats-services-protocol.md
|
|
- 02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md
|
|
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
|
- 02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md
|
|
- 02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md
|
|
- 02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md
|
|
- 02-DECISIONS/0095-the-control-plane-is-the-way-to-ask-a-module.md
|
|
- 02-DECISIONS/0035-one-implementation-several-surfaces.md
|
|
- 02-DECISIONS/0034-the-local-account-owns-the-mesh.md
|
|
---
|
|
|
|
# 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](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md)).
|
|
|
|
> **Amended 2026-10-02 by [ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md).** 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](37-the-operators-machine.md) §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](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md)). The first
|
|
consumer is the operator's agent, [36 — The operator's agent on a machine](36-the-operators-agent-on-a-machine.md) §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;
|
|
- `listens` on a port `from: machine` — the filter opens nothing for it, because loopback is not outside;
|
|
- no `emits`, no `consumes`, no `tools` — 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](../../02-DECISIONS/0034-the-local-account-owns-the-mesh.md),
|
|
[ADR 0144](../../02-DECISIONS/0144-anything-on-a-machine-may-call-anything-on-it.md)). 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](33-the-tools-the-mesh-answers.md) §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](../../02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md)):
|
|
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 <machine>* to every answer. A seat's verb takes none; the seat's scope
|
|
decides. *Later the same day ([ADR 0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)):* 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](../../02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md);
|
|
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](../../02-DECISIONS/0197-every-tool-announces-itself-on-the-bus-in-the-nats-services-protocol.md)):
|
|
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](25-the-bus-on-nats.md) §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`](../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](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md) — the decision
|
|
- [33 — The tools the mesh answers](33-the-tools-the-mesh-answers.md) — what the mesh MCP server lists
|
|
- [25 — The bus on NATS](25-the-bus-on-nats.md) §7 — the person's client this makes a module of
|
|
- [issue 147](../../04-ISSUES/147-the-operators-tools-still-dial-the-bus-that-was-removed/00-report.md) — the symptom
|