aidex-mcp 1.5.1 → 1.6.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/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to AiDex will be documented in this file.
4
4
 
5
+ ## [1.6.0] - 2026-02-01
6
+
7
+ ### Added
8
+ - **Auto CLAUDE.md instructions**: `aidex setup` now installs AI instructions in `~/.claude/CLAUDE.md`
9
+ - Tells Claude to auto-run `aidex_init` when no `.aidex/` exists
10
+ - Provides tool usage guide (prefer AiDex over Grep/Glob)
11
+ - `aidex unsetup` cleanly removes the instructions block
12
+ - **Idempotent setup**: Re-running `aidex setup` updates existing config without errors
13
+
14
+ ## [1.5.2] - 2026-02-01
15
+
16
+ ### Fixed
17
+ - **`aidex setup` for Claude Code**: Uses `claude mcp add --scope user` instead of editing settings.json directly
18
+ - Claude Desktop, Cursor, Windsurf still use JSON config editing
19
+
5
20
  ## [1.5.1] - 2026-01-31
6
21
 
7
22
  ### Fixed
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * AiDex Setup - Auto-register as MCP server in AI clients
3
3
  *
4
- * Supports: Claude Code, Claude Desktop, Cursor, Windsurf
4
+ * Supports: Claude Code (via CLI), Claude Desktop, Cursor, Windsurf
5
+ * Also installs CLAUDE.md instructions for Claude Code/Desktop.
5
6
  */
6
7
  export declare function setupMcpClients(): void;
7
8
  export declare function unsetupMcpClients(): void;
@@ -1,35 +1,89 @@
1
1
  /**
2
2
  * AiDex Setup - Auto-register as MCP server in AI clients
3
3
  *
4
- * Supports: Claude Code, Claude Desktop, Cursor, Windsurf
4
+ * Supports: Claude Code (via CLI), Claude Desktop, Cursor, Windsurf
5
+ * Also installs CLAUDE.md instructions for Claude Code/Desktop.
5
6
  */
6
- import { existsSync, readFileSync, writeFileSync } from 'fs';
7
- import { join } from 'path';
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
8
+ import { join, dirname } from 'path';
8
9
  import { homedir, platform } from 'os';
10
+ import { execSync } from 'child_process';
9
11
  // ============================================================
10
- // MCP Server Entry
12
+ // MCP Server Entry (for JSON-based clients)
11
13
  // ============================================================
12
14
  const AIDEX_MCP_ENTRY = {
13
15
  command: 'aidex',
14
16
  args: []
15
17
  };
16
18
  // ============================================================
