--- 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. 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. ## 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. ## 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?** There is none, and the way to add one is not a flag. 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. Submitting documents from outside is a different design with a quarantine in it - Gitea #32 - not a tool added here. - **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.