code-graph-context 2.3.0 → 2.4.1
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 +54 -64
- package/dist/cli/cli.js +266 -0
- package/dist/cli/neo4j-docker.js +159 -0
- package/dist/core/config/fairsquare-framework-schema.js +6 -14
- package/dist/core/config/nestjs-framework-schema.js +9 -3
- package/dist/core/config/schema.js +6 -0
- package/dist/core/embeddings/natural-language-to-cypher.service.js +31 -14
- package/dist/core/parsers/typescript-parser.js +15 -7
- package/dist/mcp/service-init.js +53 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -65,100 +65,90 @@ The system uses a dual-schema approach:
|
|
|
65
65
|
|
|
66
66
|
Choose the installation method that works best for you:
|
|
67
67
|
|
|
68
|
-
#### Option 1:
|
|
68
|
+
#### Option 1: NPM Install (Recommended)
|
|
69
69
|
|
|
70
|
-
Best for: Contributing to the project or customizing the code
|
|
71
|
-
|
|
72
|
-
1. **Clone the repository:**
|
|
73
70
|
```bash
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
```
|
|
71
|
+
# Install globally
|
|
72
|
+
npm install -g code-graph-context
|
|
77
73
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
# Set up Neo4j (requires Docker)
|
|
75
|
+
code-graph-context init
|
|
76
|
+
|
|
77
|
+
# Add to Claude Code
|
|
78
|
+
claude mcp add code-graph-context code-graph-context
|
|
81
79
|
```
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
|
|
81
|
+
Then configure your OpenAI API key in `~/.config/claude/config.json`:
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"code-graph-context": {
|
|
86
|
+
"command": "code-graph-context",
|
|
87
|
+
"env": {
|
|
88
|
+
"OPENAI_API_KEY": "sk-your-key-here"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
86
93
|
```
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
- Web interface: http://localhost:7474
|
|
90
|
-
- Bolt connection: bolt://localhost:7687
|
|
91
|
-
- Username: `neo4j`, Password: `PASSWORD`
|
|
95
|
+
#### Option 2: From Source
|
|
92
96
|
|
|
93
|
-
4. **Configure environment variables:**
|
|
94
97
|
```bash
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
# Clone and build
|
|
99
|
+
git clone https://github.com/drewdrewH/code-graph-context.git
|
|
100
|
+
cd code-graph-context
|
|
101
|
+
npm install
|
|
102
|
+
npm run build
|
|
103
|
+
|
|
104
|
+
# Set up Neo4j
|
|
105
|
+
code-graph-context init
|
|
106
|
+
|
|
107
|
+
# Add to Claude Code (use absolute path)
|
|
108
|
+
claude mcp add code-graph-context node /absolute/path/to/code-graph-context/dist/cli/cli.js
|
|
97
109
|
```
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
### CLI Commands
|
|
112
|
+
|
|
113
|
+
The package includes a CLI for managing Neo4j:
|
|
114
|
+
|
|
100
115
|
```bash
|
|
101
|
-
|
|
116
|
+
code-graph-context init [options] # Set up Neo4j container
|
|
117
|
+
code-graph-context status # Check Docker/Neo4j status
|
|
118
|
+
code-graph-context stop # Stop Neo4j container
|
|
102
119
|
```
|
|
103
120
|
|
|
104
|
-
|
|
105
|
-
```
|
|
106
|
-
|
|
121
|
+
**Init options:**
|
|
122
|
+
```
|
|
123
|
+
-p, --port <port> Bolt port (default: 7687)
|
|
124
|
+
--http-port <port> Browser port (default: 7474)
|
|
125
|
+
--password <password> Neo4j password (default: PASSWORD)
|
|
126
|
+
-m, --memory <size> Heap memory (default: 2G)
|
|
127
|
+
-f, --force Recreate container
|
|
107
128
|
```
|
|
108
129
|
|
|
109
|
-
|
|
130
|
+
### Alternative Neo4j Setup
|
|
110
131
|
|
|
111
|
-
|
|
132
|
+
If you prefer not to use the CLI, you can set up Neo4j manually:
|
|
112
133
|
|
|
113
|
-
|
|
134
|
+
**Docker Compose:**
|
|
114
135
|
```bash
|
|
115
|
-
|
|
136
|
+
docker-compose up -d
|
|
116
137
|
```
|
|
117
138
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
**Option A: Docker (Recommended)**
|
|
139
|
+
**Docker Run:**
|
|
121
140
|
```bash
|
|
122
141
|
docker run -d \
|
|
123
142
|
--name code-graph-neo4j \
|
|
124
143
|
-p 7474:7474 -p 7687:7687 \
|
|
125
144
|
-e NEO4J_AUTH=neo4j/PASSWORD \
|
|
126
|
-
-e NEO4J_PLUGINS=
|
|
145
|
+
-e 'NEO4J_PLUGINS=["apoc"]' \
|
|
127
146
|
neo4j:5.23
|
|
128
147
|
```
|
|
129
148
|
|
|
130
|
-
**
|
|
131
|
-
- Download from [neo4j.com/download](https://neo4j.com/download/)
|
|
132
|
-
- Install APOC plugin
|
|
133
|
-
- Start database
|
|
134
|
-
|
|
135
|
-
**Option C: Neo4j Aura (Cloud)**
|
|
136
|
-
- Create free account at [neo4j.com/cloud/aura](https://neo4j.com/cloud/platform/aura-graph-database/)
|
|
137
|
-
- Note your connection URI and credentials
|
|
138
|
-
|
|
139
|
-
3. **Add to Claude Code:**
|
|
140
|
-
```bash
|
|
141
|
-
claude mcp add code-graph-context code-graph-context
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Then configure in your MCP config file (`~/.config/claude/config.json`):
|
|
145
|
-
```json
|
|
146
|
-
{
|
|
147
|
-
"mcpServers": {
|
|
148
|
-
"code-graph-context": {
|
|
149
|
-
"command": "code-graph-context",
|
|
150
|
-
"env": {
|
|
151
|
-
"OPENAI_API_KEY": "sk-your-key-here",
|
|
152
|
-
"NEO4J_URI": "bolt://localhost:7687",
|
|
153
|
-
"NEO4J_USER": "neo4j",
|
|
154
|
-
"NEO4J_PASSWORD": "PASSWORD"
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
```
|
|
149
|
+
**Neo4j Desktop:** Download from [neo4j.com/download](https://neo4j.com/download/) and install APOC plugin.
|
|
160
150
|
|
|
161
|
-
**
|
|
151
|
+
**Neo4j Aura (Cloud):** Create account at [neo4j.com/cloud/aura](https://neo4j.com/cloud/platform/aura-graph-database/) and configure connection URI in env vars.
|
|
162
152
|
|
|
163
153
|
### Verify Installation
|
|
164
154
|
|
package/dist/cli/cli.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI Entry Point for Code Graph Context
|
|
4
|
+
*
|
|
5
|
+
* Handles CLI commands (init, status, stop) and delegates to MCP server
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
import { dirname, join } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { NEO4J_CONFIG, createContainer, getContainerStatus, getFullStatus, isApocAvailable, isDockerInstalled, isDockerRunning, removeContainer, startContainer, stopContainer, waitForNeo4j, } from './neo4j-docker.js';
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
// ANSI colors
|
|
15
|
+
const c = {
|
|
16
|
+
reset: '\x1b[0m',
|
|
17
|
+
bold: '\x1b[1m',
|
|
18
|
+
dim: '\x1b[2m',
|
|
19
|
+
green: '\x1b[32m',
|
|
20
|
+
red: '\x1b[31m',
|
|
21
|
+
yellow: '\x1b[33m',
|
|
22
|
+
blue: '\x1b[34m',
|
|
23
|
+
cyan: '\x1b[36m',
|
|
24
|
+
};
|
|
25
|
+
const sym = {
|
|
26
|
+
ok: `${c.green}✓${c.reset}`,
|
|
27
|
+
err: `${c.red}✗${c.reset}`,
|
|
28
|
+
warn: `${c.yellow}⚠${c.reset}`,
|
|
29
|
+
info: `${c.blue}ℹ${c.reset}`,
|
|
30
|
+
};
|
|
31
|
+
const log = (symbol, msg) => {
|
|
32
|
+
console.log(` ${symbol} ${msg}`);
|
|
33
|
+
};
|
|
34
|
+
const header = (text) => {
|
|
35
|
+
console.log(`\n${c.bold}${text}${c.reset}\n`);
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Spinner for async operations
|
|
39
|
+
*/
|
|
40
|
+
const spinner = (msg) => {
|
|
41
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
42
|
+
let i = 0;
|
|
43
|
+
const interval = setInterval(() => {
|
|
44
|
+
process.stdout.write(`\r ${c.blue}${frames[i]}${c.reset} ${msg}`);
|
|
45
|
+
i = (i + 1) % frames.length;
|
|
46
|
+
}, 80);
|
|
47
|
+
return {
|
|
48
|
+
stop: (ok, finalMsg) => {
|
|
49
|
+
clearInterval(interval);
|
|
50
|
+
process.stdout.write(`\r ${ok ? sym.ok : sym.err} ${finalMsg || msg}\n`);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Print config instructions
|
|
56
|
+
*/
|
|
57
|
+
const printConfigInstructions = (password, boltPort) => {
|
|
58
|
+
console.log(`
|
|
59
|
+
${c.bold}Next steps:${c.reset}
|
|
60
|
+
|
|
61
|
+
1. Add to Claude Code:
|
|
62
|
+
${c.dim}claude mcp add code-graph-context code-graph-context${c.reset}
|
|
63
|
+
|
|
64
|
+
2. Configure in ${c.cyan}~/.config/claude/config.json${c.reset}:
|
|
65
|
+
|
|
66
|
+
${c.dim}{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"code-graph-context": {
|
|
69
|
+
"command": "code-graph-context",
|
|
70
|
+
"env": {
|
|
71
|
+
"OPENAI_API_KEY": "sk-..."${password !== NEO4J_CONFIG.defaultPassword
|
|
72
|
+
? `,
|
|
73
|
+
"NEO4J_PASSWORD": "${password}"`
|
|
74
|
+
: ''}${boltPort !== NEO4J_CONFIG.boltPort
|
|
75
|
+
? `,
|
|
76
|
+
"NEO4J_URI": "bolt://localhost:${boltPort}"`
|
|
77
|
+
: ''}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}${c.reset}
|
|
82
|
+
|
|
83
|
+
${c.yellow}Get your OpenAI API key:${c.reset} https://platform.openai.com/api-keys
|
|
84
|
+
|
|
85
|
+
3. Restart Claude Code
|
|
86
|
+
`);
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Init command - set up Neo4j
|
|
90
|
+
*/
|
|
91
|
+
const runInit = async (options) => {
|
|
92
|
+
const boltPort = options.port ? parseInt(options.port, 10) : NEO4J_CONFIG.boltPort;
|
|
93
|
+
const httpPort = options.httpPort ? parseInt(options.httpPort, 10) : NEO4J_CONFIG.httpPort;
|
|
94
|
+
const password = options.password || NEO4J_CONFIG.defaultPassword;
|
|
95
|
+
const memory = options.memory || '4G';
|
|
96
|
+
header('Code Graph Context Setup');
|
|
97
|
+
// Check Docker
|
|
98
|
+
if (!isDockerInstalled()) {
|
|
99
|
+
log(sym.err, 'Docker is not installed');
|
|
100
|
+
console.log(`\n Install Docker: ${c.cyan}https://docs.docker.com/get-docker/${c.reset}\n`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
log(sym.ok, 'Docker installed');
|
|
104
|
+
if (!isDockerRunning()) {
|
|
105
|
+
log(sym.err, 'Docker daemon is not running');
|
|
106
|
+
console.log(`\n Start Docker Desktop or run: ${c.dim}sudo systemctl start docker${c.reset}\n`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
log(sym.ok, 'Docker daemon running');
|
|
110
|
+
// Handle existing container
|
|
111
|
+
const status = getContainerStatus();
|
|
112
|
+
if (status === 'running' && !options.force) {
|
|
113
|
+
log(sym.ok, 'Neo4j container already running');
|
|
114
|
+
const apocOk = isApocAvailable(NEO4J_CONFIG.containerName, password);
|
|
115
|
+
log(apocOk ? sym.ok : sym.warn, apocOk ? 'APOC plugin available' : 'APOC plugin not detected');
|
|
116
|
+
console.log(`\n ${c.dim}Use --force to recreate the container${c.reset}`);
|
|
117
|
+
printConfigInstructions(password, boltPort);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (status !== 'not-found' && options.force) {
|
|
121
|
+
const s = spinner('Removing existing container...');
|
|
122
|
+
stopContainer();
|
|
123
|
+
removeContainer();
|
|
124
|
+
s.stop(true, 'Removed existing container');
|
|
125
|
+
}
|
|
126
|
+
if (status === 'stopped' && !options.force) {
|
|
127
|
+
const s = spinner('Starting existing container...');
|
|
128
|
+
const started = startContainer();
|
|
129
|
+
if (!started) {
|
|
130
|
+
s.stop(false, 'Failed to start container');
|
|
131
|
+
console.log(`\n Try: ${c.dim}code-graph-context init --force${c.reset}\n`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
s.stop(true, 'Container started');
|
|
135
|
+
}
|
|
136
|
+
else if (status === 'not-found' || options.force) {
|
|
137
|
+
const s = spinner('Creating Neo4j container...');
|
|
138
|
+
const created = createContainer({ httpPort, boltPort, password, memory });
|
|
139
|
+
if (!created) {
|
|
140
|
+
s.stop(false, 'Failed to create container');
|
|
141
|
+
console.log(`
|
|
142
|
+
Check if ports are in use:
|
|
143
|
+
${c.dim}lsof -i :${httpPort}${c.reset}
|
|
144
|
+
${c.dim}lsof -i :${boltPort}${c.reset}
|
|
145
|
+
`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
s.stop(true, 'Container created');
|
|
149
|
+
}
|
|
150
|
+
// Wait for Neo4j
|
|
151
|
+
const healthSpinner = spinner('Waiting for Neo4j to be ready (this may take a minute)...');
|
|
152
|
+
const ready = await waitForNeo4j(NEO4J_CONFIG.containerName, password);
|
|
153
|
+
healthSpinner.stop(ready, ready ? 'Neo4j is ready' : 'Neo4j failed to start');
|
|
154
|
+
if (!ready) {
|
|
155
|
+
console.log(`\n Check logs: ${c.dim}docker logs ${NEO4J_CONFIG.containerName}${c.reset}\n`);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
// Check APOC
|
|
159
|
+
const apocOk = isApocAvailable(NEO4J_CONFIG.containerName, password);
|
|
160
|
+
log(apocOk ? sym.ok : sym.warn, apocOk ? 'APOC plugin verified' : 'APOC still loading (should be ready shortly)');
|
|
161
|
+
// Print connection info
|
|
162
|
+
console.log(`
|
|
163
|
+
${c.bold}Neo4j is ready${c.reset}
|
|
164
|
+
|
|
165
|
+
Browser: ${c.cyan}http://localhost:${httpPort}${c.reset}
|
|
166
|
+
Bolt URI: ${c.cyan}bolt://localhost:${boltPort}${c.reset}
|
|
167
|
+
Credentials: ${c.dim}neo4j / ${password}${c.reset}`);
|
|
168
|
+
printConfigInstructions(password, boltPort);
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Status command
|
|
172
|
+
*/
|
|
173
|
+
const runStatus = () => {
|
|
174
|
+
header('Code Graph Context Status');
|
|
175
|
+
const status = getFullStatus();
|
|
176
|
+
log(status.dockerInstalled ? sym.ok : sym.err, `Docker installed: ${status.dockerInstalled ? 'yes' : 'no'}`);
|
|
177
|
+
if (!status.dockerInstalled) {
|
|
178
|
+
console.log(`\n Install: ${c.cyan}https://docs.docker.com/get-docker/${c.reset}\n`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
log(status.dockerRunning ? sym.ok : sym.err, `Docker running: ${status.dockerRunning ? 'yes' : 'no'}`);
|
|
182
|
+
if (!status.dockerRunning) {
|
|
183
|
+
console.log(`\n Start Docker Desktop or: ${c.dim}sudo systemctl start docker${c.reset}\n`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const containerIcon = status.containerStatus === 'running' ? sym.ok : status.containerStatus === 'stopped' ? sym.warn : sym.err;
|
|
187
|
+
log(containerIcon, `Container: ${status.containerStatus}`);
|
|
188
|
+
if (status.containerStatus === 'running') {
|
|
189
|
+
log(status.neo4jReady ? sym.ok : sym.warn, `Neo4j responding: ${status.neo4jReady ? 'yes' : 'no'}`);
|
|
190
|
+
log(status.apocAvailable ? sym.ok : sym.warn, `APOC plugin: ${status.apocAvailable ? 'available' : 'not available'}`);
|
|
191
|
+
}
|
|
192
|
+
console.log('');
|
|
193
|
+
if (status.containerStatus !== 'running') {
|
|
194
|
+
console.log(` Run ${c.dim}code-graph-context init${c.reset} to start Neo4j\n`);
|
|
195
|
+
}
|
|
196
|
+
else if (!status.apocAvailable) {
|
|
197
|
+
console.log(` APOC may still be loading. Wait a moment and check again.\n`);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* Stop command
|
|
202
|
+
*/
|
|
203
|
+
const runStop = () => {
|
|
204
|
+
const status = getContainerStatus();
|
|
205
|
+
if (status === 'not-found') {
|
|
206
|
+
log(sym.info, 'No Neo4j container found');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (status === 'stopped') {
|
|
210
|
+
log(sym.info, 'Container already stopped');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const s = spinner('Stopping Neo4j...');
|
|
214
|
+
const stopped = stopContainer();
|
|
215
|
+
s.stop(stopped, stopped ? 'Neo4j stopped' : 'Failed to stop container');
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Start MCP server
|
|
219
|
+
*/
|
|
220
|
+
const startMcpServer = async () => {
|
|
221
|
+
// The MCP server is in a sibling directory after build
|
|
222
|
+
// cli/cli.js -> mcp/mcp.server.js
|
|
223
|
+
const mcpPath = join(__dirname, '..', 'mcp', 'mcp.server.js');
|
|
224
|
+
await import(mcpPath);
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* Get package version
|
|
228
|
+
*/
|
|
229
|
+
const getVersion = () => {
|
|
230
|
+
try {
|
|
231
|
+
// Go up from dist/cli to root
|
|
232
|
+
const pkgPath = join(__dirname, '..', '..', 'package.json');
|
|
233
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
234
|
+
return pkg.version;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return 'unknown';
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
// Build CLI
|
|
241
|
+
const program = new Command();
|
|
242
|
+
program.name('code-graph-context').description('MCP server for code graph analysis with Neo4j').version(getVersion());
|
|
243
|
+
program
|
|
244
|
+
.command('init')
|
|
245
|
+
.description('Set up Neo4j container and show configuration steps')
|
|
246
|
+
.option('-p, --port <port>', 'Neo4j Bolt port', '7687')
|
|
247
|
+
.option('--http-port <port>', 'Neo4j Browser port', '7474')
|
|
248
|
+
.option('--password <password>', 'Neo4j password', 'PASSWORD')
|
|
249
|
+
.option('-m, --memory <size>', 'Max heap memory (e.g., 2G, 4G)', '2G')
|
|
250
|
+
.option('-f, --force', 'Recreate container even if exists')
|
|
251
|
+
.action(runInit);
|
|
252
|
+
program.command('status').description('Check Neo4j and Docker status').action(runStatus);
|
|
253
|
+
program.command('stop').description('Stop the Neo4j container').action(runStop);
|
|
254
|
+
// Default action: start MCP server if no command given
|
|
255
|
+
const knownCommands = ['init', 'status', 'stop', 'help'];
|
|
256
|
+
const args = process.argv.slice(2);
|
|
257
|
+
const hasCommand = args.some((arg) => knownCommands.includes(arg) || arg.startsWith('-'));
|
|
258
|
+
if (args.length === 0 || !hasCommand) {
|
|
259
|
+
startMcpServer().catch((err) => {
|
|
260
|
+
console.error('Failed to start MCP server:', err);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
program.parse();
|
|
266
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neo4j Docker Management
|
|
3
|
+
*
|
|
4
|
+
* Handles Docker container lifecycle for Neo4j
|
|
5
|
+
*/
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
// Container configuration
|
|
8
|
+
export const NEO4J_CONFIG = {
|
|
9
|
+
containerName: 'code-graph-neo4j',
|
|
10
|
+
image: 'neo4j:5.23',
|
|
11
|
+
httpPort: 7474,
|
|
12
|
+
boltPort: 7687,
|
|
13
|
+
defaultPassword: 'PASSWORD',
|
|
14
|
+
defaultUser: 'neo4j',
|
|
15
|
+
healthCheckTimeoutMs: 120000,
|
|
16
|
+
healthCheckIntervalMs: 2000,
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Execute a command and return stdout, or null if failed
|
|
20
|
+
*/
|
|
21
|
+
const exec = (command) => {
|
|
22
|
+
try {
|
|
23
|
+
return execSync(command, {
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
26
|
+
}).trim();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Check if Docker CLI is available
|
|
34
|
+
*/
|
|
35
|
+
export const isDockerInstalled = () => exec('docker --version') !== null;
|
|
36
|
+
/**
|
|
37
|
+
* Check if Docker daemon is running
|
|
38
|
+
*/
|
|
39
|
+
export const isDockerRunning = () => exec('docker info') !== null;
|
|
40
|
+
/**
|
|
41
|
+
* Get container status
|
|
42
|
+
*/
|
|
43
|
+
export const getContainerStatus = (containerName = NEO4J_CONFIG.containerName) => {
|
|
44
|
+
const result = exec(`docker inspect --format='{{.State.Running}}' ${containerName} 2>/dev/null`);
|
|
45
|
+
if (result === null)
|
|
46
|
+
return 'not-found';
|
|
47
|
+
return result === 'true' ? 'running' : 'stopped';
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Start an existing stopped container
|
|
51
|
+
*/
|
|
52
|
+
export const startContainer = (containerName = NEO4J_CONFIG.containerName) => exec(`docker start ${containerName}`) !== null;
|
|
53
|
+
/**
|
|
54
|
+
* Stop a running container
|
|
55
|
+
*/
|
|
56
|
+
export const stopContainer = (containerName = NEO4J_CONFIG.containerName) => exec(`docker stop ${containerName}`) !== null;
|
|
57
|
+
/**
|
|
58
|
+
* Remove a container
|
|
59
|
+
*/
|
|
60
|
+
export const removeContainer = (containerName = NEO4J_CONFIG.containerName) => exec(`docker rm ${containerName}`) !== null;
|
|
61
|
+
/**
|
|
62
|
+
* Create and start a new Neo4j container
|
|
63
|
+
*/
|
|
64
|
+
export const createContainer = (options = {}) => {
|
|
65
|
+
const { containerName = NEO4J_CONFIG.containerName, httpPort = NEO4J_CONFIG.httpPort, boltPort = NEO4J_CONFIG.boltPort, password = NEO4J_CONFIG.defaultPassword, memory = '2G', } = options;
|
|
66
|
+
const cmd = [
|
|
67
|
+
'docker run -d',
|
|
68
|
+
`--name ${containerName}`,
|
|
69
|
+
`--restart unless-stopped`,
|
|
70
|
+
`-p ${httpPort}:7474`,
|
|
71
|
+
`-p ${boltPort}:7687`,
|
|
72
|
+
`-e NEO4J_AUTH=neo4j/${password}`,
|
|
73
|
+
`-e 'NEO4J_PLUGINS=["apoc"]'`,
|
|
74
|
+
`-e NEO4J_dbms_security_procedures_unrestricted=apoc.*`,
|
|
75
|
+
`-e NEO4J_server_memory_heap_initial__size=1G`,
|
|
76
|
+
`-e NEO4J_server_memory_heap_max__size=${memory}`,
|
|
77
|
+
`-e NEO4J_server_memory_pagecache_size=2G`,
|
|
78
|
+
NEO4J_CONFIG.image,
|
|
79
|
+
].join(' ');
|
|
80
|
+
return exec(cmd) !== null;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Check if Neo4j is accepting connections
|
|
84
|
+
*/
|
|
85
|
+
export const isNeo4jReady = (containerName = NEO4J_CONFIG.containerName, password = NEO4J_CONFIG.defaultPassword) => {
|
|
86
|
+
const result = exec(`docker exec ${containerName} cypher-shell -u neo4j -p ${password} "RETURN 1" 2>/dev/null`);
|
|
87
|
+
return result !== null;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Check if APOC plugin is available
|
|
91
|
+
*/
|
|
92
|
+
export const isApocAvailable = (containerName = NEO4J_CONFIG.containerName, password = NEO4J_CONFIG.defaultPassword) => {
|
|
93
|
+
const result = exec(`docker exec ${containerName} cypher-shell -u neo4j -p ${password} "CALL apoc.help('apoc') YIELD name RETURN count(name)" 2>/dev/null`);
|
|
94
|
+
return result !== null && !result.includes('error');
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Wait for Neo4j to be ready
|
|
98
|
+
*/
|
|
99
|
+
export const waitForNeo4j = async (containerName = NEO4J_CONFIG.containerName, password = NEO4J_CONFIG.defaultPassword, timeoutMs = NEO4J_CONFIG.healthCheckTimeoutMs) => {
|
|
100
|
+
const startTime = Date.now();
|
|
101
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
102
|
+
if (isNeo4jReady(containerName, password)) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
await new Promise((resolve) => setTimeout(resolve, NEO4J_CONFIG.healthCheckIntervalMs));
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Ensure Neo4j is running - start if needed
|
|
111
|
+
*/
|
|
112
|
+
export const ensureNeo4jRunning = async (options = {}) => {
|
|
113
|
+
const containerName = options.containerName ?? NEO4J_CONFIG.containerName;
|
|
114
|
+
const status = getContainerStatus(containerName);
|
|
115
|
+
if (status === 'running') {
|
|
116
|
+
return { success: true, action: 'already-running' };
|
|
117
|
+
}
|
|
118
|
+
if (!isDockerInstalled()) {
|
|
119
|
+
return { success: false, action: 'failed', error: 'Docker not installed' };
|
|
120
|
+
}
|
|
121
|
+
if (!isDockerRunning()) {
|
|
122
|
+
return { success: false, action: 'failed', error: 'Docker daemon not running' };
|
|
123
|
+
}
|
|
124
|
+
// Start existing container
|
|
125
|
+
if (status === 'stopped') {
|
|
126
|
+
if (startContainer(containerName)) {
|
|
127
|
+
const ready = await waitForNeo4j(containerName, options.password);
|
|
128
|
+
return ready
|
|
129
|
+
? { success: true, action: 'started' }
|
|
130
|
+
: { success: false, action: 'failed', error: 'Container started but Neo4j not responding' };
|
|
131
|
+
}
|
|
132
|
+
return { success: false, action: 'failed', error: 'Failed to start existing container' };
|
|
133
|
+
}
|
|
134
|
+
// Create new container
|
|
135
|
+
if (createContainer(options)) {
|
|
136
|
+
const ready = await waitForNeo4j(containerName, options.password);
|
|
137
|
+
return ready
|
|
138
|
+
? { success: true, action: 'created' }
|
|
139
|
+
: { success: false, action: 'failed', error: 'Container created but Neo4j not responding' };
|
|
140
|
+
}
|
|
141
|
+
return { success: false, action: 'failed', error: 'Failed to create container' };
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Get full status for diagnostics
|
|
145
|
+
*/
|
|
146
|
+
export const getFullStatus = () => {
|
|
147
|
+
const dockerInstalled = isDockerInstalled();
|
|
148
|
+
const dockerRunning = dockerInstalled && isDockerRunning();
|
|
149
|
+
const containerStatus = dockerRunning ? getContainerStatus() : 'not-found';
|
|
150
|
+
const neo4jReady = containerStatus === 'running' && isNeo4jReady();
|
|
151
|
+
const apocAvailable = neo4jReady && isApocAvailable();
|
|
152
|
+
return {
|
|
153
|
+
dockerInstalled,
|
|
154
|
+
dockerRunning,
|
|
155
|
+
containerStatus,
|
|
156
|
+
neo4jReady,
|
|
157
|
+
apocAvailable,
|
|
158
|
+
};
|
|
159
|
+
};
|
|
@@ -525,13 +525,11 @@ export const FAIRSQUARE_FRAMEWORK_SCHEMA = {
|
|
|
525
525
|
{
|
|
526
526
|
type: 'function',
|
|
527
527
|
pattern: (parsedNode) => {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const name = node.getName();
|
|
532
|
-
const typeNode = node.getTypeNode();
|
|
528
|
+
// Use pre-extracted properties (works after AST cleanup in streaming/chunking)
|
|
529
|
+
const name = parsedNode.properties.name;
|
|
530
|
+
const typeAnnotation = parsedNode.properties.typeAnnotation;
|
|
533
531
|
// Check if variable name ends with "Routes" AND has type ModuleRoute[]
|
|
534
|
-
return !!name
|
|
532
|
+
return !!name?.endsWith('Routes') && !!typeAnnotation?.includes('ModuleRoute');
|
|
535
533
|
},
|
|
536
534
|
confidence: 1.0,
|
|
537
535
|
priority: 10,
|
|
@@ -696,14 +694,8 @@ export const FAIRSQUARE_FRAMEWORK_SCHEMA = {
|
|
|
696
694
|
if (matchingRoutes.length === 0)
|
|
697
695
|
return false;
|
|
698
696
|
// CRITICAL FIX: Verify the method belongs to the correct controller
|
|
699
|
-
//
|
|
700
|
-
const
|
|
701
|
-
if (!targetNode || !Node.isMethodDeclaration(targetNode))
|
|
702
|
-
return false;
|
|
703
|
-
const parentClass = targetNode.getParent();
|
|
704
|
-
if (!parentClass || !Node.isClassDeclaration(parentClass))
|
|
705
|
-
return false;
|
|
706
|
-
const parentClassName = parentClass.getName();
|
|
697
|
+
// Use pre-extracted parentClassName property (works after AST cleanup in streaming/chunking)
|
|
698
|
+
const parentClassName = parsedTargetNode.properties.parentClassName;
|
|
707
699
|
if (!parentClassName)
|
|
708
700
|
return false;
|
|
709
701
|
// Check if any matching route's controller name matches the parent class
|
|
@@ -434,7 +434,7 @@ export const NESTJS_FRAMEWORK_SCHEMA = {
|
|
|
434
434
|
},
|
|
435
435
|
{
|
|
436
436
|
type: 'function',
|
|
437
|
-
pattern: (
|
|
437
|
+
pattern: (parsedNode) => parsedNode.sourceNode?.getName()?.endsWith('Service'),
|
|
438
438
|
confidence: 0.7,
|
|
439
439
|
priority: 7,
|
|
440
440
|
},
|
|
@@ -516,7 +516,10 @@ export const NESTJS_FRAMEWORK_SCHEMA = {
|
|
|
516
516
|
detectionPatterns: [
|
|
517
517
|
{
|
|
518
518
|
type: 'function',
|
|
519
|
-
pattern: (
|
|
519
|
+
pattern: (parsedNode) => {
|
|
520
|
+
const node = parsedNode.sourceNode;
|
|
521
|
+
if (!node)
|
|
522
|
+
return false;
|
|
520
523
|
const decorators = node.getDecorators?.() ?? [];
|
|
521
524
|
const messageDecorators = ['MessagePattern', 'EventPattern'];
|
|
522
525
|
return decorators.some((d) => messageDecorators.includes(d.getName()));
|
|
@@ -567,7 +570,10 @@ export const NESTJS_FRAMEWORK_SCHEMA = {
|
|
|
567
570
|
detectionPatterns: [
|
|
568
571
|
{
|
|
569
572
|
type: 'function',
|
|
570
|
-
pattern: (
|
|
573
|
+
pattern: (parsedNode) => {
|
|
574
|
+
const node = parsedNode.sourceNode;
|
|
575
|
+
if (!node)
|
|
576
|
+
return false;
|
|
571
577
|
const decorators = node.getDecorators?.() ?? [];
|
|
572
578
|
const httpDecorators = ['Get', 'Post', 'Put', 'Delete', 'Patch', 'Head', 'Options'];
|
|
573
579
|
return decorators.some((d) => httpDecorators.includes(d.getName()));
|
|
@@ -480,6 +480,12 @@ export const CORE_TYPESCRIPT_SCHEMA = {
|
|
|
480
480
|
extraction: { method: 'static', defaultValue: false }, // We'll set this manually
|
|
481
481
|
neo4j: { indexed: true, unique: false, required: true },
|
|
482
482
|
},
|
|
483
|
+
{
|
|
484
|
+
name: 'typeAnnotation',
|
|
485
|
+
type: 'string',
|
|
486
|
+
extraction: { method: 'ast', source: 'getTypeNode', transform: 'getText' },
|
|
487
|
+
neo4j: { indexed: false, unique: false, required: false },
|
|
488
|
+
},
|
|
483
489
|
],
|
|
484
490
|
relationships: [],
|
|
485
491
|
children: {},
|
|
@@ -277,16 +277,24 @@ Provide ONLY the JSON response with no additional text, markdown formatting, or
|
|
|
277
277
|
frameworkHint = '\nFRAMEWORK DETECTED: React/functional codebase. Use Function nodes for components.';
|
|
278
278
|
}
|
|
279
279
|
return `
|
|
280
|
-
|
|
280
|
+
=== VALID NODE LABELS (use ONLY these after the colon) ===
|
|
281
|
+
${nodeTypes}
|
|
281
282
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
283
|
+
=== VALID RELATIONSHIP TYPES ===
|
|
284
|
+
${relTypes}
|
|
285
|
+
|
|
286
|
+
=== SEMANTIC TYPES (these are PROPERTY values, NOT labels) ===
|
|
287
|
+
${semTypes}
|
|
288
|
+
Query semantic types via property: WHERE n.semanticType = 'TypeName'
|
|
285
289
|
${frameworkHint}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
+
|
|
291
|
+
=== CRITICAL RULES ===
|
|
292
|
+
1. Use ONLY the labels listed above after the colon (:Label)
|
|
293
|
+
2. Semantic types are PROPERTY values, NOT labels
|
|
294
|
+
3. Class/service names are PROPERTY values, NOT labels
|
|
295
|
+
4. WRONG: (n:MyService), (n:MyController) - names as labels
|
|
296
|
+
5. CORRECT: (n:Service {name: 'MyService'}), (n:Controller {name: 'MyController'})
|
|
297
|
+
6. CORRECT: (n:Class) WHERE n.semanticType = 'Service'
|
|
290
298
|
`.trim();
|
|
291
299
|
}
|
|
292
300
|
catch (error) {
|
|
@@ -507,7 +515,7 @@ Remember to include WHERE n.projectId = $projectId for all node patterns.
|
|
|
507
515
|
}
|
|
508
516
|
/**
|
|
509
517
|
* Load valid labels dynamically from the schema file.
|
|
510
|
-
* Returns all keys from rawSchema which represent actual Neo4j labels.
|
|
518
|
+
* Returns all keys from rawSchema AND discoveredSchema.nodeTypes which represent actual Neo4j labels.
|
|
511
519
|
*/
|
|
512
520
|
loadValidLabelsFromSchema() {
|
|
513
521
|
// Fallback to core TypeScript labels if schema not available
|
|
@@ -535,12 +543,21 @@ Remember to include WHERE n.projectId = $projectId for all node patterns.
|
|
|
535
543
|
try {
|
|
536
544
|
const content = fs.readFileSync(this.schemaPath, 'utf-8');
|
|
537
545
|
const schema = JSON.parse(content);
|
|
538
|
-
|
|
539
|
-
|
|
546
|
+
const allLabels = new Set(coreLabels);
|
|
547
|
+
// Extract labels from rawSchema keys
|
|
548
|
+
if (schema.rawSchema?.records?.[0]?._fields?.[0]) {
|
|
549
|
+
const schemaLabels = Object.keys(schema.rawSchema.records[0]._fields[0]);
|
|
550
|
+
schemaLabels.forEach((label) => allLabels.add(label));
|
|
551
|
+
}
|
|
552
|
+
// Also extract labels from discoveredSchema.nodeTypes (includes framework labels)
|
|
553
|
+
if (schema.discoveredSchema?.nodeTypes) {
|
|
554
|
+
for (const nodeType of schema.discoveredSchema.nodeTypes) {
|
|
555
|
+
if (nodeType.label) {
|
|
556
|
+
allLabels.add(nodeType.label);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
540
559
|
}
|
|
541
|
-
|
|
542
|
-
const schemaLabels = Object.keys(schema.rawSchema.records[0]._fields[0]);
|
|
543
|
-
return new Set([...coreLabels, ...schemaLabels]);
|
|
560
|
+
return allLabels;
|
|
544
561
|
}
|
|
545
562
|
catch {
|
|
546
563
|
return coreLabels;
|
|
@@ -542,6 +542,11 @@ export class TypeScriptParser {
|
|
|
542
542
|
return astNode.getName();
|
|
543
543
|
}
|
|
544
544
|
break;
|
|
545
|
+
case CoreNodeType.VARIABLE_DECLARATION:
|
|
546
|
+
if (Node.isVariableDeclaration(astNode)) {
|
|
547
|
+
return astNode.getName();
|
|
548
|
+
}
|
|
549
|
+
break;
|
|
545
550
|
default:
|
|
546
551
|
return astNode.getKindName();
|
|
547
552
|
}
|
|
@@ -552,13 +557,18 @@ export class TypeScriptParser {
|
|
|
552
557
|
return 'Unknown';
|
|
553
558
|
}
|
|
554
559
|
extractProperty(astNode, propDef) {
|
|
555
|
-
const { method, source, defaultValue } = propDef.extraction;
|
|
560
|
+
const { method, source, defaultValue, transform } = propDef.extraction;
|
|
556
561
|
try {
|
|
557
562
|
switch (method) {
|
|
558
563
|
case 'ast':
|
|
559
564
|
if (typeof source === 'string') {
|
|
560
565
|
const fn = astNode[source];
|
|
561
|
-
|
|
566
|
+
let result = typeof fn === 'function' ? fn.call(astNode) : defaultValue;
|
|
567
|
+
// Apply transform if specified (e.g., 'getText' on a returned node)
|
|
568
|
+
if (result && transform && typeof result[transform] === 'function') {
|
|
569
|
+
result = result[transform]();
|
|
570
|
+
}
|
|
571
|
+
return result ?? defaultValue;
|
|
562
572
|
}
|
|
563
573
|
return defaultValue;
|
|
564
574
|
case 'function':
|
|
@@ -1284,11 +1294,9 @@ export class TypeScriptParser {
|
|
|
1284
1294
|
}
|
|
1285
1295
|
case 'function':
|
|
1286
1296
|
if (typeof pattern.pattern === 'function') {
|
|
1287
|
-
// Pass the
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
return false;
|
|
1291
|
-
return pattern.pattern(astNode);
|
|
1297
|
+
// Pass the ParsedNode to pattern functions
|
|
1298
|
+
// Patterns should use pre-extracted properties for cross-chunk compatibility
|
|
1299
|
+
return pattern.pattern(node);
|
|
1292
1300
|
}
|
|
1293
1301
|
return false;
|
|
1294
1302
|
case 'classname':
|
package/dist/mcp/service-init.js
CHANGED
|
@@ -4,14 +4,67 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import fs from 'fs/promises';
|
|
6
6
|
import { join } from 'path';
|
|
7
|
+
import { ensureNeo4jRunning, isDockerInstalled, isDockerRunning, } from '../cli/neo4j-docker.js';
|
|
7
8
|
import { Neo4jService, QUERIES } from '../storage/neo4j/neo4j.service.js';
|
|
8
9
|
import { FILE_PATHS, LOG_CONFIG } from './constants.js';
|
|
9
10
|
import { initializeNaturalLanguageService } from './tools/natural-language-to-cypher.tool.js';
|
|
10
11
|
import { debugLog } from './utils.js';
|
|
12
|
+
/**
|
|
13
|
+
* Log startup warnings for missing configuration
|
|
14
|
+
*/
|
|
15
|
+
const checkConfiguration = async () => {
|
|
16
|
+
if (!process.env.OPENAI_API_KEY) {
|
|
17
|
+
console.error(JSON.stringify({
|
|
18
|
+
level: 'warn',
|
|
19
|
+
message: '[code-graph-context] OPENAI_API_KEY not set. Semantic search and NL queries unavailable.',
|
|
20
|
+
}));
|
|
21
|
+
await debugLog('Configuration warning', { warning: 'OPENAI_API_KEY not set' });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Ensure Neo4j is running - auto-start if Docker available, fail if not
|
|
26
|
+
*/
|
|
27
|
+
const ensureNeo4j = async () => {
|
|
28
|
+
// Check if Docker is available
|
|
29
|
+
if (!isDockerInstalled()) {
|
|
30
|
+
const msg = 'Docker not installed. Install Docker or run: code-graph-context init';
|
|
31
|
+
console.error(JSON.stringify({ level: 'error', message: `[code-graph-context] ${msg}` }));
|
|
32
|
+
throw new Error(msg);
|
|
33
|
+
}
|
|
34
|
+
if (!isDockerRunning()) {
|
|
35
|
+
const msg = 'Docker not running. Start Docker or run: code-graph-context init';
|
|
36
|
+
console.error(JSON.stringify({ level: 'error', message: `[code-graph-context] ${msg}` }));
|
|
37
|
+
throw new Error(msg);
|
|
38
|
+
}
|
|
39
|
+
const result = await ensureNeo4jRunning();
|
|
40
|
+
if (!result.success) {
|
|
41
|
+
const msg = `Neo4j failed to start: ${result.error}. Run: code-graph-context init`;
|
|
42
|
+
console.error(JSON.stringify({ level: 'error', message: `[code-graph-context] ${msg}` }));
|
|
43
|
+
throw new Error(msg);
|
|
44
|
+
}
|
|
45
|
+
if (result.action === 'created') {
|
|
46
|
+
console.error(JSON.stringify({
|
|
47
|
+
level: 'info',
|
|
48
|
+
message: '[code-graph-context] Neo4j container created and started',
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
else if (result.action === 'started') {
|
|
52
|
+
console.error(JSON.stringify({
|
|
53
|
+
level: 'info',
|
|
54
|
+
message: '[code-graph-context] Neo4j container started',
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
await debugLog('Neo4j ready', result);
|
|
58
|
+
};
|
|
11
59
|
/**
|
|
12
60
|
* Initialize all external services required by the MCP server
|
|
13
61
|
*/
|
|
14
62
|
export const initializeServices = async () => {
|
|
63
|
+
// Check for missing configuration (non-fatal warnings)
|
|
64
|
+
await checkConfiguration();
|
|
65
|
+
// Ensure Neo4j is running (fatal if not)
|
|
66
|
+
await ensureNeo4j();
|
|
67
|
+
// Initialize services
|
|
15
68
|
await Promise.all([initializeNeo4jSchema(), initializeNaturalLanguageService()]);
|
|
16
69
|
};
|
|
17
70
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-graph-context",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "MCP server that builds code graphs to provide rich context to LLMs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/drewdrewH/code-graph-context#readme",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"main": "dist/mcp/mcp.server.js",
|
|
32
32
|
"bin": {
|
|
33
|
-
"code-graph-context": "dist/
|
|
33
|
+
"code-graph-context": "dist/cli/cli.js"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"dist/**/*",
|