Compare commits
1 Commits
dd431fffca
...
archive/wi
| Author | SHA1 | Date | |
|---|---|---|---|
| 0209acdaae |
@@ -126,7 +126,6 @@ bin/context-kit status
|
|||||||
bin/context-kit doctor
|
bin/context-kit doctor
|
||||||
bin/context-kit install claude
|
bin/context-kit install claude
|
||||||
bin/context-kit install opencode
|
bin/context-kit install opencode
|
||||||
bin/context-kit redaction-check
|
|
||||||
```
|
```
|
||||||
|
|
||||||
MCP entrypoints:
|
MCP entrypoints:
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ Usage:
|
|||||||
context-kit build Build MCP images
|
context-kit build Build MCP images
|
||||||
context-kit status Show services, images, sources, and the docs HTTP endpoint
|
context-kit status Show services, images, sources, and the docs HTTP endpoint
|
||||||
context-kit doctor Check Docker, services, images, sources, and HTTP endpoints
|
context-kit doctor Check Docker, services, images, sources, and HTTP endpoints
|
||||||
context-kit redaction-check Scan this repo for local paths and secret patterns
|
|
||||||
|
|
||||||
MCP server commands:
|
MCP server commands:
|
||||||
context-kit web-search Per-call SearXNG-backed web-search MCP (stdio)
|
context-kit web-search Per-call SearXNG-backed web-search MCP (stdio)
|
||||||
@@ -168,17 +167,6 @@ warn() {
|
|||||||
printf 'warn: %s\n' "$*" >&2
|
printf 'warn: %s\n' "$*" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
print_relative_paths() {
|
|
||||||
local path
|
|
||||||
while IFS= read -r path; do
|
|
||||||
[[ -n "${path}" ]] || continue
|
|
||||||
if [[ "${path}" == "${ROOT}/"* ]]; then
|
|
||||||
path="${path#"${ROOT}/"}"
|
|
||||||
fi
|
|
||||||
printf '%s\n' "${path}"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
json_escape() {
|
json_escape() {
|
||||||
local s="$1"
|
local s="$1"
|
||||||
s="${s//\\/\\\\}"
|
s="${s//\\/\\\\}"
|
||||||
@@ -550,50 +538,6 @@ cmd_install() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_redaction_check() {
|
|
||||||
local bad=0
|
|
||||||
local scan_paths=("${ROOT}")
|
|
||||||
if [[ "$#" -gt 0 ]]; then
|
|
||||||
scan_paths=("$@")
|
|
||||||
fi
|
|
||||||
local local_path_terms='/(home|Users)/[^/[:space:]]+|/data/(projects|opencode-mcp)[^[:space:]]*|[A-Za-z]:\\Users\\[^\\[:space:]]+'
|
|
||||||
local secret_terms='AKIA[0-9A-Z]{16}|BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY|xox[baprs]-|sk-[A-Za-z0-9_-]{20,}|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{20,}|gitea_[A-Za-z0-9_-]{20,}'
|
|
||||||
|
|
||||||
# Scan only what would be published: skip .git plus everything .gitignore
|
|
||||||
# excludes by convention (local .env files, caches, logs).
|
|
||||||
local grep_opts=(
|
|
||||||
-RInE
|
|
||||||
--exclude-dir=.git
|
|
||||||
--exclude-dir=.cache
|
|
||||||
--exclude-dir=tmp
|
|
||||||
--exclude=.env
|
|
||||||
--exclude=.env.local
|
|
||||||
--exclude=*.log
|
|
||||||
)
|
|
||||||
|
|
||||||
local matches
|
|
||||||
matches="$(grep "${grep_opts[@]}" --files-with-matches "${local_path_terms}" "${scan_paths[@]}" 2>/dev/null || true)"
|
|
||||||
if [[ -n "${matches}" ]]; then
|
|
||||||
printf 'fail redaction-check found local path patterns in:\n' >&2
|
|
||||||
printf '%s\n' "${matches}" | print_relative_paths | sed 's/^/- /' >&2
|
|
||||||
bad=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
matches="$(grep "${grep_opts[@]}" --files-with-matches "${secret_terms}" "${scan_paths[@]}" 2>/dev/null || true)"
|
|
||||||
if [[ -n "${matches}" ]]; then
|
|
||||||
printf 'fail redaction-check found secret-like patterns in:\n' >&2
|
|
||||||
printf '%s\n' "${matches}" | print_relative_paths | sed 's/^/- /' >&2
|
|
||||||
bad=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${bad}" -eq 0 ]]; then
|
|
||||||
printf 'pass redaction-check found no local absolute paths or common secret patterns\n'
|
|
||||||
else
|
|
||||||
printf 'fail redaction-check found blocked content\n' >&2
|
|
||||||
fi
|
|
||||||
return "${bad}"
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
start) shift; cmd_start "$@" ;;
|
start) shift; cmd_start "$@" ;;
|
||||||
stop) shift; cmd_stop "$@" ;;
|
stop) shift; cmd_stop "$@" ;;
|
||||||
@@ -605,7 +549,6 @@ case "${1:-}" in
|
|||||||
docs) shift; cmd_docs "$@" ;;
|
docs) shift; cmd_docs "$@" ;;
|
||||||
repomix) shift; cmd_repomix "$@" ;;
|
repomix) shift; cmd_repomix "$@" ;;
|
||||||
install) shift; cmd_install "$@" ;;
|
install) shift; cmd_install "$@" ;;
|
||||||
redaction-check) shift; cmd_redaction_check "$@" ;;
|
|
||||||
-h|--help|help|"") usage ;;
|
-h|--help|help|"") usage ;;
|
||||||
*) usage >&2; exit 64 ;;
|
*) usage >&2; exit 64 ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ Tracked public files:
|
|||||||
- `config/sources.*.txt`: optional public source profiles.
|
- `config/sources.*.txt`: optional public source profiles.
|
||||||
- `snippets/`: portable assistant config snippets that use `context-kit` on
|
- `snippets/`: portable assistant config snippets that use `context-kit` on
|
||||||
`PATH`.
|
`PATH`.
|
||||||
- `compose.yml`, `docker/`, `bin/`, and `scripts/`: generic runtime and release
|
- `compose.yml`, `docker/`, `bin/`, and `scripts/check`: generic runtime logic.
|
||||||
logic.
|
|
||||||
|
|
||||||
Ignored or external local files:
|
Ignored or external local files:
|
||||||
|
|
||||||
@@ -50,8 +49,7 @@ host.
|
|||||||
|
|
||||||
## User-Facing Variables
|
## User-Facing Variables
|
||||||
|
|
||||||
Only the variables below are part of the public configuration surface. Other
|
The variables below are the supported public configuration surface.
|
||||||
`CONTEXT_KIT_*` variables used by scripts are release/test hooks and may change.
|
|
||||||
|
|
||||||
| Variable | Default | Purpose |
|
| Variable | Default | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|||||||
@@ -9,19 +9,6 @@ bin/context-kit doctor
|
|||||||
This checks Docker, Compose, images, the Docker network, SearXNG health, docs
|
This checks Docker, Compose, images, the Docker network, SearXNG health, docs
|
||||||
HTTP readiness, and docs source configuration.
|
HTTP readiness, and docs source configuration.
|
||||||
|
|
||||||
For release-grade MCP protocol checks, run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
scripts/release-check
|
|
||||||
```
|
|
||||||
|
|
||||||
Live provider checks are opt-in because search engines, remote docs, and model
|
|
||||||
downloads can fail independently of this repo:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
CONTEXT_KIT_LIVE_CHECKS=1 scripts/release-check
|
|
||||||
```
|
|
||||||
|
|
||||||
## SearXNG Is Not Responding
|
## SearXNG Is Not Responding
|
||||||
|
|
||||||
Start it:
|
Start it:
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
import { spawn, spawnSync } from "node:child_process";
|
|
||||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
||||||
import { tmpdir } from "node:os";
|
|
||||||
import { join } from "node:path";
|
|
||||||
|
|
||||||
export function textFrom(result) {
|
|
||||||
return (result.content || [])
|
|
||||||
.filter(part => part.type === "text")
|
|
||||||
.map(part => part.text)
|
|
||||||
.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function requireToolSuccess(name, result) {
|
|
||||||
if (result?.isError) {
|
|
||||||
const payload = textFrom(result) || JSON.stringify(result);
|
|
||||||
throw new Error(`${name} returned an error: ${payload.slice(0, 500)}`);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runSmoke({ usage, tmpPrefix, timeoutMs, clientInfo, scenario }) {
|
|
||||||
const command = process.argv[2];
|
|
||||||
const args = process.argv.slice(3);
|
|
||||||
|
|
||||||
if (!command) {
|
|
||||||
throw new Error(usage);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new McpSmokeClient({ command, args, tmpPrefix });
|
|
||||||
const timeout = setTimeout(async () => {
|
|
||||||
await client.stop();
|
|
||||||
console.error(`MCP smoke timed out. stderr: ${client.stderrTail(2000)}`);
|
|
||||||
process.exit(1);
|
|
||||||
}, timeoutMs);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.initialize(clientInfo);
|
|
||||||
const result = await scenario(client);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
await client.stop();
|
|
||||||
console.log(JSON.stringify(result, null, 2));
|
|
||||||
} catch (error) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
await client.stop();
|
|
||||||
console.error(error.message);
|
|
||||||
const stderr = client.stderrTail(4000);
|
|
||||||
if (stderr) console.error(stderr);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class McpSmokeClient {
|
|
||||||
constructor({ command, args, tmpPrefix }) {
|
|
||||||
this.tmpDir = mkdtempSync(join(tmpdir(), tmpPrefix));
|
|
||||||
this.cidFile = join(this.tmpDir, "container.cid");
|
|
||||||
this.nextId = 1;
|
|
||||||
this.pending = new Map();
|
|
||||||
this.stdoutBuffer = "";
|
|
||||||
this.stderrBuffer = "";
|
|
||||||
this.exited = false;
|
|
||||||
|
|
||||||
this.child = spawn(command, args, {
|
|
||||||
cwd: new URL("..", import.meta.url).pathname,
|
|
||||||
env: { ...process.env, CONTEXT_KIT_DOCKER_CIDFILE: this.cidFile },
|
|
||||||
stdio: ["pipe", "pipe", "pipe"]
|
|
||||||
});
|
|
||||||
|
|
||||||
this.child.once("exit", (code, signal) => {
|
|
||||||
this.exited = true;
|
|
||||||
if (this.pending.size > 0) {
|
|
||||||
const error = new Error(`MCP child exited before responding (code=${code}, signal=${signal}). stderr: ${this.stderrTail(2000)}`);
|
|
||||||
for (const { reject } of this.pending.values()) reject(error);
|
|
||||||
this.pending.clear();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.child.stderr.on("data", chunk => {
|
|
||||||
this.stderrBuffer += chunk.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.child.stdout.on("data", chunk => this.handleStdout(chunk));
|
|
||||||
}
|
|
||||||
|
|
||||||
stderrTail(length) {
|
|
||||||
return this.stderrBuffer.slice(-length);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleStdout(chunk) {
|
|
||||||
this.stdoutBuffer += chunk.toString();
|
|
||||||
let newline;
|
|
||||||
while ((newline = this.stdoutBuffer.indexOf("\n")) >= 0) {
|
|
||||||
const line = this.stdoutBuffer.slice(0, newline).trim();
|
|
||||||
this.stdoutBuffer = this.stdoutBuffer.slice(newline + 1);
|
|
||||||
if (!line) continue;
|
|
||||||
let message;
|
|
||||||
try {
|
|
||||||
message = JSON.parse(line);
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (message.id && this.pending.has(message.id)) {
|
|
||||||
const { resolve, reject } = this.pending.get(message.id);
|
|
||||||
this.pending.delete(message.id);
|
|
||||||
if (message.error) reject(new Error(JSON.stringify(message.error)));
|
|
||||||
else resolve(message.result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request(method, params = {}) {
|
|
||||||
if (this.exited) {
|
|
||||||
return Promise.reject(new Error(`MCP child already exited. stderr: ${this.stderrTail(2000)}`));
|
|
||||||
}
|
|
||||||
const id = this.nextId++;
|
|
||||||
this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`);
|
|
||||||
return new Promise((resolve, reject) => this.pending.set(id, { resolve, reject }));
|
|
||||||
}
|
|
||||||
|
|
||||||
notify(method, params = {}) {
|
|
||||||
this.child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async initialize(clientInfo) {
|
|
||||||
await this.request("initialize", {
|
|
||||||
protocolVersion: "2024-11-05",
|
|
||||||
capabilities: {},
|
|
||||||
clientInfo
|
|
||||||
});
|
|
||||||
this.notify("notifications/initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
async tools() {
|
|
||||||
const listed = await this.request("tools/list");
|
|
||||||
return new Set((listed.tools || []).map(tool => tool.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
async requireTools(names) {
|
|
||||||
const toolNames = await this.tools();
|
|
||||||
for (const name of names) {
|
|
||||||
if (!toolNames.has(name)) throw new Error(`missing tool: ${name}`);
|
|
||||||
}
|
|
||||||
return toolNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
callTool(name, args = {}) {
|
|
||||||
return this.request("tools/call", { name, arguments: args });
|
|
||||||
}
|
|
||||||
|
|
||||||
stopContainer() {
|
|
||||||
if (!existsSync(this.cidFile)) return;
|
|
||||||
const containerId = readFileSync(this.cidFile, "utf8").trim();
|
|
||||||
if (!containerId) return;
|
|
||||||
spawnSync("docker", ["stop", containerId], { stdio: "ignore" });
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if (this.exited) {
|
|
||||||
this.stopContainer();
|
|
||||||
rmSync(this.tmpDir, { recursive: true, force: true });
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let stopTimer;
|
|
||||||
let termTimer;
|
|
||||||
let killTimer;
|
|
||||||
this.child.once("exit", () => {
|
|
||||||
this.stopContainer();
|
|
||||||
clearTimeout(stopTimer);
|
|
||||||
clearTimeout(termTimer);
|
|
||||||
clearTimeout(killTimer);
|
|
||||||
rmSync(this.tmpDir, { recursive: true, force: true });
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
stopTimer = setTimeout(() => this.stopContainer(), 1000);
|
|
||||||
termTimer = setTimeout(() => {
|
|
||||||
if (!this.exited) this.child.kill("SIGTERM");
|
|
||||||
}, 3000);
|
|
||||||
killTimer = setTimeout(() => {
|
|
||||||
if (!this.exited) this.child.kill("SIGKILL");
|
|
||||||
}, 6000);
|
|
||||||
|
|
||||||
this.child.stdin.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
cd "${ROOT}"
|
|
||||||
|
|
||||||
tmp_dir="$(mktemp -d)"
|
|
||||||
pick_port() {
|
|
||||||
python - <<'PY'
|
|
||||||
import socket
|
|
||||||
|
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
||||||
sock.bind(("127.0.0.1", 0))
|
|
||||||
print(sock.getsockname()[1])
|
|
||||||
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_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
|
|
||||||
rm -rf "${tmp_dir}"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
check_node() {
|
|
||||||
local file
|
|
||||||
for file in "$@"; do
|
|
||||||
node --check "${file}"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_redaction_check_does_not_disclose_matches() {
|
|
||||||
local fixture="${tmp_dir}/redaction-fixture.txt"
|
|
||||||
local output="${tmp_dir}/redaction-output.txt"
|
|
||||||
local blocked_path="/data/proj""ects/context-kit-private-fixture"
|
|
||||||
printf 'blocked=%s\n' "${blocked_path}" > "${fixture}"
|
|
||||||
if bin/context-kit redaction-check "${fixture}" >"${output}" 2>&1; then
|
|
||||||
printf 'redaction-check test unexpectedly passed\n' >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if grep -F "${blocked_path}" "${output}" >/dev/null; then
|
|
||||||
printf 'redaction-check disclosed matched content\n' >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_web_search_image() {
|
|
||||||
docker run --rm --entrypoint node \
|
|
||||||
-e EXPECTED_MAX_BYTES="${CONTEXT_KIT_WEB_SEARCH_MAX_BYTES:-52428800}" \
|
|
||||||
"${CONTEXT_KIT_WEB_SEARCH_IMAGE}" \
|
|
||||||
-e '
|
|
||||||
const fs = require("node:fs");
|
|
||||||
const expected = Number(process.env.EXPECTED_MAX_BYTES) || 0;
|
|
||||||
const actual = Number(process.env.MAX_BYTES) || 0;
|
|
||||||
if (process.getuid && process.getuid() === 0) process.exit(1);
|
|
||||||
if (actual !== expected) process.exit(1);
|
|
||||||
|
|
||||||
const serverPath = "/usr/local/lib/node_modules/@zhafron/mcp-web-search/dist/src/server.js";
|
|
||||||
const server = fs.readFileSync(serverPath, "utf8");
|
|
||||||
if (!server.includes("max_download_bytes: z.number().int().min(1).max(MAX_BYTES).optional()")) process.exit(1);
|
|
||||||
|
|
||||||
const bingPath = "/usr/local/lib/node_modules/@zhafron/mcp-web-search/dist/src/providers/bing.js";
|
|
||||||
const bing = fs.readFileSync(bingPath, "utf8");
|
|
||||||
if (!bing.includes("Context Kit override for @zhafron/mcp-web-search 1.3.0")) process.exit(1);
|
|
||||||
if (!bing.includes("waitForSelector")) process.exit(1);
|
|
||||||
if (!bing.includes("decodeBingRedirect")) process.exit(1);
|
|
||||||
' >/dev/null
|
|
||||||
|
|
||||||
docker run --rm --entrypoint /usr/bin/test \
|
|
||||||
"${CONTEXT_KIT_WEB_SEARCH_IMAGE}" \
|
|
||||||
-x "${CONTEXT_KIT_WEB_SEARCH_CHROME_PATH:-/usr/bin/chromium}"
|
|
||||||
}
|
|
||||||
|
|
||||||
git diff --check HEAD
|
|
||||||
git show --check --format= HEAD >/dev/null
|
|
||||||
git ls-files --cached --error-unmatch \
|
|
||||||
docker/web-search/patch-mcp-web-search.mjs \
|
|
||||||
docker/web-search/overrides/bing.js \
|
|
||||||
docker/docs/constraints.txt \
|
|
||||||
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 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"
|
|
||||||
bin/context-kit install claude --absolute > "${tmp_dir}/claude-absolute.json"
|
|
||||||
node -e 'const fs=require("node:fs"); for (const file of process.argv.slice(1)) JSON.parse(fs.readFileSync(file, "utf8"));' \
|
|
||||||
"${tmp_dir}/opencode.json" \
|
|
||||||
"${tmp_dir}/opencode-absolute.json" \
|
|
||||||
"${tmp_dir}/claude.json" \
|
|
||||||
"${tmp_dir}/claude-absolute.json"
|
|
||||||
bin/context-kit redaction-check "${tmp_dir}/opencode.json" "${tmp_dir}/claude.json"
|
|
||||||
assert_redaction_check_does_not_disclose_matches
|
|
||||||
|
|
||||||
bin/context-kit redaction-check
|
|
||||||
docker compose -p "${CONTEXT_KIT_COMPOSE_PROJECT}" -f compose.yml config >/dev/null
|
|
||||||
if env -u HOME -u CONTEXT_KIT_DATA_DIR -u CONTEXT_KIT_DOCS_LOCAL_SOURCES_DIR docker compose --env-file /dev/null -p context-kit-release-home-check -f compose.yml config >"${tmp_dir}/compose-no-home.out" 2>"${tmp_dir}/compose-no-home.err"; then
|
|
||||||
printf 'compose config unexpectedly succeeded without HOME or CONTEXT_KIT_DATA_DIR\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
CONTEXT_KIT_DATA_DIR="${tmp_dir}/compose-data" env -u HOME docker compose --env-file /dev/null -p context-kit-release-home-check -f compose.yml config >/dev/null
|
|
||||||
bin/context-kit build
|
|
||||||
assert_web_search_image
|
|
||||||
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,53 +0,0 @@
|
|||||||
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...]",
|
|
||||||
tmpPrefix: "context-kit-docs-smoke-",
|
|
||||||
timeoutMs: 300000,
|
|
||||||
clientInfo: { name: "context-kit-docs-smoke", version: "0.0.0" },
|
|
||||||
scenario: async client => {
|
|
||||||
const toolNames = await client.requireTools(["docs_query", "docs_sources"]);
|
|
||||||
|
|
||||||
const sources = requireToolSuccess("docs_sources", await client.callTool("docs_sources"));
|
|
||||||
if (!Array.isArray(sources?.structuredContent?.result)) {
|
|
||||||
const sourcesText = JSON.stringify(sources);
|
|
||||||
throw new Error(`docs_sources returned unexpected payload: ${sourcesText.slice(0, 500)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
tools: Array.from(toolNames).sort(),
|
|
||||||
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",
|
|
||||||
limit: 3,
|
|
||||||
auto_retrieve: true,
|
|
||||||
auto_retrieve_threshold: 0.1,
|
|
||||||
auto_retrieve_limit: 1,
|
|
||||||
max_bytes: 12000
|
|
||||||
}));
|
|
||||||
const queryText = JSON.stringify(query);
|
|
||||||
if (!queryText.includes("search_results") && !queryText.includes("Model Context Protocol")) {
|
|
||||||
throw new Error(`docs_query returned unexpected payload: ${queryText.slice(0, 500)}`);
|
|
||||||
}
|
|
||||||
result.docs_query = "pass";
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { requireToolSuccess, runSmoke, textFrom } from "./mcp-smoke-client.mjs";
|
|
||||||
|
|
||||||
const live = process.env.CONTEXT_KIT_LIVE_CHECKS === "1";
|
|
||||||
|
|
||||||
runSmoke({
|
|
||||||
usage: "usage: node scripts/smoke-web-search.mjs <command> [args...]",
|
|
||||||
tmpPrefix: "context-kit-web-search-smoke-",
|
|
||||||
timeoutMs: 120000,
|
|
||||||
clientInfo: { name: "context-kit-web-search-smoke", version: "0.0.0" },
|
|
||||||
scenario: async client => {
|
|
||||||
const toolNames = await client.requireTools(["search_web", "fetch_url"]);
|
|
||||||
|
|
||||||
const localResult = await client.callTool("fetch_url", {
|
|
||||||
url: "http://127.0.0.1:1/",
|
|
||||||
max_download_bytes: 52428800
|
|
||||||
});
|
|
||||||
const localBlocked = Boolean(localResult.isError) && textFrom(localResult).includes("Blocked localhost/private URL");
|
|
||||||
if (!localBlocked) throw new Error("localhost/private URL was not blocked as expected");
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
tools: Array.from(toolNames).sort(),
|
|
||||||
localhost_guard: "pass"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (live) {
|
|
||||||
const searxng = textFrom(requireToolSuccess("search_web/searxng", await client.callTool("search_web", {
|
|
||||||
q: "Model Context Protocol",
|
|
||||||
limit: 2,
|
|
||||||
provider: "searxng"
|
|
||||||
})));
|
|
||||||
if (!searxng.includes("Model")) throw new Error(`SearXNG smoke returned unexpected text: ${searxng.slice(0, 500)}`);
|
|
||||||
|
|
||||||
const bing = textFrom(requireToolSuccess("search_web/bing", await client.callTool("search_web", {
|
|
||||||
q: "Model Context Protocol",
|
|
||||||
limit: 2,
|
|
||||||
provider: "bing"
|
|
||||||
})));
|
|
||||||
if (!bing.includes("Model")) throw new Error(`Bing smoke returned unexpected text: ${bing.slice(0, 500)}`);
|
|
||||||
|
|
||||||
const fetch = textFrom(requireToolSuccess("fetch_url/http", await client.callTool("fetch_url", {
|
|
||||||
url: "https://example.com/",
|
|
||||||
format: "markdown",
|
|
||||||
max_download_bytes: 52428800
|
|
||||||
})));
|
|
||||||
if (!fetch.includes("Example Domain")) throw new Error(`fetch smoke returned unexpected text: ${fetch.slice(0, 500)}`);
|
|
||||||
|
|
||||||
const browserFetch = textFrom(requireToolSuccess("fetch_url/browser", await client.callTool("fetch_url", {
|
|
||||||
url: "https://example.com/",
|
|
||||||
format: "markdown",
|
|
||||||
engine: "browser",
|
|
||||||
max_download_bytes: 52428800
|
|
||||||
})));
|
|
||||||
if (!browserFetch.includes("Example Domain")) throw new Error(`browser fetch smoke returned unexpected text: ${browserFetch.slice(0, 500)}`);
|
|
||||||
|
|
||||||
result.searxng = "pass";
|
|
||||||
result.bing = "pass";
|
|
||||||
result.fetch_url = "pass";
|
|
||||||
result.fetch_url_browser_engine_currently_http = "pass";
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user