Harden docs MCP local exposure defaults

This commit is contained in:
2026-06-08 15:52:02 -07:00
parent 6a4d8673d1
commit 8fcd94d2c5
7 changed files with 43 additions and 6 deletions

View File

@@ -18,6 +18,9 @@ CONTEXT_KIT_SEARXNG_SECRET=change-me-local-only
CONTEXT_KIT_DOCS_PORT=8776 CONTEXT_KIT_DOCS_PORT=8776
# Override only if you proxy the service behind another hostname or path. # Override only if you proxy the service behind another hostname or path.
# CONTEXT_KIT_DOCS_HTTP_URL=http://127.0.0.1:8776/mcp # CONTEXT_KIT_DOCS_HTTP_URL=http://127.0.0.1:8776/mcp
# Browser CORS is disabled by default. If a browser-based local client needs
# access, set one or more exact origins separated by spaces. Avoid `*`.
# CONTEXT_KIT_DOCS_ALLOW_ORIGIN=http://127.0.0.1:3000
# Docs indexing defaults. # Docs indexing defaults.
CONTEXT_KIT_DOCS_TTL=24h CONTEXT_KIT_DOCS_TTL=24h

View File

@@ -60,6 +60,8 @@ config that will not be committed.
HTTP MCP) so every client shares one indexer and one Chroma writer. The HTTP MCP) so every client shares one indexer and one Chroma writer. The
`bin/context-kit docs` stdio command is kept as a compatibility shim for `bin/context-kit docs` stdio command is kept as a compatibility shim for
clients that cannot speak HTTP MCP. clients that cannot speak HTTP MCP.
- `context-docs` browser CORS is disabled by default; set exact local origins
only when a browser-based client needs direct access.
- Docs and model caches live in `$HOME/.local/share/context-kit`. - Docs and model caches live in `$HOME/.local/share/context-kit`.
- Docs refresh TTL defaults to `24h`. - Docs refresh TTL defaults to `24h`.
- MCP containers are labeled `dev.context-kit=true` for safe inspection and cleanup. - MCP containers are labeled `dev.context-kit=true` for safe inspection and cleanup.

View File

