From 813f95705677ab8ca474bdc294a5912f16796f72 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:35:39 +0800 Subject: [PATCH 01/34] feat: add multi-client one-click installer --- src/installer.ts | 446 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 446 insertions(+) create mode 100644 src/installer.ts diff --git a/src/installer.ts b/src/installer.ts new file mode 100644 index 0000000..dff5ddd --- /dev/null +++ b/src/installer.ts @@ -0,0 +1,446 @@ +import { spawnSync } from "node:child_process"; +import { + chmodSync, + copyFileSync, + existsSync, + mkdirSync, + readFileSync, + renameSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { homedir } from "node:os"; +import { dirname, join, resolve } from "node:path"; + +import { parseEnvFile } from "./core.js"; + +export const PACKAGE_SPEC = "github:Afloat16/jev-mcp#main"; +export const SERVER_NAME = "jev"; + +export type InstallTarget = + | "codex" + | "claude-code" + | "kimi" + | "zcode" + | "cursor" + | "gemini" + | "windsurf" + | "agents" + | "vscode"; + +export const INSTALL_TARGETS: InstallTarget[] = [ + "codex", + "claude-code", + "kimi", + "zcode", + "cursor", + "gemini", + "windsurf", + "agents", + "vscode", +]; + +type JsonObject = Record; + +export function configHome(): string { + return process.env.JEV_MCP_CONFIG_HOME?.trim() || join(homedir(), ".jev-mcp"); +} + +export function userEnvPath(): string { + return join(configHome(), ".env"); +} + +export function serverLauncher(platform = process.platform): { + command: string; + args: string[]; +} { + const args = [ + "-y", + `--package=${PACKAGE_SPEC}`, + "jev-mcp", + "server", + ]; + + if (platform === "win32") { + return { + command: "cmd", + args: ["/d", "/s", "/c", "npx", ...args], + }; + } + + return { command: "npx", args }; +} + +export function genericMcpEntry(platform = process.platform): JsonObject { + const launcher = serverLauncher(platform); + return { + command: launcher.command, + args: launcher.args, + }; +} + +function isObject(value: unknown): value is JsonObject { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function readJson(path: string): JsonObject { + if (!existsSync(path)) return {}; + const text = readFileSync(path, "utf8").trim(); + if (!text) return {}; + const parsed: unknown = JSON.parse(text); + if (!isObject(parsed)) { + throw new Error(`Expected a JSON object in ${path}`); + } + return parsed; +} + +function atomicWriteJson(path: string, value: JsonObject): void { + mkdirSync(dirname(path), { recursive: true }); + const temp = `${path}.jev-mcp.tmp-${process.pid}`; + const backup = `${path}.jev-mcp.bak`; + + if (existsSync(path)) copyFileSync(path, backup); + writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, "utf8"); + renameSync(temp, path); +} + +function setNested(root: JsonObject, path: string[], value: unknown): void { + let current = root; + for (const key of path.slice(0, -1)) { + if (!isObject(current[key])) current[key] = {}; + current = current[key] as JsonObject; + } + current[path[path.length - 1]] = value; +} + +function deleteNested(root: JsonObject, path: string[]): boolean { + let current = root; + for (const key of path.slice(0, -1)) { + if (!isObject(current[key])) return false; + current = current[key] as JsonObject; + } + return delete current[path[path.length - 1]]; +} + +export function jsonConfigForTarget( + target: Exclude, + platform = process.platform, +): { path: string; keyPath: string[]; value: JsonObject } { + const home = homedir(); + const launcher = serverLauncher(platform); + const base = { + command: launcher.command, + args: launcher.args, + }; + + switch (target) { + case "kimi": + return { + path: join(home, ".kimi-code", "mcp.json"), + keyPath: ["mcpServers", SERVER_NAME], + value: { + ...base, + deferred: true, + startupTimeoutMs: 120000, + toolTimeoutMs: 120000, + }, + }; + case "zcode": + return { + path: join(home, ".zcode", "cli", "config.json"), + keyPath: ["mcp", "servers", SERVER_NAME], + value: { ...base, enable: true }, + }; + case "cursor": + return { + path: join(home, ".cursor", "mcp.json"), + keyPath: ["mcpServers", SERVER_NAME], + value: { type: "stdio", ...base }, + }; + case "gemini": + return { + path: join(home, ".gemini", "settings.json"), + keyPath: ["mcpServers", SERVER_NAME], + value: { ...base, timeout: 120000 }, + }; + case "windsurf": + return { + path: join(home, ".codeium", "windsurf", "mcp_config.json"), + keyPath: ["mcpServers", SERVER_NAME], + value: base, + }; + case "agents": + return { + path: join(home, ".agents", "mcp.json"), + keyPath: ["mcpServers", SERVER_NAME], + value: base, + }; + case "vscode": + return { + path: resolve(process.cwd(), ".vscode", "mcp.json"), + keyPath: ["servers", SERVER_NAME], + value: { type: "stdio", ...base }, + }; + } +} + +function commandExists(command: string): boolean { + const probe = + process.platform === "win32" + ? spawnSync("where", [command], { stdio: "ignore" }) + : spawnSync("sh", ["-lc", `command -v ${command}`], { stdio: "ignore" }); + return probe.status === 0; +} + +function runCli( + command: string, + args: string[], + options: { ignoreFailure?: boolean } = {}, +): boolean { + const result = + process.platform === "win32" + ? spawnSync("cmd", ["/d", "/s", "/c", command, ...args], { + stdio: options.ignoreFailure ? "ignore" : "inherit", + }) + : spawnSync(command, args, { + stdio: options.ignoreFailure ? "ignore" : "inherit", + }); + + if (result.error && !options.ignoreFailure) throw result.error; + if (result.status !== 0 && !options.ignoreFailure) { + throw new Error(`${command} exited with status ${result.status ?? "unknown"}`); + } + return result.status === 0; +} + +async function readSecret(prompt: string): Promise { + if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") { + throw new Error( + "Interactive secret input requires a TTY. Set TYPESAFE_API_KEY in the environment and rerun.", + ); + } + + process.stdout.write(prompt); + process.stdin.setEncoding("utf8"); + process.stdin.setRawMode(true); + process.stdin.resume(); + + return await new Promise((resolvePromise, reject) => { + let value = ""; + + const cleanup = () => { + process.stdin.off("data", onData); + process.stdin.setRawMode(false); + process.stdin.pause(); + }; + + const onData = (chunk: string | Buffer) => { + const text = String(chunk); + for (const char of text) { + if (char === "\u0003") { + cleanup(); + process.stdout.write("\n"); + reject(new Error("Cancelled.")); + return; + } + + if (char === "\r" || char === "\n") { + cleanup(); + process.stdout.write("\n"); + resolvePromise(value); + return; + } + + if (char === "\u007f" || char === "\b") { + value = value.slice(0, -1); + continue; + } + + if (char >= " ") value += char; + } + }; + + process.stdin.on("data", onData); + }); +} + +function validateKey(key: string): string { + const value = key.trim(); + if (!value) throw new Error("TypeSafe API key cannot be empty."); + if (/\s/.test(value)) throw new Error("TypeSafe API key must not contain whitespace."); + return value; +} + +export async function ensureCredential(options: { + reset?: boolean; + skip?: boolean; +} = {}): Promise { + if (options.skip) return null; + + const path = userEnvPath(); + if (!options.reset && existsSync(path)) { + const parsed = parseEnvFile(readFileSync(path, "utf8")); + if (parsed.TYPESAFE_API_KEY?.trim()) return path; + } + + const key = validateKey( + process.env.TYPESAFE_API_KEY ?? + (await readSecret("TypeSafe API key (input hidden): ")), + ); + + mkdirSync(dirname(path), { recursive: true, mode: 0o700 }); + const temp = `${path}.tmp-${process.pid}`; + writeFileSync( + temp, + [ + "# Local jev-mcp credential file. Never commit or share this file.", + `TYPESAFE_API_KEY=${key}`, + "JEV_MODEL=jev-latest", + "TYPESAFE_BASE_URL=https://api.typesafe.ai", + "TYPESAFE_TIMEOUT_MS=15000", + "", + ].join("\n"), + { encoding: "utf8", mode: 0o600 }, + ); + renameSync(temp, path); + try { + chmodSync(path, 0o600); + } catch { + // Best effort on platforms/filesystems without POSIX permissions. + } + + return path; +} + +function installJsonTarget( + target: Exclude, +): string { + const config = jsonConfigForTarget(target); + const root = readJson(config.path); + setNested(root, config.keyPath, config.value); + atomicWriteJson(config.path, root); + return config.path; +} + +function uninstallJsonTarget( + target: Exclude, +): string { + const config = jsonConfigForTarget(target); + if (!existsSync(config.path)) return config.path; + const root = readJson(config.path); + if (deleteNested(root, config.keyPath)) atomicWriteJson(config.path, root); + return config.path; +} + +function installCliTarget(target: "codex" | "claude-code"): void { + const launcher = serverLauncher(); + + if (target === "codex") { + if (!commandExists("codex")) { + throw new Error("Codex CLI was not found on PATH."); + } + runCli("codex", ["mcp", "remove", SERVER_NAME], { ignoreFailure: true }); + runCli("codex", [ + "mcp", + "add", + SERVER_NAME, + "--", + launcher.command, + ...launcher.args, + ]); + return; + } + + if (!commandExists("claude")) { + throw new Error("Claude Code CLI was not found on PATH."); + } + runCli("claude", ["mcp", "remove", SERVER_NAME], { ignoreFailure: true }); + runCli("claude", [ + "mcp", + "add", + "--scope", + "user", + SERVER_NAME, + "--", + launcher.command, + ...launcher.args, + ]); +} + +function uninstallCliTarget(target: "codex" | "claude-code"): void { + const command = target === "codex" ? "codex" : "claude"; + if (!commandExists(command)) return; + runCli(command, ["mcp", "remove", SERVER_NAME], { ignoreFailure: true }); +} + +function targetLooksInstalled(target: InstallTarget): boolean { + if (target === "codex") return commandExists("codex"); + if (target === "claude-code") return commandExists("claude"); + + const config = jsonConfigForTarget(target); + if (target === "vscode") return existsSync(dirname(config.path)); + return existsSync(dirname(config.path)); +} + +export async function installTargets( + targets: InstallTarget[], + options: { skipKey?: boolean; detectOnly?: boolean } = {}, +): Promise> { + await ensureCredential({ skip: options.skipKey }); + + const results: Array<{ + target: InstallTarget; + status: "installed" | "skipped"; + detail: string; + }> = []; + + for (const target of targets) { + if (options.detectOnly && !targetLooksInstalled(target) && target !== "agents") { + results.push({ + target, + status: "skipped", + detail: "client/config directory not detected", + }); + continue; + } + + try { + if (target === "codex" || target === "claude-code") { + installCliTarget(target); + results.push({ target, status: "installed", detail: "configured via client CLI" }); + } else { + const path = installJsonTarget(target); + results.push({ target, status: "installed", detail: path }); + } + } catch (error) { + if (options.detectOnly) { + results.push({ + target, + status: "skipped", + detail: error instanceof Error ? error.message : String(error), + }); + continue; + } + throw error; + } + } + + return results; +} + +export function uninstallTargets( + targets: InstallTarget[], +): Array<{ target: InstallTarget; detail: string }> { + return targets.map((target) => { + if (target === "codex" || target === "claude-code") { + uninstallCliTarget(target); + return { target, detail: "removed via client CLI when present" }; + } + return { target, detail: uninstallJsonTarget(target) }; + }); +} + +export function removeStoredCredential(): void { + const path = userEnvPath(); + if (existsSync(path)) rmSync(path); +} From a4deacbfd9ae857493d88c45a21cb35337b8ab49 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:35:41 +0800 Subject: [PATCH 02/34] feat: add multi-client one-click installer --- src/cli.ts | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 src/cli.ts diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..08915bf --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,120 @@ +#!/usr/bin/env node +import { + INSTALL_TARGETS, + ensureCredential, + installTargets, + removeStoredCredential, + uninstallTargets, + type InstallTarget, +} from "./installer.js"; + +function printHelp(): void { + console.log(` +jev-mcp + +Usage: + jev-mcp server + jev-mcp setup [--reset] + jev-mcp install [--skip-key] + jev-mcp uninstall + jev-mcp targets + jev-mcp forget-key + +Targets: + codex OpenAI Codex CLI + VS Code extension shared MCP config + claude-code Anthropic Claude Code (user scope) + kimi Kimi Code CLI + zcode ZCode + cursor Cursor + gemini Gemini CLI + windsurf Windsurf / Devin Desktop legacy-compatible MCP config + agents Generic ~/.agents/mcp.json + vscode VS Code workspace .vscode/mcp.json + +Examples: + npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex + npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install all +`.trim()); +} + +function parseTarget(raw: string): InstallTarget { + if (!INSTALL_TARGETS.includes(raw as InstallTarget)) { + throw new Error(`Unknown target: ${raw}. Run 'jev-mcp targets'.`); + } + return raw as InstallTarget; +} + +async function main(): Promise { + const [command = "server", ...rest] = process.argv.slice(2); + + if (command === "server") { + await import("./index.js"); + return; + } + + if (command === "help" || command === "--help" || command === "-h") { + printHelp(); + return; + } + + if (command === "targets") { + console.log(INSTALL_TARGETS.join("\n")); + return; + } + + if (command === "setup") { + const path = await ensureCredential({ reset: rest.includes("--reset") }); + console.log(`Credential configured locally at ${path}. The key value was not printed.`); + return; + } + + if (command === "forget-key") { + removeStoredCredential(); + console.log("Removed the locally stored jev-mcp credential file."); + return; + } + + if (command === "install") { + const rawTarget = rest.find((arg) => !arg.startsWith("-")); + if (!rawTarget) throw new Error("Missing target. Use 'jev-mcp install '."); + const skipKey = rest.includes("--skip-key"); + + const results = + rawTarget === "all" + ? await installTargets(INSTALL_TARGETS.filter((target) => target !== "vscode"), { + skipKey, + detectOnly: true, + }) + : await installTargets([parseTarget(rawTarget)], { skipKey }); + + for (const result of results) { + const symbol = result.status === "installed" ? "✓" : "·"; + console.log(`${symbol} ${result.target}: ${result.status} — ${result.detail}`); + } + + console.log("\nRestart the configured AI client or start a new session before using Jev."); + return; + } + + if (command === "uninstall") { + const rawTarget = rest.find((arg) => !arg.startsWith("-")); + if (!rawTarget) throw new Error("Missing target. Use 'jev-mcp uninstall '."); + const targets = + rawTarget === "all" + ? INSTALL_TARGETS.filter((target) => target !== "vscode") + : [parseTarget(rawTarget)]; + + for (const result of uninstallTargets(targets)) { + console.log(`✓ ${result.target}: ${result.detail}`); + } + console.log("Stored TypeSafe credentials were left untouched. Run 'jev-mcp forget-key' to remove them."); + return; + } + + throw new Error(`Unknown command: ${command}`); +} + +main().catch((error) => { + console.error(`jev-mcp: ${error instanceof Error ? error.message : String(error)}`); + process.exitCode = 1; +}); From 562156b136a7deae027d38d6b1ae71b0e8529911 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:35:44 +0800 Subject: [PATCH 03/34] feat: add multi-client one-click installer --- test/installer.test.ts | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 test/installer.test.ts diff --git a/test/installer.test.ts b/test/installer.test.ts new file mode 100644 index 0000000..5c9a2df --- /dev/null +++ b/test/installer.test.ts @@ -0,0 +1,51 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + PACKAGE_SPEC, + SERVER_NAME, + genericMcpEntry, + jsonConfigForTarget, + serverLauncher, +} from "../src/installer.js"; + +test("POSIX launcher uses npx and the GitHub package", () => { + const launcher = serverLauncher("linux"); + assert.equal(launcher.command, "npx"); + assert.deepEqual(launcher.args, [ + "-y", + `--package=${PACKAGE_SPEC}`, + "jev-mcp", + "server", + ]); +}); + +test("Windows launcher wraps npx with cmd", () => { + const launcher = serverLauncher("win32"); + assert.equal(launcher.command, "cmd"); + assert.deepEqual(launcher.args.slice(0, 4), ["/d", "/s", "/c", "npx"]); +}); + +test("generic MCP entry never contains a TypeSafe API key", () => { + const entry = JSON.stringify(genericMcpEntry("linux")); + assert.match(entry, /jev-mcp/); + assert.doesNotMatch(entry, /TYPESAFE_API_KEY/); + assert.doesNotMatch(entry, /apikey_/); +}); + +test("Kimi configuration uses deferred loading", () => { + const config = jsonConfigForTarget("kimi", "linux"); + assert.deepEqual(config.keyPath, ["mcpServers", SERVER_NAME]); + assert.equal(config.value.deferred, true); +}); + +test("ZCode configuration uses its native user-level MCP nesting", () => { + const config = jsonConfigForTarget("zcode", "linux"); + assert.deepEqual(config.keyPath, ["mcp", "servers", SERVER_NAME]); +}); + +test("VS Code configuration is workspace-scoped", () => { + const config = jsonConfigForTarget("vscode", "linux"); + assert.deepEqual(config.keyPath, ["servers", SERVER_NAME]); + assert.match(config.path, /\.vscode[\\/]mcp\.json$/); +}); From ebdc5c7d332b970b7db33742e14c9c5948bca4e5 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:36:32 +0800 Subject: [PATCH 04/34] feat: expose jev-mcp installer CLI --- package.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d552263..96f2db6 100644 --- a/package.json +++ b/package.json @@ -22,19 +22,25 @@ "typesafe-ai", "jev", "codex", - "decision-model" + "decision-model", + "claude-code", + "kimi", + "zcode", + "cursor", + "gemini" ], "scripts": { "dev": "tsx src/index.ts", "build": "tsc -p tsconfig.json", "start": "node dist/index.js", "typecheck": "tsc -p tsconfig.json --noEmit", - "test": "node --import tsx --test test/core.test.ts", + "test": "node --import tsx --test test/core.test.ts test/installer.test.ts", "inspect": "npx @modelcontextprotocol/inspector npx tsx src/index.ts", "doctor": "node scripts/doctor.mjs", "docs:check": "node scripts/check-docs.mjs", "secrets:check": "node scripts/check-secrets.mjs", - "check": "npm run secrets:check && npm run docs:check && npm run test && npm run typecheck && npm run build" + "check": "npm run secrets:check && npm run docs:check && npm run test && npm run typecheck && npm run build", + "prepare": "npm run build" }, "dependencies": { "@modelcontextprotocol/server": "^2.0.0", @@ -44,5 +50,8 @@ "@types/node": "^24.10.1", "tsx": "^4.21.0", "typescript": "^5.8.3" + }, + "bin": { + "jev-mcp": "dist/cli.js" } } From eecb0745dd868631e2a8d1652082355a3aade622 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:36:34 +0800 Subject: [PATCH 05/34] feat: support global user credential file --- src/index.ts | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index ed6dc0b..38371ff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { existsSync, readFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { homedir } from "node:os"; import { McpServer } from "@modelcontextprotocol/server"; import { serveStdio } from "@modelcontextprotocol/server/stdio"; @@ -19,20 +20,32 @@ import { type JsonValue, } from "./core.js"; -function loadProjectEnv(): void { - const moduleDir = dirname(fileURLToPath(import.meta.url)); - const projectRoot = resolve(moduleDir, ".."); - const envPath = process.env.JEV_ENV_FILE ?? resolve(projectRoot, ".env"); - +function loadEnvFile(envPath: string): void { if (!existsSync(envPath)) return; - const parsed = parseEnvFile(readFileSync(envPath, "utf8")); for (const [key, value] of Object.entries(parsed)) { if (process.env[key] === undefined) process.env[key] = value; } } -loadProjectEnv(); +function loadEnvironment(): void { + if (process.env.JEV_ENV_FILE?.trim()) { + loadEnvFile(process.env.JEV_ENV_FILE.trim()); + return; + } + + const moduleDir = dirname(fileURLToPath(import.meta.url)); + const projectRoot = resolve(moduleDir, ".."); + const userConfigHome = + process.env.JEV_MCP_CONFIG_HOME?.trim() || resolve(homedir(), ".jev-mcp"); + + // Explicit process environment wins. A checkout-local .env has priority over + // the global installer-managed credential file. + loadEnvFile(resolve(projectRoot, ".env")); + loadEnvFile(resolve(userConfigHome, ".env")); +} + +loadEnvironment(); const stateSchema = z.union([ z.string(), From f4b4e205bb0fc653e495b9d69c8e4fa9bbde8757 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:36:37 +0800 Subject: [PATCH 06/34] chore: check global installer credentials --- scripts/doctor.mjs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/scripts/doctor.mjs b/scripts/doctor.mjs index 9833a73..adc3134 100644 --- a/scripts/doctor.mjs +++ b/scripts/doctor.mjs @@ -1,4 +1,5 @@ import { existsSync, readFileSync } from "node:fs"; +import { homedir } from "node:os"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -20,22 +21,40 @@ function fail(message) { if (major >= 20) ok(`Node ${process.version}`); else fail(`Node 20+ required; found ${process.version}`); -const envPath = process.env.JEV_ENV_FILE || resolve(root, ".env"); -if (existsSync(envPath)) { +const candidates = process.env.JEV_ENV_FILE + ? [process.env.JEV_ENV_FILE] + : [ + resolve(root, ".env"), + resolve( + process.env.JEV_MCP_CONFIG_HOME || resolve(homedir(), ".jev-mcp"), + ".env", + ), + ]; + +const envPath = candidates.find((path) => path && existsSync(path)); +if (envPath) { ok(`Environment file found: ${envPath}`); const envText = readFileSync(envPath, "utf8"); - const keyLine = envText.split(/\r?\n/).find((line) => line.trim().startsWith("TYPESAFE_API_KEY=")); - if (!keyLine) fail("TYPESAFE_API_KEY is missing from the local env file"); - else if (/=\s*(?:replace_me)?\s*$/.test(keyLine)) fail("TYPESAFE_API_KEY is still a placeholder"); + const keyLine = envText + .split(/\r?\n/) + .find((line) => line.trim().startsWith("TYPESAFE_API_KEY=")); + if (!keyLine) fail("TYPESAFE_API_KEY is missing from the environment file"); + else if (/=\s*(?:replace_me)?\s*$/.test(keyLine)) + fail("TYPESAFE_API_KEY is still a placeholder"); else ok("TYPESAFE_API_KEY is configured (value not printed)"); } else if (process.env.TYPESAFE_API_KEY) { ok("TYPESAFE_API_KEY is available from the process environment (value not printed)"); } else { - warn("No .env file or process TYPESAFE_API_KEY found. The server can build, but live Jev calls will fail."); + warn( + "No project/user env file or process TYPESAFE_API_KEY found. Live Jev calls will fail until configured.", + ); } if (existsSync(resolve(root, "dist/index.js"))) ok("Built server found at dist/index.js"); else warn("dist/index.js not found; run `npm run build`"); +if (existsSync(resolve(root, "dist/cli.js"))) ok("Installer CLI found at dist/cli.js"); +else warn("dist/cli.js not found; run `npm run build`"); + console.log("\nNo network request was made. Run MCP Inspector for an explicit live test."); process.exit(failed ? 1 : 0); From a6e5eff9cc5c5f265a2dc28d0c2691cddc97d094 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:38:37 +0800 Subject: [PATCH 07/34] docs: add one-click installation guide --- docs/INSTALLATION.md | 201 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 docs/INSTALLATION.md diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md new file mode 100644 index 0000000..f098cb2 --- /dev/null +++ b/docs/INSTALLATION.md @@ -0,0 +1,201 @@ +# Installation + +`jev-mcp` can be installed into supported AI coding clients without cloning +this repository or manually editing MCP configuration. + +## Prerequisites + +- Node.js 20+ with `npm` / `npx` +- a TypeSafe API key / applicable TypeSafe access and credits +- the target AI client installed when the installer uses that client's CLI + +The first install prompts for the TypeSafe API key with hidden terminal input +and stores it at: + +```text +~/.jev-mcp/.env +``` + +On POSIX systems the installer attempts to use mode `0600`. The key is **not** +written into Codex, Claude Code, Cursor, Kimi, ZCode, Gemini, Windsurf, VS Code, +or generic MCP configuration. + +Existing JSON configuration files are preserved and backed up to a sibling +`.jev-mcp.bak` file before modification. + +## One-command installation + +Use the same command on macOS, Linux, and Windows PowerShell: + +### Codex + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex +``` + +The installer uses `codex mcp add`. Codex CLI and the Codex IDE extension +share MCP configuration, so this configures both surfaces. + +### Claude Code + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install claude-code +``` + +The server is added at Claude Code user scope. + +### Kimi Code + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install kimi +``` + +This updates `~/.kimi-code/mcp.json` and enables deferred MCP loading so the +tools can be loaded on demand. + +### ZCode + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install zcode +``` + +This updates ZCode's user-level `~/.zcode/cli/config.json`. + +### Cursor + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install cursor +``` + +This updates `~/.cursor/mcp.json`. + +If you prefer Cursor's official deeplink flow, configure the local credential +once: + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp setup +``` + +Then use: + +[Add Jev MCP to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=jev&config=eyJqZXYiOnsidHlwZSI6InN0ZGlvIiwiY29tbWFuZCI6Im5weCIsImFyZ3MiOlsiLXkiLCItLXBhY2thZ2U9Z2l0aHViOkFmbG9hdDE2L2pldi1tY3AjbWFpbiIsImpldi1tY3AiLCJzZXJ2ZXIiXX19) + +### Gemini CLI + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install gemini +``` + +This updates `~/.gemini/settings.json`. + +### Windsurf / Devin Desktop compatible config + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install windsurf +``` + +This writes the MCP entry to the Windsurf-compatible user configuration at +`~/.codeium/windsurf/mcp_config.json`. Existing installations that retain +this configuration layout can use the server after restart. + +### Generic `.agents` + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install agents +``` + +This updates `~/.agents/mcp.json`. ZCode can use/import this industry-style +configuration when its native MCP configuration does not override it. + +### VS Code / GitHub Copilot Agent mode + +Run from the project that should receive the MCP server: + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install vscode +``` + +This updates the project-level `.vscode/mcp.json`. + +## Configure every detected client + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install all +``` + +`all` configures detected user-level clients and the generic `.agents` +configuration. It intentionally skips the VS Code target because that target is +workspace-scoped; run `install vscode` explicitly from the desired project. + +## Credential-only setup + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp setup +``` + +Rotate/replace the locally stored key: + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp setup --reset +``` + +If `TYPESAFE_API_KEY` is already present in the environment, the installer can +persist that value locally without printing it. + +For an environment-managed credential where you do not want the installer to +create `~/.jev-mcp/.env`, use: + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex --skip-key +``` + +The MCP subprocess must then receive `TYPESAFE_API_KEY` through its environment. + +## Uninstall + +Remove one client integration: + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp uninstall cursor +``` + +Remove all supported user-level integrations: + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp uninstall all +``` + +Uninstalling client configuration intentionally leaves the locally stored +TypeSafe credential untouched. Remove it separately: + +```bash +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp forget-key +``` + +## How the launcher works + +Installed clients start Jev through npm's remote-package execution: + +```text +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp server +``` + +npm supports GitHub repositories as package specs. The repository exposes a +`jev-mcp` binary and builds its TypeScript during Git-based package +installation. + +The repository currently tracks `main` because the project remains pre-1.0. +A future stable release can replace `#main` with a version tag. + +## Official client behavior referenced by the installer + +- Codex supports local stdio MCP servers through `codex mcp add ... -- `. +- Claude Code supports local stdio servers and user scope through `claude mcp add`. +- Kimi Code uses user-level `~/.kimi-code/mcp.json` and supports deferred tools. +- ZCode uses `~/.zcode/cli/config.json` and can import MCP servers from Codex, + Claude Code, OpenCode, and generic `.agents`. +- Cursor uses `~/.cursor/mcp.json` and supports MCP install deeplinks. +- Gemini CLI uses `~/.gemini/settings.json` and its `mcpServers` object. + +Because client configuration formats can evolve, the installer is covered by CI +and should be updated when upstream client documentation changes. From 61d6affc2a3607f1645c5deb9f673d4b17326092 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:38:39 +0800 Subject: [PATCH 08/34] docs: add one-click installation guide --- docs/CLIENTS.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 docs/CLIENTS.md diff --git a/docs/CLIENTS.md b/docs/CLIENTS.md new file mode 100644 index 0000000..c5f0c17 --- /dev/null +++ b/docs/CLIENTS.md @@ -0,0 +1,39 @@ +# Supported AI clients + +The one-click installer currently targets the following MCP clients. + +| Target | Installer ID | Scope | Method | +| --- | --- | --- | --- | +| OpenAI Codex CLI + IDE extension | `codex` | user | official `codex mcp` CLI | +| Anthropic Claude Code | `claude-code` | user | official `claude mcp` CLI | +| Kimi Code | `kimi` | user | `~/.kimi-code/mcp.json` | +| ZCode | `zcode` | user | `~/.zcode/cli/config.json` | +| Cursor | `cursor` | user | `~/.cursor/mcp.json` | +| Gemini CLI | `gemini` | user | `~/.gemini/settings.json` | +| Windsurf / compatible Devin Desktop migration | `windsurf` | user | Windsurf MCP config | +| Generic agents config | `agents` | user | `~/.agents/mcp.json` | +| VS Code / Copilot Agent | `vscode` | project | `.vscode/mcp.json` | + +## Design rules + +The installer: + +- never embeds the TypeSafe API key in an AI client's MCP config; +- writes the key only to the local jev-mcp credential file unless `--skip-key` + is used; +- backs up existing JSON configuration before modification; +- preserves unrelated keys in existing JSON configuration; +- uses the client CLI where that is the safer documented integration path; +- uses a Windows `cmd /c npx ...` wrapper for local stdio startup when needed; +- never sends a TypeSafe API request during installation. + +## New client requests + +When adding another client, prefer in this order: + +1. an official client CLI for MCP registration; +2. an officially documented user-level configuration file; +3. an industry-standard generic MCP file; +4. a clearly labeled compatibility path when no first-party mechanism exists. + +Every new target should include a no-secret unit test and documentation link. From 36fd807ff074bef437869bd8e1304ba4db80b6d6 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:40:03 +0800 Subject: [PATCH 09/34] docs: add one-command install entry points --- README.md | 57 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 51b967f..e1f867c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ **Unofficial, community-maintained MCP server for TypeSafe AI Jev.** -[简体中文](README.zh-CN.md) · [Quick start](docs/QUICKSTART.md) · [Examples](examples/README.md) · [Security](SECURITY.md) · [FAQ](docs/FAQ.md) +[简体中文](README.zh-CN.md) · [One-click install](docs/INSTALLATION.md) · [Quick start](docs/QUICKSTART.md) · [Examples](examples/README.md) · [Security](SECURITY.md) · [FAQ](docs/FAQ.md) `jev-mcp` exposes TypeSafe AI's Jev decision model as four conservative, read-only MCP tools for **bounded probabilistic decisions**. @@ -92,15 +92,50 @@ Successful responses include local metadata similar to: That metadata is added by this MCP server; it is not Jev model output. -## 60-second install +## One-command install -Requirements: +No clone and no manual MCP JSON/TOML editing is required. -- Node.js 20+ -- a TypeSafe API key / applicable TypeSafe access and credits -- an MCP host that can launch a local stdio server +```bash +# Codex +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex + +# Claude Code +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install claude-code + +# Kimi Code +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install kimi + +# ZCode +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install zcode + +# Cursor +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install cursor + +# Gemini CLI +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install gemini + +# Configure every detected user-level client +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install all +``` -Clone and set up: +On the first install, the CLI asks for the TypeSafe API key with **hidden +terminal input** and stores it only in `~/.jev-mcp/.env`. The key is not +inserted into client MCP configuration. + +The installer preserves unrelated JSON settings and creates a +`.jev-mcp.bak` backup before changing an existing JSON config. + +Supported targets include Codex, Claude Code, Kimi Code, ZCode, Cursor, +Gemini CLI, Windsurf-compatible config, generic `.agents/mcp.json`, and +project-scoped VS Code/Copilot Agent configuration. + +See [Installation](docs/INSTALLATION.md) for all commands, uninstall steps, +Windows behavior, and the Cursor deeplink. + +### Source install + +For contributors or users who prefer a local checkout: ```bash git clone https://github.com/Afloat16/jev-mcp.git @@ -116,12 +151,6 @@ cd jev-mcp ./setup.ps1 ``` -The setup script reads the API key without echoing it, stores it only in the -local gitignored `.env` file when needed, installs dependencies, and runs local -checks. - -For a more explicit walkthrough, see [Quick start](docs/QUICKSTART.md). - ## Codex configuration Add this to `~/.codex/config.toml` and replace the path: @@ -227,6 +256,8 @@ should be documented in [CHANGELOG.md](CHANGELOG.md) and migration notes. ## Documentation +- [One-click installation](docs/INSTALLATION.md) +- [Supported AI clients](docs/CLIENTS.md) - [Quick start](docs/QUICKSTART.md) - [Architecture](docs/ARCHITECTURE.md) - [Security and privacy model](docs/SECURITY-MODEL.md) From 7c8df4af1ab23a86410d2b5f002c1199694f5e41 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:40:08 +0800 Subject: [PATCH 10/34] docs: add Chinese one-command install guide --- README.zh-CN.md | 54 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/README.zh-CN.md b/README.zh-CN.md index 5491cdf..a87c3f8 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -7,7 +7,7 @@ **非官方、社区维护的 TypeSafe AI Jev MCP Server。** -[English](README.md) · [快速开始](docs/QUICKSTART.md) · [示例](examples/README.md) · [安全说明](SECURITY.md) · [FAQ](docs/FAQ.md) +[English](README.md) · [一键安装](docs/INSTALLATION.md) · [快速开始](docs/QUICKSTART.md) · [示例](examples/README.md) · [安全说明](SECURITY.md) · [FAQ](docs/FAQ.md) `jev-mcp` 将 TypeSafe AI 的 Jev 决策模型暴露为 4 个保守、只读的 MCP 工具, 用于**边界明确的概率决策**。 @@ -72,15 +72,49 @@ API key 只放在本地进程环境变量或被 Git 忽略的 `.env` 文件中 四个工具均声明为只读,不会修改文件、执行 shell、部署基础设施,也不会切换你选择的模型。 -## 60 秒安装 +## 一条命令安装 -要求: +无需 clone 仓库,也无需手工修改 MCP JSON/TOML: -- Node.js 20+ -- TypeSafe API key / 可用的 TypeSafe 账户与额度 -- 支持启动本地 stdio MCP server 的客户端 +```bash +# Codex +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex + +# Claude Code +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install claude-code + +# Kimi Code +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install kimi + +# ZCode +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install zcode + +# Cursor +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install cursor + +# Gemini CLI +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install gemini + +# 自动配置检测到的用户级客户端 +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install all +``` -macOS / Linux: +第一次安装时会在终端中**隐藏输入** TypeSafe API key,只保存到本机 +`~/.jev-mcp/.env`,不会把 key 写进 Codex、Claude Code、Cursor、Kimi、 +ZCode、Gemini 等 MCP 配置。 + +修改已有 JSON 配置前会保留 `.jev-mcp.bak` 备份,并尽量保留其他设置。 + +目前支持 Codex、Claude Code、Kimi Code、ZCode、Cursor、Gemini CLI、 +Windsurf 兼容配置、通用 `.agents/mcp.json`,以及项目级 VS Code/Copilot +Agent 配置。 + +完整命令、卸载方式和 Cursor deeplink 见 +[安装说明](docs/INSTALLATION.md)。 + +### 从源码安装 + +适合贡献者或希望固定本地 checkout 的用户: ```bash git clone https://github.com/Afloat16/jev-mcp.git @@ -96,10 +130,6 @@ cd jev-mcp ./setup.ps1 ``` -安装脚本会静默读取 API key,需要时写入本地 gitignored `.env`,安装依赖并运行本地检查。 - -更详细步骤见 [快速开始](docs/QUICKSTART.md)。 - ## Codex 配置 加入 `~/.codex/config.toml`,并替换绝对路径: @@ -188,6 +218,8 @@ npm run inspect ## 文档 +- [一键安装](docs/INSTALLATION.md) +- [支持的 AI 客户端](docs/CLIENTS.md) - [快速开始](docs/QUICKSTART.md) - [架构](docs/ARCHITECTURE.md) - [安全与隐私模型](docs/SECURITY-MODEL.md) From c04f5ffd71b54142e2a99e71f085b0cbc076eb84 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:40:11 +0800 Subject: [PATCH 11/34] docs: make one-click installer the quick start --- docs/QUICKSTART.md | 108 ++++++++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index 3e8c413..ce766fe 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -1,14 +1,14 @@ # Quick start -This guide gets a local `jev-mcp` server running without putting a TypeSafe -credential in your MCP client configuration. +The fastest path is the one-command installer. It downloads the package from +GitHub through npm, configures the selected MCP client, and stores the TypeSafe +credential outside the client config. ## 1. Requirements -- Node.js 20+ -- Git +- Node.js 20+ with npm/npx - a TypeSafe API key / applicable TypeSafe access and credits -- an MCP host that can launch a local stdio server +- the target AI client Check Node: @@ -16,78 +16,64 @@ Check Node: node --version ``` -## 2. Clone +## 2. Install into your client -```bash -git clone https://github.com/Afloat16/jev-mcp.git -cd jev-mcp -``` - -## 3. Configure the local credential - -macOS / Linux: +Example for Codex: ```bash -./setup.sh +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex ``` -Windows PowerShell: +Other target IDs: -```powershell -./setup.ps1 +```text +claude-code +kimi +zcode +cursor +gemini +windsurf +agents +vscode ``` -The setup script reads the key without echoing it and, when needed, stores it -in a local `.env` file that is ignored by Git. - -Do not paste a real key into README files, `AGENTS.md`, MCP configuration, -issues, screenshots, or shell history. - -You may instead provide `TYPESAFE_API_KEY` through your own process -environment. Existing process variables override values in `.env`. - -## 4. Verify locally - -These checks do not call the TypeSafe API: +Or configure every detected user-level client: ```bash -npm run doctor -npm run check +npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install all ``` -Expected result: configuration checks, tests, type checking, and build succeed. +The first install asks for the TypeSafe API key using hidden terminal input. +It is stored in: -## 5. Configure Codex +```text +~/.jev-mcp/.env +``` -Add this to `~/.codex/config.toml` and replace the path: +The key is not placed in the target client's MCP configuration. -```toml -[mcp_servers.jev] -command = "node" -args = ["/ABSOLUTE/PATH/TO/jev-mcp/dist/index.js"] -``` +See [INSTALLATION.md](INSTALLATION.md) for platform-specific details. -Restart Codex / start a new session. +## 3. Restart the AI client -Other MCP clients can use the same executable through their local stdio-server -configuration. +MCP tool discovery normally happens when a new client/session starts. Restart +the client or open a new session after installation. -## 6. Start with a safe test +## 4. Start with a safe synthetic test -Use a synthetic decision first. For example, ask the host to obtain a second -opinion between: +Ask the host model to get a second opinion on a bounded decision such as: -- retry once; -- roll back; -- escalate for review. +```text +retry / rollback / escalate +``` -Do not use production data or credentials as test input. +Do not use production secrets or customer data as test input. Examples are available in [../examples/README.md](../examples/README.md). -## 7. Understand the trust model +## 5. Trust model -Jev is advisory. The intended priority order is: +Jev remains advisory: ```text deterministic evidence @@ -97,6 +83,18 @@ host-model repository-aware reasoning Jev probabilistic advice ``` -Anything placed in `state` is sent to the configured TypeSafe API endpoint. -Read [SECURITY-MODEL.md](SECURITY-MODEL.md) before using the tool with sensitive -projects. +Anything placed in Jev `state` is sent to the configured TypeSafe API +endpoint. Read [SECURITY-MODEL.md](SECURITY-MODEL.md) before using the tool with +sensitive projects. + +## Source checkout alternative + +If you are contributing to the project or want a pinned local checkout: + +```bash +git clone https://github.com/Afloat16/jev-mcp.git +cd jev-mcp +./setup.sh +``` + +On Windows PowerShell use `./setup.ps1`. From 2acb86a11ecb99bf2ba818735daef836cd3d1637 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:40:13 +0800 Subject: [PATCH 12/34] test: add installer CLI smoke check --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 96f2db6..6d05126 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,9 @@ "doctor": "node scripts/doctor.mjs", "docs:check": "node scripts/check-docs.mjs", "secrets:check": "node scripts/check-secrets.mjs", - "check": "npm run secrets:check && npm run docs:check && npm run test && npm run typecheck && npm run build", - "prepare": "npm run build" + "check": "npm run secrets:check && npm run docs:check && npm run test && npm run typecheck && npm run build && npm run cli:smoke", + "prepare": "npm run build", + "cli:smoke": "node dist/cli.js targets" }, "dependencies": { "@modelcontextprotocol/server": "^2.0.0", From 117ff49e311947d4705ac0b30332705c4bfbb8d3 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:40:15 +0800 Subject: [PATCH 13/34] docs: record multi-client installer --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcacd48..fae5d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable project changes are documented here. ## Unreleased +- Added a GitHub-backed `jev-mcp` executable that can run directly through + `npx` without cloning the repository. +- Added one-command installers for Codex, Claude Code, Kimi Code, ZCode, + Cursor, Gemini CLI, Windsurf-compatible config, generic `.agents`, and + project-scoped VS Code MCP configuration. +- Added a user-level `~/.jev-mcp/.env` credential store so GUI clients do not + need API keys embedded in MCP configuration. +- Added safe JSON config merging with local backups and platform-aware Windows + stdio launching. +- Added installer unit tests, CLI smoke checks, and dedicated client/install + documentation. + - Redesigned the English and Chinese project homepages around quick onboarding, tool boundaries, privacy, and a clear host-model/Jev authority model. - Added quick-start, FAQ, troubleshooting, examples, support, governance, From 9ca03504ad87bed28059da03145aea81602ba687 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:41:38 +0800 Subject: [PATCH 14/34] fix: allow documented application deeplinks --- scripts/check-docs.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs index 4d3f065..e15595c 100644 --- a/scripts/check-docs.mjs +++ b/scripts/check-docs.mjs @@ -22,7 +22,7 @@ for (const file of markdownFiles) { if ( !target || target.startsWith("#") || - /^(?:https?:|mailto:)/i.test(target) + /^(?:https?:|mailto:|cursor:|vscode:)/i.test(target) ) { continue; } From 9cacad3bb83f30b53654613e651187d13a564a09 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:42:45 +0800 Subject: [PATCH 15/34] test: verify GitHub-backed one-click installer --- .github/workflows/ci.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cef46ac..11df5f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,3 +37,33 @@ jobs: - name: Repository checks run: npm run check + + + installer-smoke: + name: GitHub package installer + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version: 20.x + + - name: Run installer from GitHub package in isolated home + shell: bash + env: + PACKAGE_REF: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + set -euo pipefail + temp_home="$(mktemp -d)" + HOME="$temp_home" npx -y \ + --package="github:Afloat16/jev-mcp#${PACKAGE_REF}" \ + jev-mcp install cursor --skip-key + + test -f "$temp_home/.cursor/mcp.json" + grep -q '"jev"' "$temp_home/.cursor/mcp.json" + ! grep -q 'TYPESAFE_API_KEY' "$temp_home/.cursor/mcp.json" From 1b6a39db60023a1ace02bf79ec7a2508f633c4e3 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:45:56 +0800 Subject: [PATCH 16/34] fix: use durable local runtime instead of npm GitFetcher --- src/installer.ts | 55 ++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/installer.ts b/src/installer.ts index dff5ddd..3be60c4 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -11,10 +11,10 @@ import { } from "node:fs"; import { homedir } from "node:os"; import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; import { parseEnvFile } from "./core.js"; -export const PACKAGE_SPEC = "github:Afloat16/jev-mcp#main"; export const SERVER_NAME = "jev"; export type InstallTarget = @@ -50,35 +50,34 @@ export function userEnvPath(): string { return join(configHome(), ".env"); } -export function serverLauncher(platform = process.platform): { - command: string; - args: string[]; -} { - const args = [ - "-y", - `--package=${PACKAGE_SPEC}`, - "jev-mcp", - "server", - ]; - - if (platform === "win32") { - return { - command: "cmd", - args: ["/d", "/s", "/c", "npx", ...args], - }; - } +export function runtimeRoot(): string { + const explicit = process.env.JEV_MCP_RUNTIME_DIR?.trim(); + if (explicit) return resolve(explicit); - return { command: "npx", args }; + const moduleDir = dirname(fileURLToPath(import.meta.url)); + return resolve(moduleDir, ".."); } -export function genericMcpEntry(platform = process.platform): JsonObject { - const launcher = serverLauncher(platform); +export function serverLauncher( + runtime = runtimeRoot(), + nodeExecutable = process.execPath, +): { + command: string; + args: string[]; +} { return { - command: launcher.command, - args: launcher.args, + command: nodeExecutable, + args: [resolve(runtime, "dist", "cli.js"), "server"], }; } +export function genericMcpEntry( + runtime = runtimeRoot(), + nodeExecutable = process.execPath, +): JsonObject { + return serverLauncher(runtime, nodeExecutable); +} + function isObject(value: unknown): value is JsonObject { return typeof value === "object" && value !== null && !Array.isArray(value); } @@ -124,14 +123,11 @@ function deleteNested(root: JsonObject, path: string[]): boolean { export function jsonConfigForTarget( target: Exclude, - platform = process.platform, + runtime = runtimeRoot(), + nodeExecutable = process.execPath, ): { path: string; keyPath: string[]; value: JsonObject } { const home = homedir(); - const launcher = serverLauncher(platform); - const base = { - command: launcher.command, - args: launcher.args, - }; + const base = serverLauncher(runtime, nodeExecutable); switch (target) { case "kimi": @@ -378,7 +374,6 @@ function targetLooksInstalled(target: InstallTarget): boolean { if (target === "claude-code") return commandExists("claude"); const config = jsonConfigForTarget(target); - if (target === "vscode") return existsSync(dirname(config.path)); return existsSync(dirname(config.path)); } From a7cb6967808350123df92def10a252060a73c56e Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:48:47 +0800 Subject: [PATCH 17/34] feat: add Unix one-click bootstrap installer --- scripts/install.sh | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 scripts/install.sh diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100644 index 0000000..603ac1a --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -euo pipefail + +target="${1:-all}" +if [ "$#" -gt 0 ]; then + shift +fi + +for command in git node npm; do + if ! command -v "$command" >/dev/null 2>&1; then + echo "jev-mcp installer: $command is required." >&2 + exit 1 + fi +done + +node_major="$(node -p 'process.versions.node.split(".")[0]')" +if [ "$node_major" -lt 20 ]; then + echo "jev-mcp installer: Node.js 20+ is required; found $(node -v)." >&2 + exit 1 +fi + +config_home="${JEV_MCP_CONFIG_HOME:-$HOME/.jev-mcp}" +runtime="${JEV_MCP_RUNTIME_DIR:-$config_home/runtime}" +repo_url="${JEV_MCP_REPO_URL:-https://github.com/Afloat16/jev-mcp.git}" +git_ref="${JEV_MCP_GIT_REF:-main}" + +mkdir -p "$config_home" +chmod 700 "$config_home" 2>/dev/null || true + +if [ -e "$runtime" ] && [ ! -d "$runtime/.git" ]; then + echo "jev-mcp installer: $runtime exists but is not a jev-mcp Git checkout." >&2 + echo "Set JEV_MCP_RUNTIME_DIR to another directory or remove that path." >&2 + exit 1 +fi + +if [ ! -d "$runtime/.git" ]; then + echo "Installing jev-mcp runtime into $runtime" + git clone --filter=blob:none --no-checkout "$repo_url" "$runtime" +else + echo "Updating existing jev-mcp runtime in $runtime" +fi + +git -C "$runtime" fetch --prune origin "$git_ref" +git -C "$runtime" checkout --detach FETCH_HEAD + +( + cd "$runtime" + npm ci --no-audit --no-fund + npm run build +) + +export JEV_MCP_RUNTIME_DIR="$runtime" +node "$runtime/dist/cli.js" install "$target" "$@" + +echo +echo "jev-mcp runtime: $runtime" +echo "Restart the configured AI client or start a new session before using Jev." From f4802fb18d42de48ed7e865172a7b635008efe1b Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:48:49 +0800 Subject: [PATCH 18/34] feat: add Windows one-click bootstrap installer --- scripts/install.ps1 | 85 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 scripts/install.ps1 diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 0000000..9fe5009 --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,85 @@ +param( + [string]$Target = "all", + [switch]$SkipKey +) + +$ErrorActionPreference = "Stop" + +foreach ($command in @("git", "node", "npm")) { + if (-not (Get-Command $command -ErrorAction SilentlyContinue)) { + throw "jev-mcp installer: $command is required." + } +} + +$nodeMajor = [int](& node -p 'process.versions.node.split(".")[0]') +if ($nodeMajor -lt 20) { + throw "jev-mcp installer: Node.js 20+ is required; found $(& node -v)." +} + +$configHome = if ([string]::IsNullOrWhiteSpace($env:JEV_MCP_CONFIG_HOME)) { + Join-Path $HOME ".jev-mcp" +} else { + $env:JEV_MCP_CONFIG_HOME +} + +$runtime = if ([string]::IsNullOrWhiteSpace($env:JEV_MCP_RUNTIME_DIR)) { + Join-Path $configHome "runtime" +} else { + $env:JEV_MCP_RUNTIME_DIR +} + +$repoUrl = if ([string]::IsNullOrWhiteSpace($env:JEV_MCP_REPO_URL)) { + "https://github.com/Afloat16/jev-mcp.git" +} else { + $env:JEV_MCP_REPO_URL +} + +$gitRef = if ([string]::IsNullOrWhiteSpace($env:JEV_MCP_GIT_REF)) { + "main" +} else { + $env:JEV_MCP_GIT_REF +} + +New-Item -ItemType Directory -Force -Path $configHome | Out-Null + +if ((Test-Path $runtime) -and -not (Test-Path (Join-Path $runtime ".git"))) { + throw "jev-mcp installer: $runtime exists but is not a jev-mcp Git checkout." +} + +if (-not (Test-Path (Join-Path $runtime ".git"))) { + Write-Host "Installing jev-mcp runtime into $runtime" + & git clone --filter=blob:none --no-checkout $repoUrl $runtime + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +} else { + Write-Host "Updating existing jev-mcp runtime in $runtime" +} + +& git -C $runtime fetch --prune origin $gitRef +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +& git -C $runtime checkout --detach FETCH_HEAD +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +Push-Location $runtime +try { + & npm ci --no-audit --no-fund + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + & npm run build + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +} finally { + Pop-Location +} + +$previousRuntime = $env:JEV_MCP_RUNTIME_DIR +$env:JEV_MCP_RUNTIME_DIR = $runtime +try { + $args = @((Join-Path $runtime "dist/cli.js"), "install", $Target) + if ($SkipKey) { $args += "--skip-key" } + & node @args + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +} finally { + $env:JEV_MCP_RUNTIME_DIR = $previousRuntime +} + +Write-Host "" +Write-Host "jev-mcp runtime: $runtime" +Write-Host "Restart the configured AI client or start a new session before using Jev." From 5ebf3c0aea5f4d034a4e6d6ddfc7b145c21ca478 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:48:54 +0800 Subject: [PATCH 19/34] test: cover durable local launcher --- test/installer.test.ts | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/test/installer.test.ts b/test/installer.test.ts index 5c9a2df..09f9273 100644 --- a/test/installer.test.ts +++ b/test/installer.test.ts @@ -1,51 +1,52 @@ import assert from "node:assert/strict"; +import { resolve } from "node:path"; import test from "node:test"; import { - PACKAGE_SPEC, SERVER_NAME, genericMcpEntry, jsonConfigForTarget, serverLauncher, } from "../src/installer.js"; -test("POSIX launcher uses npx and the GitHub package", () => { - const launcher = serverLauncher("linux"); - assert.equal(launcher.command, "npx"); - assert.deepEqual(launcher.args, [ - "-y", - `--package=${PACKAGE_SPEC}`, - "jev-mcp", - "server", - ]); -}); +const runtime = resolve("/tmp", "jev-mcp-runtime"); +const nodeExecutable = resolve("/opt", "node", "bin", "node"); -test("Windows launcher wraps npx with cmd", () => { - const launcher = serverLauncher("win32"); - assert.equal(launcher.command, "cmd"); - assert.deepEqual(launcher.args.slice(0, 4), ["/d", "/s", "/c", "npx"]); +test("launcher uses a durable local runtime and explicit Node executable", () => { + const launcher = serverLauncher(runtime, nodeExecutable); + assert.equal(launcher.command, nodeExecutable); + assert.deepEqual(launcher.args, [resolve(runtime, "dist", "cli.js"), "server"]); }); test("generic MCP entry never contains a TypeSafe API key", () => { - const entry = JSON.stringify(genericMcpEntry("linux")); - assert.match(entry, /jev-mcp/); + const entry = JSON.stringify(genericMcpEntry(runtime, nodeExecutable)); + assert.match(entry, /dist/); + assert.match(entry, /cli\.js/); assert.doesNotMatch(entry, /TYPESAFE_API_KEY/); assert.doesNotMatch(entry, /apikey_/); }); test("Kimi configuration uses deferred loading", () => { - const config = jsonConfigForTarget("kimi", "linux"); + const config = jsonConfigForTarget("kimi", runtime, nodeExecutable); assert.deepEqual(config.keyPath, ["mcpServers", SERVER_NAME]); assert.equal(config.value.deferred, true); }); test("ZCode configuration uses its native user-level MCP nesting", () => { - const config = jsonConfigForTarget("zcode", "linux"); + const config = jsonConfigForTarget("zcode", runtime, nodeExecutable); assert.deepEqual(config.keyPath, ["mcp", "servers", SERVER_NAME]); }); +test("Cursor config uses stdio and the durable runtime", () => { + const config = jsonConfigForTarget("cursor", runtime, nodeExecutable); + assert.deepEqual(config.keyPath, ["mcpServers", SERVER_NAME]); + assert.equal(config.value.command, nodeExecutable); + assert.deepEqual(config.value.args, [resolve(runtime, "dist", "cli.js"), "server"]); + assert.doesNotMatch(JSON.stringify(config.value), /TYPESAFE_API_KEY/); +}); + test("VS Code configuration is workspace-scoped", () => { - const config = jsonConfigForTarget("vscode", "linux"); + const config = jsonConfigForTarget("vscode", runtime, nodeExecutable); assert.deepEqual(config.keyPath, ["servers", SERVER_NAME]); assert.match(config.path, /\.vscode[\\/]mcp\.json$/); }); From 00c7767dc17e96f717702baffb553a7d53b9b146 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:48:56 +0800 Subject: [PATCH 20/34] docs: update CLI bootstrap help --- src/cli.ts | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 08915bf..d5010b6 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -4,6 +4,7 @@ import { ensureCredential, installTargets, removeStoredCredential, + runtimeRoot, uninstallTargets, type InstallTarget, } from "./installer.js"; @@ -18,22 +19,28 @@ Usage: jev-mcp install [--skip-key] jev-mcp uninstall jev-mcp targets + jev-mcp runtime jev-mcp forget-key Targets: codex OpenAI Codex CLI + VS Code extension shared MCP config claude-code Anthropic Claude Code (user scope) - kimi Kimi Code CLI + kimi Kimi Code zcode ZCode cursor Cursor gemini Gemini CLI - windsurf Windsurf / Devin Desktop legacy-compatible MCP config + windsurf Windsurf-compatible MCP config agents Generic ~/.agents/mcp.json vscode VS Code workspace .vscode/mcp.json -Examples: - npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex - npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install all +Recommended bootstrap: + macOS/Linux: + curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- codex + + Windows PowerShell: + & ([scriptblock]::Create((irm https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.ps1))) -Target codex + +After bootstrap, the local CLI lives under ~/.jev-mcp/runtime. `.trim()); } @@ -62,6 +69,11 @@ async function main(): Promise { return; } + if (command === "runtime") { + console.log(runtimeRoot()); + return; + } + if (command === "setup") { const path = await ensureCredential({ reset: rest.includes("--reset") }); console.log(`Credential configured locally at ${path}. The key value was not printed.`); @@ -107,7 +119,9 @@ async function main(): Promise { for (const result of uninstallTargets(targets)) { console.log(`✓ ${result.target}: ${result.detail}`); } - console.log("Stored TypeSafe credentials were left untouched. Run 'jev-mcp forget-key' to remove them."); + console.log( + "Stored TypeSafe credentials were left untouched. Run 'jev-mcp forget-key' to remove them.", + ); return; } From 0ed8d819525b73ed35ccd3b608fd77b7ab540007 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:48:58 +0800 Subject: [PATCH 21/34] chore: remove Git-package prepare hook --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 6d05126..8a7444d 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "docs:check": "node scripts/check-docs.mjs", "secrets:check": "node scripts/check-secrets.mjs", "check": "npm run secrets:check && npm run docs:check && npm run test && npm run typecheck && npm run build && npm run cli:smoke", - "prepare": "npm run build", "cli:smoke": "node dist/cli.js targets" }, "dependencies": { From f07e01b72021262b20402d3d9667f6094995683e Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:49:46 +0800 Subject: [PATCH 22/34] fix: preserve hidden key input for piped installer --- scripts/install.sh | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 603ac1a..35a6e2b 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -50,7 +50,26 @@ git -C "$runtime" checkout --detach FETCH_HEAD ) export JEV_MCP_RUNTIME_DIR="$runtime" -node "$runtime/dist/cli.js" install "$target" "$@" + +skip_key=false +for arg in "$@"; do + if [ "$arg" = "--skip-key" ]; then + skip_key=true + break + fi +done + +if [ "$skip_key" = false ] && [ -z "${TYPESAFE_API_KEY:-}" ] && [ ! -t 0 ]; then + if [ -e /dev/tty ]; then + node "$runtime/dist/cli.js" install "$target" "$@" < /dev/tty + else + echo "jev-mcp installer: interactive key entry needs a TTY." >&2 + echo "Set TYPESAFE_API_KEY in the environment or rerun from an interactive terminal." >&2 + exit 1 + fi +else + node "$runtime/dist/cli.js" install "$target" "$@" +fi echo echo "jev-mcp runtime: $runtime" From eaaa7f4d7431ced0031f33ee25383a0dd257b539 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:50:51 +0800 Subject: [PATCH 23/34] docs: document durable bootstrap installers --- docs/INSTALLATION.md | 204 +++++++++++++++++++++---------------------- 1 file changed, 101 insertions(+), 103 deletions(-) diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index f098cb2..6019661 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -1,201 +1,199 @@ # Installation -`jev-mcp` can be installed into supported AI coding clients without cloning -this repository or manually editing MCP configuration. +`jev-mcp` provides a bootstrap installer for major MCP-capable AI coding +clients. Users do not need to clone this repository or manually edit JSON/TOML. ## Prerequisites -- Node.js 20+ with `npm` / `npx` +- Node.js 20+ +- Git +- npm (bundled with Node.js) - a TypeSafe API key / applicable TypeSafe access and credits -- the target AI client installed when the installer uses that client's CLI -The first install prompts for the TypeSafe API key with hidden terminal input -and stores it at: +The runtime is installed to: ```text -~/.jev-mcp/.env +~/.jev-mcp/runtime ``` -On POSIX systems the installer attempts to use mode `0600`. The key is **not** -written into Codex, Claude Code, Cursor, Kimi, ZCode, Gemini, Windsurf, VS Code, -or generic MCP configuration. +The TypeSafe credential is stored separately at: -Existing JSON configuration files are preserved and backed up to a sibling -`.jev-mcp.bak` file before modification. +```text +~/.jev-mcp/.env +``` -## One-command installation +The key is entered with hidden terminal input and is **not** written into the +AI client's MCP configuration. -Use the same command on macOS, Linux, and Windows PowerShell: +Existing JSON client configs are backed up to a sibling `.jev-mcp.bak` before +they are changed. + +## macOS / Linux ### Codex ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- codex ``` -The installer uses `codex mcp add`. Codex CLI and the Codex IDE extension -share MCP configuration, so this configures both surfaces. - ### Claude Code ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install claude-code +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- claude-code ``` -The server is added at Claude Code user scope. - ### Kimi Code ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install kimi +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- kimi ``` -This updates `~/.kimi-code/mcp.json` and enables deferred MCP loading so the -tools can be loaded on demand. - ### ZCode ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install zcode +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- zcode ``` -This updates ZCode's user-level `~/.zcode/cli/config.json`. - ### Cursor ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install cursor +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- cursor ``` -This updates `~/.cursor/mcp.json`. - -If you prefer Cursor's official deeplink flow, configure the local credential -once: +### Gemini CLI ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp setup +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- gemini ``` -Then use: - -[Add Jev MCP to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=jev&config=eyJqZXYiOnsidHlwZSI6InN0ZGlvIiwiY29tbWFuZCI6Im5weCIsImFyZ3MiOlsiLXkiLCItLXBhY2thZ2U9Z2l0aHViOkFmbG9hdDE2L2pldi1tY3AjbWFpbiIsImpldi1tY3AiLCJzZXJ2ZXIiXX19) - -### Gemini CLI +### All detected user-level clients ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install gemini +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash ``` -This updates `~/.gemini/settings.json`. +The `all` flow intentionally skips the project-scoped VS Code target. Run the +installer with `vscode` from the project that should receive `.vscode/mcp.json`. -### Windsurf / Devin Desktop compatible config +## Windows PowerShell -```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install windsurf +### Codex + +```powershell +& ([scriptblock]::Create((irm https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.ps1))) -Target codex ``` -This writes the MCP entry to the Windsurf-compatible user configuration at -`~/.codeium/windsurf/mcp_config.json`. Existing installations that retain -this configuration layout can use the server after restart. +Replace `codex` with `claude-code`, `kimi`, `zcode`, `cursor`, +`gemini`, `windsurf`, `agents`, or `vscode`. -### Generic `.agents` +Configure all detected user-level clients: -```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install agents +```powershell +irm https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.ps1 | iex ``` -This updates `~/.agents/mcp.json`. ZCode can use/import this industry-style -configuration when its native MCP configuration does not override it. +## What the bootstrap does -### VS Code / GitHub Copilot Agent mode +1. verifies Git, Node.js 20+, and npm; +2. installs or updates a managed checkout at `~/.jev-mcp/runtime`; +3. performs a locked `npm ci` install and TypeScript build; +4. prompts for the TypeSafe key with hidden input when no local key exists; +5. stores that key only in `~/.jev-mcp/.env`; +6. configures the selected AI client to launch the local runtime directly. -Run from the project that should receive the MCP server: +Client configs therefore use a durable command equivalent to: -```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install vscode +```text + ~/.jev-mcp/runtime/dist/cli.js server ``` -This updates the project-level `.vscode/mcp.json`. +They do not depend on npm/GitHub every time the AI starts. -## Configure every detected client +Rerunning the same bootstrap command updates the managed runtime and refreshes +the client configuration. -```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install all -``` +## Supported targets -`all` configures detected user-level clients and the generic `.agents` -configuration. It intentionally skips the VS Code target because that target is -workspace-scoped; run `install vscode` explicitly from the desired project. +| Client | Target | Configuration method | +| --- | --- | --- | +| OpenAI Codex CLI + IDE extension | `codex` | `codex mcp add` | +| Anthropic Claude Code | `claude-code` | `claude mcp add --scope user` | +| Kimi Code | `kimi` | `~/.kimi-code/mcp.json` | +| ZCode | `zcode` | `~/.zcode/cli/config.json` | +| Cursor | `cursor` | `~/.cursor/mcp.json` | +| Gemini CLI | `gemini` | `~/.gemini/settings.json` | +| Windsurf-compatible MCP config | `windsurf` | `~/.codeium/windsurf/mcp_config.json` | +| Generic agents config | `agents` | `~/.agents/mcp.json` | +| VS Code / Copilot Agent workspace | `vscode` | `.vscode/mcp.json` | -## Credential-only setup +Kimi is configured with deferred MCP loading so Jev tools can be loaded on +demand. -```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp setup -``` +## Environment-managed credentials -Rotate/replace the locally stored key: +If you intentionally manage `TYPESAFE_API_KEY` outside jev-mcp, skip local +credential creation: ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp setup --reset +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- codex --skip-key ``` -If `TYPESAFE_API_KEY` is already present in the environment, the installer can -persist that value locally without printing it. +The MCP process must then inherit `TYPESAFE_API_KEY` from its environment. + +## Updating -For an environment-managed credential where you do not want the installer to -create `~/.jev-mcp/.env`, use: +Rerun the installer: ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex --skip-key +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- codex ``` -The MCP subprocess must then receive `TYPESAFE_API_KEY` through its environment. +The managed runtime is fetched again and checked out to the configured ref. -## Uninstall +## Uninstalling a client integration -Remove one client integration: +After installation: ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp uninstall cursor +node ~/.jev-mcp/runtime/dist/cli.js uninstall cursor ``` Remove all supported user-level integrations: ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp uninstall all +node ~/.jev-mcp/runtime/dist/cli.js uninstall all ``` -Uninstalling client configuration intentionally leaves the locally stored -TypeSafe credential untouched. Remove it separately: +Removing client configuration does not delete the TypeSafe credential. To +remove the locally stored credential: ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp forget-key +node ~/.jev-mcp/runtime/dist/cli.js forget-key ``` -## How the launcher works +On Windows use the corresponding path under `$HOME\.jev-mcp\runtime`. -Installed clients start Jev through npm's remote-package execution: +## Security notes -```text -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp server -``` +Downloading and executing a remote install script is convenient but carries +normal supply-chain risk. Users who prefer to inspect everything first should +download the script or clone the repository, review it, and run it locally. + +The installer never makes a live Jev API call. Live calls only happen after an +MCP client invokes a Jev tool. -npm supports GitHub repositories as package specs. The repository exposes a -`jev-mcp` binary and builds its TypeScript during Git-based package -installation. +Anything later placed in a Jev tool's `state` is sent to the configured +TypeSafe endpoint. See [SECURITY-MODEL.md](SECURITY-MODEL.md). -The repository currently tracks `main` because the project remains pre-1.0. -A future stable release can replace `#main` with a version tag. +## Advanced installer controls -## Official client behavior referenced by the installer +The bootstrap recognizes: -- Codex supports local stdio MCP servers through `codex mcp add ... -- `. -- Claude Code supports local stdio servers and user scope through `claude mcp add`. -- Kimi Code uses user-level `~/.kimi-code/mcp.json` and supports deferred tools. -- ZCode uses `~/.zcode/cli/config.json` and can import MCP servers from Codex, - Claude Code, OpenCode, and generic `.agents`. -- Cursor uses `~/.cursor/mcp.json` and supports MCP install deeplinks. -- Gemini CLI uses `~/.gemini/settings.json` and its `mcpServers` object. +| Variable | Purpose | +| --- | --- | +| `JEV_MCP_CONFIG_HOME` | Override `~/.jev-mcp` | +| `JEV_MCP_RUNTIME_DIR` | Override the managed runtime checkout | +| `JEV_MCP_REPO_URL` | Override the Git repository URL | +| `JEV_MCP_GIT_REF` | Override the fetched branch/tag/ref | -Because client configuration formats can evolve, the installer is covered by CI -and should be updated when upstream client documentation changes. +These are primarily useful for testing, forks, and pinned deployments. From 9530273a01f0295d3d1f330b8dc7dfed9c545ba9 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:50:53 +0800 Subject: [PATCH 24/34] docs: document shared multi-client runtime --- docs/CLIENTS.md | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/docs/CLIENTS.md b/docs/CLIENTS.md index c5f0c17..08e31df 100644 --- a/docs/CLIENTS.md +++ b/docs/CLIENTS.md @@ -1,8 +1,9 @@ # Supported AI clients -The one-click installer currently targets the following MCP clients. +The installer targets common MCP-capable coding agents while keeping the +TypeSafe credential outside every client config. -| Target | Installer ID | Scope | Method | +| Client | Installer ID | Scope | Integration | | --- | --- | --- | --- | | OpenAI Codex CLI + IDE extension | `codex` | user | official `codex mcp` CLI | | Anthropic Claude Code | `claude-code` | user | official `claude mcp` CLI | @@ -10,22 +11,42 @@ The one-click installer currently targets the following MCP clients. | ZCode | `zcode` | user | `~/.zcode/cli/config.json` | | Cursor | `cursor` | user | `~/.cursor/mcp.json` | | Gemini CLI | `gemini` | user | `~/.gemini/settings.json` | -| Windsurf / compatible Devin Desktop migration | `windsurf` | user | Windsurf MCP config | +| Windsurf-compatible config | `windsurf` | user | Windsurf MCP config | | Generic agents config | `agents` | user | `~/.agents/mcp.json` | | VS Code / Copilot Agent | `vscode` | project | `.vscode/mcp.json` | +## Shared runtime model + +All configured clients point to the same managed local runtime: + +```text +~/.jev-mcp/runtime +``` + +and the same local credential store: + +```text +~/.jev-mcp/.env +``` + +This has three advantages: + +- no API key is duplicated into multiple AI configuration files; +- clients do not need network access just to start the MCP server; +- rerunning the bootstrap updates one shared runtime for every configured + client. + ## Design rules The installer: - never embeds the TypeSafe API key in an AI client's MCP config; -- writes the key only to the local jev-mcp credential file unless `--skip-key` - is used; - backs up existing JSON configuration before modification; - preserves unrelated keys in existing JSON configuration; -- uses the client CLI where that is the safer documented integration path; -- uses a Windows `cmd /c npx ...` wrapper for local stdio startup when needed; -- never sends a TypeSafe API request during installation. +- uses an official client CLI when that is the safer documented path; +- uses absolute local Node/runtime paths for stable stdio startup; +- never sends a TypeSafe API request during installation; +- configures Kimi for deferred Jev tool loading. ## New client requests @@ -36,4 +57,5 @@ When adding another client, prefer in this order: 3. an industry-standard generic MCP file; 4. a clearly labeled compatibility path when no first-party mechanism exists. -Every new target should include a no-secret unit test and documentation link. +Every new target should include installer tests, a bootstrap smoke test where +practical, and a check that no credential is written to client config. From c129779216d8bf2d9ee279e5dc463c0c3690813f Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:52:15 +0800 Subject: [PATCH 25/34] docs: switch README to durable bootstrap installer --- README.md | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index e1f867c..5238ed8 100644 --- a/README.md +++ b/README.md @@ -94,44 +94,53 @@ That metadata is added by this MCP server; it is not Jev model output. ## One-command install -No clone and no manual MCP JSON/TOML editing is required. +No manual MCP JSON/TOML editing is required. The bootstrap installs a shared +runtime at `~/.jev-mcp/runtime`, asks for the TypeSafe key with hidden input, +and configures the selected client. + +macOS / Linux: ```bash # Codex -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- codex # Claude Code -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install claude-code +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- claude-code # Kimi Code -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install kimi +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- kimi # ZCode -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install zcode +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- zcode # Cursor -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install cursor +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- cursor # Gemini CLI -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install gemini +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- gemini +``` -# Configure every detected user-level client -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install all +Windows PowerShell (replace `codex` with another target as needed): + +```powershell +& ([scriptblock]::Create((irm https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.ps1))) -Target codex ``` -On the first install, the CLI asks for the TypeSafe API key with **hidden -terminal input** and stores it only in `~/.jev-mcp/.env`. The key is not -inserted into client MCP configuration. +To configure every detected user-level client: + +```bash +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash +``` -The installer preserves unrelated JSON settings and creates a -`.jev-mcp.bak` backup before changing an existing JSON config. +The key is stored only in `~/.jev-mcp/.env`; client MCP configs contain no +TypeSafe credential. Existing JSON configs are backed up before modification. -Supported targets include Codex, Claude Code, Kimi Code, ZCode, Cursor, -Gemini CLI, Windsurf-compatible config, generic `.agents/mcp.json`, and -project-scoped VS Code/Copilot Agent configuration. +Supported targets: Codex, Claude Code, Kimi Code, ZCode, Cursor, Gemini CLI, +Windsurf-compatible config, generic `.agents/mcp.json`, and project-scoped +VS Code/Copilot Agent configuration. -See [Installation](docs/INSTALLATION.md) for all commands, uninstall steps, -Windows behavior, and the Cursor deeplink. +See [Installation](docs/INSTALLATION.md) for Windows commands, updates, +uninstall, `--skip-key`, and advanced controls. ### Source install From 2a7d55a8c8713ddd7b74d82a4b17554376d8ea4e Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:52:18 +0800 Subject: [PATCH 26/34] docs: switch Chinese README to bootstrap installer --- README.zh-CN.md | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/README.zh-CN.md b/README.zh-CN.md index a87c3f8..59e4ddb 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -74,42 +74,51 @@ API key 只放在本地进程环境变量或被 Git 忽略的 `.env` 文件中 ## 一条命令安装 -无需 clone 仓库,也无需手工修改 MCP JSON/TOML: +无需手工修改 MCP JSON/TOML。安装器会把共享运行时安装到 +`~/.jev-mcp/runtime`,隐藏输入 TypeSafe key,并自动配置目标 AI。 + +macOS / Linux: ```bash # Codex -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- codex # Claude Code -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install claude-code +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- claude-code # Kimi Code -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install kimi +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- kimi # ZCode -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install zcode +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- zcode # Cursor -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install cursor +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- cursor # Gemini CLI -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install gemini +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- gemini +``` -# 自动配置检测到的用户级客户端 -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install all +Windows PowerShell(把 `codex` 换成其他目标即可): + +```powershell +& ([scriptblock]::Create((irm https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.ps1))) -Target codex ``` -第一次安装时会在终端中**隐藏输入** TypeSafe API key,只保存到本机 -`~/.jev-mcp/.env`,不会把 key 写进 Codex、Claude Code、Cursor、Kimi、 -ZCode、Gemini 等 MCP 配置。 +自动配置检测到的用户级客户端: + +```bash +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash +``` -修改已有 JSON 配置前会保留 `.jev-mcp.bak` 备份,并尽量保留其他设置。 +TypeSafe key 只保存在本机 `~/.jev-mcp/.env`,不会写入各 AI 的 MCP 配置; +修改已有 JSON 配置前会自动保存备份。 -目前支持 Codex、Claude Code、Kimi Code、ZCode、Cursor、Gemini CLI、 -Windsurf 兼容配置、通用 `.agents/mcp.json`,以及项目级 VS Code/Copilot -Agent 配置。 +当前支持 Codex、Claude Code、Kimi Code、ZCode、Cursor、Gemini CLI、 +Windsurf 兼容配置、通用 `.agents/mcp.json`,以及项目级 +VS Code/Copilot Agent 配置。 -完整命令、卸载方式和 Cursor deeplink 见 +完整 Windows 命令、更新、卸载、`--skip-key` 和高级参数见 [安装说明](docs/INSTALLATION.md)。 ### 从源码安装 From 891576966982fbe4c3a714096faacb301b41dfa9 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:52:21 +0800 Subject: [PATCH 27/34] docs: update quick start for managed runtime --- docs/QUICKSTART.md | 54 ++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index ce766fe..c9f500c 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -1,27 +1,29 @@ # Quick start -The fastest path is the one-command installer. It downloads the package from -GitHub through npm, configures the selected MCP client, and stores the TypeSafe -credential outside the client config. +The fastest path is the bootstrap installer. It creates a managed local runtime, +configures the selected MCP client, and keeps the TypeSafe credential outside +client configuration. ## 1. Requirements -- Node.js 20+ with npm/npx +- Node.js 20+ +- Git +- npm - a TypeSafe API key / applicable TypeSafe access and credits - the target AI client -Check Node: +## 2. Install + +Codex on macOS/Linux: ```bash -node --version +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash -s -- codex ``` -## 2. Install into your client - -Example for Codex: +Codex on Windows PowerShell: -```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install codex +```powershell +& ([scriptblock]::Create((irm https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.ps1))) -Target codex ``` Other target IDs: @@ -37,31 +39,33 @@ agents vscode ``` -Or configure every detected user-level client: +Configure every detected user-level client on macOS/Linux: ```bash -npx -y --package=github:Afloat16/jev-mcp#main jev-mcp install all +curl -fsSL https://raw.githubusercontent.com/Afloat16/jev-mcp/main/scripts/install.sh | bash ``` -The first install asks for the TypeSafe API key using hidden terminal input. -It is stored in: +The installer creates: ```text -~/.jev-mcp/.env +~/.jev-mcp/runtime # managed local runtime +~/.jev-mcp/.env # local credential file ``` -The key is not placed in the target client's MCP configuration. +The API key is entered with hidden input and is not placed in the target +client's MCP configuration. -See [INSTALLATION.md](INSTALLATION.md) for platform-specific details. +See [INSTALLATION.md](INSTALLATION.md) for all clients and lifecycle commands. ## 3. Restart the AI client -MCP tool discovery normally happens when a new client/session starts. Restart -the client or open a new session after installation. +MCP tool discovery normally happens at client/session startup. Restart the +client or open a new session after installation. -## 4. Start with a safe synthetic test +## 4. Safe first test -Ask the host model to get a second opinion on a bounded decision such as: +Use synthetic state and ask the host model for a second opinion on a bounded +decision such as: ```text retry / rollback / escalate @@ -69,12 +73,8 @@ retry / rollback / escalate Do not use production secrets or customer data as test input. -Examples are available in [../examples/README.md](../examples/README.md). - ## 5. Trust model -Jev remains advisory: - ```text deterministic evidence > @@ -89,8 +89,6 @@ sensitive projects. ## Source checkout alternative -If you are contributing to the project or want a pinned local checkout: - ```bash git clone https://github.com/Afloat16/jev-mcp.git cd jev-mcp From 8c352c0cffc79f7074f9102d8479f5aa09bdd11e Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:52:24 +0800 Subject: [PATCH 28/34] docs: record durable bootstrap design --- CHANGELOG.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fae5d34..e3be11e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,17 +4,18 @@ All notable project changes are documented here. ## Unreleased -- Added a GitHub-backed `jev-mcp` executable that can run directly through - `npx` without cloning the repository. -- Added one-command installers for Codex, Claude Code, Kimi Code, ZCode, - Cursor, Gemini CLI, Windsurf-compatible config, generic `.agents`, and +- Added cross-platform bootstrap installers that create/update a durable local + runtime at `~/.jev-mcp/runtime` instead of relying on npm Git-package + execution at every MCP startup. +- Added one-command setup for Codex, Claude Code, Kimi Code, ZCode, Cursor, + Gemini CLI, Windsurf-compatible config, generic `.agents`, and project-scoped VS Code MCP configuration. - Added a user-level `~/.jev-mcp/.env` credential store so GUI clients do not need API keys embedded in MCP configuration. -- Added safe JSON config merging with local backups and platform-aware Windows - stdio launching. -- Added installer unit tests, CLI smoke checks, and dedicated client/install - documentation. +- Added safe JSON config merging with local backups and absolute local + Node/runtime launch paths. +- Added installer unit tests, Linux/Windows bootstrap smoke checks, CLI smoke + checks, and dedicated client/install documentation. - Redesigned the English and Chinese project homepages around quick onboarding, tool boundaries, privacy, and a clear host-model/Jev authority model. From 10480f3fd2e1a2c1c4d820b40931d63460fa1397 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:53:04 +0800 Subject: [PATCH 29/34] test: verify Linux and Windows bootstrap installers --- .github/workflows/ci.yml | 67 ++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11df5f6..a42abc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,11 +38,12 @@ jobs: - name: Repository checks run: npm run check - - installer-smoke: - name: GitHub package installer + bootstrap-linux: + name: Bootstrap / Linux runs-on: ubuntu-latest timeout-minutes: 10 + env: + TEST_REF: ${{ github.event.pull_request.head.ref || github.ref_name }} steps: - name: Checkout @@ -53,17 +54,65 @@ jobs: with: node-version: 20.x - - name: Run installer from GitHub package in isolated home + - name: Run isolated bootstrap shell: bash - env: - PACKAGE_REF: ${{ github.event.pull_request.head.sha || github.sha }} run: | set -euo pipefail temp_home="$(mktemp -d)" - HOME="$temp_home" npx -y \ - --package="github:Afloat16/jev-mcp#${PACKAGE_REF}" \ - jev-mcp install cursor --skip-key + export HOME="$temp_home" + export JEV_MCP_CONFIG_HOME="$temp_home/.jev-mcp" + export JEV_MCP_RUNTIME_DIR="$temp_home/.jev-mcp/runtime" + export JEV_MCP_GIT_REF="$TEST_REF" + + bash scripts/install.sh cursor --skip-key test -f "$temp_home/.cursor/mcp.json" + test -f "$temp_home/.jev-mcp/runtime/dist/cli.js" grep -q '"jev"' "$temp_home/.cursor/mcp.json" + grep -q 'dist/cli.js' "$temp_home/.cursor/mcp.json" ! grep -q 'TYPESAFE_API_KEY' "$temp_home/.cursor/mcp.json" + ! test -f "$temp_home/.jev-mcp/.env" + + bootstrap-windows: + name: Bootstrap / Windows + runs-on: windows-latest + timeout-minutes: 12 + env: + TEST_REF: ${{ github.event.pull_request.head.ref || github.ref_name }} + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version: 20.x + + - name: Run isolated bootstrap + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + $tempHome = Join-Path $env:RUNNER_TEMP "jev-mcp-user" + New-Item -ItemType Directory -Force -Path $tempHome | Out-Null + + $env:HOME = $tempHome + $env:USERPROFILE = $tempHome + $env:JEV_MCP_CONFIG_HOME = Join-Path $tempHome ".jev-mcp" + $env:JEV_MCP_RUNTIME_DIR = Join-Path $env:JEV_MCP_CONFIG_HOME "runtime" + $env:JEV_MCP_GIT_REF = $env:TEST_REF + + ./scripts/install.ps1 -Target cursor -SkipKey + + $config = Join-Path $tempHome ".cursor/mcp.json" + $cli = Join-Path $env:JEV_MCP_RUNTIME_DIR "dist/cli.js" + if (-not (Test-Path $config)) { throw "Cursor MCP config was not created." } + if (-not (Test-Path $cli)) { throw "Managed jev-mcp runtime was not built." } + + $text = Get-Content $config -Raw + if ($text -notmatch '"jev"') { throw "Jev MCP entry missing." } + if ($text -notmatch 'dist[\\\\/]cli\.js') { throw "Durable runtime path missing." } + if ($text -match 'TYPESAFE_API_KEY') { throw "Credential leaked into client config." } + if (Test-Path (Join-Path $env:JEV_MCP_CONFIG_HOME ".env")) { + throw "Skip-key smoke test unexpectedly created a credential file." + } From b58d142d02dceccd89068accb686413ef50a6109 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:54:08 +0800 Subject: [PATCH 30/34] fix: validate Windows installer config structurally --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a42abc2..49281e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,8 +110,12 @@ jobs: if (-not (Test-Path $cli)) { throw "Managed jev-mcp runtime was not built." } $text = Get-Content $config -Raw - if ($text -notmatch '"jev"') { throw "Jev MCP entry missing." } - if ($text -notmatch 'dist[\\\\/]cli\.js') { throw "Durable runtime path missing." } + $parsed = $text | ConvertFrom-Json + $entry = $parsed.mcpServers.jev + if ($null -eq $entry) { throw "Jev MCP entry missing." } + if ($entry.args[0] -ne $cli) { + throw "Durable runtime path mismatch: $($entry.args[0])" + } if ($text -match 'TYPESAFE_API_KEY') { throw "Credential leaked into client config." } if (Test-Path (Join-Path $env:JEV_MCP_CONFIG_HOME ".env")) { throw "Skip-key smoke test unexpectedly created a credential file." From f9352a505df3db51455f64400d6e0bc8b5114405 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:55:28 +0800 Subject: [PATCH 31/34] docs: describe installer-managed credential precedence --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5238ed8..1c7bef1 100644 --- a/README.md +++ b/README.md @@ -204,9 +204,9 @@ structured output, orchestration, explicit thresholds, or shared agent workflows | `JEV_MODEL` | no | `jev-latest` | Jev model override | | `TYPESAFE_BASE_URL` | no | `https://api.typesafe.ai` | API base URL | | `TYPESAFE_TIMEOUT_MS` | no | `15000` | Request timeout, 250–120000 ms | -| `JEV_ENV_FILE` | no | project `.env` | Alternate env file path | +| `JEV_ENV_FILE` | no | auto | Explicit env file; otherwise project `.env`, then `~/.jev-mcp/.env` | -Existing process environment variables override values loaded from `.env`. +Existing process environment variables override file values. Without `JEV_ENV_FILE`, a checkout-local `.env` is loaded before the installer-managed `~/.jev-mcp/.env`. ### Secret-handling rules From 2d93eda2737ef409d0a5103c86a0824188d3c429 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:55:33 +0800 Subject: [PATCH 32/34] docs: clarify credential precedence in Chinese README --- README.zh-CN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.zh-CN.md b/README.zh-CN.md index 59e4ddb..3870b91 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -181,9 +181,9 @@ MCP 真正有价值的地方是**稳定工具边界**:结构化输出、可重 | `JEV_MODEL` | 否 | `jev-latest` | Jev 模型覆盖 | | `TYPESAFE_BASE_URL` | 否 | `https://api.typesafe.ai` | API 地址 | | `TYPESAFE_TIMEOUT_MS` | 否 | `15000` | 250–120000 ms 请求超时 | -| `JEV_ENV_FILE` | 否 | 项目 `.env` | 其他 env 文件路径 | +| `JEV_ENV_FILE` | 否 | 自动 | 显式 env 文件;否则先项目 `.env`,再 `~/.jev-mcp/.env` | -进程环境变量优先于 `.env` 中的同名值。 +进程环境变量优先于文件中的同名值。未指定 `JEV_ENV_FILE` 时,会先读取项目 `.env`,再读取安装器管理的 `~/.jev-mcp/.env`。 ### 密钥规则 From 3cb15681b10071604642b7abe9258b60edec5f13 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:55:36 +0800 Subject: [PATCH 33/34] docs: document shared credential security model --- docs/SECURITY-MODEL.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/SECURITY-MODEL.md b/docs/SECURITY-MODEL.md index 415e06d..bfb0200 100644 --- a/docs/SECURITY-MODEL.md +++ b/docs/SECURITY-MODEL.md @@ -36,12 +36,15 @@ alternate endpoint. Review environment configuration before use. ### Credential exposure -The API key is read locally from `.env` or the process environment and sent as -a Bearer token to the configured endpoint. - -Mitigation: `.env` is ignored by Git; setup scripts do not print the key; the -repository includes a tracked-file secret scanner. Rotate any key that has -appeared in a shared surface or Git history. +The API key is read locally from the process environment, an explicitly +configured `JEV_ENV_FILE`, a checkout-local `.env`, or the installer-managed +`~/.jev-mcp/.env`, then sent as a Bearer token to the configured endpoint. + +Mitigation: credential files are kept outside client MCP configuration; +checkout `.env` is ignored by Git; the bootstrap stores its shared credential +under the user's `~/.jev-mcp` directory; setup/install flows do not print the +key; and the repository includes a tracked-file secret scanner. Rotate any key +that has appeared in a shared surface or Git history. ### Dependency / supply-chain risk From d515d14053e11309a69a8b91ed0a4926d29435d3 Mon Sep 17 00:00:00 2001 From: Afloat <132809511+Afloat16@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:55:38 +0800 Subject: [PATCH 34/34] docs: explain installer credential storage --- docs/FAQ.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 511ee17..dc4329d 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -29,9 +29,11 @@ change the host model selected by the user. ## Does the API key get sent to the host model? -The server reads the key from its local environment and uses it as an -Authorization header for the configured API request. The tool output does not -intentionally include the credential. +The server reads the key from its local process environment or a local +credential file. The one-command installer stores the shared key in +`~/.jev-mcp/.env` and does not place it in AI client MCP configuration. The +server uses the key only as the Authorization header for the configured API +request; tool output does not intentionally include the credential. Do not put credentials into tool `state`, prompts, screenshots, logs, issues, or examples.