Compare commits
3 Commits
8da552bea1
...
8de9658b8c
| Author | SHA1 | Date | |
|---|---|---|---|
| 8de9658b8c | |||
| 2ad80684ad | |||
| c11ecfdd51 |
@@ -41,10 +41,12 @@ CONTEXT_KIT_DOCS_EMBED_MODEL=BAAI/bge-small-en-v1.5
|
||||
# fast; call the docs_refresh MCP tool when you want to populate the index.
|
||||
# CONTEXT_KIT_DOCS_PREINDEX=1
|
||||
|
||||
# One or more source files, separated by spaces.
|
||||
# One or more source files, separated by spaces. Keep committed profiles generic.
|
||||
# Add private absolute-path profiles only in your ignored local .env.
|
||||
CONTEXT_KIT_DOCS_SOURCES=config/sources.default.txt
|
||||
|
||||
# Optional machine-local llms.txt tree. Files are served only inside docs-mcp at
|
||||
# http://127.0.0.1:8769/ so absolute local paths do not leak into source files.
|
||||
# Keep private menus here or in another local path, not under config/.
|
||||
# CONTEXT_KIT_DOCS_LOCAL_SOURCES_DIR=/path/to/context-kit-local-sources
|
||||
# CONTEXT_KIT_DOCS_LOCAL_SOURCES_PORT=8769
|
||||
|
||||
22
README.md
22
README.md
@@ -66,6 +66,28 @@ config that will not be committed.
|
||||
- Repomix mounts only the current project read-only.
|
||||
- No code-editing MCP server is enabled by default.
|
||||
|
||||
## Public Repo vs Local Runtime State
|
||||
|
||||
This repository is the public, portable Context Kit distribution. It should only
|
||||
contain generic defaults, Docker/service code, install snippets, and optional
|
||||
public docs source profiles.
|
||||
|
||||
Machine-specific configuration stays out of git:
|
||||
|
||||
- `.env` is ignored. Use it for local ports, data paths, and private source
|
||||
profile paths.
|
||||
- `CONTEXT_KIT_DATA_DIR` stores runtime state: docs indexes, model caches, the
|
||||
generated `docs-sources.txt`, and any local source tree you choose to keep
|
||||
there.
|
||||
- Private docs source profiles can live anywhere outside the repo and can be
|
||||
referenced from `.env` with an absolute path.
|
||||
- Private `llms.txt` menus belong under `CONTEXT_KIT_DOCS_LOCAL_SOURCES_DIR`, not
|
||||
under `config/`.
|
||||
|
||||
The public default is intentionally small: `config/sources.default.txt`. If a
|
||||
machine adds extra local menus, they affect only that machine's running
|
||||
`context-docs` service.
|
||||
|
||||
## Docs Sources
|
||||
|
||||
The default docs index is intentionally small:
|
||||
|
||||
@@ -46,10 +46,28 @@ for source_url in $sources; do
|
||||
done
|
||||
|
||||
if [ -d "$local_sources_dir" ]; then
|
||||
python -m http.server "$local_sources_port" \
|
||||
--bind 127.0.0.1 \
|
||||
--directory "$local_sources_dir" \
|
||||
>/tmp/context-kit-local-sources.log 2>&1 &
|
||||
python - "$local_sources_port" "$local_sources_dir" >/tmp/context-kit-local-sources.log 2>&1 <<'PY' &
|
||||
import functools
|
||||
import http.server
|
||||
import sys
|
||||
|
||||
|
||||
class LocalSourceHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def send_head(self):
|
||||
# llms-txt-mcp 0.2.0 treats 304 responses from local sources as fetch
|
||||
# failures, so serve machine-local docs as plain 200 responses.
|
||||
for header in ("If-Modified-Since", "If-None-Match"):
|
||||
if header in self.headers:
|
||||
del self.headers[header]
|
||||
return super().send_head()
|
||||
|
||||
|
||||
port = int(sys.argv[1])
|
||||
directory = sys.argv[2]
|
||||
handler = functools.partial(LocalSourceHandler, directory=directory)
|
||||
with http.server.ThreadingHTTPServer(("127.0.0.1", port), handler) as server:
|
||||
server.serve_forever()
|
||||
PY
|
||||
local_sources_pid="$!"
|
||||
if ! python - "$local_sources_port" <<'PY'
|
||||
import sys
|
||||
|
||||
@@ -7,6 +7,47 @@ Explicit environment variables win over `.env` values. The `.env` parser accepts
|
||||
simple `KEY=VALUE` lines for `CONTEXT_KIT_*` variables only; it does not execute
|
||||
shell code.
|
||||
|
||||
## Public Files vs Local State
|
||||
|
||||
Context Kit is meant to be a public repo plus private local runtime state.
|
||||
|
||||
Tracked public files:
|
||||
|
||||
- `config/sources.default.txt`: small default docs index.
|
||||
- `config/sources.*.txt`: optional public source profiles.
|
||||
- `snippets/`: portable assistant config snippets that use `context-kit` on
|
||||
`PATH`.
|
||||
- `compose.yml`, `docker/`, `bin/`, and `scripts/`: generic runtime and release
|
||||
logic.
|
||||
|
||||
Ignored or external local files:
|
||||
|
||||
- `.env`: local overrides; never commit it.
|
||||
- `CONTEXT_KIT_DATA_DIR`: docs indexes, model caches, generated
|
||||
`docs-sources.txt`, and local source trees.
|
||||
- Private source profile files referenced by absolute path from `.env`.
|
||||
- Private `llms.txt` menus under `CONTEXT_KIT_DOCS_LOCAL_SOURCES_DIR`.
|
||||
|
||||
Do not put personal project menus, private repo names, or local filesystem paths
|
||||
in `config/`. Put them in a private source profile outside the repo, then add
|
||||
that profile to `.env`:
|
||||
|
||||
```sh
|
||||
CONTEXT_KIT_DOCS_SOURCES="config/sources.default.txt /path/to/private-sources.txt"
|
||||
CONTEXT_KIT_DOCS_LOCAL_SOURCES_DIR=/path/to/local-sources
|
||||
```
|
||||
|
||||
Entries in the private profile should still be URLs, not filesystem paths. For a
|
||||
local menu stored at `/path/to/local-sources/my-project/llms.txt`, reference it
|
||||
as:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8769/my-project/llms.txt
|
||||
```
|
||||
|
||||
That loopback URL is inside the `docs-mcp` container. It is not exposed on the
|
||||
host.
|
||||
|
||||
## User-Facing Variables
|
||||
|
||||
Only the variables below are part of the public configuration surface. Other
|
||||
|
||||
@@ -18,13 +18,29 @@ PY
|
||||
release_id="release-$$"
|
||||
export CONTEXT_KIT_COMPOSE_PROJECT="context-kit-${release_id}"
|
||||
export CONTEXT_KIT_DATA_DIR="${tmp_dir}/data"
|
||||
export CONTEXT_KIT_PROJECT_DIR="${ROOT}"
|
||||
export CONTEXT_KIT_SEARXNG_PORT="$(pick_port)"
|
||||
export CONTEXT_KIT_DOCS_PORT="$(pick_port)"
|
||||
export CONTEXT_KIT_DOCS_SOURCES="config/sources.default.txt"
|
||||
export CONTEXT_KIT_DOCS_LOCAL_SOURCES_DIR="${tmp_dir}/local-sources"
|
||||
export CONTEXT_KIT_DOCS_LOCAL_SOURCES_PORT="8769"
|
||||
local_sources_profile="${tmp_dir}/sources.local.txt"
|
||||
export CONTEXT_KIT_DOCS_SOURCES="config/sources.default.txt ${local_sources_profile}"
|
||||
export CONTEXT_KIT_LOCAL_SOURCE_SMOKE_URL="http://127.0.0.1:${CONTEXT_KIT_DOCS_LOCAL_SOURCES_PORT}/release-fixture/llms.txt"
|
||||
export CONTEXT_KIT_WEB_SEARCH_IMAGE="context-kit/web-search-mcp:${release_id}"
|
||||
export CONTEXT_KIT_DOCS_IMAGE="context-kit/docs-mcp:${release_id}"
|
||||
|
||||
mkdir -p "${CONTEXT_KIT_DOCS_LOCAL_SOURCES_DIR}/release-fixture"
|
||||
printf '%s\n' \
|
||||
'# Release Check Local Source' \
|
||||
'' \
|
||||
'> Exercises machine-local llms.txt serving and refresh behavior.' \
|
||||
'' \
|
||||
'## MCP' \
|
||||
'' \
|
||||
'- [Model Context Protocol](https://modelcontextprotocol.io/)' \
|
||||
> "${CONTEXT_KIT_DOCS_LOCAL_SOURCES_DIR}/release-fixture/llms.txt"
|
||||
printf '%s\n' "${CONTEXT_KIT_LOCAL_SOURCE_SMOKE_URL}" > "${local_sources_profile}"
|
||||
|
||||
cleanup() {
|
||||
docker compose -p "${CONTEXT_KIT_COMPOSE_PROJECT}" -f compose.yml down -v --remove-orphans >/dev/null 2>&1 || true
|
||||
docker image rm "${CONTEXT_KIT_WEB_SEARCH_IMAGE}" "${CONTEXT_KIT_DOCS_IMAGE}" >/dev/null 2>&1 || true
|
||||
@@ -90,13 +106,26 @@ git ls-files --cached --error-unmatch \
|
||||
scripts/mcp-smoke-client.mjs \
|
||||
scripts/smoke-web-search.mjs \
|
||||
scripts/smoke-docs.mjs \
|
||||
scripts/smoke-repomix.mjs \
|
||||
scripts/release-check >/dev/null
|
||||
bash -n bin/context-kit
|
||||
bash -n scripts/release-check
|
||||
sh -n docker/docs/entrypoint.sh
|
||||
check_node docker/web-search/patch-mcp-web-search.mjs docker/web-search/overrides/bing.js scripts/mcp-smoke-client.mjs scripts/smoke-web-search.mjs scripts/smoke-docs.mjs
|
||||
check_node docker/web-search/patch-mcp-web-search.mjs docker/web-search/overrides/bing.js scripts/mcp-smoke-client.mjs scripts/smoke-web-search.mjs scripts/smoke-docs.mjs scripts/smoke-repomix.mjs
|
||||
|
||||
node -e 'const fs=require("node:fs"); JSON.parse(fs.readFileSync("snippets/opencode.json", "utf8")); JSON.parse(fs.readFileSync("snippets/claude.mcp.json", "utf8"));'
|
||||
CONTEXT_KIT_DOCS_HTTP_URL="http://127.0.0.1:8776/mcp" bin/context-kit install opencode > "${tmp_dir}/opencode-default.json"
|
||||
cmp -s snippets/opencode.json "${tmp_dir}/opencode-default.json" || {
|
||||
printf 'snippets/opencode.json differs from bin/context-kit install opencode output\n' >&2
|
||||
diff -u snippets/opencode.json "${tmp_dir}/opencode-default.json" >&2 || true
|
||||
exit 1
|
||||
}
|
||||
CONTEXT_KIT_DOCS_HTTP_URL="http://127.0.0.1:8776/mcp" bin/context-kit install claude > "${tmp_dir}/claude-default.json"
|
||||
cmp -s snippets/claude.mcp.json "${tmp_dir}/claude-default.json" || {
|
||||
printf 'snippets/claude.mcp.json differs from bin/context-kit install claude output\n' >&2
|
||||
diff -u snippets/claude.mcp.json "${tmp_dir}/claude-default.json" >&2 || true
|
||||
exit 1
|
||||
}
|
||||
bin/context-kit install opencode > "${tmp_dir}/opencode.json"
|
||||
bin/context-kit install opencode --absolute > "${tmp_dir}/opencode-absolute.json"
|
||||
bin/context-kit install claude > "${tmp_dir}/claude.json"
|
||||
@@ -122,5 +151,6 @@ bin/context-kit restart
|
||||
bin/context-kit doctor
|
||||
node scripts/smoke-web-search.mjs bin/context-kit web-search
|
||||
node scripts/smoke-docs.mjs bin/context-kit docs
|
||||
node scripts/smoke-repomix.mjs bin/context-kit repomix
|
||||
|
||||
printf 'pass release-check\n'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { requireToolSuccess, runSmoke } from "./mcp-smoke-client.mjs";
|
||||
|
||||
const live = process.env.CONTEXT_KIT_LIVE_CHECKS === "1";
|
||||
const localSourceSmokeUrl = process.env.CONTEXT_KIT_LOCAL_SOURCE_SMOKE_URL;
|
||||
|
||||
runSmoke({
|
||||
usage: "usage: node scripts/smoke-docs.mjs <command> [args...]",
|
||||
@@ -21,6 +22,16 @@ runSmoke({
|
||||
docs_sources: "pass"
|
||||
};
|
||||
|
||||
if (localSourceSmokeUrl) {
|
||||
requireToolSuccess("docs_refresh/local_source_first", await client.callTool("docs_refresh", {
|
||||
source: localSourceSmokeUrl
|
||||
}));
|
||||
requireToolSuccess("docs_refresh/local_source_second", await client.callTool("docs_refresh", {
|
||||
source: localSourceSmokeUrl
|
||||
}));
|
||||
result.local_source_refresh = "pass";
|
||||
}
|
||||
|
||||
if (live) {
|
||||
const query = requireToolSuccess("docs_query", await client.callTool("docs_query", {
|
||||
query: "Model Context Protocol documentation",
|
||||
|
||||
30
scripts/smoke-repomix.mjs
Normal file
30
scripts/smoke-repomix.mjs
Normal file
@@ -0,0 +1,30 @@
|
||||
import { requireToolSuccess, runSmoke, textFrom } from "./mcp-smoke-client.mjs";
|
||||
|
||||
runSmoke({
|
||||
usage: "usage: node scripts/smoke-repomix.mjs <command> [args...]",
|
||||
tmpPrefix: "context-kit-repomix-smoke-",
|
||||
timeoutMs: 120000,
|
||||
clientInfo: { name: "context-kit-repomix-smoke", version: "0.0.0" },
|
||||
scenario: async client => {
|
||||
const toolNames = await client.requireTools(["pack_codebase"]);
|
||||
|
||||
const pack = requireToolSuccess("pack_codebase", await client.callTool("pack_codebase", {
|
||||
directory: process.cwd(),
|
||||
compress: true,
|
||||
includePatterns: "README.md,snippets/opencode.json",
|
||||
ignorePatterns: "",
|
||||
topFilesLength: 2,
|
||||
style: "xml"
|
||||
}));
|
||||
|
||||
const packText = textFrom(pack) || JSON.stringify(pack);
|
||||
if (!packText.includes("Successfully packed codebase") && !packText.includes("outputId")) {
|
||||
throw new Error(`pack_codebase returned unexpected payload: ${packText.slice(0, 500)}`);
|
||||
}
|
||||
|
||||
return {
|
||||
tools: Array.from(toolNames).sort(),
|
||||
pack_codebase: "pass"
|
||||
};
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user