From 8fcd94d2c5656cd4607634e07d2721969e54fca5 Mon Sep 17 00:00:00 2001 From: Ajay Krishnan Date: Mon, 8 Jun 2026 15:52:02 -0700 Subject: [PATCH] Harden docs MCP local exposure defaults --- .env.example | 3 +++ README.md | 2 ++ bin/context-kit | 9 ++++++--- compose.yml | 1 + docker/docs/entrypoint.sh | 9 +++++++-- docs/configuration.md | 16 +++++++++++++++- docs/security.md | 9 +++++++++ 7 files changed, 43 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 65e9170..4a88880 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,9 @@ CONTEXT_KIT_SEARXNG_SECRET=change-me-local-only CONTEXT_KIT_DOCS_PORT=8776 # Override only if you proxy the service behind another hostname or path. # 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. CONTEXT_KIT_DOCS_TTL=24h diff --git a/README.md b/README.md index 9bc58cf..865d2a0 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ config that will not be committed. 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 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 refresh TTL defaults to `24h`. - MCP containers are labeled `dev.context-kit=true` for safe inspection and cleanup. diff --git a/bin/context-kit b/bin/context-kit index 02edb0c..d85f507 100755 --- a/bin/context-kit +++ b/bin/context-kit @@ -343,21 +343,24 @@ cmd_docs() { # Prefer the `type: remote` MCP config pointing at ${DOCS_HTTP_URL}. # 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 - # 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_network 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 fail "long-lived docs-mcp not running; start it with: context-kit start" fi + local bridge_url="http://${DOCS_CONTAINER_NAME}:8000/mcp" exec docker run --rm -i \ --label dev.context-kit=true \ - --network host \ + --network "${NETWORK}" \ --entrypoint mcp-proxy \ "${DOCS_IMAGE}" \ --transport streamablehttp \ - "${DOCS_HTTP_URL}" + "${bridge_url}" } cmd_repomix() { diff --git a/compose.yml b/compose.yml index 5955354..c14c644 100644 --- a/compose.yml +++ b/compose.yml @@ -52,6 +52,7 @@ services: DOCS_MCP_TTL: "${CONTEXT_KIT_DOCS_TTL:-24h}" 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_ALLOW_ORIGIN: "${CONTEXT_KIT_DOCS_ALLOW_ORIGIN:-}" # 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. DOCS_MCP_PREINDEX: "${CONTEXT_KIT_DOCS_PREINDEX:-0}" diff --git a/docker/docs/entrypoint.sh b/docker/docs/entrypoint.sh index 46e868b..c2cfbc7 100644 --- a/docker/docs/entrypoint.sh +++ b/docker/docs/entrypoint.sh @@ -37,12 +37,17 @@ if [ "${DOCS_MCP_PREINDEX:-0}" = "1" ]; then preindex_flag="" 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 \ --host "${DOCS_MCP_HTTP_HOST:-0.0.0.0}" \ --port "${DOCS_MCP_HTTP_PORT:-8000}" \ --pass-environment \ - --allow-origin "${DOCS_MCP_ALLOW_ORIGIN:-*}" \ + $allow_origin_args \ -- \ llms-txt-mcp \ --store-path /data \ diff --git a/docs/configuration.md b/docs/configuration.md index 286fb56..fd6aaf7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -15,7 +15,8 @@ shell code. | `CONTEXT_KIT_COMPOSE_PROJECT` | `context-kit` | Docker Compose project and network prefix | | `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_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_SOURCES` | `config/sources.default.txt` | Space-separated source profile files | | `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 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 The docs MCP accepts one or more source files: diff --git a/docs/security.md b/docs/security.md index 8debbf0..d81f040 100644 --- a/docs/security.md +++ b/docs/security.md @@ -33,3 +33,12 @@ their mount paths and permissions separately. Do not expose SearXNG or MCP servers to the public internet without a separate 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.