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
# 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

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
`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.

View File

@@ -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() {

View File

@@ -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}"

View File

@@ -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 \

View File

@@ -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:

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
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.