Files
chemenu/instructions/mcp-read-server.md
T
torben 828521861d
CI / verify (push) Successful in 53s
Release / release (push) Successful in 36s
stack: MCP submit-Tool mit Upload Review Gate und Quarantäne-Schreibpfad (schliesst #32)
Files changed:
- .gitignore
- AGENTS.md
- CHANGES.md
- INSTALL-MCP.md
- README.md
- VERSION
- docs/why-gates-are-code.md
- instructions/gates.md
- instructions/ingest-queue.md
- instructions/mcp-read-server.md
- instructions/wiki-ingest/SKILL.md
- raw/CONTRACT.md
- tools/CONTRACT.md
- tools/chemenu/cli.py
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/upload_cmd.py
- tools/chemenu/config.py
- tools/chemenu/mcp/server.py
- tools/chemenu/tests/test_doctor.py
- tools/chemenu/tests/test_mcp_server.py
- tools/chemenu/tests/test_upload.py
- tools/chemenu/tests/test_upload_cmd.py
- tools/chemenu/upload.py
2026-09-11 09:51:37 +02:00

130 lines
6.5 KiB
Markdown

---
type: types/instruction.md
name: mcp-read-server
description: Run and keep current the MCP read server that serves this wiki to a second consumer
---
# Running the MCP read server
Chemenu has a second consumer. `search`, `types`, `describe_type`, `lint` and `status` are
served over MCP to callers that are not this terminal - the CLI and the server are two adapters
over one core (`chemenu.api.Corpus`), not a CLI with a network interface bolted on. A sixth
tool, `submit`, is opt-in: a checkout that creates `.wikitool-upload.json` also
offers a quarantined write path for documents pushed from outside - see
[instructions/ingest-queue.md](ingest-queue.md) for reviewing what lands there.
This document is about *operating* it: how to start it, what has to be true of the checkout it
serves, and how that checkout stays current. What it exposes and why is in
[tools/CONTRACT.md](../tools/CONTRACT.md) and in the module's own docstring
(`tools/chemenu/mcp/server.py`).
**Deployment is deliberately not here.** Which cluster, which ingress host, where the credential
lives - that is private infrastructure and this is a public repository. What is here is
everything an operator needs that is *true of the software* rather than of one installation.
<!-- wikitool:toc -->
## Contents
- [When to run](#when-to-run)
- [Steps](#steps)
- [Decision points](#decision-points)
- [Scope](#scope)
<!-- /wikitool:toc -->
## When to run
- Standing something up for a consumer that is not a terminal on this machine.
- Diagnosing an answer that looks stale, or one that disagrees with the CLI.
- Before pointing a new consumer at an existing server.
## Steps
1. **Install the server's dependency.** It is deliberately not in `requirements.txt`: an
instance that only uses the CLI should not be made to install pydantic, starlette, uvicorn
and cryptography to do it.
```bash
tools/.venv/bin/pip install -r tools/requirements-mcp.txt
```
2. **Decide which checkout it serves.** The root resolves by precedence - an explicit `--root`,
then `$CHEMENU_ROOT`, then the checkout the package lives in. A deployment points at its
corpus with one variable and no code:
```bash
export CHEMENU_ROOT=/srv/chemenu
```
3. **Take tracing out of the served tree.** The server refuses to start otherwise, and the
refusal is the point: telemetry defaults to on and writes under `reports/telemetry/` inside
the repo, which step 5's sync is entitled to wipe. Either is fine:
```bash
export WIKI_TRACE=0 # off
export WIKI_TRACE_DIR=/var/log/chemenu # or elsewhere, outside the corpus
```
4. **Start it on the transport that matches what is in front of it.**
```bash
tools/.venv/bin/python -m chemenu.mcp # stdio
tools/.venv/bin/python -m chemenu.mcp --transport streamable-http # deployed
```
`stdio` is for developing and testing without a network - one process per consumer, started
locally. `streamable-http` is what a deployed instance speaks, and the only one the
authentication middleware can sit in front of, because that middleware is an HTTP reverse
proxy. `sse` is reachable through the SDK and deliberately not offered: it is the superseded
remote transport, and building on it now only moves the migration later.
5. **Keep the checkout current by polling, and keep it clean.**
```bash
git -C "$CHEMENU_ROOT" fetch --quiet origin && \
git -C "$CHEMENU_ROOT" reset --hard --quiet origin/main
```
Every few minutes, from a timer beside the server. Polling rather than a webhook on purpose:
it needs no inbound endpoint and no signature checking, which is a smaller surface than the
thing it would optimize. A webhook is a later optimization, not a starting point.
`reset --hard` is load-bearing, not a convenience. The corpus cache reuses a parse while the
commit is unchanged and **refuses to cache a dirty tree at all**, so a checkout that has
drifted answers correctly but reparses on every request - and every answer it gives is
stamped `"commit": null`, because a dirty tree corresponds to no revision.
**Never add `git clean` to this sync.** `reset --hard` leaves every gitignored path alone by
design, which is exactly what keeps `mcp-upload/` (the `submit` tool's own quarantine) and
`reports/telemetry/` intact across a sync - a `git clean -xd` bolted on "to tidy up" would
delete a submission nobody has reviewed yet, silently, on the next poll.
## Decision points
- **An answer looks stale?** Read `commit` in the response. If it names an old revision, the
sync is not running. If it is `null`, the served tree has uncommitted changes - something is
writing into the corpus that should not be.
- **The server disagrees with `wikitool` on the same query?** That is a defect, not a
configuration difference: the two go through the same functions and a golden test holds their
output together (`tools/chemenu/tests/test_mcp_server.py`). Check first that both are pointed
at the same root - `CHEMENU_ROOT` is easy to set for one and not the other.
- **Asked to expose a write tool?** Five of the six tools have none, structurally: the server
imports nothing under `chemenu.commands`, so `new`, `touch`, `xref`, `cite`, `publish` and
`migrate` are unreachable from it rather than filtered out of a list. The one exception is
`submit` (opt-in via `.wikitool-upload.json`): it may write, but only into
`mcp-upload/`, a quarantine no other command reads - a **positive list** enforced in code
(`chemenu.upload._write_atomic_within`), not an absence. The commands that move a submission
*out* of that quarantine (`upload accept`/`upload reject`) still have the absence property:
they live under `chemenu.commands` and stay unreachable from the server. Reviewing what
`submit` receives is [instructions/ingest-queue.md](ingest-queue.md), not this file.
- **Asked to rate-limit inside the server?** Rate limiting belongs in the middleware in front of
the process, next to authentication. Not the Iteration Budget Gate: that exists to stop an
agent *session* from iterating unnoticed over the wiki's state, which is why retrieval is
exempt from it, and using it as a rate limiter would dilute it into one.
## Scope
Not for setting up an instance ([setup-instance.md](setup-instance.md)) or a fresh clone
([bootstrap.md](bootstrap.md)). Not for the authentication or rate-limiting middleware, which is
infrastructure configuration rather than part of this repository. Not a write path: see the
decision point above.