Run docs-mcp as a long-lived shared HTTP service #1

Merged
ajaynomics merged 1 commits from feat/docs-mcp-shared-http into main 2026-05-24 15:48:25 +00:00
Owner

Refactors context-docs from per-call stdio spawn to a long-lived Streamable HTTP MCP service so concurrent clients share one indexer / one Chroma writer.

What changes for users

  • Run bin/context-kit start once; context-kit-docs-mcp stays up.
  • HTTP-capable MCP clients (OpenCode type: remote, Claude Code type: http) connect directly to http://127.0.0.1:8776/mcp — no per-call docker spawn.
  • bin/context-kit docs still works for stdio-only clients as a thin mcp-proxy bridge.
  • TTL default drops from 7d to 24h now that re-embedding is cheap (the model stays loaded across calls).
  • New env vars: CONTEXT_KIT_DOCS_PORT, CONTEXT_KIT_DOCS_HTTP_URL, CONTEXT_KIT_DOCS_PREINDEX.

Why

The previous design spawned a fresh docs-mcp container per MCP call, paying full cold-start cost (embedding model load + Chroma open) and racing with concurrent clients for the same Chroma writer. The publish audit surfaced 50+ orphan containers as the visible symptom.

How

  • compose: docs-mcp leaves the mcp profile, gets container_name, restart: unless-stopped, healthcheck, and 127.0.0.1:8776 host port. Runs as host UID/GID so bind mounts stay user-owned.
  • Dockerfile: adds mcp-proxy==0.12.0 and an entrypoint that fronts llms-txt-mcp's stdio as Streamable HTTP. Reads sources from a flat file. Disables eager preindex by default.
  • bin/context-kit: start generates the sources file and waits for /status; docs becomes an mcp-proxy --transport streamablehttp bridge; doctor and status surface the new endpoint.
  • snippets + docs + .env.example updated for the new wiring and defaults.

Verified

  • bash -n, sh -n, compose config -q all clean
  • HTTP /status returns 200
  • stdio bridge returns initialize + tools/list with same 3 tools
  • doctor passes all 10 checks (added HTTP probe)
  • web-search and repomix MCP handshakes still work
  • redaction-check clean
  • install JSON valid for both targets, byte-identical to snippet files
Refactors context-docs from per-call stdio spawn to a long-lived Streamable HTTP MCP service so concurrent clients share one indexer / one Chroma writer. ## What changes for users - Run `bin/context-kit start` once; `context-kit-docs-mcp` stays up. - HTTP-capable MCP clients (OpenCode `type: remote`, Claude Code `type: http`) connect directly to `http://127.0.0.1:8776/mcp` — no per-call docker spawn. - `bin/context-kit docs` still works for stdio-only clients as a thin `mcp-proxy` bridge. - TTL default drops from `7d` to `24h` now that re-embedding is cheap (the model stays loaded across calls). - New env vars: `CONTEXT_KIT_DOCS_PORT`, `CONTEXT_KIT_DOCS_HTTP_URL`, `CONTEXT_KIT_DOCS_PREINDEX`. ## Why The previous design spawned a fresh `docs-mcp` container per MCP call, paying full cold-start cost (embedding model load + Chroma open) and racing with concurrent clients for the same Chroma writer. The publish audit surfaced 50+ orphan containers as the visible symptom. ## How - compose: `docs-mcp` leaves the `mcp` profile, gets `container_name`, `restart: unless-stopped`, healthcheck, and `127.0.0.1:8776` host port. Runs as host UID/GID so bind mounts stay user-owned. - Dockerfile: adds `mcp-proxy==0.12.0` and an entrypoint that fronts `llms-txt-mcp`'s stdio as Streamable HTTP. Reads sources from a flat file. Disables eager preindex by default. - bin/context-kit: `start` generates the sources file and waits for `/status`; `docs` becomes an `mcp-proxy --transport streamablehttp` bridge; `doctor` and `status` surface the new endpoint. - snippets + docs + .env.example updated for the new wiring and defaults. ## Verified - `bash -n`, `sh -n`, `compose config -q` all clean - HTTP `/status` returns 200 - stdio bridge returns initialize + tools/list with same 3 tools - doctor passes all 10 checks (added HTTP probe) - web-search and repomix MCP handshakes still work - redaction-check clean - install JSON valid for both targets, byte-identical to snippet files
ajaynomics added 1 commit 2026-05-24 15:48:03 +00:00
context-docs was previously spawned per call as a fresh stdio container,
which meant every MCP request paid full cold-start cost (embedding model
load + Chroma open) and concurrent clients raced for the same Chroma
writer. The 50+ orphan container build-up I saw during the publish audit
was the visible symptom.

This refactor runs docs-mcp as one long-lived service:

- compose: docs-mcp leaves the 'mcp' profile, gets container_name,
  restart: unless-stopped, healthcheck, and a host port (127.0.0.1:8776
  by default). Runs as the host UID/GID so bind mounts don't end up
  root-owned.
- docker image: adds mcp-proxy (0.12.0) and an entrypoint that fronts
  llms-txt-mcp's stdio as Streamable HTTP. Reads sources from a flat
  file mounted at /etc/context-kit/docs-sources.txt. Disables eager
  preindex by default; callers refresh on demand via the docs_refresh
  tool. Set CONTEXT_KIT_DOCS_PREINDEX=1 to restore eager behavior.
- bin/context-kit: 'start' brings up the docs service alongside SearXNG,
  generates the sources file from CONTEXT_KIT_DOCS_SOURCES, and waits
  for the HTTP endpoint to become ready (up to 180s for first-run model
  download). 'docs' still works for stdio-only clients but is now a
  thin mcp-proxy bridge onto the shared HTTP service. 'doctor' and
  'status' both surface the new endpoint.
- install snippets: context-docs is now 'type: remote'/'type: http'
  pointing at ${CONTEXT_KIT_DOCS_HTTP_URL}. HTTP-capable MCP clients
  bypass the bridge entirely. snippets/*.json and the install command
  output stay byte-identical.
- docs and .env.example updated for new vars (CONTEXT_KIT_DOCS_PORT,
  CONTEXT_KIT_DOCS_HTTP_URL, CONTEXT_KIT_DOCS_PREINDEX) and the new
  24h TTL default (down from 7d; the long-lived service makes shorter
  defaults cheap).

Verified end-to-end:
- compose config -q, bash -n, sh -n all clean
- HTTP /status returns 200
- stdio bridge returns initialize + tools/list with the same 3 tools
  (docs_sources, docs_refresh, docs_query)
- doctor passes all 10 checks including the new HTTP probe
- web-search and repomix MCP handshakes still work
- redaction-check clean
- install JSON valid for both targets + --absolute
ajaynomics merged commit 6629a9b284 into main 2026-05-24 15:48:25 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ajaynomics/context-kit#1