576df2cddd
Files changed: - .gitea/workflows/ci.yml - CHANGES.md - README.md - VERSION - instructions/mcp-read-server.md - tools/CONTRACT.md - tools/README.md - tools/chemenu/api.py - tools/chemenu/commands/doctor.py - tools/chemenu/commands/lint.py - tools/chemenu/commands/search.py - tools/chemenu/commands/types_cmd.py - tools/chemenu/config.py - tools/chemenu/corpus_cache.py - tools/chemenu/errors.py - tools/chemenu/frontmatter_io.py - tools/chemenu/lint_core.py - tools/chemenu/mcp/__init__.py - tools/chemenu/mcp/__main__.py - tools/chemenu/mcp/server.py - tools/chemenu/page.py - tools/chemenu/search/filters.py - tools/chemenu/search/registry.py - tools/chemenu/search/ripgrep.py - tools/chemenu/search/service.py - tools/chemenu/tests/conftest.py - tools/chemenu/tests/test_api.py - tools/chemenu/tests/test_corpus_cache.py - tools/chemenu/tests/test_doctor.py - tools/chemenu/tests/test_frontmatter_io.py - tools/chemenu/tests/test_instructions_cmd.py - tools/chemenu/tests/test_mcp_server.py - tools/chemenu/tests/test_new_page.py - tools/chemenu/tests/test_search.py - tools/chemenu/type_resolver.py - tools/chemenu/types_core.py - tools/requirements-mcp.txt
109 lines
5.3 KiB
Markdown
109 lines
5.3 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.
|
|
|
|
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.
|