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.
|
# fast; call the docs_refresh MCP tool when you want to populate the index.
|
||||||
# CONTEXT_KIT_DOCS_PREINDEX=1
|
# 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
|
CONTEXT_KIT_DOCS_SOURCES=config/sources.default.txt
|
||||||
|
|
||||||
# Optional machine-local llms.txt tree. Files are served only inside docs-mcp at
|
# 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.
|
# 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_DIR=/path/to/context-kit-local-sources
|
||||||
# CONTEXT_KIT_DOCS_LOCAL_SOURCES_PORT=8769
|
# 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.
|
- Repomix mounts only the current project read-only.
|
||||||
- No code-editing MCP server is enabled by default.
|
- 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
|
## Docs Sources
|
||||||
|
|
||||||
The default docs index is intentionally small:
|
The default docs index is intentionally small:
|
||||||
|
|||||||
@@ -46,10 +46,28 @@ for source_url in $sources; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [ -d "$local_sources_dir" ]; then
|
if [ -d "$local_sources_dir" ]; then
|
||||||
python -m http.server "$local_sources_port" \
|
python - "$local_sources_port" "$local_sources_dir" >/tmp/context-kit-local-sources.log 2>&1 <<'PY' &
|
||||||
--bind 127.0.0.1 \
|
import functools
|
||||||
--directory "$local_sources_dir" \
|
import http.server
|
||||||
>/tmp/context-kit-local-sources.log 2>&1 &
|
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="$!"
|
local_sources_pid="$!"
|
||||||
if ! python - "$local_sources_port" <<'PY'
|
if ! python - "$local_sources_port" <<'PY'
|
||||||
import sys
|
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
|
simple `KEY=VALUE` lines for `CONTEXT_KIT_*` variables only; it does not execute
|
||||||
shell code.
|
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
|
## User-Facing Variables
|
||||||
|
|
||||||
Only the variables below are part of the public configuration surface. Other
|
Only the variables below are part of the public configuration surface. Other
|
||||||
|
|||||||
@@ -18,13 +18,29 @@ PY
|
|||||||
release_id="release-$$"
|
release_id="release-$$"
|
||||||
export CONTEXT_KIT_COMPOSE_PROJECT="context-kit-${release_id}"
|
export CONTEXT_KIT_COMPOSE_PROJECT="context-kit-${release_id}"
|
||||||
export CONTEXT_KIT_DATA_DIR="${tmp_dir}/data"
|
export CONTEXT_KIT_DATA_DIR="${tmp_dir}/data"
|
||||||
|
export CONTEXT_KIT_PROJECT_DIR="${ROOT}"
|
||||||
export CONTEXT_KIT_SEARXNG_PORT="$(pick_port)"
|
export CONTEXT_KIT_SEARXNG_PORT="$(pick_port)"
|
||||||
export CONTEXT_KIT_DOCS_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_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_WEB_SEARCH_IMAGE="context-kit/web-search-mcp:${release_id}"
|
||||||
export CONTEXT_KIT_DOCS_IMAGE="context-kit/docs-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() {
|
cleanup() {
|
||||||
docker compose -p "${CONTEXT_KIT_COMPOSE_PROJECT}" -f compose.yml down -v --remove-orphans >/dev/null 2>&1 || true
|
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
|
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/mcp-smoke-client.mjs \
|
||||||
scripts/smoke-web-search.mjs \
|
scripts/smoke-web-search.mjs \
|
||||||
scripts/smoke-docs.mjs \
|
scripts/smoke-docs.mjs \
|
||||||
|
scripts/smoke-repomix.mjs \
|
||||||
scripts/release-check >/dev/null
|
scripts/release-check >/dev/null
|
||||||
bash -n bin/context-kit
|
bash -n bin/context-kit
|
||||||
bash -n scripts/release-check
|
bash -n scripts/release-check
|
||||||
sh -n docker/docs/entrypoint.sh
|
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"));'
|
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 > "${tmp_dir}/opencode.json"
|
||||||
bin/context-kit install opencode --absolute > "${tmp_dir}/opencode-absolute.json"
|
bin/context-kit install opencode --absolute > "${tmp_dir}/opencode-absolute.json"
|
||||||
bin/context-kit install claude > "${tmp_dir}/claude.json"
|
bin/context-kit install claude > "${tmp_dir}/claude.json"
|
||||||
@@ -122,5 +151,6 @@ bin/context-kit restart
|
|||||||
bin/context-kit doctor
|
bin/context-kit doctor
|
||||||
node scripts/smoke-web-search.mjs bin/context-kit web-search
|
node scripts/smoke-web-search.mjs bin/context-kit web-search
|
||||||
node scripts/smoke-docs.mjs bin/context-kit docs
|
node scripts/smoke-docs.mjs bin/context-kit docs
|
||||||
|
node scripts/smoke-repomix.mjs bin/context-kit repomix
|
||||||
|
|
||||||
printf 'pass release-check\n'
|
printf 'pass release-check\n'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { requireToolSuccess, runSmoke } from "./mcp-smoke-client.mjs";
|
import { requireToolSuccess, runSmoke } from "./mcp-smoke-client.mjs";
|
||||||
|
|
||||||
const live = process.env.CONTEXT_KIT_LIVE_CHECKS === "1";
|
const live = process.env.CONTEXT_KIT_LIVE_CHECKS === "1";
|
||||||
|
const localSourceSmokeUrl = process.env.CONTEXT_KIT_LOCAL_SOURCE_SMOKE_URL;
|
||||||
|
|
||||||
runSmoke({
|
runSmoke({
|
||||||
usage: "usage: node scripts/smoke-docs.mjs <command> [args...]",
|
usage: "usage: node scripts/smoke-docs.mjs <command> [args...]",
|
||||||
@@ -21,6 +22,16 @@ runSmoke({
|
|||||||
docs_sources: "pass"
|
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) {
|
if (live) {
|
||||||
const query = requireToolSuccess("docs_query", await client.callTool("docs_query", {
|
const query = requireToolSuccess("docs_query", await client.callTool("docs_query", {
|
||||||
query: "Model Context Protocol documentation",
|
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