@danielblomma/cortex-mcp 1.7.2 → 2.0.3
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 +4 -24
- package/bin/cortex.mjs +679 -32
- package/bin/style.mjs +349 -0
- package/package.json +4 -3
- package/scaffold/mcp/src/cli/enterprise-setup.ts +124 -0
- package/scaffold/mcp/src/cli/govern.ts +987 -0
- package/scaffold/mcp/src/cli/run.ts +306 -0
- package/scaffold/mcp/src/cli/telemetry-test.ts +158 -0
- package/scaffold/mcp/src/cli/ungoverned-detector.ts +168 -0
- package/scaffold/mcp/src/core/audit/query.ts +81 -0
- package/scaffold/mcp/src/core/audit/writer.ts +68 -0
- package/scaffold/mcp/src/core/config.ts +329 -0
- package/scaffold/mcp/src/core/index.ts +34 -0
- package/scaffold/mcp/src/core/license.ts +202 -0
- package/scaffold/mcp/src/core/policy/enforce.ts +98 -0
- package/scaffold/mcp/src/core/policy/injection.ts +229 -0
- package/scaffold/mcp/src/core/policy/store.ts +197 -0
- package/scaffold/mcp/src/core/rbac/check.ts +40 -0
- package/scaffold/mcp/src/core/telemetry/collector.ts +408 -0
- package/scaffold/mcp/src/core/validators/builtins.ts +711 -0
- package/scaffold/mcp/src/core/validators/config.ts +47 -0
- package/scaffold/mcp/src/core/validators/engine.ts +199 -0
- package/scaffold/mcp/src/core/validators/evaluators/code_comments.ts +294 -0
- package/scaffold/mcp/src/core/validators/evaluators/regex.ts +144 -0
- package/scaffold/mcp/src/daemon/client.ts +155 -0
- package/scaffold/mcp/src/daemon/egress-proxy.ts +331 -0
- package/scaffold/mcp/src/daemon/heartbeat-pusher.ts +147 -0
- package/scaffold/mcp/src/daemon/heartbeat-tracker.ts +223 -0
- package/scaffold/mcp/src/daemon/host-events-pusher.ts +285 -0
- package/scaffold/mcp/src/daemon/main.ts +435 -0
- package/scaffold/mcp/src/daemon/paths.ts +41 -0
- package/scaffold/mcp/src/daemon/protocol.ts +101 -0
- package/scaffold/mcp/src/daemon/server.ts +227 -0
- package/scaffold/mcp/src/daemon/sync-checker.ts +213 -0
- package/scaffold/mcp/src/daemon/ungoverned-scanner.ts +149 -0
- package/scaffold/mcp/src/enterprise/audit/push.ts +84 -0
- package/scaffold/mcp/src/enterprise/index.ts +386 -0
- package/scaffold/mcp/src/enterprise/model/deploy.ts +33 -0
- package/scaffold/mcp/src/enterprise/policy/sync.ts +146 -0
- package/scaffold/mcp/src/enterprise/privacy/boundary.ts +214 -0
- package/scaffold/mcp/src/enterprise/reviews/push.ts +79 -0
- package/scaffold/mcp/src/enterprise/telemetry/sync.ts +73 -0
- package/scaffold/mcp/src/enterprise/tools/enterprise.ts +1031 -0
- package/scaffold/mcp/src/enterprise/tools/walk.ts +79 -0
- package/scaffold/mcp/src/enterprise/violations/push.ts +102 -0
- package/scaffold/mcp/src/enterprise/workflow/push.ts +60 -0
- package/scaffold/mcp/src/enterprise/workflow/state.ts +535 -0
- package/scaffold/mcp/src/hooks/pre-compact.ts +54 -0
- package/scaffold/mcp/src/hooks/pre-tool-use.ts +96 -0
- package/scaffold/mcp/src/hooks/session-end.ts +73 -0
- package/scaffold/mcp/src/hooks/session-start.ts +78 -0
- package/scaffold/mcp/src/hooks/shared.ts +134 -0
- package/scaffold/mcp/src/hooks/stop.ts +60 -0
- package/scaffold/mcp/src/hooks/user-prompt-submit.ts +64 -0
- package/scaffold/mcp/src/loadGraph.ts +2 -0
- package/scaffold/mcp/src/plugin.ts +150 -0
- package/scaffold/mcp/src/server.ts +218 -7
- package/scaffold/mcp/tests/copilot-shim.test.mjs +146 -0
- package/scaffold/mcp/tests/daemon-client.test.mjs +32 -0
- package/scaffold/mcp/tests/egress-proxy.test.mjs +239 -0
- package/scaffold/mcp/tests/enterprise-config.test.mjs +154 -0
- package/scaffold/mcp/tests/govern-install.test.mjs +320 -0
- package/scaffold/mcp/tests/govern-repair.test.mjs +157 -0
- package/scaffold/mcp/tests/govern-status.test.mjs +538 -0
- package/scaffold/mcp/tests/govern.test.mjs +74 -0
- package/scaffold/mcp/tests/heartbeat-pusher.test.mjs +154 -0
- package/scaffold/mcp/tests/heartbeat-tracker.test.mjs +237 -0
- package/scaffold/mcp/tests/host-events-pusher.test.mjs +347 -0
- package/scaffold/mcp/tests/policy-check.test.mjs +220 -0
- package/scaffold/mcp/tests/repo-name.test.mjs +134 -0
- package/scaffold/mcp/tests/run.test.mjs +109 -0
- package/scaffold/mcp/tests/sync-checker.test.mjs +188 -0
- package/scaffold/mcp/tests/telemetry-collector.test.mjs +30 -0
- package/scaffold/mcp/tests/ungoverned-detector.test.mjs +191 -0
- package/scaffold/mcp/tests/ungoverned-scanner.test.mjs +198 -0
- package/scaffold/scripts/bootstrap.sh +0 -11
- package/scaffold/scripts/doctor.sh +24 -4
- package/types.js +5 -0
- package/docs/MCP_MARKETPLACE.md +0 -160
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import { runScanOnce, writeHostAuditEvent, startUngovernedScanner } from "../dist/daemon/ungoverned-scanner.js";
|
|
8
|
+
|
|
9
|
+
function makeWorkspace(governMode, installs = null) {
|
|
10
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "cortex-ungoverned-"));
|
|
11
|
+
const ctx = path.join(root, ".context");
|
|
12
|
+
fs.mkdirSync(ctx, { recursive: true });
|
|
13
|
+
if (governMode) {
|
|
14
|
+
const defaultInstalls = {
|
|
15
|
+
claude: { mode: governMode, path: "/x", version: "v", frameworks: [], installed_at: "now" },
|
|
16
|
+
};
|
|
17
|
+
fs.writeFileSync(
|
|
18
|
+
path.join(ctx, "govern.local.json"),
|
|
19
|
+
JSON.stringify({
|
|
20
|
+
installs: installs ?? defaultInstalls,
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return { root, ctx };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test("writeHostAuditEvent appends one JSONL line per call", async () => {
|
|
28
|
+
const { root } = makeWorkspace();
|
|
29
|
+
try {
|
|
30
|
+
await writeHostAuditEvent(root, { event_type: "ungoverned_ai_session_detected", pid: 100 });
|
|
31
|
+
await writeHostAuditEvent(root, { event_type: "ungoverned_ai_session_detected", pid: 200 });
|
|
32
|
+
|
|
33
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
34
|
+
const file = path.join(root, ".context", "audit", `host-events-${date}.jsonl`);
|
|
35
|
+
const content = fs.readFileSync(file, "utf8").trim().split("\n");
|
|
36
|
+
assert.equal(content.length, 2);
|
|
37
|
+
assert.equal(JSON.parse(content[0]).pid, 100);
|
|
38
|
+
assert.equal(JSON.parse(content[1]).pid, 200);
|
|
39
|
+
} finally {
|
|
40
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("runScanOnce: writes audit event with action=logged in advisory mode", async () => {
|
|
45
|
+
const { root } = makeWorkspace("advisory");
|
|
46
|
+
try {
|
|
47
|
+
const fakeProcs = [
|
|
48
|
+
{ pid: 1, ppid: 0, user: "root", comm: "init", args: "init" },
|
|
49
|
+
{ pid: 100, ppid: 1, user: os.userInfo().username, comm: "claude", args: "claude --prompt hi" },
|
|
50
|
+
];
|
|
51
|
+
const findings = await runScanOnce({
|
|
52
|
+
cwd: root,
|
|
53
|
+
detectorOptions: { processes: fakeProcs, hostId: "test-host" },
|
|
54
|
+
});
|
|
55
|
+
assert.equal(findings.length, 1);
|
|
56
|
+
|
|
57
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
58
|
+
const file = path.join(root, ".context", "audit", `host-events-${date}.jsonl`);
|
|
59
|
+
const events = fs.readFileSync(file, "utf8").trim().split("\n").map(JSON.parse);
|
|
60
|
+
assert.equal(events.length, 1);
|
|
61
|
+
assert.equal(events[0].event_type, "ungoverned_ai_session_detected");
|
|
62
|
+
assert.equal(events[0].cli, "claude");
|
|
63
|
+
assert.equal(events[0].mode, "advisory");
|
|
64
|
+
assert.equal(events[0].action, "logged");
|
|
65
|
+
assert.equal(events[0].host_id, "test-host");
|
|
66
|
+
} finally {
|
|
67
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("runScanOnce: enforced mode marks action=sigterm but our mock doesn't actually signal real procs", async () => {
|
|
72
|
+
const { root } = makeWorkspace("enforced");
|
|
73
|
+
try {
|
|
74
|
+
const me = os.userInfo().username;
|
|
75
|
+
const fakeProcs = [
|
|
76
|
+
{ pid: 99999, ppid: 1, user: me, comm: "claude", args: "claude --prompt hi" },
|
|
77
|
+
];
|
|
78
|
+
let killed = null;
|
|
79
|
+
// Monkey-patch process.kill for the test (the enforce function uses it as default).
|
|
80
|
+
const origKill = process.kill;
|
|
81
|
+
process.kill = (pid, sig) => {
|
|
82
|
+
killed = [pid, sig];
|
|
83
|
+
};
|
|
84
|
+
try {
|
|
85
|
+
const findings = await runScanOnce({
|
|
86
|
+
cwd: root,
|
|
87
|
+
detectorOptions: { processes: fakeProcs, hostId: "test-host" },
|
|
88
|
+
});
|
|
89
|
+
assert.equal(findings.length, 1);
|
|
90
|
+
} finally {
|
|
91
|
+
process.kill = origKill;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
95
|
+
const file = path.join(root, ".context", "audit", `host-events-${date}.jsonl`);
|
|
96
|
+
const events = fs.readFileSync(file, "utf8").trim().split("\n").map(JSON.parse);
|
|
97
|
+
assert.equal(events[0].mode, "enforced");
|
|
98
|
+
assert.equal(events[0].action, "sigterm");
|
|
99
|
+
assert.deepEqual(killed, [99999, "SIGTERM"]);
|
|
100
|
+
} finally {
|
|
101
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("runScanOnce: emits onFinding callback per detection", async () => {
|
|
106
|
+
const { root } = makeWorkspace("advisory");
|
|
107
|
+
try {
|
|
108
|
+
const fakeProcs = [
|
|
109
|
+
{ pid: 200, ppid: 1, user: "alice", comm: "codex", args: "codex --prompt hi" },
|
|
110
|
+
{ pid: 300, ppid: 1, user: "alice", comm: "copilot", args: "copilot --prompt hi" },
|
|
111
|
+
];
|
|
112
|
+
const seen = [];
|
|
113
|
+
await runScanOnce({
|
|
114
|
+
cwd: root,
|
|
115
|
+
mode: "advisory",
|
|
116
|
+
detectorOptions: { processes: fakeProcs },
|
|
117
|
+
onFinding: (f) => seen.push({ cli: f.cli, action: f.action }),
|
|
118
|
+
});
|
|
119
|
+
assert.equal(seen.length, 2);
|
|
120
|
+
assert.deepEqual(seen.map((s) => s.cli).sort(), ["codex", "copilot"]);
|
|
121
|
+
for (const s of seen) assert.equal(s.action, "logged");
|
|
122
|
+
} finally {
|
|
123
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("runScanOnce: skips Tier 1 CLIs that already have managed installs", async () => {
|
|
128
|
+
const { root } = makeWorkspace("enforced");
|
|
129
|
+
const managedClaudePath = path.join(root, "managed-settings.json");
|
|
130
|
+
fs.writeFileSync(managedClaudePath, "{}\n");
|
|
131
|
+
const managedCodexPath = path.join(root, "requirements.toml");
|
|
132
|
+
fs.writeFileSync(managedCodexPath, "# managed\n");
|
|
133
|
+
fs.writeFileSync(
|
|
134
|
+
path.join(root, ".context", "govern.local.json"),
|
|
135
|
+
JSON.stringify({
|
|
136
|
+
installs: {
|
|
137
|
+
claude: {
|
|
138
|
+
mode: "enforced",
|
|
139
|
+
path: managedClaudePath,
|
|
140
|
+
version: "v1",
|
|
141
|
+
frameworks: [],
|
|
142
|
+
installed_at: "now",
|
|
143
|
+
},
|
|
144
|
+
codex: {
|
|
145
|
+
mode: "enforced",
|
|
146
|
+
path: managedCodexPath,
|
|
147
|
+
version: "v2",
|
|
148
|
+
frameworks: [],
|
|
149
|
+
installed_at: "now",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
try {
|
|
155
|
+
const fakeProcs = [
|
|
156
|
+
{ pid: 200, ppid: 1, user: os.userInfo().username, comm: "claude", args: "claude --prompt hi" },
|
|
157
|
+
{ pid: 300, ppid: 1, user: os.userInfo().username, comm: "codex", args: "codex exec hi" },
|
|
158
|
+
{ pid: 400, ppid: 1, user: os.userInfo().username, comm: "copilot", args: "copilot suggest" },
|
|
159
|
+
];
|
|
160
|
+
const findings = await runScanOnce({
|
|
161
|
+
cwd: root,
|
|
162
|
+
mode: "enforced",
|
|
163
|
+
detectorOptions: { processes: fakeProcs, hostId: "test-host" },
|
|
164
|
+
});
|
|
165
|
+
assert.equal(findings.length, 1);
|
|
166
|
+
assert.equal(findings[0].cli, "copilot");
|
|
167
|
+
|
|
168
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
169
|
+
const file = path.join(root, ".context", "audit", `host-events-${date}.jsonl`);
|
|
170
|
+
const events = fs.readFileSync(file, "utf8").trim().split("\n").map(JSON.parse);
|
|
171
|
+
assert.equal(events.length, 1);
|
|
172
|
+
assert.equal(events[0].cli, "copilot");
|
|
173
|
+
} finally {
|
|
174
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("startUngovernedScanner: stop() halts further ticks", async () => {
|
|
179
|
+
const { root } = makeWorkspace("advisory");
|
|
180
|
+
try {
|
|
181
|
+
let calls = 0;
|
|
182
|
+
const handle = startUngovernedScanner({
|
|
183
|
+
cwd: root,
|
|
184
|
+
intervalMs: 50,
|
|
185
|
+
mode: "advisory",
|
|
186
|
+
detectorOptions: { processes: [] },
|
|
187
|
+
onFinding: () => {
|
|
188
|
+
calls += 1;
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
// Wait a moment to allow at least the immediate tick.
|
|
192
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
193
|
+
handle.stop();
|
|
194
|
+
assert.equal(handle.isRunning(), false);
|
|
195
|
+
} finally {
|
|
196
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
197
|
+
}
|
|
198
|
+
});
|
|
@@ -6,16 +6,6 @@ MCP_DIR="$REPO_ROOT/mcp"
|
|
|
6
6
|
TOTAL_STEPS=6
|
|
7
7
|
STEP_INDEX=0
|
|
8
8
|
|
|
9
|
-
print_logo() {
|
|
10
|
-
cat <<'EOF'
|
|
11
|
-
CCC OOO RRRR TTTTT EEEEE X X
|
|
12
|
-
C C O O R R T E X X
|
|
13
|
-
C O O RRRR T EEEE X
|
|
14
|
-
C C O O R R T E X X
|
|
15
|
-
CCC OOO R R T EEEEE X X
|
|
16
|
-
EOF
|
|
17
|
-
}
|
|
18
|
-
|
|
19
9
|
step() {
|
|
20
10
|
STEP_INDEX=$((STEP_INDEX + 1))
|
|
21
11
|
echo ""
|
|
@@ -26,7 +16,6 @@ info() {
|
|
|
26
16
|
echo "[cortex] $1"
|
|
27
17
|
}
|
|
28
18
|
|
|
29
|
-
print_logo
|
|
30
19
|
info "bootstrap start"
|
|
31
20
|
info "repo: $REPO_ROOT"
|
|
32
21
|
info "pipeline: deps -> ingest -> embeddings -> graph -> status"
|
|
@@ -44,8 +44,9 @@ else
|
|
|
44
44
|
fail ".context/config.yaml not found — run: cortex init"
|
|
45
45
|
fi
|
|
46
46
|
|
|
47
|
-
# Enterprise config
|
|
47
|
+
# Enterprise config — gate on api_key being set (file may exist as a stub).
|
|
48
48
|
ENTERPRISE_CONFIG=""
|
|
49
|
+
ENTERPRISE_ENABLED=0
|
|
49
50
|
if [[ -f "$CONTEXT_DIR/enterprise.yml" ]]; then
|
|
50
51
|
ENTERPRISE_CONFIG="$CONTEXT_DIR/enterprise.yml"
|
|
51
52
|
elif [[ -f "$CONTEXT_DIR/enterprise.yaml" ]]; then
|
|
@@ -53,9 +54,28 @@ elif [[ -f "$CONTEXT_DIR/enterprise.yaml" ]]; then
|
|
|
53
54
|
fi
|
|
54
55
|
|
|
55
56
|
if [[ -n "$ENTERPRISE_CONFIG" ]]; then
|
|
56
|
-
|
|
57
|
+
ENTERPRISE_API_KEY=$(node -e '
|
|
58
|
+
const fs = require("node:fs");
|
|
59
|
+
const raw = fs.readFileSync(process.argv[1], "utf8");
|
|
60
|
+
let section = "", fields = {};
|
|
61
|
+
for (const line of raw.split("\n")) {
|
|
62
|
+
const t = line.trimEnd();
|
|
63
|
+
if (!t || t.startsWith("#")) continue;
|
|
64
|
+
const sm = t.match(/^(\w+):\s*$/);
|
|
65
|
+
if (sm) { section = sm[1]; continue; }
|
|
66
|
+
const kv = t.match(/^\s+(\w+):\s*(.+?)\s*$/);
|
|
67
|
+
if (kv && section) fields[section + "." + kv[1]] = kv[2].replace(/^["\x27]|["\x27]$/g, "");
|
|
68
|
+
}
|
|
69
|
+
console.log(fields["enterprise.api_key"] || "");
|
|
70
|
+
' "$ENTERPRISE_CONFIG" 2>/dev/null || echo "")
|
|
71
|
+
if [[ -n "$ENTERPRISE_API_KEY" ]]; then
|
|
72
|
+
pass "enterprise config found: $(basename "$ENTERPRISE_CONFIG")"
|
|
73
|
+
ENTERPRISE_ENABLED=1
|
|
74
|
+
else
|
|
75
|
+
info "enterprise config present but no api_key set — see https://cortx.se for more info"
|
|
76
|
+
fi
|
|
57
77
|
else
|
|
58
|
-
info "no enterprise config (community mode)"
|
|
78
|
+
info "no enterprise config (community mode) — see https://cortx.se for more info"
|
|
59
79
|
fi
|
|
60
80
|
|
|
61
81
|
# ── Index ───────────────────────────────────────────────
|
|
@@ -177,7 +197,7 @@ fi
|
|
|
177
197
|
|
|
178
198
|
# ── Enterprise ──────────────────────────────────────────
|
|
179
199
|
|
|
180
|
-
if [[
|
|
200
|
+
if [[ "$ENTERPRISE_ENABLED" == "1" ]]; then
|
|
181
201
|
echo ""
|
|
182
202
|
echo " Enterprise"
|
|
183
203
|
|
package/types.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// v2.0.0: empty types module preserved for the published `./types` export.
|
|
2
|
+
// Pre-v2 the source lived in mcp/dist/types.js (also empty). External
|
|
3
|
+
// consumers who imported from @danielblomma/cortex-mcp/types continue
|
|
4
|
+
// to resolve, even though there are no runtime exports.
|
|
5
|
+
export {};
|
package/docs/MCP_MARKETPLACE.md
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
# MCP Marketplace Submission
|
|
2
|
-
|
|
3
|
-
## Package Information
|
|
4
|
-
|
|
5
|
-
**Name:** `@danielblomma/cortex-mcp`
|
|
6
|
-
**Description:** Local, repo-scoped context platform for coding assistants. Semantic search, graph relationships, and architectural rule context.
|
|
7
|
-
**Author:** Daniel Blomma
|
|
8
|
-
**License:** MIT
|
|
9
|
-
**Repository:** https://github.com/DanielBlomma/cortex
|
|
10
|
-
|
|
11
|
-
## MCP Server Details
|
|
12
|
-
|
|
13
|
-
### Tools Provided
|
|
14
|
-
|
|
15
|
-
1. **context.search**
|
|
16
|
-
- Semantic search across indexed entities (files, rules, ADRs)
|
|
17
|
-
- Hybrid ranking (semantic + graph + trust + recency)
|
|
18
|
-
- Optional content return for high-signal snippets
|
|
19
|
-
|
|
20
|
-
2. **context.get_related**
|
|
21
|
-
- Graph-based entity relationships
|
|
22
|
-
- Finds connected rules/files/ADRs with optional edge details
|
|
23
|
-
|
|
24
|
-
3. **context.get_rules**
|
|
25
|
-
- Active rules and architectural decisions
|
|
26
|
-
- Scope-based filtering
|
|
27
|
-
|
|
28
|
-
4. **context.reload**
|
|
29
|
-
- Hot-reload graph after code changes
|
|
30
|
-
|
|
31
|
-
### Advanced Features (Experimental)
|
|
32
|
-
|
|
33
|
-
Cortex can extract function-level chunks and build call graphs in experimental builds:
|
|
34
|
-
|
|
35
|
-
- `context.find_callers` - What calls this function?
|
|
36
|
-
- `context.trace_calls` - What does this function call?
|
|
37
|
-
- `context.impact_analysis` - What is impacted if this function changes?
|
|
38
|
-
- Requires JavaScript/TypeScript codebase and semantic chunking/call graph indexing enabled.
|
|
39
|
-
|
|
40
|
-
Note: these APIs are experimental and are not part of the stable tool contract in this submission.
|
|
41
|
-
|
|
42
|
-
### Installation
|
|
43
|
-
|
|
44
|
-
#### For MCP Marketplace Users
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
# Install CLI globally
|
|
48
|
-
npm i -g @danielblomma/cortex-mcp
|
|
49
|
-
|
|
50
|
-
# Navigate to your project
|
|
51
|
-
cd ~/my-project
|
|
52
|
-
|
|
53
|
-
# Initialize Cortex in your project
|
|
54
|
-
cortex init --bootstrap
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
This will:
|
|
58
|
-
- Create `.context/` directory with graph schema
|
|
59
|
-
- Set up MCP server for Claude Desktop/Code
|
|
60
|
-
- Start background sync for automatic updates
|
|
61
|
-
- Build a local context graph for indexed files/rules/ADRs
|
|
62
|
-
|
|
63
|
-
#### Manual MCP Configuration
|
|
64
|
-
|
|
65
|
-
If `cortex init` doesn't auto-register, add to Claude's MCP config:
|
|
66
|
-
|
|
67
|
-
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
68
|
-
```json
|
|
69
|
-
{
|
|
70
|
-
"mcpServers": {
|
|
71
|
-
"cortex": {
|
|
72
|
-
"command": "cortex",
|
|
73
|
-
"args": ["mcp"],
|
|
74
|
-
"env": {
|
|
75
|
-
"CORTEX_PROJECT_ROOT": "/absolute/path/to/your-project"
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
**Codex** (`~/.config/codex/mcp-config.json`):
|
|
83
|
-
```json
|
|
84
|
-
{
|
|
85
|
-
"mcpServers": {
|
|
86
|
-
"cortex-myproject": {
|
|
87
|
-
"command": "cortex",
|
|
88
|
-
"args": ["mcp"],
|
|
89
|
-
"cwd": "/absolute/path/to/your-project"
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Usage
|
|
96
|
-
|
|
97
|
-
Once installed and initialized, Cortex tools are available in Claude:
|
|
98
|
-
|
|
99
|
-
```
|
|
100
|
-
"Find files that handle authentication"
|
|
101
|
-
"Show related files for this ADR"
|
|
102
|
-
"What are the active architectural rules for this API?"
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Key Features
|
|
106
|
-
|
|
107
|
-
- **Semantic search**: ranked retrieval across source files, rules and ADRs
|
|
108
|
-
- **Graph relationships**: quickly discover related entities and constraints
|
|
109
|
-
- **Experimental call graph APIs**: function caller/callee and impact traversal in semantic chunking builds
|
|
110
|
-
- **Local & private**: All data stays on your machine
|
|
111
|
-
- **Incremental updates**: Background sync keeps context fresh
|
|
112
|
-
- **Flexible ingestion**: configurable source paths and ranking signals
|
|
113
|
-
|
|
114
|
-
### Requirements
|
|
115
|
-
|
|
116
|
-
- Node.js 18+
|
|
117
|
-
- Git repository (for change tracking)
|
|
118
|
-
- ~50MB disk space per project
|
|
119
|
-
|
|
120
|
-
### Unique Value Proposition
|
|
121
|
-
|
|
122
|
-
Unlike other MCP servers that provide external data (GitHub, web search), Cortex provides **deep, structured knowledge of YOUR codebase**:
|
|
123
|
-
|
|
124
|
-
- Search with semantic ranking across files, rules, and ADRs
|
|
125
|
-
- Understand rule and ADR dependencies in your repo
|
|
126
|
-
- Enforce architectural rules and ADRs
|
|
127
|
-
- Context that evolves with your code
|
|
128
|
-
|
|
129
|
-
Perfect for:
|
|
130
|
-
- Large codebases where plain keyword search is not enough
|
|
131
|
-
- Refactoring guided by rule and ADR context
|
|
132
|
-
- Onboarding (architectural rules, design decisions)
|
|
133
|
-
- Code review (what constraints and related entities apply?)
|
|
134
|
-
|
|
135
|
-
### Limitations
|
|
136
|
-
|
|
137
|
-
- **Setup required**: Not instant plug-and-play (needs `cortex init`)
|
|
138
|
-
- **Per-project**: Each repo needs its own Cortex instance
|
|
139
|
-
- **Local only**: No cloud sync (by design - your code stays private)
|
|
140
|
-
|
|
141
|
-
### Support
|
|
142
|
-
|
|
143
|
-
- Issues: https://github.com/DanielBlomma/cortex/issues
|
|
144
|
-
- Docs: https://github.com/DanielBlomma/cortex/blob/main/README.md
|
|
145
|
-
|
|
146
|
-
## Submission Checklist
|
|
147
|
-
|
|
148
|
-
- [x] MCP SDK integration (JSON-RPC over stdio)
|
|
149
|
-
- [x] Tools documented with schemas
|
|
150
|
-
- [ ] npm package published (@danielblomma/cortex-mcp)
|
|
151
|
-
- [x] Marketplace-ready README
|
|
152
|
-
- [ ] Example usage screenshots/GIFs
|
|
153
|
-
- [ ] Submit PR to modelcontextprotocol/servers
|
|
154
|
-
|
|
155
|
-
## Next Steps
|
|
156
|
-
|
|
157
|
-
1. Publish to npm as `@danielblomma/cortex-mcp`
|
|
158
|
-
2. Test installation from marketplace perspective
|
|
159
|
-
3. Submit to https://github.com/modelcontextprotocol/servers
|
|
160
|
-
4. Add to Anthropic's community registry
|