19
+ // CLAUDE.md Instructions Block
20
+ // ============================================================
21
+ const CLAUDE_MD_START = '<!-- AIDEX-START -->';
22
+ const CLAUDE_MD_END = '<!-- AIDEX-END -->';
23
+ const CLAUDE_MD_BLOCK = `${CLAUDE_MD_START}
24
+ ## AiDex - Persistent Code Index (MCP Server)
25
+
26
+ AiDex provides fast, precise code search through a pre-built index. **Always prefer AiDex over Grep/Glob for code searches.**
27
+
28
+ ### Auto-Init Rule
29
+
30
+ **At the start of every session, check if \`.aidex/\` exists in the project directory.**
31
+ - If \`.aidex/\` does NOT exist → **automatically run \`aidex_init\`** to create the index. Do not ask, just do it.
32
+ - If \`.aidex/\` exists → use AiDex tools for all code searches.
33
+
34
+ ### When to Use AiDex (instead of Grep/Glob)
35
+
36
+ | Task | Do NOT use | Use instead |
37
+ |------|-----------|-------------|
38
+ | Find a function/class/variable | \`Grep pattern="name"\` | \`aidex_query term="name"\` |
39
+ | See all methods in a file | \`Read entire_file.cs\` | \`aidex_signature file="..."\` |
40
+ | Explore multiple files | Multiple Read calls | \`aidex_signatures pattern="src/**"\` |
41
+ | Project overview | Many Glob/Read calls | \`aidex_summary\` + \`aidex_tree\` |
42
+ | What changed recently? | \`git log\` + Read | \`aidex_query term="X" modified_since="2h"\` |
43
+
44
+ ### Available Tools
45
+
46
+ | Tool | Purpose |
47
+ |------|---------|
48
+ | \`aidex_init\` | Index a project (creates \`.aidex/\`) |
49
+ | \`aidex_query\` | Search by term (exact/contains/starts_with) |
50
+ | \`aidex_signature\` | Get one file's classes + methods |
51
+ | \`aidex_signatures\` | Get signatures for multiple files (glob) |
52
+ | \`aidex_update\` | Re-index a single changed file |
53
+ | \`aidex_summary\` | Project overview with entry points |
54
+ | \`aidex_tree\` | File tree with statistics |
55
+ | \`aidex_files\` | List project files by type |
56
+ | \`aidex_session\` | Start session, detect external changes |
57
+ | \`aidex_note\` | Read/write session notes |
58
+ | \`aidex_viewer\` | Open interactive project tree in browser |
59
+
60
+ ### Why AiDex over Grep?
61
+
62
+ - **~50 tokens** per search vs 2000+ with Grep
63
+ - **Identifiers only** - no noise from comments/strings
64
+ - **Persistent** - index survives between sessions
65
+ - **Structure-aware** - knows methods, classes, types
66
+ ${CLAUDE_MD_END}`;
67
+ // ============================================================
17
68
  // Client Detection
18
69
  // ============================================================