@@ -343,21 +343,24 @@ cmd_docs() {
# Prefer the `type: remote` MCP config pointing at ${DOCS_HTTP_URL}. # Prefer the `type: remote` MCP config pointing at ${DOCS_HTTP_URL}.
# This stdio entrypoint is kept for clients that cannot speak HTTP MCP: # This stdio entrypoint is kept for clients that cannot speak HTTP MCP:
# it spawns a thin mcp-proxy bridge per call but all calls multiplex onto # it spawns a thin mcp-proxy bridge per call but all calls multiplex onto
# the single long-lived docs-mcp container (no Chroma write contention). # the single long-lived docs-mcp container over the Context Kit Docker
# network (no Chroma write contention, no host networking).
require_docker require_docker
require_network
require_image "${DOCS_IMAGE}" "context-kit build" require_image "${DOCS_IMAGE}" "context-kit build"
if ! docker ps --filter "name=^${DOCS_CONTAINER_NAME}$" --filter "status=running" --format '{{.Names}}' | grep -qx "${DOCS_CONTAINER_NAME}"; then if ! docker ps --filter "name=^${DOCS_CONTAINER_NAME}$" --filter "status=running" --format '{{.Names}}' | grep -qx "${DOCS_CONTAINER_NAME}"; then
fail "long-lived docs-mcp not running; start it with: context-kit start" fail "long-lived docs-mcp not running; start it with: context-kit start"
fi fi
local bridge_url="http://${DOCS_CONTAINER_NAME}:8000/mcp"
exec docker run --rm -i \ exec docker run --rm -i \
--label dev.context-kit=true \ --label dev.context-kit=true \
--network host \ --network "${NETWORK}" \
--entrypoint mcp-proxy \ --entrypoint mcp-proxy \
"${DOCS_IMAGE}" \ "${DOCS_IMAGE}" \
--transport streamablehttp \ --transport streamablehttp \
"${DOCS_HTTP_URL}" "${bridge_url}"
} }
cmd_repomix() { cmd_repomix() {

View File

@@ -52,6 +52,7 @@ services:
DOCS_MCP_TTL: "${CONTEXT_KIT_DOCS_TTL:-24h}" DOCS_MCP_TTL: "${CONTEXT_KIT_DOCS_TTL:-24h}"
DOCS_MCP_MAX_GET_BYTES: "${CONTEXT_KIT_DOCS_MAX_GET_BYTES:-75000}" DOCS_MCP_MAX_GET_BYTES: "${CONTEXT_KIT_DOCS_MAX_GET_BYTES:-75000}"
DOCS_MCP_EMBED_MODEL: "${CONTEXT_KIT_DOCS_EMBED_MODEL:-BAAI/bge-small-en-v1.5}" DOCS_MCP_EMBED_MODEL: "${CONTEXT_KIT_DOCS_EMBED_MODEL:-BAAI/bge-small-en-v1.5}"
DOCS_MCP_ALLOW_ORIGIN: "${CONTEXT_KIT_DOCS_ALLOW_ORIGIN:-}"
# Preindex on startup is off by default; use the docs_refresh tool to # Preindex on startup is off by default; use the docs_refresh tool to
# refresh on demand. Set CONTEXT_KIT_DOCS_PREINDEX=1 to restore eager. # refresh on demand. Set CONTEXT_KIT_DOCS_PREINDEX=1 to restore eager.
DOCS_MCP_PREINDEX: "${CONTEXT_KIT_DOCS_PREINDEX:-0}" DOCS_MCP_PREINDEX: "${CONTEXT_KIT_DOCS_PREINDEX:-0}"

View File

@@ -37,12 +37,17 @@ if [ "${DOCS_MCP_PREINDEX:-0}" = "1" ]; then
preindex_flag="" preindex_flag=""
fi fi
# shellcheck disable=SC2086 # intentional word-splitting on $sources / $preindex_flag allow_origin_args=""
if [ -n "${DOCS_MCP_ALLOW_ORIGIN:-}" ]; then
allow_origin_args="--allow-origin ${DOCS_MCP_ALLOW_ORIGIN}"
fi
# shellcheck disable=SC2086 # intentional word-splitting on $sources / $preindex_flag / $allow_origin_args
exec mcp-proxy \ exec mcp-proxy \
--host "${DOCS_MCP_HTTP_HOST:-0.0.0.0}" \ --host "${DOCS_MCP_HTTP_HOST:-0.0.0.0}" \
--port "${DOCS_MCP_HTTP_PORT:-8000}" \ --port "${DOCS_MCP_HTTP_PORT:-8000}" \
--pass-environment \ --pass-environment \
--allow-origin "${DOCS_MCP_ALLOW_ORIGIN:-*}" \ $allow_origin_args \
-- \ -- \
llms-txt-mcp \ llms-txt-mcp \
--store-path /data \ --store-path /data \

View File

@@ -15,7 +15,8 @@ shell code.
| `CONTEXT_KIT_COMPOSE_PROJECT` | `context-kit` | Docker Compose project and network prefix | | `CONTEXT_KIT_COMPOSE_PROJECT` | `context-kit` | Docker Compose project and network prefix |
| `CONTEXT_KIT_SEARXNG_PORT` | `8099` | Localhost SearXNG port | | `CONTEXT_KIT_SEARXNG_PORT` | `8099` | Localhost SearXNG port |
| `CONTEXT_KIT_DOCS_PORT` | `8776` | Localhost port for the long-lived docs-mcp HTTP service | | `CONTEXT_KIT_DOCS_PORT` | `8776` | Localhost port for the long-lived docs-mcp HTTP service |
| `CONTEXT_KIT_DOCS_HTTP_URL` | `http://127.0.0.1:${CONTEXT_KIT_DOCS_PORT}/mcp` | URL emitted into install snippets and used by the stdio bridge | | `CONTEXT_KIT_DOCS_HTTP_URL` | `http://127.0.0.1:${CONTEXT_KIT_DOCS_PORT}/mcp` | URL emitted into HTTP MCP install snippets |
| `CONTEXT_KIT_DOCS_ALLOW_ORIGIN` | unset | Optional exact browser CORS origin(s) for docs-mcp, separated by spaces |
| `CONTEXT_KIT_DOCS_TTL` | `24h` | Docs re-fetch cadence | | `CONTEXT_KIT_DOCS_TTL` | `24h` | Docs re-fetch cadence |
| `CONTEXT_KIT_DOCS_SOURCES` | `config/sources.default.txt` | Space-separated source profile files | | `CONTEXT_KIT_DOCS_SOURCES` | `config/sources.default.txt` | Space-separated source profile files |
| `CONTEXT_KIT_DOCS_MAX_GET_BYTES` | `75000` | Max bytes returned by docs retrieval | | `CONTEXT_KIT_DOCS_MAX_GET_BYTES` | `75000` | Max bytes returned by docs retrieval |
@@ -43,6 +44,19 @@ The docs-mcp container reads `CONTEXT_KIT_DOCS_TTL` at startup, so changes
require `bin/context-kit restart`. When freshness matters for one task, prefer require `bin/context-kit restart`. When freshness matters for one task, prefer
calling the `docs_refresh` MCP tool instead of lowering the global TTL. calling the `docs_refresh` MCP tool instead of lowering the global TTL.
## Browser CORS
`context-docs` disables browser CORS by default. CLI assistants and server-side
HTTP clients do not need CORS. If a browser-based local client must call the MCP
endpoint directly, allow only the exact local origin(s) it uses:
```sh
CONTEXT_KIT_DOCS_ALLOW_ORIGIN="http://127.0.0.1:3000 http://localhost:3000" \
bin/context-kit restart
```
Avoid `*`; the docs MCP is a local unauthenticated endpoint.
## Source Profiles ## Source Profiles
The docs MCP accepts one or more source files: The docs MCP accepts one or more source files:

View File

@@ -33,3 +33,12 @@ their mount paths and permissions separately.
Do not expose SearXNG or MCP servers to the public internet without a separate Do not expose SearXNG or MCP servers to the public internet without a separate
review. The default setup is for localhost development. review. The default setup is for localhost development.
The containers may bind to `0.0.0.0` internally, but the Compose file publishes
SearXNG and docs-mcp only on `127.0.0.1`. If you run the images outside the
provided Compose file, review port publishing, SearXNG's limiter/secret, and MCP
authentication separately.
Browser CORS for `context-docs` is disabled by default. Only set
`CONTEXT_KIT_DOCS_ALLOW_ORIGIN` for exact local origins that need direct browser
access; avoid wildcard origins for unauthenticated local MCP endpoints.