@misterhuydo/cairn-mcp 1.2.1 → 1.2.2
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 +45 -7
- package/bin/cairn-cli.js +43 -5
- package/bin/cairn.js +1 -1
- package/index.js +178 -165
- package/package.json +44 -44
- package/src/tools/minify.js +40 -0
package/README.md
CHANGED
|
@@ -36,6 +36,28 @@ Restart Claude Code. Done.
|
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
+
## Passive hooks (recommended)
|
|
40
|
+
|
|
41
|
+
Install once and Cairn works automatically — no need to call `cairn_bundle` or `cairn_checkpoint` manually.
|
|
42
|
+
|
|
43
|
+
**Global (all projects):**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cairn install-hooks --global
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Project-only:**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cairn install-hooks
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
| Hook | Trigger | Effect |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `PreToolUse[Read]` | Every file read | Source files compressed ~68% before Claude sees them |
|
|
58
|
+
| `Stop` | Every response | Session auto-saved to `.cairn/session.json` |
|
|
59
|
+
| `UserPromptSubmit` | First message of session | Claude reminded of prior session, prompted to call `cairn_resume` |
|
|
60
|
+
|
|
39
61
|
## How it works
|
|
40
62
|
|
|
41
63
|
Cairn works like git — it looks for `.cairn/` in your current working directory. Run Claude Code from your project root and Cairn automatically stores the index at `.cairn/index.db` and bundles at `.cairn/bundles/`.
|
|
@@ -158,6 +180,15 @@ Covers: XSS · XXE · SQL injection · command injection · weak crypto · hardc
|
|
|
158
180
|
|
|
159
181
|
---
|
|
160
182
|
|
|
183
|
+
### `cairn_minify` — Minify a single file
|
|
184
|
+
Minify one source file on demand. Returns compressed content with a `[cairn: N → M lines]` header. Useful when passive hooks are not installed.
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
cairn_minify({ file_path: "/abs/path/to/Service.java" })
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
161
192
|
### `cairn_checkpoint` — Save session state
|
|
162
193
|
Tell Cairn what you were working on so the next session can pick up where you left off.
|
|
163
194
|
|
|
@@ -223,7 +254,16 @@ cairn_resume()
|
|
|
223
254
|
|
|
224
255
|
## Typical session
|
|
225
256
|
|
|
226
|
-
**
|
|
257
|
+
**With passive hooks (recommended):**
|
|
258
|
+
```
|
|
259
|
+
1. cairn_maintain → index project
|
|
260
|
+
2. cairn_search → find the symbols you need
|
|
261
|
+
3. cairn_describe → understand a module before modifying it
|
|
262
|
+
4. cairn_security → check for issues before a PR
|
|
263
|
+
(reads auto-compressed, session auto-saved — nothing else needed)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Without hooks:**
|
|
227
267
|
```
|
|
228
268
|
1. cairn_maintain → index project (persists between sessions)
|
|
229
269
|
2. cairn_search → find the symbols you need
|
|
@@ -251,16 +291,14 @@ Claude: cairn_resume
|
|
|
251
291
|
Ready to continue."
|
|
252
292
|
|
|
253
293
|
─── DURING SESSION ────────────────────────────────────────────
|
|
254
|
-
Claude uses: cairn_search,
|
|
294
|
+
Claude uses: cairn_search, cairn_code_graph, cairn_security
|
|
255
295
|
as needed — all reading from .cairn/index.db in cwd
|
|
296
|
+
(file reads auto-compressed by PreToolUse hook)
|
|
256
297
|
|
|
257
298
|
─── END OF SESSION ────────────────────────────────────────────
|
|
258
299
|
You: "Ok let's stop here for today"
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
active_files=["src/components/checkout/PaymentStep.vue"]
|
|
262
|
-
notes=["Still need to add JPY — no decimal places", "QA needs to test AED"]
|
|
263
|
-
→ "Session saved. Resume anytime with cairn_resume."
|
|
300
|
+
(Stop hook fires → .cairn/session.json auto-saved)
|
|
301
|
+
→ Next session: cairn_resume picks up automatically
|
|
264
302
|
```
|
|
265
303
|
|
|
266
304
|
---
|
package/bin/cairn-cli.js
CHANGED
|
@@ -9,6 +9,12 @@ import { checkpoint } from '../src/tools/checkpoint.js';
|
|
|
9
9
|
|
|
10
10
|
const MINIFY_LANGS = new Set(['java', 'typescript', 'javascript', 'vue', 'python', 'sql']);
|
|
11
11
|
|
|
12
|
+
// Ensure any unexpected crash in a hook subcommand passes through rather than blocking
|
|
13
|
+
process.on('uncaughtException', (e) => {
|
|
14
|
+
process.stderr.write(`cairn: ${e.message}\n`);
|
|
15
|
+
process.exit(0);
|
|
16
|
+
});
|
|
17
|
+
|
|
12
18
|
const subcommand = process.argv[2];
|
|
13
19
|
|
|
14
20
|
if (subcommand === 'minify') {
|
|
@@ -56,6 +62,27 @@ if (subcommand === 'minify') {
|
|
|
56
62
|
checkpoint(null, { message, active_files: [], notes: [] });
|
|
57
63
|
process.exit(0);
|
|
58
64
|
|
|
65
|
+
} else if (subcommand === 'resume-hint') {
|
|
66
|
+
const cairnDir = path.join(process.cwd(), '.cairn');
|
|
67
|
+
const sessionPath = path.join(cairnDir, 'session.json');
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(sessionPath)) process.exit(0);
|
|
70
|
+
|
|
71
|
+
// Rate-limit: show at most once every 30 minutes to avoid spamming mid-session
|
|
72
|
+
const lockPath = path.join(cairnDir, '.hint-lock');
|
|
73
|
+
const LOCK_TTL_MS = 30 * 60 * 1000;
|
|
74
|
+
if (fs.existsSync(lockPath)) {
|
|
75
|
+
const age = Date.now() - fs.statSync(lockPath).mtimeMs;
|
|
76
|
+
if (age < LOCK_TTL_MS) process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const session = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
|
|
80
|
+
const when = new Date(session.checkpoint_at).toLocaleString();
|
|
81
|
+
process.stdout.write(`[cairn] Prior session: "${session.message}" (${when}). Call cairn_resume to restore prior context.\n`);
|
|
82
|
+
|
|
83
|
+
fs.writeFileSync(lockPath, new Date().toISOString(), 'utf8');
|
|
84
|
+
process.exit(0);
|
|
85
|
+
|
|
59
86
|
} else if (subcommand === 'install-hooks') {
|
|
60
87
|
const isGlobal = process.argv.includes('--global');
|
|
61
88
|
const settingsDir = isGlobal
|
|
@@ -74,7 +101,7 @@ if (subcommand === 'minify') {
|
|
|
74
101
|
|
|
75
102
|
settings.hooks = settings.hooks || {};
|
|
76
103
|
|
|
77
|
-
//
|
|
104
|
+
// PreToolUse[Read] → cairn minify
|
|
78
105
|
const preHooks = settings.hooks.PreToolUse || [];
|
|
79
106
|
const hasMinify = preHooks.some(h =>
|
|
80
107
|
h.matcher === 'Read' && h.hooks?.some(hh => hh.command === 'cairn minify')
|
|
@@ -84,7 +111,7 @@ if (subcommand === 'minify') {
|
|
|
84
111
|
settings.hooks.PreToolUse = preHooks;
|
|
85
112
|
}
|
|
86
113
|
|
|
87
|
-
//
|
|
114
|
+
// Stop → cairn checkpoint --auto
|
|
88
115
|
const stopHooks = settings.hooks.Stop || [];
|
|
89
116
|
const hasCheckpoint = stopHooks.some(h =>
|
|
90
117
|
h.hooks?.some(hh => hh.command === 'cairn checkpoint --auto')
|
|
@@ -94,6 +121,16 @@ if (subcommand === 'minify') {
|
|
|
94
121
|
settings.hooks.Stop = stopHooks;
|
|
95
122
|
}
|
|
96
123
|
|
|
124
|
+
// UserPromptSubmit → cairn resume-hint
|
|
125
|
+
const submitHooks = settings.hooks.UserPromptSubmit || [];
|
|
126
|
+
const hasResumeHint = submitHooks.some(h =>
|
|
127
|
+
h.hooks?.some(hh => hh.command === 'cairn resume-hint')
|
|
128
|
+
);
|
|
129
|
+
if (!hasResumeHint) {
|
|
130
|
+
submitHooks.push({ hooks: [{ type: 'command', command: 'cairn resume-hint' }] });
|
|
131
|
+
settings.hooks.UserPromptSubmit = submitHooks;
|
|
132
|
+
}
|
|
133
|
+
|
|
97
134
|
fs.mkdirSync(settingsDir, { recursive: true });
|
|
98
135
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
99
136
|
|
|
@@ -101,11 +138,12 @@ if (subcommand === 'minify') {
|
|
|
101
138
|
console.log(`Cairn hooks installed in ${scope}`);
|
|
102
139
|
console.log('');
|
|
103
140
|
console.log('Active hooks:');
|
|
104
|
-
console.log(' PreToolUse[Read]
|
|
105
|
-
console.log(' Stop
|
|
141
|
+
console.log(' PreToolUse[Read] → cairn minify (compress source files)');
|
|
142
|
+
console.log(' Stop → cairn checkpoint --auto (auto-save session)');
|
|
143
|
+
console.log(' UserPromptSubmit → cairn resume-hint (remind Claude of prior session)');
|
|
106
144
|
process.exit(0);
|
|
107
145
|
|
|
108
146
|
} else {
|
|
109
|
-
console.error('Usage: cairn <minify|checkpoint --auto|install-hooks>');
|
|
147
|
+
console.error('Usage: cairn <minify|checkpoint --auto|resume-hint|install-hooks [--global]>');
|
|
110
148
|
process.exit(1);
|
|
111
149
|
}
|
package/bin/cairn.js
CHANGED
package/index.js
CHANGED
|
@@ -1,165 +1,178 @@
|
|
|
1
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
-
|
|
5
|
-
import { openDB } from './src/graph/db.js';
|
|
6
|
-
import { maintain } from './src/tools/maintain.js';
|
|
7
|
-
import { search } from './src/tools/search.js';
|
|
8
|
-
import { describe } from './src/tools/describe.js';
|
|
9
|
-
import { codeGraph } from './src/tools/codeGraph.js';
|
|
10
|
-
import { security } from './src/tools/security.js';
|
|
11
|
-
import { bundle } from './src/tools/bundle.js';
|
|
12
|
-
import { checkpoint } from './src/tools/checkpoint.js';
|
|
13
|
-
import { resume } from './src/tools/resume.js';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
|
|
5
|
+
import { openDB } from './src/graph/db.js';
|
|
6
|
+
import { maintain } from './src/tools/maintain.js';
|
|
7
|
+
import { search } from './src/tools/search.js';
|
|
8
|
+
import { describe } from './src/tools/describe.js';
|
|
9
|
+
import { codeGraph } from './src/tools/codeGraph.js';
|
|
10
|
+
import { security } from './src/tools/security.js';
|
|
11
|
+
import { bundle } from './src/tools/bundle.js';
|
|
12
|
+
import { checkpoint } from './src/tools/checkpoint.js';
|
|
13
|
+
import { resume } from './src/tools/resume.js';
|
|
14
|
+
import { minify } from './src/tools/minify.js';
|
|
15
|
+
|
|
16
|
+
const db = openDB();
|
|
17
|
+
|
|
18
|
+
const server = new Server(
|
|
19
|
+
{ name: 'cairn', version: '1.0.0' },
|
|
20
|
+
{ capabilities: { tools: {} } }
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
24
|
+
tools: [
|
|
25
|
+
{
|
|
26
|
+
name: 'cairn_maintain',
|
|
27
|
+
description: 'Index the current project into Cairn\'s polyglot knowledge graph. Supports Java, TypeScript, JavaScript, Vue, Python, SQL, config, and Markdown. Indexes from the current working directory. Run at session start or after major changes.',
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
languages: {
|
|
32
|
+
type: 'array', items: { type: 'string' },
|
|
33
|
+
description: 'Optional: limit to specific languages e.g. ["java","typescript"]',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'cairn_search',
|
|
40
|
+
description: 'Search the codebase by concept across all languages. Returns ranked symbols with scores.',
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
query: { type: 'string' },
|
|
45
|
+
limit: { type: 'number', default: 10 },
|
|
46
|
+
language: { type: 'string', description: 'Optional: filter by language' },
|
|
47
|
+
kind: { type: 'string', description: 'Optional: class/function/component/table/...' },
|
|
48
|
+
},
|
|
49
|
+
required: ['query'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'cairn_describe',
|
|
54
|
+
description: 'Describe what a directory/module does: symbols, responsibilities, upstream/downstream deps.',
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
path: { type: 'string', description: 'Directory or file path to describe' },
|
|
59
|
+
},
|
|
60
|
+
required: ['path'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'cairn_code_graph',
|
|
65
|
+
description: 'Analyze module dependencies and instability metrics across all languages. Use before refactoring.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
module: { type: 'string', description: 'Optional: scope to a specific module/package' },
|
|
70
|
+
mode: { type: 'string', enum: ['instability', 'health', 'cycles'] },
|
|
71
|
+
},
|
|
72
|
+
required: ['mode'],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'cairn_security',
|
|
77
|
+
description: 'Scan for security vulnerabilities across all languages (XSS, XXE, SQLi, weak crypto, hardcoded secrets, etc.)',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
paths: { type: 'array', items: { type: 'string' }, description: 'Optional: scope to specific paths' },
|
|
82
|
+
severity: { type: 'string', enum: ['HIGH', 'MEDIUM', 'LOW', 'ALL'], default: 'HIGH' },
|
|
83
|
+
language: { type: 'string', description: 'Optional: scope to a specific language' },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'cairn_bundle',
|
|
89
|
+
description: `Produce a minified single-file snapshot of source code for Claude to read.
|
|
90
|
+
Strips comments, empty lines, and optionally styles. Returns the bundle file path and compression stats.
|
|
91
|
+
Use AFTER cairn_search to get a readable version of the files Claude needs to work with.
|
|
92
|
+
Typical workflow: cairn_search → find files → cairn_bundle those paths → Claude reads bundle.`,
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
bundle_name: {
|
|
97
|
+
type: 'string', default: 'default',
|
|
98
|
+
description: 'Name for the bundle file (saved to .cairn/bundles/<name>.txt)',
|
|
99
|
+
},
|
|
100
|
+
filter_paths: {
|
|
101
|
+
type: 'array', items: { type: 'string' },
|
|
102
|
+
description: 'Optional: only include files under these relative paths',
|
|
103
|
+
},
|
|
104
|
+
filter_language: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
description: 'Optional: only bundle this language (java/typescript/vue/python/...)',
|
|
107
|
+
},
|
|
108
|
+
no_comments: { type: 'boolean', default: true, description: 'Strip code comments' },
|
|
109
|
+
no_empty_lines: { type: 'boolean', default: true, description: 'Remove empty lines' },
|
|
110
|
+
no_style: { type: 'boolean', default: false, description: 'Skip <style> blocks in Vue files' },
|
|
111
|
+
aggressive: { type: 'boolean', default: false, description: 'Aggressive whitespace/punctuation minification' },
|
|
112
|
+
only_changed: { type: 'boolean', default: false, description: 'Incremental: only re-bundle files changed since last run' },
|
|
113
|
+
max_size_kb: { type: 'number', default: 800, description: 'Safety cap in KB to avoid context window overflow' },
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'cairn_checkpoint',
|
|
119
|
+
description: 'Save current session state to .cairn/session.json so it can be restored next session with cairn_resume.',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
message: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
description: 'What you were working on — will be shown on resume',
|
|
126
|
+
},
|
|
127
|
+
active_files: {
|
|
128
|
+
type: 'array', items: { type: 'string' },
|
|
129
|
+
description: 'Optional: absolute paths to files actively being worked on',
|
|
130
|
+
},
|
|
131
|
+
notes: {
|
|
132
|
+
type: 'array', items: { type: 'string' },
|
|
133
|
+
description: 'Optional: things Claude should remember next session',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
required: ['message'],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'cairn_resume',
|
|
141
|
+
description: 'Restore the last saved session state. Detects which files changed since the checkpoint and incrementally re-indexes only those files. Call at the start of a session instead of cairn_maintain when resuming work.',
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
properties: {},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'cairn_minify',
|
|
149
|
+
description: 'Minify a single source file and return compressed content. Strips comments and empty lines. Useful when passive hooks are not installed. Supports java, typescript, javascript, vue, python, sql.',
|
|
150
|
+
inputSchema: {
|
|
151
|
+
type: 'object',
|
|
152
|
+
properties: {
|
|
153
|
+
file_path: { type: 'string', description: 'Absolute path to the file to minify' },
|
|
154
|
+
},
|
|
155
|
+
required: ['file_path'],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
162
|
+
const { name, arguments: args } = request.params;
|
|
163
|
+
switch (name) {
|
|
164
|
+
case 'cairn_maintain': return await maintain(db, args);
|
|
165
|
+
case 'cairn_search': return search(db, args);
|
|
166
|
+
case 'cairn_describe': return describe(db, args);
|
|
167
|
+
case 'cairn_code_graph': return codeGraph(db, args);
|
|
168
|
+
case 'cairn_security': return security(db, args);
|
|
169
|
+
case 'cairn_bundle': return await bundle(db, args);
|
|
170
|
+
case 'cairn_checkpoint': return checkpoint(db, args);
|
|
171
|
+
case 'cairn_resume': return await resume(db);
|
|
172
|
+
case 'cairn_minify': return minify(db, args);
|
|
173
|
+
default: throw new Error(`Unknown tool: ${name}`);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const transport = new StdioServerTransport();
|
|
178
|
+
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@misterhuydo/cairn-mcp",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "MCP server that gives Claude Code persistent memory across sessions. Index your codebase once, search symbols, bundle source, scan for vulnerabilities, and checkpoint/resume work — across Java, TypeScript, Vue, Python, SQL and more.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"cairn-mcp": "bin/cairn-mcp.js",
|
|
9
|
-
"cairn": "bin/cairn.js"
|
|
10
|
-
},
|
|
11
|
-
"scripts": {
|
|
12
|
-
"start": "node --experimental-sqlite index.js"
|
|
13
|
-
},
|
|
14
|
-
"engines": {
|
|
15
|
-
"node": ">=22.15.0"
|
|
16
|
-
},
|
|
17
|
-
"keywords": [
|
|
18
|
-
"mcp",
|
|
19
|
-
"claude",
|
|
20
|
-
"claude-code",
|
|
21
|
-
"knowledge-graph",
|
|
22
|
-
"codebase-search",
|
|
23
|
-
"polyglot",
|
|
24
|
-
"java",
|
|
25
|
-
"typescript",
|
|
26
|
-
"vue",
|
|
27
|
-
"python",
|
|
28
|
-
"sqlite",
|
|
29
|
-
"developer-tools"
|
|
30
|
-
],
|
|
31
|
-
"license": "MIT",
|
|
32
|
-
"repository": {
|
|
33
|
-
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/misterhuydo/Cairn.git"
|
|
35
|
-
},
|
|
36
|
-
"homepage": "https://github.com/misterhuydo/Cairn#readme",
|
|
37
|
-
"bugs": {
|
|
38
|
-
"url": "https://github.com/misterhuydo/Cairn/issues"
|
|
39
|
-
},
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
42
|
-
"fast-glob": "^3.3.0"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@misterhuydo/cairn-mcp",
|
|
3
|
+
"version": "1.2.2",
|
|
4
|
+
"description": "MCP server that gives Claude Code persistent memory across sessions. Index your codebase once, search symbols, bundle source, scan for vulnerabilities, and checkpoint/resume work — across Java, TypeScript, Vue, Python, SQL and more.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"cairn-mcp": "bin/cairn-mcp.js",
|
|
9
|
+
"cairn": "bin/cairn.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"start": "node --experimental-sqlite index.js"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=22.15.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"claude",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"knowledge-graph",
|
|
22
|
+
"codebase-search",
|
|
23
|
+
"polyglot",
|
|
24
|
+
"java",
|
|
25
|
+
"typescript",
|
|
26
|
+
"vue",
|
|
27
|
+
"python",
|
|
28
|
+
"sqlite",
|
|
29
|
+
"developer-tools"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/misterhuydo/Cairn.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/misterhuydo/Cairn#readme",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/misterhuydo/Cairn/issues"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
42
|
+
"fast-glob": "^3.3.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { LANGUAGE_MAP } from '../indexer/fileWalker.js';
|
|
4
|
+
import { minifyContent } from '../bundler/minifier.js';
|
|
5
|
+
import { minifyVue } from '../bundler/vueMinifier.js';
|
|
6
|
+
|
|
7
|
+
const MINIFY_LANGS = new Set(['java', 'typescript', 'javascript', 'vue', 'python', 'sql']);
|
|
8
|
+
|
|
9
|
+
export function minify(_db, { file_path }) {
|
|
10
|
+
if (!file_path) throw new Error('file_path is required');
|
|
11
|
+
|
|
12
|
+
const ext = path.extname(file_path).toLowerCase();
|
|
13
|
+
const lang = LANGUAGE_MAP[ext];
|
|
14
|
+
|
|
15
|
+
if (!lang || !MINIFY_LANGS.has(lang)) {
|
|
16
|
+
return {
|
|
17
|
+
content: [{
|
|
18
|
+
type: 'text',
|
|
19
|
+
text: JSON.stringify({ skipped: true, reason: `language '${lang ?? ext}' is not minified` }),
|
|
20
|
+
}],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const content = fs.readFileSync(file_path, 'utf8');
|
|
25
|
+
const originalLines = content.split('\n').length;
|
|
26
|
+
|
|
27
|
+
const minified = lang === 'vue'
|
|
28
|
+
? minifyVue(content)
|
|
29
|
+
: minifyContent(content, lang);
|
|
30
|
+
|
|
31
|
+
const minifiedLines = minified.split('\n').length;
|
|
32
|
+
const pct = originalLines > 0 ? Math.round((1 - minifiedLines / originalLines) * 100) : 0;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
content: [{
|
|
36
|
+
type: 'text',
|
|
37
|
+
text: `[cairn: ${originalLines} → ${minifiedLines} lines (-${pct}%)]\n${minified}`,
|
|
38
|
+
}],
|
|
39
|
+
};
|
|
40
|
+
}
|