openkrak-mcp 1.0.11 → 1.0.12
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 +55 -26
- package/dist/index.js +37 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
MCP server for developer tooling. Performs static analysis on a software repository via the Dorchester engine and delivers a structured context brief to the language model before any file is accessed.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Supported:** OpenCode (TUI + Desktop). Claude Code, Codex, and Cursor support in development.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -46,9 +46,21 @@ Requires Node.js ≥ 18.
|
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
49
|
-
## Setup
|
|
49
|
+
## Setup
|
|
50
50
|
|
|
51
|
-
**
|
|
51
|
+
OpenKrak works with both **OpenCode TUI** and **OpenCode Desktop**. The config file and setup steps are identical for both.
|
|
52
|
+
|
|
53
|
+
### Config file location
|
|
54
|
+
|
|
55
|
+
| Platform | Path |
|
|
56
|
+
|----------|------|
|
|
57
|
+
| Windows | `C:\Users\<username>\.config\opencode\opencode.jsonc` |
|
|
58
|
+
| macOS | `~/.config/opencode/opencode.jsonc` |
|
|
59
|
+
| Linux | `~/.config/opencode/opencode.jsonc` |
|
|
60
|
+
|
|
61
|
+
### Step 1 — Add OpenKrak to your config
|
|
62
|
+
|
|
63
|
+
Open the config file and add the `mcp` block:
|
|
52
64
|
|
|
53
65
|
```jsonc
|
|
54
66
|
{
|
|
@@ -63,45 +75,62 @@ Requires Node.js ≥ 18.
|
|
|
63
75
|
}
|
|
64
76
|
```
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
> macOS / Linux: `~/.config/opencode/opencode.jsonc`
|
|
78
|
+
### Step 2 — Restart OpenCode
|
|
68
79
|
|
|
69
|
-
|
|
80
|
+
Close and reopen OpenCode (TUI or Desktop). Confirm **openkrak Connected** appears in the MCP panel.
|
|
70
81
|
|
|
71
|
-
**
|
|
82
|
+
> **OpenCode Desktop:** MCP status is shown in the bottom status bar. Green dot = connected.
|
|
83
|
+
> **OpenCode TUI:** MCP status is shown in the top-right panel.
|
|
84
|
+
|
|
85
|
+
### Step 3 — Run your first analysis
|
|
86
|
+
|
|
87
|
+
Open any repository in OpenCode and send a message:
|
|
72
88
|
|
|
73
|
-
To invoke explicitly:
|
|
74
89
|
```
|
|
75
|
-
|
|
90
|
+
analyze_repo on /absolute/path/to/your/repo
|
|
76
91
|
```
|
|
77
92
|
|
|
93
|
+
Or just start a coding task normally — OpenKrak runs automatically before the model reads any file.
|
|
94
|
+
|
|
78
95
|
---
|
|
79
96
|
|
|
80
|
-
##
|
|
97
|
+
## Tools
|
|
81
98
|
|
|
82
|
-
|
|
99
|
+
| Tool | When to use |
|
|
100
|
+
|------|-------------|
|
|
101
|
+
| `analyze_repo` | Full pipeline. Call before any coding task on a new repo. Returns dependency graph, hotspot rankings, blast radius, and threat matrix. |
|
|
102
|
+
| `get_mahadata` | Compact repo brief — structure, entry points, constraints, risk score. Use when you need repo-wide context without a full re-analysis. |
|
|
103
|
+
| `get_hotspots` | Ranked list of high-risk files by complexity, coupling, and change frequency. Use before reviewing or refactoring. |
|
|
104
|
+
| `blast_radius` | Impact map for a specific file. Which files, modules, and APIs are affected by a change. Use before modifying a critical file. |
|
|
105
|
+
|
|
106
|
+
### Example prompts
|
|
83
107
|
|
|
84
|
-
```
|
|
85
|
-
"
|
|
86
|
-
|
|
108
|
+
```
|
|
109
|
+
analyze_repo on /path/to/repo with objective "refactor the auth module"
|
|
110
|
+
```
|
|
111
|
+
```
|
|
112
|
+
Run get_hotspots on /path/to/repo — which files should I avoid touching?
|
|
113
|
+
```
|
|
114
|
+
```
|
|
115
|
+
blast_radius on /path/to/repo for file src/api/routes/user.ts
|
|
87
116
|
```
|
|
88
117
|
|
|
89
118
|
---
|
|
90
119
|
|
|
91
|
-
##
|
|
120
|
+
## Enable / Disable
|
|
92
121
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
122
|
+
Set `"enabled"` in your config and restart OpenCode:
|
|
123
|
+
|
|
124
|
+
```jsonc
|
|
125
|
+
"enabled": true // on
|
|
126
|
+
"enabled": false // off
|
|
127
|
+
```
|
|
99
128
|
|
|
100
129
|
---
|
|
101
130
|
|
|
102
|
-
## License
|
|
131
|
+
## Pro License
|
|
103
132
|
|
|
104
|
-
Free tier is active by default — no account
|
|
133
|
+
Free tier is active by default — no account required.
|
|
105
134
|
|
|
106
135
|
| Plan | Price | Queries |
|
|
107
136
|
|------|-------|---------|
|
|
@@ -109,7 +138,7 @@ Free tier is active by default — no account or configuration required.
|
|
|
109
138
|
| Pro Monthly | $8 / month | Unlimited |
|
|
110
139
|
| Pro Annual | $67.20 / year | Unlimited |
|
|
111
140
|
|
|
112
|
-
To activate
|
|
141
|
+
To activate Pro, add your key to the config:
|
|
113
142
|
|
|
114
143
|
```jsonc
|
|
115
144
|
{
|
|
@@ -127,7 +156,7 @@ To activate a Pro license, add your key to the MCP config:
|
|
|
127
156
|
}
|
|
128
157
|
```
|
|
129
158
|
|
|
130
|
-
License keys
|
|
159
|
+
License keys: [openkrak-web.vercel.app](https://openkrak-web.vercel.app). Delivered via email after purchase. No subscription, no auto-renewal.
|
|
131
160
|
|
|
132
161
|
---
|
|
133
162
|
|
|
@@ -135,7 +164,7 @@ License keys are available at [openkrak-web.vercel.app](https://openkrak-web.ver
|
|
|
135
164
|
|
|
136
165
|
- Static analysis only. No AI inference in the pipeline.
|
|
137
166
|
- Anonymous usage telemetry is collected (query count, tool name, error events). No source code or file contents are transmitted.
|
|
138
|
-
- License validation requires a network call to
|
|
167
|
+
- License validation requires a network call to the license server on each tool invocation.
|
|
139
168
|
|
|
140
169
|
---
|
|
141
170
|
|
package/dist/index.js
CHANGED
|
@@ -241794,7 +241794,7 @@ function inferLanguage(filePath) {
|
|
|
241794
241794
|
if (filePath.endsWith(".js") || filePath.endsWith(".jsx")) return "javascript";
|
|
241795
241795
|
return "unknown";
|
|
241796
241796
|
}
|
|
241797
|
-
function extractRawEdges(filePath, ast, fileNodeId) {
|
|
241797
|
+
function extractRawEdges(filePath, ast, fileNodeId, tsconfigPaths2) {
|
|
241798
241798
|
const edges = [];
|
|
241799
241799
|
const symbolRanges = [];
|
|
241800
241800
|
(0, import_typescript_estree3.simpleTraverse)(ast, {
|
|
@@ -241845,8 +241845,7 @@ function extractRawEdges(filePath, ast, fileNodeId) {
|
|
|
241845
241845
|
if (node.type === "ImportDeclaration") {
|
|
241846
241846
|
const importPath = node.source.value;
|
|
241847
241847
|
const line = node.loc?.start.line ?? null;
|
|
241848
|
-
const
|
|
241849
|
-
const resolvedTarget = resolveImportTarget(filePath, importPath);
|
|
241848
|
+
const resolvedTarget = resolveImportTarget(filePath, importPath, tsconfigPaths2);
|
|
241850
241849
|
edges.push({
|
|
241851
241850
|
from: importFromNodeId,
|
|
241852
241851
|
to: resolvedTarget,
|
|
@@ -241875,7 +241874,7 @@ function extractRawEdges(filePath, ast, fileNodeId) {
|
|
|
241875
241874
|
const sourceLiteral = node.source.type === "Literal" ? node.source.value : "<dynamic>";
|
|
241876
241875
|
edges.push({
|
|
241877
241876
|
from: fileNodeId,
|
|
241878
|
-
to: resolveImportTarget(filePath, sourceLiteral),
|
|
241877
|
+
to: resolveImportTarget(filePath, sourceLiteral, tsconfigPaths2),
|
|
241879
241878
|
kind: "dynamic_import",
|
|
241880
241879
|
file: filePath,
|
|
241881
241880
|
line,
|
|
@@ -241889,7 +241888,6 @@ function extractRawEdges(filePath, ast, fileNodeId) {
|
|
|
241889
241888
|
edges.push({
|
|
241890
241889
|
from: fileNodeId,
|
|
241891
241890
|
to: superName,
|
|
241892
|
-
// resolved to node_id in assembly
|
|
241893
241891
|
kind: "extend",
|
|
241894
241892
|
file: filePath,
|
|
241895
241893
|
line: node.loc.start.line,
|
|
@@ -241954,7 +241952,7 @@ function extractRawEdges(filePath, ast, fileNodeId) {
|
|
|
241954
241952
|
});
|
|
241955
241953
|
return edges;
|
|
241956
241954
|
}
|
|
241957
|
-
function extractBarrelReExports(filePath, ast) {
|
|
241955
|
+
function extractBarrelReExports(filePath, ast, tsconfigPaths2) {
|
|
241958
241956
|
const reExports = [];
|
|
241959
241957
|
(0, import_typescript_estree3.simpleTraverse)(ast, {
|
|
241960
241958
|
enter(node) {
|
|
@@ -241962,7 +241960,7 @@ function extractBarrelReExports(filePath, ast) {
|
|
|
241962
241960
|
if (!node.source) return;
|
|
241963
241961
|
const sourcePath = node.source.value;
|
|
241964
241962
|
if (!sourcePath.startsWith(".")) return;
|
|
241965
|
-
const resolvedSource = resolveImportTarget(filePath, sourcePath);
|
|
241963
|
+
const resolvedSource = resolveImportTarget(filePath, sourcePath, tsconfigPaths2);
|
|
241966
241964
|
if (node.type === "ExportAllDeclaration") {
|
|
241967
241965
|
reExports.push({
|
|
241968
241966
|
barrelFile: filePath,
|
|
@@ -241985,7 +241983,7 @@ function extractBarrelReExports(filePath, ast) {
|
|
|
241985
241983
|
});
|
|
241986
241984
|
return reExports;
|
|
241987
241985
|
}
|
|
241988
|
-
function resolveImportTarget(fromFile, importPath) {
|
|
241986
|
+
function resolveImportTarget(fromFile, importPath, tsconfigPaths2) {
|
|
241989
241987
|
if (importPath.startsWith(".")) {
|
|
241990
241988
|
const dir = import_path3.default.dirname(fromFile);
|
|
241991
241989
|
const resolved = import_path3.default.join(dir, importPath).replace(/\\/g, "/");
|
|
@@ -241995,11 +241993,29 @@ function resolveImportTarget(fromFile, importPath) {
|
|
|
241995
241993
|
}
|
|
241996
241994
|
return normalized;
|
|
241997
241995
|
}
|
|
241996
|
+
if (tsconfigPaths2) {
|
|
241997
|
+
for (const [prefix, target] of Object.entries(tsconfigPaths2)) {
|
|
241998
|
+
if (importPath.startsWith(prefix)) {
|
|
241999
|
+
const rest = importPath.slice(prefix.length);
|
|
242000
|
+
const resolved = (target + rest).replace(/\\/g, "/");
|
|
242001
|
+
const normalized = resolved.replace(/\.js$/, ".ts");
|
|
242002
|
+
if (!import_path3.default.extname(normalized)) {
|
|
242003
|
+
return normalized + ".ts";
|
|
242004
|
+
}
|
|
242005
|
+
return normalized;
|
|
242006
|
+
}
|
|
242007
|
+
}
|
|
242008
|
+
}
|
|
242009
|
+
if (importPath.startsWith("@/")) {
|
|
242010
|
+
const stripped = importPath.slice(2);
|
|
242011
|
+
const normalized = stripped.replace(/\.js$/, ".ts");
|
|
242012
|
+
if (!import_path3.default.extname(normalized)) {
|
|
242013
|
+
return normalized + ".ts";
|
|
242014
|
+
}
|
|
242015
|
+
return normalized;
|
|
242016
|
+
}
|
|
241998
242017
|
return importPath;
|
|
241999
242018
|
}
|
|
242000
|
-
function isBarrelPath(importPath) {
|
|
242001
|
-
return importPath.endsWith("/index") || importPath.endsWith("/index.ts") || importPath.endsWith("/index.js") || importPath === "index";
|
|
242002
|
-
}
|
|
242003
242019
|
function assembleGraph(allNodes, rawEdges, barrelReExports = []) {
|
|
242004
242020
|
for (const node of allNodes) {
|
|
242005
242021
|
node.id = node.id.replace(/\\/g, "/");
|
|
@@ -242511,7 +242527,14 @@ function inferModule(filePath) {
|
|
|
242511
242527
|
return parts.length >= 2 ? parts[0] : null;
|
|
242512
242528
|
}
|
|
242513
242529
|
function isEntryPoint(filePath) {
|
|
242514
|
-
|
|
242530
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
242531
|
+
if (/\/(index|main|server|cli)\.(ts|js)x?$/.test(normalized)) return true;
|
|
242532
|
+
if (/^app\/.*page\.(ts|js)x?$/.test(normalized)) return true;
|
|
242533
|
+
if (/^app\/layout\.(ts|js)x?$/.test(normalized)) return true;
|
|
242534
|
+
if (/^app\/.*route\.(ts|js)x?$/.test(normalized)) return true;
|
|
242535
|
+
if (/^pages\//.test(normalized) && /\.(ts|js)x?$/.test(normalized)) return true;
|
|
242536
|
+
if (/^src\/(main|index|App)\.(ts|js)x?$/.test(normalized)) return true;
|
|
242537
|
+
return false;
|
|
242515
242538
|
}
|
|
242516
242539
|
function walkAst(node, onEnter, onLeave) {
|
|
242517
242540
|
if (!node || typeof node !== "object" || !("type" in node)) return;
|
|
@@ -242622,9 +242645,9 @@ async function runDeepStrike(store, opts) {
|
|
|
242622
242645
|
for (const s of symbols) s.content_hash = currentHash;
|
|
242623
242646
|
allNodes.push(...symbols);
|
|
242624
242647
|
const fileNodeId = symbols.find((n) => n.kind === "file")?.id ?? `${relPath}::file::0`;
|
|
242625
|
-
const rawEdges = extractRawEdges(relPath, ast, fileNodeId);
|
|
242648
|
+
const rawEdges = extractRawEdges(relPath, ast, fileNodeId, tsconfigPaths);
|
|
242626
242649
|
allRawEdges.push(...rawEdges);
|
|
242627
|
-
const barrelReExports = extractBarrelReExports(relPath, ast);
|
|
242650
|
+
const barrelReExports = extractBarrelReExports(relPath, ast, tsconfigPaths);
|
|
242628
242651
|
allBarrelReExports.push(...barrelReExports);
|
|
242629
242652
|
const secFindings = detectSecurityPatterns(relPath, content);
|
|
242630
242653
|
allFindings.push(...secFindings);
|