dist export leaves out tools/chemenu/tests/, tools/pytest.ini and tools/.coveragerc by exact path - the suite tests the origin repository, and 257 of its tests failed in a fresh export. dist upgrade now deletes a no-longer-shipped file that is unchanged since install, with any directory that leaves empty, and blocks one changed since install like any local change (--take-release deletes it, --keep-local keeps it). --prune is accepted and ignored. Files changed: - CHANGES.md - EVALS.md - VERSION - instructions/mcp-read-server.md - instructions/upgrade-instance.md - tools/CONTRACT.md - tools/README.md - tools/chemenu/commands/dist_cmd.py - tools/chemenu/tests/test_dist_cmd.py - tools/chemenu/tests/test_dist_upgrade.py - tools/requirements-mcp.txt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
132 lines
6.7 KiB
Markdown
132 lines
6.7 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/python -m 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, set in the environment the server process starts in
|
|
(its service unit or container spec):
|
|
|
|
| Variable | Value |
|
|
|---|---|
|
|
| `CHEMENU_ROOT` | The checkout it serves, for instance `/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. Set one of the two in the same
|
|
environment:
|
|
|
|
| Variable | Value |
|
|
|---|---|
|
|
| `WIKI_TRACE` | `0` - tracing off |
|
|
| `WIKI_TRACE_DIR` | A directory outside the corpus, for instance `/var/log/chemenu` |
|
|
|
|
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 <checkout> fetch --quiet origin
|
|
git -C <checkout> reset --hard --quiet origin/main
|
|
```
|
|
|
|
The second only after the first succeeded, 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 in the origin
|
|
repository holds their output together. 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.
|