19
70
  function getClients() {
20
71
  const home = homedir();
21
72
  const plat = platform();
22
73
  const clients = [];
23
- // Claude Code
74
+ // Claude Code - uses its own CLI for MCP management
24
75
  clients.push({
76
+ type: 'cli',
25
77
  name: 'Claude Code',
26
- configPath: join(home, '.claude', 'settings.json'),
27
- detectDir: join(home, '.claude')
78
+ detectCmd: 'claude --version',
79
+ addCmd: ['claude', 'mcp', 'add', '--scope', 'user', 'aidex', '--', 'aidex'],
80
+ removeCmd: ['claude', 'mcp', 'remove', '--scope', 'user', 'aidex']
28
81
  });
29
- // Claude Desktop
82
+ // Claude Desktop - JSON config
30
83
  if (plat === 'win32') {
31
84
  const appData = process.env.APPDATA || join(home, 'AppData', 'Roaming');
32
85
  clients.push({
86
+ type: 'json',
33
87
  name: 'Claude Desktop',
34
88
  configPath: join(appData, 'Claude', 'claude_desktop_config.json'),
35
89
  detectDir: join(appData, 'Claude')
@@ -37,6 +91,7 @@ function getClients() {
37
91
  }
38
92
  else if (plat === 'darwin') {
39
93
  clients.push({
94
+ type: 'json',
40
95
  name: 'Claude Desktop',
41
96
  configPath: join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
42
97
  detectDir: join(home, 'Library', 'Application Support', 'Claude')
@@ -44,19 +99,22 @@ function getClients() {
44
99
  }
45
100
  else {
46
101
  clients.push({
102
+ type: 'json',
47
103
  name: 'Claude Desktop',
48
104
  configPath: join(home, '.config', 'Claude', 'claude_desktop_config.json'),
49
105
  detectDir: join(home, '.config', 'Claude')
50
106
  });
51
107
  }
52
- // Cursor
108
+ // Cursor - JSON config
53
109
  clients.push({
110
+ type: 'json',
54
111
  name: 'Cursor',
55
112
  configPath: join(home, '.cursor', 'mcp.json'),
56
113
  detectDir: join(home, '.cursor')
57
114
  });
58
- // Windsurf
115
+ // Windsurf - JSON config
59
116
  clients.push({
117
+ type: 'json',
60
118
  name: 'Windsurf',
61
119
  configPath: join(home, '.codeium', 'windsurf', 'mcp_config.json'),
62
120
  detectDir: join(home, '.codeium', 'windsurf')
@@ -64,7 +122,29 @@ function getClients() {
64
122
  return clients;
65
123
  }
66
124
  // ============================================================
67
- // Config Read/Write
125
+ // CLI helpers
126
+ // ============================================================
127
+ function isCmdAvailable(cmd) {
128
+ try {
129
+ execSync(cmd, { stdio: 'pipe', timeout: 5000 });
130
+ return true;
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ }
136
+ function runCmd(args) {
137
+ try {
138
+ const output = execSync(args.join(' '), { stdio: 'pipe', timeout: 10000 }).toString().trim();
139
+ return { success: true, output };
140
+ }
141
+ catch (err) {
142
+ const msg = err instanceof Error ? err.message : String(err);
143
+ return { success: false, error: msg };
144
+ }
145
+ }
146
+ // ============================================================
147
+ // JSON Config Read/Write
68
148
  // ============================================================
69
149
  function readJsonConfig(filePath) {
70
150
  try {
@@ -92,83 +172,187 @@ function writeJsonConfig(filePath, data) {
92
172
  }
93
173
  }
94
174
  // ============================================================
95
- // Setup / Unsetup
175
+ // CLAUDE.md Management
176
+ // ============================================================
177
+ function getClaudeMdPath() {
178
+ return join(homedir(), '.claude', 'CLAUDE.md');
179
+ }
180
+ function installClaudeMd() {
181
+ const mdPath = getClaudeMdPath();
182
+ const dir = dirname(mdPath);
183
+ // Ensure directory exists
184
+ if (!existsSync(dir)) {
185
+ mkdirSync(dir, { recursive: true });
186
+ }
187
+ let content = '';
188
+ if (existsSync(mdPath)) {
189
+ content = readFileSync(mdPath, 'utf8');
190
+ // Already has AiDex block? Replace it
191
+ if (content.includes(CLAUDE_MD_START)) {
192
+ const regex = new RegExp(`${CLAUDE_MD_START}[\\s\\S]*?${CLAUDE_MD_END}`, 'g');
193
+ content = content.replace(regex, CLAUDE_MD_BLOCK);
194
+ writeFileSync(mdPath, content, 'utf8');
195
+ return { success: true, action: 'updated' };
196
+ }
197
+ // Append to existing file
198
+ content = content.trimEnd() + '\n\n' + CLAUDE_MD_BLOCK + '\n';
199
+ writeFileSync(mdPath, content, 'utf8');
200
+ return { success: true, action: 'appended' };
201
+ }
202
+ // Create new file
203
+ writeFileSync(mdPath, CLAUDE_MD_BLOCK + '\n', 'utf8');
204
+ return { success: true, action: 'created' };
205
+ }
206
+ function uninstallClaudeMd() {
207
+ const mdPath = getClaudeMdPath();
208
+ if (!existsSync(mdPath)) {
209
+ return { success: true, removed: false };
210
+ }
211
+ let content = readFileSync(mdPath, 'utf8');
212
+ if (!content.includes(CLAUDE_MD_START)) {
213
+ return { success: true, removed: false };
214
+ }
215
+ const regex = new RegExp(`\\n?\\n?${CLAUDE_MD_START}[\\s\\S]*?${CLAUDE_MD_END}\\n?`, 'g');
216
+ content = content.replace(regex, '').trim();
217
+ if (content.length === 0) {
218
+ // File would be empty, but don't delete it - might have been user-created
219
+ writeFileSync(mdPath, '', 'utf8');
220
+ }
221
+ else {
222
+ writeFileSync(mdPath, content + '\n', 'utf8');
223
+ }
224
+ return { success: true, removed: true };
225
+ }
226
+ // ============================================================
227
+ // Setup
96
228
  // ============================================================
229
+ function setupCliClient(client) {
230
+ if (!isCmdAvailable(client.detectCmd)) {
231
+ return { status: ` - ${client.name} (not installed)`, registered: false };
232
+ }
233
+ const result = runCmd(client.addCmd);
234
+ if (result.success) {
235
+ return { status: ` ✓ ${client.name}`, registered: true };
236
+ }
237
+ // "already exists" is not an error
238
+ if (result.error && result.error.includes('already exists')) {
239
+ return { status: ` ✓ ${client.name} (already registered)`, registered: true };
240
+ }
241
+ return { status: ` ✗ ${client.name} (${result.error})`, registered: false };
242
+ }
243
+ function setupJsonClient(client) {
244
+ if (!existsSync(client.detectDir)) {
245
+ return { status: ` - ${client.name} (not installed)`, registered: false };
246
+ }
247
+ let data;
248
+ if (existsSync(client.configPath)) {
249
+ const config = readJsonConfig(client.configPath);
250
+ if (!config.success || !config.data) {
251
+ return { status: ` ✗ ${client.name} (${config.error})`, registered: false };
252
+ }
253
+ data = config.data;
254
+ }
255
+ else {
256
+ data = {};
257
+ }
258
+ if (!data.mcpServers || typeof data.mcpServers !== 'object') {
259
+ data.mcpServers = {};
260
+ }
261
+ data.mcpServers.aidex = { ...AIDEX_MCP_ENTRY };
262
+ const writeResult = writeJsonConfig(client.configPath, data);
263
+ if (!writeResult.success) {
264
+ return { status: ` ✗ ${client.name} (${writeResult.error})`, registered: false };
265
+ }
266
+ return { status: ` ✓ ${client.name} (${client.configPath})`, registered: true };
267
+ }
97
268
  export function setupMcpClients() {
98
269
  const clients = getClients();
99
- const results = [];
100
270
  let registered = 0;
101
271
  console.log('\nAiDex MCP Server Registration');
102
272
  console.log('==============================\n');
273
+ // Register with AI clients
274
+ console.log(' MCP Servers:');
103
275
  for (const client of clients) {
104
- // Check if client is installed (detect directory exists)
105
- if (!existsSync(client.detectDir)) {
106
- results.push({ client: client.name, status: 'skipped', configPath: client.configPath });
107
- console.log(` - ${client.name} (not installed)`);
108
- continue;
109
- }
110
- // Read existing config or start with empty object
111
- let data;
112
- if (existsSync(client.configPath)) {
113
- const config = readJsonConfig(client.configPath);
114
- if (!config.success || !config.data) {
115
- results.push({ client: client.name, status: 'error', configPath: client.configPath, message: config.error });
116
- console.log(` ${client.name} (${config.error})`);
117
- continue;
118
- }
119
- data = config.data;
120
- }
121
- else {
122
- data = {};
123
- }
124
- if (!data.mcpServers || typeof data.mcpServers !== 'object') {
125
- data.mcpServers = {};
126
- }
127
- data.mcpServers.aidex = { ...AIDEX_MCP_ENTRY };
128
- const writeResult = writeJsonConfig(client.configPath, data);
129
- if (!writeResult.success) {
130
- results.push({ client: client.name, status: 'error', configPath: client.configPath, message: writeResult.error });
131
- console.log(` ✗ ${client.name} (${writeResult.error})`);
132
- continue;
133
- }
134
- registered++;
135
- results.push({ client: client.name, status: 'registered', configPath: client.configPath });
136
- console.log(` ✓ ${client.name} (${client.configPath})`);
276
+ const result = client.type === 'cli'
277
+ ? setupCliClient(client)
278
+ : setupJsonClient(client);
279
+ console.log(result.status);
280
+ if (result.registered)
281
+ registered++;
282
+ }
283
+ // Install CLAUDE.md instructions
284
+ console.log('\n AI Instructions:');
285
+ const mdResult = installClaudeMd();
286
+ const mdPath = getClaudeMdPath();
287
+ if (mdResult.success) {
288
+ console.log(` CLAUDE.md (${mdResult.action}: ${mdPath})`);
137
289
  }
138
290
  console.log(`\nRegistered AiDex with ${registered} client(s).\n`);
139
291
  if (registered > 0) {
140
292
  console.log('Restart your AI client(s) to activate AiDex.\n');
141
293
  }
142
294
  }
295
+ // ============================================================
296
+ // Unsetup
297
+ // ============================================================
298
+ function unsetupCliClient(client) {
299
+ if (!isCmdAvailable(client.detectCmd)) {
300
+ return { status: ` - ${client.name} (not installed)`, removed: false };
301
+ }
302
+ const result = runCmd(client.removeCmd);
303
+ if (result.success) {
304
+ return { status: ` ✓ Removed from ${client.name}`, removed: true };
305
+ }
306
+ else {
307
+ return { status: ` - ${client.name} (not registered)`, removed: false };
308
+ }
309
+ }
310
+ function unsetupJsonClient(client) {
311
+ if (!existsSync(client.detectDir)) {
312
+ return { status: ` - ${client.name} (not installed)`, removed: false };
313
+ }
314
+ if (!existsSync(client.configPath)) {
315
+ return { status: ` - ${client.name} (not registered)`, removed: false };
316
+ }
317
+ const config = readJsonConfig(client.configPath);
318
+ if (!config.success || !config.data) {
319
+ return { status: ` ✗ ${client.name} (${config.error})`, removed: false };
320
+ }
321
+ const data = config.data;
322
+ const servers = data.mcpServers;
323
+ if (!servers || !servers.aidex) {
324
+ return { status: ` - ${client.name} (not registered)`, removed: false };
325
+ }
326
+ delete servers.aidex;
327
+ const writeResult = writeJsonConfig(client.configPath, data);
328
+ if (!writeResult.success) {
329
+ return { status: ` ✗ ${client.name} (${writeResult.error})`, removed: false };
330
+ }
331
+ return { status: ` ✓ Removed from ${client.name}`, removed: true };
332
+ }
143
333
  export function unsetupMcpClients() {
144
334
  const clients = getClients();
145
335
  let removed = 0;
146
336
  console.log('\nAiDex MCP Server Unregistration');
147
337
  console.log('================================\n');
338
+ // Unregister from AI clients
339
+ console.log(' MCP Servers:');
148
340
  for (const client of clients) {
149
- if (!existsSync(client.configPath)) {
150
- console.log(` - ${client.name} (not installed)`);
151
- continue;
152
- }
153
- const config = readJsonConfig(client.configPath);
154
- if (!config.success || !config.data) {
155
- console.log(` ✗ ${client.name} (${config.error})`);
156
- continue;
157
- }
158
- const data = config.data;
159
- const servers = data.mcpServers;
160
- if (!servers || !servers.aidex) {
161
- console.log(` - ${client.name} (not registered)`);
162
- continue;
163
- }
164
- delete servers.aidex;
165
- const writeResult = writeJsonConfig(client.configPath, data);
166
- if (!writeResult.success) {
167
- console.log(` ✗ ${client.name} (${writeResult.error})`);
168
- continue;
169
- }
170
- removed++;
171
- console.log(` ✓ Removed from ${client.name}`);
341
+ const result = client.type === 'cli'
342
+ ? unsetupCliClient(client)
343
+ : unsetupJsonClient(client);
344
+ console.log(result.status);
345
+ if (result.removed)
346
+ removed++;
347
+ }
348
+ // Remove CLAUDE.md instructions
349
+ console.log('\n AI Instructions:');
350
+ const mdResult = uninstallClaudeMd();
351
+ if (mdResult.removed) {
352
+ console.log(` ✓ Removed AiDex block from CLAUDE.md`);
353
+ }
354
+ else {
355
+ console.log(` - CLAUDE.md (no AiDex block found)`);
172
356
  }
173
357
  console.log(`\nUnregistered AiDex from ${removed} client(s).\n`);
174
358
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aidex-mcp",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "MCP Server providing persistent code indexing for Claude Code",
5
5
  "main": "build/index.js",
6
6
  "bin": {