@codexguild/mcp 1.6.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/codexguild-mcp.js +66 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ skills and MCP servers, knowledge base) and adds what has to run on your machine
|
|
|
10
10
|
| `codexguild_scan_skills` | Audits the skills installed for every supported harness (`~/.claude/skills`, `~/.claude/plugins`, `~/.codex/skills`, `~/.agents/skills`, `~/.hermes/skills`, `~/.cursor/skills`, `~/.gemini/skills`, … and the project's own `.claude/`, `.agents/`, `.github/` skills) **offline**, with the same scanner CodexGuild runs server-side (pipe-to-shell, secret exfiltration, prompt injection, hidden unicode, config tampering…). Nothing is uploaded. |
|
|
11
11
|
| `codexguild_sync` | Detects your stack with the **exact installed versions from your lockfiles** (package-lock, pnpm-lock, yarn.lock, bun.lock, poetry.lock, uv.lock, Pipfile.lock, Cargo.lock; go.mod), the Node/Python runtime (.nvmrc, .tool-versions, engines) and your installed skills, keys it to the current repository (each project keeps its own sync window), then asks CodexGuild what changed and which installed versions have known vulnerabilities. |
|
|
12
12
|
| `codexguild_scan_code` | Finds APIs that are **deprecated or removed in the versions you run** in your own code — file, line and the replacement (e.g. `app.mount` in hono 4.13 → `mount()` from `hono/mount`). Only package names and versions are sent; the code is searched locally. Runs automatically after `codexguild_sync`. |
|
|
13
|
+
| `codexguild_note_save` / `codexguild_notes` | Fill in the repository from its **git remote** (credentials in the URL are dropped first), so your agent's notes — lessons it saved after correcting its own mistakes — follow the repository across machines and harnesses. `codexguild_sync` sends it too, so every session starts with them. |
|
|
13
14
|
| `codexguild_upgrade_plan` | Fills in the installed version from your lockfile: ask for a package and get the releases up to the target, possibly breaking ones, removed APIs with replacements and the steps in order. |
|
|
14
15
|
| `codexguild_advise` | Detects the instruction files in your project (`CLAUDE.md`, `AGENTS.md`, …) before proposing setup changes. Proposals always need your approval. |
|
|
15
16
|
| `codexguild_scan_workflows` | Reviews the project's GitHub Actions workflows that run coding agents: untrusted triggers with secrets, issue/PR text in prompts or shell scripts, `pull_request_target` with fork code, unrestricted agent tools, permissions, unpinned agent actions. Runs locally. |
|
package/dist/codexguild-mcp.js
CHANGED
|
@@ -760,7 +760,9 @@ function formatLocalScan({ roots, reports }, verbose = false) {
|
|
|
760
760
|
}
|
|
761
761
|
|
|
762
762
|
// src/stack-detect.ts
|
|
763
|
+
import { execFile } from "node:child_process";
|
|
763
764
|
import { createHash as createHash2 } from "node:crypto";
|
|
765
|
+
import { promisify } from "node:util";
|
|
764
766
|
import { readdir as readdir2, readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
765
767
|
import { basename, dirname, join as join2, relative as relative2, resolve as resolve2, sep } from "node:path";
|
|
766
768
|
|
|
@@ -1058,6 +1060,29 @@ async function detectStack(reader, base = "") {
|
|
|
1058
1060
|
ranges: sent.filter((d) => !d.runtime).length - exact
|
|
1059
1061
|
};
|
|
1060
1062
|
}
|
|
1063
|
+
function normalizeRepoKey(raw) {
|
|
1064
|
+
let s = (raw ?? "").trim();
|
|
1065
|
+
if (!s) return null;
|
|
1066
|
+
if (s.startsWith("local:")) return s.slice(0, 200).toLowerCase();
|
|
1067
|
+
const scp = s.match(/^[\w.-]+@([\w.-]+):(?!\/)(.+)$/);
|
|
1068
|
+
if (scp) s = `${scp[1]}/${scp[2]}`;
|
|
1069
|
+
else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) {
|
|
1070
|
+
try {
|
|
1071
|
+
const u = new URL(s);
|
|
1072
|
+
s = `${u.hostname}${u.pathname}`;
|
|
1073
|
+
} catch {
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
} else if (/^[\w.-]+\/[\w.-]+$/.test(s)) s = `github.com/${s}`;
|
|
1077
|
+
else if (!/^[\w.-]+\.[a-z]{2,}\/[\w.-]+\/[\w.\/-]+$/i.test(s)) {
|
|
1078
|
+
const local = s.replace(/[^\w.#-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1079
|
+
return local ? `local:${local.slice(0, 120).toLowerCase()}` : null;
|
|
1080
|
+
}
|
|
1081
|
+
const key = s.toLowerCase().replace(/\.git$/, "").replace(/\/+$/, "").replace(/^www\./, "");
|
|
1082
|
+
const parts = key.split("/").filter(Boolean);
|
|
1083
|
+
if (parts.length < 3 || parts.some((p) => !/^[\w.-]+$/.test(p) || p === "..")) return null;
|
|
1084
|
+
return parts.join("/").slice(0, 200);
|
|
1085
|
+
}
|
|
1061
1086
|
|
|
1062
1087
|
// src/stack-detect.ts
|
|
1063
1088
|
async function read(path) {
|
|
@@ -1087,6 +1112,17 @@ async function projectKey(cwd) {
|
|
|
1087
1112
|
const root = await projectRoot(cwd);
|
|
1088
1113
|
return `${basename(root).slice(0, 100)}#${createHash2("sha256").update(root).digest("hex").slice(0, 8)}`;
|
|
1089
1114
|
}
|
|
1115
|
+
async function repoKey(cwd) {
|
|
1116
|
+
const root = await projectRoot(cwd);
|
|
1117
|
+
const git = async (...args2) => (await promisify(execFile)("git", ["-C", root, ...args2], { timeout: 3e3 })).stdout.trim();
|
|
1118
|
+
try {
|
|
1119
|
+
const url = await git("config", "--get", "remote.origin.url").catch(() => "") || await git("remote", "get-url", (await git("remote")).split("\n")[0] || "origin");
|
|
1120
|
+
const key = normalizeRepoKey(url);
|
|
1121
|
+
if (key && !key.startsWith("local:")) return key;
|
|
1122
|
+
} catch {
|
|
1123
|
+
}
|
|
1124
|
+
return `local:${(await projectKey(cwd)).toLowerCase()}`;
|
|
1125
|
+
}
|
|
1090
1126
|
function fsReader(root) {
|
|
1091
1127
|
const abs = (p) => p ? join2(root, ...p.split("/")) : root;
|
|
1092
1128
|
return {
|
|
@@ -1460,6 +1496,7 @@ async function scanCode(root, deps) {
|
|
|
1460
1496
|
}
|
|
1461
1497
|
return { hits, files: files.length };
|
|
1462
1498
|
}
|
|
1499
|
+
var KEY_REJECTED = "CODEXGUILD_API_KEY was rejected (revoked or deleted). Create a new agent key at https://codexguild.com/dashboard/agents, set it as CODEXGUILD_API_KEY and restart the MCP client.";
|
|
1463
1500
|
async function applicableDeprecations(api, key, deps) {
|
|
1464
1501
|
const packages = deps.filter((d) => d.version && !d.runtime).slice(0, 300).map((d) => ({ name: d.name, version: d.version, ...d.ecosystem ? { ecosystem: d.ecosystem } : {} }));
|
|
1465
1502
|
if (!packages.length || !key) return [];
|
|
@@ -1469,6 +1506,7 @@ async function applicableDeprecations(api, key, deps) {
|
|
|
1469
1506
|
body: JSON.stringify({ packages }),
|
|
1470
1507
|
signal: AbortSignal.timeout(15e3)
|
|
1471
1508
|
});
|
|
1509
|
+
if (res.status === 401 || res.status === 403) throw new Error(KEY_REJECTED);
|
|
1472
1510
|
if (!res.ok) throw new Error(`deprecation check failed (${res.status})`);
|
|
1473
1511
|
return (await res.json()).items;
|
|
1474
1512
|
}
|
|
@@ -1499,7 +1537,7 @@ function formatCodeScan(hits, files, checked) {
|
|
|
1499
1537
|
// src/index.ts
|
|
1500
1538
|
var API = (process.env.CODEXGUILD_API_URL || "https://api.codexguild.com/v1").replace(/\/$/, "");
|
|
1501
1539
|
var KEY = process.env.CODEXGUILD_API_KEY ?? "";
|
|
1502
|
-
var VERSION = "1.
|
|
1540
|
+
var VERSION = "1.8.0";
|
|
1503
1541
|
if (process.argv[2] === "guard") process.exit(await runGuard({ api: API, key: KEY }));
|
|
1504
1542
|
var INSTRUCTION_FILES = ["CLAUDE.md", ".claude/CLAUDE.md", "AGENTS.md", "AGENTS.override.md", ".hermes.md", "HERMES.md", "GEMINI.md", ".cursorrules"];
|
|
1505
1543
|
async function existingInstructionFiles(cwd) {
|
|
@@ -1507,14 +1545,21 @@ async function existingInstructionFiles(cwd) {
|
|
|
1507
1545
|
return present.filter((f) => f !== null);
|
|
1508
1546
|
}
|
|
1509
1547
|
var remote = null;
|
|
1548
|
+
var keyRejected = false;
|
|
1510
1549
|
function remoteClient() {
|
|
1511
1550
|
if (!KEY) return Promise.reject(new Error("CODEXGUILD_API_KEY is not set \u2014 create an agent key in the CodexGuild dashboard."));
|
|
1551
|
+
if (keyRejected) return Promise.reject(new Error(KEY_REJECTED));
|
|
1512
1552
|
remote ??= (async () => {
|
|
1513
1553
|
const client = new Client({ name: "codexguild-local", version: VERSION });
|
|
1514
1554
|
await client.connect(new StreamableHTTPClientTransport(new URL(`${API}/mcp`), { requestInit: { headers: { Authorization: `Bearer ${KEY}` } } }));
|
|
1515
1555
|
return client;
|
|
1516
1556
|
})().catch((e) => {
|
|
1517
1557
|
remote = null;
|
|
1558
|
+
const code = e.code;
|
|
1559
|
+
if (code === 401 || code === 403) {
|
|
1560
|
+
keyRejected = true;
|
|
1561
|
+
throw new Error(KEY_REJECTED);
|
|
1562
|
+
}
|
|
1518
1563
|
throw e;
|
|
1519
1564
|
});
|
|
1520
1565
|
return remote;
|
|
@@ -1535,7 +1580,10 @@ var ENHANCED = {
|
|
|
1535
1580
|
codexguild_sync: "Runs locally first: the stack (package.json, requirements.txt, go.mod, \u2026) and installed skill NAMES are detected automatically \u2014 only names/versions are sent, never file contents; you only need force/since.",
|
|
1536
1581
|
codexguild_advise: "Runs locally first: stack, installed skills and the instruction files present in the project are detected automatically; you only need harness.",
|
|
1537
1582
|
codexguild_debug: "Runs locally first: the project's package versions are read from its lockfiles and sent with the error \u2014 you only need error (and stack).",
|
|
1538
|
-
codexguild_upgrade_plan: "Runs locally first: `from` (and the ecosystem) is read from the lockfile when you only give `package`."
|
|
1583
|
+
codexguild_upgrade_plan: "Runs locally first: `from` (and the ecosystem) is read from the lockfile when you only give `package`.",
|
|
1584
|
+
codexguild_note_save: "Runs locally first: `repo` is read from the git remote of the current repository \u2014 you only need text and kind.",
|
|
1585
|
+
codexguild_notes: "Runs locally first: `repo` is read from the git remote of the current repository.",
|
|
1586
|
+
codexguild_fix_submit: "Runs locally first: `packages` (names and versions after your changes) is read from the project's manifests and lockfiles \u2014 you only need task_id and summary. Run the package manager first so the lockfile is current."
|
|
1539
1587
|
};
|
|
1540
1588
|
function enhance(t) {
|
|
1541
1589
|
const note = ENHANCED[t.name];
|
|
@@ -1548,7 +1596,9 @@ ${note}`,
|
|
|
1548
1596
|
...t.inputSchema,
|
|
1549
1597
|
properties: { ...t.inputSchema.properties, cwd: { type: "string", description: "Project directory (defaults to the current directory)" } },
|
|
1550
1598
|
// Filled from the lockfile here, so the agent need not pass it.
|
|
1551
|
-
...t.name === "codexguild_upgrade_plan" && Array.isArray(t.inputSchema.required) ? { required: t.inputSchema.required.filter((r) => r !== "from") } : {}
|
|
1599
|
+
...t.name === "codexguild_upgrade_plan" && Array.isArray(t.inputSchema.required) ? { required: t.inputSchema.required.filter((r) => r !== "from") } : {},
|
|
1600
|
+
...t.name === "codexguild_fix_submit" && Array.isArray(t.inputSchema.required) ? { required: t.inputSchema.required.filter((r) => r !== "packages") } : {},
|
|
1601
|
+
...(t.name === "codexguild_note_save" || t.name === "codexguild_notes") && Array.isArray(t.inputSchema.required) ? { required: t.inputSchema.required.filter((r) => r !== "repo") } : {}
|
|
1552
1602
|
}
|
|
1553
1603
|
};
|
|
1554
1604
|
}
|
|
@@ -1559,11 +1609,17 @@ async function detected(name, cwd, explicit = {}) {
|
|
|
1559
1609
|
const d = deps2.find((x) => !x.runtime && x.name === pkg && x.version);
|
|
1560
1610
|
return { args: d ? { from: d.version, ...d.ecosystem ? { ecosystem: d.ecosystem } : {} } : {} };
|
|
1561
1611
|
}
|
|
1612
|
+
if (name === "codexguild_fix_submit") {
|
|
1613
|
+
const { deps: deps2, lockfiles: lockfiles2 } = await detectStack2(cwd);
|
|
1614
|
+
const packages = deps2.slice(0, 300).map((d) => ({ name: d.name, ...d.version ? { version: d.version } : {}, ...d.ecosystem ? { ecosystem: d.ecosystem } : {}, ...d.runtime ? { runtime: true } : {} }));
|
|
1615
|
+
return { args: { packages }, summary: `CodexGuild: submitted ${packages.length} dependencies from ${lockfiles2.length ? lockfiles2.join(", ") : "manifests (no lockfile \u2014 versions are ranges)"}.` };
|
|
1616
|
+
}
|
|
1617
|
+
if (name === "codexguild_note_save" || name === "codexguild_notes") return { args: { repo: await repoKey(cwd) } };
|
|
1562
1618
|
if (name === "codexguild_debug") {
|
|
1563
1619
|
const { deps: deps2 } = await detectStack2(cwd);
|
|
1564
1620
|
return { args: { packages: deps2.slice(0, 300).map((d) => ({ name: d.name, ...d.version ? { version: d.version } : {}, ...d.ecosystem ? { ecosystem: d.ecosystem } : {} })) } };
|
|
1565
1621
|
}
|
|
1566
|
-
const [{ deps, manifests, lockfiles, exact, ranges }, local, project] = await Promise.all([detectStack2(cwd), scanLocalSkills(), projectKey(cwd)]);
|
|
1622
|
+
const [{ deps, manifests, lockfiles, exact, ranges }, local, project, repo] = await Promise.all([detectStack2(cwd), scanLocalSkills(), projectKey(cwd), repoKey(cwd)]);
|
|
1567
1623
|
const installedSkills = await installedSkillNames(local.reports.map((r) => r.dir));
|
|
1568
1624
|
if (name === "codexguild_advise") {
|
|
1569
1625
|
return { args: { stack: deps, installedSkills, existingInstructionFiles: await existingInstructionFiles(cwd), project } };
|
|
@@ -1576,7 +1632,7 @@ async function detected(name, cwd, explicit = {}) {
|
|
|
1576
1632
|
...flagged.slice(0, 10).map((x) => ` - FLAGGED ${x.dir}: ${x.result.findings[0]?.title ?? ""}`),
|
|
1577
1633
|
...flagged.length ? [" Run codexguild_scan_skills for details and ask the user before using flagged skills."] : []
|
|
1578
1634
|
].join("\n");
|
|
1579
|
-
return { args: { stack: deps, installedSkills, project }, summary };
|
|
1635
|
+
return { args: { stack: deps, installedSkills, project, repo }, summary };
|
|
1580
1636
|
}
|
|
1581
1637
|
var LINT_TOOL = {
|
|
1582
1638
|
name: "codexguild_lint_instructions",
|
|
@@ -1655,13 +1711,13 @@ async function remoteTools() {
|
|
|
1655
1711
|
try {
|
|
1656
1712
|
return (await (await remoteClient()).listTools()).tools;
|
|
1657
1713
|
} catch (e) {
|
|
1658
|
-
if (!KEY || attempt >= RETRY_MS.length) throw e;
|
|
1714
|
+
if (!KEY || keyRejected || attempt >= RETRY_MS.length) throw e;
|
|
1659
1715
|
await new Promise((r) => setTimeout(r, RETRY_MS[attempt]));
|
|
1660
1716
|
}
|
|
1661
1717
|
}
|
|
1662
1718
|
}
|
|
1663
1719
|
function scheduleReconnect() {
|
|
1664
|
-
if (reconnectTimer || !KEY) return;
|
|
1720
|
+
if (reconnectTimer || !KEY || keyRejected) return;
|
|
1665
1721
|
reconnectTimer = setInterval(async () => {
|
|
1666
1722
|
try {
|
|
1667
1723
|
await (await remoteClient()).listTools();
|
|
@@ -1678,7 +1734,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1678
1734
|
try {
|
|
1679
1735
|
return { tools: [...LOCAL_TOOLS, ...(await remoteTools()).map(enhance)] };
|
|
1680
1736
|
} catch (e) {
|
|
1681
|
-
|
|
1737
|
+
const retry = KEY && !keyRejected ? "; retrying in the background" : "";
|
|
1738
|
+
console.error(`CodexGuild: remote tools unavailable (${e instanceof Error ? e.message : String(e)}) \u2014 serving local tools only${retry}.`);
|
|
1682
1739
|
scheduleReconnect();
|
|
1683
1740
|
return { tools: LOCAL_TOOLS };
|
|
1684
1741
|
}
|
|
@@ -1711,7 +1768,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1711
1768
|
}
|
|
1712
1769
|
const r = await (await remoteClient()).callTool({ name, arguments: args2 });
|
|
1713
1770
|
const codeNote = name === "codexguild_sync" && !r.isError ? await codeScanLocal(cwd).catch(() => null) : null;
|
|
1714
|
-
const extra = [...summary ? [{ type: "text", text: summary }] : []];
|
|
1771
|
+
const extra = [...summary && !r.isError ? [{ type: "text", text: summary }] : []];
|
|
1715
1772
|
const tail = codeNote && !codeNote.startsWith("Code check:") ? [{ type: "text", text: codeNote }] : [];
|
|
1716
1773
|
return extra.length || tail.length ? { ...r, content: [...extra, ...r.content ?? [], ...tail] } : r;
|
|
1717
1774
|
} catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codexguild/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "CodexGuild MCP server (stdio) \u2014 boosts your coding agent: remote CodexGuild tools plus local stack detection from lockfiles, deprecated APIs found in your own code, and offline security audits of skills, instructions and agent workflows",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|