context-mcp-server 1.0.6 → 1.0.7
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 +99 -359
- package/codegraph/server.py +37 -46
- package/package.json +3 -2
- package/pyproject.toml +1 -1
- package/src/assests/main.png +0 -0
- package/src/cli.js +5 -5
- package/src/db.js +11 -4
- package/src/templates/skills/SKILL.md +2 -3
- package/src/tools/codegraph.js +8 -77
- package/src/tools/context.js +20 -6
- package/uv.lock +1 -1
- package/codegraph/extractors/audio_extractor.py +0 -8
- package/codegraph/extractors/doc_extractor.py +0 -34
- package/codegraph/extractors/image_extractor.py +0 -26
package/README.md
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="src/assests/main.png" alt="context-mcp" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/context-mcp-server"><img src="https://img.shields.io/npm/v/context-mcp-server?style=flat-square" alt="npm version" /></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/context-mcp-server"><img src="https://img.shields.io/npm/dm/context-mcp-server?style=flat-square" alt="npm downloads" /></a>
|
|
8
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square" alt="License: MIT" /></a>
|
|
9
|
+
<a href="package.json"><img src="https://img.shields.io/node/v/context-mcp-server?style=flat-square" alt="Node.js" /></a>
|
|
10
|
+
</p>
|
|
4
11
|
|
|
5
|
-
|
|
12
|
+
Persistent memory and codebase knowledge graph for AI coding assistants — delivered as a single MCP server.
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
One shared context store across Claude Code, Cursor, Gemini CLI, Codex, Windsurf, VS Code Copilot, Claude.ai, and ChatGPT. Save context from one AI, pick it up in another.
|
|
8
15
|
|
|
9
16
|
---
|
|
10
17
|
|
|
11
18
|
## The Problem
|
|
12
19
|
|
|
13
|
-
Every conversation with an AI assistant starts from zero. The AI re-reads files it already read yesterday, re-discovers architecture it already understood, re-derives decisions that were already made. You repeat context. You paste the same background.
|
|
20
|
+
Every conversation with an AI assistant starts from zero. The AI re-reads files it already read yesterday, re-discovers architecture it already understood, re-derives decisions that were already made. You repeat context. You paste the same background.
|
|
14
21
|
|
|
15
|
-
This gets worse as projects grow
|
|
22
|
+
This gets worse as projects grow — reading 20 files to answer "what calls this function?" burns thousands of tokens every time.
|
|
16
23
|
|
|
17
24
|
---
|
|
18
25
|
|
|
19
|
-
## What
|
|
26
|
+
## What It Solves
|
|
20
27
|
|
|
21
|
-
**
|
|
22
|
-
|
|
28
|
+
- **Persistent memory** — decisions, bugs, notes, and architecture saved across sessions, loaded automatically at conversation start
|
|
29
|
+
- **Shared store** — `~/.context-mcp/` is one place on your machine; all AI tools read and write it
|
|
30
|
+
- **ContextGraph** — build a knowledge graph of your codebase once, answer structural questions in ~500 tokens instead of ~50,000
|
|
23
31
|
|
|
24
|
-
**
|
|
25
|
-
You fix a bug with Claude Code, then open Cursor and it knows nothing about it. context-mcp stores everything in `~/.context-mcp/` — a single shared store on your machine. Any AI that connects reads and writes the same store.
|
|
26
|
-
|
|
27
|
-
**3. Structural understanding costs too many tokens.**
|
|
28
|
-
Reading 20 files to answer "what calls this function?" is wasteful. context-mcp builds a knowledge graph of your codebase once, then answers structural questions in ~500 tokens instead of ~50,000.
|
|
32
|
+
Real measured reduction on this project: **162× fewer tokens**, **99.38% reduction** per conversation.
|
|
29
33
|
|
|
30
34
|
---
|
|
31
35
|
|
|
@@ -35,36 +39,37 @@ Reading 20 files to answer "what calls this function?" is wasteful. context-mcp
|
|
|
35
39
|
npm install -g context-mcp-server
|
|
36
40
|
```
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
Requires Node.js ≥ 18. Installs `context-mcp`, `context-mcp-http`, and the `ctx` CLI.
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
**ContextGraph requires [uv](https://docs.astral.sh/uv/)** (Python runner). Memory tools work without it.
|
|
41
45
|
|
|
42
46
|
```bash
|
|
43
|
-
|
|
47
|
+
# macOS / Linux
|
|
48
|
+
curl -Ls https://astral.sh/uv/install.sh | sh
|
|
49
|
+
|
|
50
|
+
# Windows
|
|
51
|
+
winget install astral-sh.uv
|
|
44
52
|
```
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
---
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
> ```bash
|
|
50
|
-
> curl -Ls https://astral.sh/uv/install.sh | sh # macOS / Linux
|
|
51
|
-
> winget install astral-sh.uv # Windows
|
|
52
|
-
> ```
|
|
53
|
-
> Memory tools work with npm alone — uv is only needed for `codegraph_build` and graph queries.
|
|
56
|
+
## Quick Start
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
Run from your project root:
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
```bash
|
|
61
|
+
ctx install --initial
|
|
62
|
+
```
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
|---------|-------------|
|
|
61
|
-
| `context-mcp` | Stdio MCP server (for local AI clients) |
|
|
62
|
-
| `context-mcp-http` | HTTP MCP server with OAuth 2.0 (for web clients) |
|
|
63
|
-
| `ctx` | Interactive CLI — browse, search, manage context |
|
|
64
|
+
This installs Node.js + Python (ContextGraph) dependencies. Run once after installing the npm package.
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
Then write MCP config + AI instruction files:
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
```bash
|
|
69
|
+
ctx install --all
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
To install for a specific platform only:
|
|
68
73
|
|
|
69
74
|
```bash
|
|
70
75
|
ctx install --claude # Claude Code
|
|
@@ -73,398 +78,133 @@ ctx install --vscode # VS Code Copilot
|
|
|
73
78
|
ctx install --gemini # Gemini CLI
|
|
74
79
|
ctx install --codex # Codex CLI
|
|
75
80
|
ctx install --windsurf # Windsurf
|
|
76
|
-
ctx install --all # all platforms + Python setup at once
|
|
77
81
|
```
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
### Claude Code
|
|
84
|
-
|
|
85
|
-
`ctx install --claude` writes:
|
|
86
|
-
- `.claude/mcp.json` — MCP server config
|
|
87
|
-
- `CLAUDE.md` — instructions Claude reads automatically at conversation start
|
|
88
|
-
|
|
89
|
-
Manual config — add to `.claude/mcp.json`:
|
|
83
|
+
For web clients (Claude.ai, ChatGPT), start the HTTP server:
|
|
90
84
|
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"command": "npx",
|
|
96
|
-
"args": ["-y", "context-mcp-server@latest"]
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
85
|
+
```bash
|
|
86
|
+
ctx online # start in background, prints OAuth credentials + URL
|
|
87
|
+
ctx online --restart # force restart
|
|
88
|
+
ctx online --port 3200 # different port
|
|
100
89
|
```
|
|
101
90
|
|
|
102
91
|
---
|
|
103
92
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
`ctx install --cursor` writes:
|
|
107
|
-
- `.cursor/mcp.json` — MCP server config
|
|
108
|
-
- `.cursor/rules/context-mcp.mdc` — Cursor rules file
|
|
109
|
-
|
|
110
|
-
Manual config — add to `.cursor/mcp.json`:
|
|
93
|
+
## CLI Reference
|
|
111
94
|
|
|
112
|
-
|
|
113
|
-
{
|
|
114
|
-
"mcpServers": {
|
|
115
|
-
"context-mcp": {
|
|
116
|
-
"command": "npx",
|
|
117
|
-
"args": ["-y", "context-mcp-server@latest"]
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
```
|
|
95
|
+
Both `ctx` and `context` are aliases for the same CLI.
|
|
122
96
|
|
|
123
|
-
|
|
97
|
+
```bash
|
|
98
|
+
ctx # interactive mode (UI — no "ctx" prefix inside)
|
|
124
99
|
|
|
125
|
-
|
|
100
|
+
# Context
|
|
101
|
+
ctx list [project] # list entries, discussions, graphs
|
|
102
|
+
ctx projects # all projects with graph status + recent entries
|
|
103
|
+
ctx search "query" # keyword → semantic fallback search
|
|
104
|
+
ctx add # add entry interactively
|
|
105
|
+
ctx summary [project] # summarize recent entries
|
|
126
106
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
107
|
+
# Delete
|
|
108
|
+
ctx delete <id-prefix> # delete one entry
|
|
109
|
+
ctx delete project <name> # delete all entries for a project
|
|
130
110
|
|
|
131
|
-
|
|
111
|
+
# Server
|
|
112
|
+
ctx online # start HTTP server (idempotent)
|
|
113
|
+
ctx online --restart # force stop + restart
|
|
114
|
+
ctx settings # view and edit config interactively
|
|
132
115
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
"context-mcp": {
|
|
137
|
-
"type": "stdio",
|
|
138
|
-
"command": "npx",
|
|
139
|
-
"args": ["-y", "context-mcp-server@latest"]
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
116
|
+
# Tools
|
|
117
|
+
ctx benchmark # token savings report (memory + graph)
|
|
118
|
+
ctx discuss [project] # view discussions
|
|
143
119
|
```
|
|
144
120
|
|
|
145
121
|
---
|
|
146
122
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
`ctx install --gemini` writes:
|
|
150
|
-
- `.gemini/settings.json` — MCP server config
|
|
151
|
-
- `GEMINI.md` — instructions Gemini reads automatically
|
|
123
|
+
## Security
|
|
152
124
|
|
|
153
|
-
|
|
125
|
+
File and git tools are sandboxed to your project root. Pass `rootPath` when calling `context.resume`:
|
|
154
126
|
|
|
155
127
|
```json
|
|
156
|
-
{
|
|
157
|
-
"mcpServers": {
|
|
158
|
-
"context-mcp": {
|
|
159
|
-
"command": "npx",
|
|
160
|
-
"args": ["-y", "context-mcp-server@latest"]
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
128
|
+
{ "action": "resume", "project": "my-app", "rootPath": "/home/user/my-app" }
|
|
164
129
|
```
|
|
165
130
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
### Codex CLI
|
|
169
|
-
|
|
170
|
-
`ctx install --codex` writes:
|
|
171
|
-
- `.codex/config.toml` — MCP server config
|
|
172
|
-
- `AGENTS.md` — instructions Codex reads automatically
|
|
173
|
-
|
|
174
|
-
Manual config — add to `.codex/config.toml`:
|
|
175
|
-
|
|
176
|
-
```toml
|
|
177
|
-
[[mcp_servers]]
|
|
178
|
-
name = "context-mcp"
|
|
179
|
-
command = "npx"
|
|
180
|
-
args = ["-y", "context-mcp-server@latest"]
|
|
181
|
-
```
|
|
131
|
+
Any file or git operation outside that directory is rejected. Applies to all HTTP-connected clients.
|
|
182
132
|
|
|
183
133
|
---
|
|
184
134
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
`ctx install --windsurf` writes:
|
|
188
|
-
- `.windsurf/rules/context-mcp.md` — local rules file (project scope)
|
|
189
|
-
- `~/.codeium/windsurf/mcp_config.json` — global MCP config (merged, not overwritten)
|
|
190
|
-
|
|
191
|
-
Manual config — add to `~/.codeium/windsurf/mcp_config.json`:
|
|
192
|
-
|
|
193
|
-
```json
|
|
194
|
-
{
|
|
195
|
-
"mcpServers": {
|
|
196
|
-
"context-mcp": {
|
|
197
|
-
"command": "npx",
|
|
198
|
-
"args": ["-y", "context-mcp-server@latest"]
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
---
|
|
135
|
+
## Features
|
|
205
136
|
|
|
206
|
-
###
|
|
137
|
+
### Memory
|
|
207
138
|
|
|
208
|
-
|
|
139
|
+
- `context.resume` — loads recent entries, discussions, and graph status; registers `rootPath` for sandboxing
|
|
140
|
+
- `context.save` — store decisions, bugs, notes, architecture with type tags
|
|
141
|
+
- `context.get` / `context.update` / `context.delete` — full CRUD
|
|
142
|
+
- `search` — keyword-first, semantic fallback
|
|
143
|
+
- `discussion` — threaded plans with steps and cross-session continuity
|
|
144
|
+
- Auto-deduplication on save; auto-compact at 20 entries
|
|
209
145
|
|
|
210
|
-
|
|
146
|
+
### ContextGraph
|
|
211
147
|
|
|
212
|
-
|
|
213
|
-
ctx online
|
|
214
|
-
```
|
|
148
|
+
> Also called **CodeGraph**. MCP tools use the `codegraph_*` prefix — both names mean the same thing.
|
|
215
149
|
|
|
216
|
-
|
|
150
|
+
**Step 1 — Build** (once per project, runs locally, no API cost):
|
|
217
151
|
|
|
218
|
-
```bash
|
|
219
|
-
ctx online --restart # force restart
|
|
220
|
-
ctx online --port 3200 # use a different port
|
|
221
152
|
```
|
|
222
|
-
|
|
223
|
-
Or start directly:
|
|
224
|
-
|
|
225
|
-
```bash
|
|
226
|
-
context-mcp-http --port 3100 --host localhost --access-git
|
|
153
|
+
codegraph_build(path)
|
|
227
154
|
```
|
|
228
155
|
|
|
229
|
-
|
|
156
|
+
Parses codebase via tree-sitter AST (16 languages, regex fallback). Extracts functions, classes, imports, call edges. Saved to `~/.context-mcp/`.
|
|
230
157
|
|
|
231
|
-
|
|
232
|
-
2. Enter your server URL (e.g. `http://localhost:3100`)
|
|
233
|
-
3. Use the **Client ID** and **Client Secret** from `~/.context-mcp/contextconfig.json`
|
|
158
|
+
**Step 2 — Query** (instant, forever):
|
|
234
159
|
|
|
235
|
-
**View or edit config:**
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
ctx settings
|
|
239
160
|
```
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
File and git tools are sandboxed to your project root. Pass `rootPath` when calling `context.resume` to register it:
|
|
246
|
-
|
|
247
|
-
```json
|
|
248
|
-
{ "action": "resume", "project": "my-app", "rootPath": "/home/user/my-app" }
|
|
161
|
+
codegraph_query(path, question?, node?) → structural question OR single-node lookup (or both)
|
|
162
|
+
codegraph_path(path, from, to) → shortest path between two concepts
|
|
163
|
+
codegraph_nodes(path, type) → list all nodes of a type
|
|
164
|
+
codegraph_report(path) → god nodes, clusters, surprising connections
|
|
249
165
|
```
|
|
250
166
|
|
|
251
|
-
|
|
167
|
+
`codegraph_query` accepts `question` (natural language), `node` (exact/partial name for type + file + deps + callers), or both in one call. Use before reading any files.
|
|
252
168
|
|
|
253
|
-
|
|
169
|
+
### File & Git Tools
|
|
254
170
|
|
|
255
|
-
|
|
171
|
+
Available to HTTP-connected clients (Claude.ai, ChatGPT). Local AI clients use their native IDE tools.
|
|
256
172
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
# Context
|
|
261
|
-
ctx list [project] # list entries, discussions, graphs
|
|
262
|
-
ctx projects # all projects with IDs, graph status, recent entries
|
|
263
|
-
ctx search "query" # keyword → semantic fallback search
|
|
264
|
-
ctx add # add entry interactively
|
|
265
|
-
ctx summary [project] # summarize recent entries
|
|
266
|
-
|
|
267
|
-
# Delete
|
|
268
|
-
ctx delete <id-prefix> # delete one entry by ID prefix
|
|
269
|
-
ctx delete project <name|id> # delete all entries for a project
|
|
270
|
-
|
|
271
|
-
# Server
|
|
272
|
-
ctx online # start HTTP server (idempotent)
|
|
273
|
-
ctx online --restart # force stop + restart
|
|
274
|
-
ctx online --port 3200 # use a different port
|
|
275
|
-
ctx settings # view and edit config interactively
|
|
276
|
-
|
|
277
|
-
# Setup
|
|
278
|
-
ctx install --claude # write MCP config for Claude Code
|
|
279
|
-
ctx install --cursor # write MCP config for Cursor
|
|
280
|
-
ctx install --vscode # write MCP config for VS Code
|
|
281
|
-
ctx install --gemini # write MCP config for Gemini CLI
|
|
282
|
-
ctx install --codex # write MCP config for Codex CLI
|
|
283
|
-
ctx install --windsurf # write MCP config for Windsurf
|
|
284
|
-
ctx install --all # all platforms + Python setup
|
|
173
|
+
- `read_file`, `write_file`, `patch_file`, `create_dir`, `list_dir`, `delete_file`
|
|
174
|
+
- `git_status`, `git_diff`, `git_log`, `git_add`, `git_commit`, `git_push`, `git_pull`, `git_branch`, `git_stash`, `git_reset`, `git_show`
|
|
285
175
|
|
|
286
|
-
|
|
287
|
-
ctx benchmark # real token savings report (memory + graph)
|
|
288
|
-
ctx discuss [project] # view discussions
|
|
289
|
-
```
|
|
176
|
+
Enable git tools with `--access-git` flag or `access_git: true` in config.
|
|
290
177
|
|
|
291
178
|
---
|
|
292
179
|
|
|
293
180
|
## Server Flags
|
|
294
181
|
|
|
295
|
-
### `context-mcp` (stdio)
|
|
296
|
-
|
|
297
182
|
```
|
|
298
|
-
context-mcp [
|
|
183
|
+
context-mcp [--data-dir <path>]
|
|
299
184
|
|
|
300
|
-
|
|
301
|
-
--data-dir <path> Override storage directory (default: ~/.context-mcp)
|
|
302
|
-
Also via env: CONTEXT_MCP_DIR=<path>
|
|
303
|
-
--help, -h Show help
|
|
185
|
+
context-mcp-http [--port <number>] [--host <string>] [--access-git] [--data-dir <path>]
|
|
304
186
|
```
|
|
305
187
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
```
|
|
309
|
-
context-mcp-http [options]
|
|
310
|
-
|
|
311
|
-
Options:
|
|
312
|
-
--port <number> HTTP listen port (default: 3100)
|
|
313
|
-
--host <string> Bind address (default: localhost)
|
|
314
|
-
--access-git Enable git tools for connected clients
|
|
315
|
-
--data-dir <path> Override storage directory (default: ~/.context-mcp)
|
|
316
|
-
Also via env: CONTEXT_MCP_DIR=<path>
|
|
317
|
-
--help, -h Show help
|
|
318
|
-
```
|
|
188
|
+
Default port: `3100`. Default data dir: `~/.context-mcp`.
|
|
319
189
|
|
|
320
190
|
---
|
|
321
191
|
|
|
322
192
|
## Config Reference
|
|
323
193
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
```json
|
|
327
|
-
{
|
|
328
|
-
"client_id": "context-mcp",
|
|
329
|
-
"client_secret": "<auto-generated>",
|
|
330
|
-
"port": 3100,
|
|
331
|
-
"host": "localhost",
|
|
332
|
-
"access_git": false,
|
|
333
|
-
"public_url": null,
|
|
334
|
-
"allowed_redirect_uris": ["https://claude.ai"],
|
|
335
|
-
"allowed_origins": []
|
|
336
|
-
}
|
|
337
|
-
```
|
|
194
|
+
`~/.context-mcp/contextconfig.json` — auto-created on first run:
|
|
338
195
|
|
|
339
196
|
| Field | Default | Description |
|
|
340
197
|
|-------|---------|-------------|
|
|
341
198
|
| `client_id` | `"context-mcp"` | OAuth client ID |
|
|
342
|
-
| `client_secret` | auto-generated | OAuth signing secret
|
|
199
|
+
| `client_secret` | auto-generated | OAuth signing secret |
|
|
343
200
|
| `port` | `3100` | HTTP server port |
|
|
344
201
|
| `host` | `"localhost"` | HTTP bind host |
|
|
345
202
|
| `access_git` | `false` | Enable git tools for HTTP clients |
|
|
346
|
-
| `public_url` | `null` | Public URL
|
|
203
|
+
| `public_url` | `null` | Public URL for `ctx online` output |
|
|
347
204
|
| `allowed_redirect_uris` | `["https://claude.ai"]` | OAuth redirect URI whitelist |
|
|
348
|
-
| `allowed_origins` | `[]` | Extra CORS origins
|
|
349
|
-
|
|
350
|
-
Edit any field interactively with `ctx settings`.
|
|
205
|
+
| `allowed_origins` | `[]` | Extra CORS origins |
|
|
351
206
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
## Features
|
|
355
|
-
|
|
356
|
-
### Memory
|
|
357
|
-
- `context.resume` — loads recent entries, active discussions, and graph status. Pass `rootPath` to sandbox file/git tools to your project directory.
|
|
358
|
-
- `context.save` — store decisions, bugs, notes, code snippets, architecture with type tags
|
|
359
|
-
- `context.get` / `context.update` / `context.delete` — full CRUD
|
|
360
|
-
- `search` — keyword-first, semantic fallback, searches all past context
|
|
361
|
-
- `discussion` — threaded plans with steps, status tracking, cross-session continuity
|
|
362
|
-
- Auto-deduplication on save
|
|
363
|
-
- Auto-compact at 50 entries (oldest entries summarized into a digest)
|
|
364
|
-
- Per-project isolation with stable UUIDs
|
|
365
|
-
|
|
366
|
-
### File & Git Tools (HTTP mode)
|
|
367
|
-
Available to web clients (Claude.ai, ChatGPT) only — local AI clients use their native IDE tools directly.
|
|
368
|
-
|
|
369
|
-
- `read_file`, `write_file`, `patch_file`, `create_dir`, `list_dir`, `delete_file`
|
|
370
|
-
- `git_status`, `git_diff`, `git_log`, `git_add`, `git_commit`, `git_push`, `git_pull`, `git_branch`, `git_stash`, `git_reset`, `git_show`
|
|
371
|
-
|
|
372
|
-
All file and git operations are sandboxed to the registered project root. Enable git tools with `--access-git` or `access_git: true` in config.
|
|
373
|
-
|
|
374
|
-
### ContextGraph (CodeGraph)
|
|
375
|
-
|
|
376
|
-
> Also referred to as **ContextGraph** — the MCP tools use the `codegraph_*` prefix but both names mean the same thing.
|
|
377
|
-
|
|
378
|
-
- `codegraph_build` — AST scan using tree-sitter: functions, classes, imports, edges. Runs locally, no API cost.
|
|
379
|
-
- `codegraph_query` — fetch any details about the codebase using natural language: find functions, classes, files, dependencies, callers
|
|
380
|
-
- `codegraph_explain` — single node: type, file location, all direct connections (depends_on, used_by)
|
|
381
|
-
- `codegraph_path` — shortest path between two concepts
|
|
382
|
-
- `codegraph_nodes` — list all nodes of a given type
|
|
383
|
-
- `codegraph_report` — full graph analysis: god nodes, clusters, surprising connections
|
|
384
|
-
|
|
385
|
-
### Multi-AI Support
|
|
386
|
-
|
|
387
|
-
| AI | Config File | Instructions | Slash Commands |
|
|
388
|
-
|----|------------|--------------|----------------|
|
|
389
|
-
| Claude Code | `.claude/mcp.json` | Skill → `~/.claude/skills/context-mcp/` (global) | ✓ (3 commands) |
|
|
390
|
-
| Cursor | `.cursor/mcp.json` | Rule → `.cursor/rules/context-mcp.mdc` | — |
|
|
391
|
-
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | Rule → `.windsurf/rules/context-mcp.md` | — |
|
|
392
|
-
| Gemini CLI | `.gemini/settings.json` | `GEMINI.md` | — |
|
|
393
|
-
| Codex CLI | `.codex/config.toml` | `AGENTS.md` | — |
|
|
394
|
-
| VS Code Copilot | `.vscode/mcp.json` | — | — |
|
|
395
|
-
| Claude.ai / ChatGPT | HTTP (`ctx online`) | — | — |
|
|
396
|
-
|
|
397
|
-
> Claude Code installs context-mcp as a **skill** (`~/.claude/skills/context-mcp/SKILL.md`) — available globally across all projects, not just the current one. Cursor and Windsurf use their native rules system. Gemini and Codex use plain instruction files since they have no skill/rules system.
|
|
398
|
-
|
|
399
|
-
`ctx install --claude` also writes slash commands into `.claude/commands/`:
|
|
400
|
-
|
|
401
|
-
| Command | What it does |
|
|
402
|
-
|---------|-------------|
|
|
403
|
-
| `/context-resume` | Resume context for the current project |
|
|
404
|
-
| `/graph-build` | Build or rebuild the ContextGraph |
|
|
405
|
-
| `/save-context` | Save a note, decision, or bug to context |
|
|
406
|
-
|
|
407
|
-
> The context store lives at `~/.context-mcp/` — not inside any tool, IDE, or session. A decision saved in Claude Code is visible in Cursor. A bug logged from Gemini CLI shows up when you resume in Codex.
|
|
408
|
-
|
|
409
|
-
---
|
|
410
|
-
|
|
411
|
-
## Token Reduction
|
|
412
|
-
|
|
413
|
-
| Scenario | Without context-mcp | With context-mcp |
|
|
414
|
-
|----------|-------------------|--------------------|
|
|
415
|
-
| Start of conversation | Paste background, re-explain project | `context.resume` → 15 entries, ~750 tokens |
|
|
416
|
-
| "What calls function X?" | Read 10 files to trace callers | `codegraph_query` → subgraph, ~400 tokens |
|
|
417
|
-
| "What does module Y depend on?" | Read module + all imports | `codegraph_explain` → node + edges, ~200 tokens |
|
|
418
|
-
| Understand architecture | Read 20+ files | Graph built once, queried forever |
|
|
419
|
-
| Remember last session's decision | Ask user or re-derive | `context.resume` loads it automatically |
|
|
420
|
-
|
|
421
|
-
Real measured reduction on this project: **162× fewer tokens**, **99.38% reduction** per conversation.
|
|
422
|
-
|
|
423
|
-
---
|
|
424
|
-
|
|
425
|
-
## Architecture
|
|
426
|
-
|
|
427
|
-
```
|
|
428
|
-
context-mcp/
|
|
429
|
-
├── src/
|
|
430
|
-
│ ├── index.js Stdio MCP server entrypoint
|
|
431
|
-
│ ├── server.js MCP server — registers all tools
|
|
432
|
-
│ ├── db.js JSON store — in-memory cache, debounced writes, project registry
|
|
433
|
-
│ ├── guard.js Path sandboxing — enforces project root on all file/git ops
|
|
434
|
-
│ ├── search.js Keyword + semantic search
|
|
435
|
-
│ ├── summarizer.js Auto-compact summarization
|
|
436
|
-
│ ├── cli.js Interactive CLI (ctx)
|
|
437
|
-
│ ├── http.js HTTP server — OAuth 2.0 + Streamable HTTP transport
|
|
438
|
-
│ ├── config.js Config loader — contextconfig.json + keytar
|
|
439
|
-
│ ├── vector.js Embedding helpers
|
|
440
|
-
│ └── tools/
|
|
441
|
-
│ ├── context.js Memory tool (resume/save/get/update/delete)
|
|
442
|
-
│ ├── discussion.js Discussion tool (threaded plans + steps)
|
|
443
|
-
│ ├── codegraph.js CodeGraph tool — bridge to Python subprocess
|
|
444
|
-
│ ├── search.js Search tool
|
|
445
|
-
│ ├── fileTools.js File read/write (HTTP mode, sandboxed to project root)
|
|
446
|
-
│ ├── gitTools.js Git integration (HTTP mode, sandboxed to project root)
|
|
447
|
-
│ └── errorCheck.js Error checking tool
|
|
448
|
-
├── codegraph/ Python package — AST extraction + graph queries
|
|
449
|
-
│ ├── server.py MCP server — tool definitions + dispatch
|
|
450
|
-
│ ├── scanner.py File walker + classifier (SKIP/BUILD/CODE/CONFIG/DOC/MEDIA)
|
|
451
|
-
│ ├── config.py File type taxonomy
|
|
452
|
-
│ ├── cache.py AST cache (hash-based, incremental)
|
|
453
|
-
│ ├── report.py Graph report generator
|
|
454
|
-
│ ├── extractors/
|
|
455
|
-
│ │ ├── ast_extractor.py Tree-sitter AST (16 languages) + regex fallback
|
|
456
|
-
│ │ └── build_extractor.py Single-node extraction for build files
|
|
457
|
-
│ └── graph/
|
|
458
|
-
│ ├── builder.py NetworkX graph construction
|
|
459
|
-
│ ├── query.py Natural language → subgraph traversal
|
|
460
|
-
│ └── clustering.py Community detection
|
|
461
|
-
└── ~/.context-mcp/ Data directory (outside repo, never committed)
|
|
462
|
-
├── contexts.json
|
|
463
|
-
├── discussions.json
|
|
464
|
-
├── projects.json Project registry — includes rootPath per project
|
|
465
|
-
├── graphs.json Knowledge graph (nodes, edges, communities)
|
|
466
|
-
└── contextconfig.json OAuth config + server settings
|
|
467
|
-
```
|
|
207
|
+
Edit with `ctx settings`.
|
|
468
208
|
|
|
469
209
|
---
|
|
470
210
|
|
package/codegraph/server.py
CHANGED
|
@@ -4,8 +4,7 @@ codegraph/server.py — MCP server exposing codebase knowledge graph tools.
|
|
|
4
4
|
|
|
5
5
|
Tools:
|
|
6
6
|
codegraph_build — scan project, extract AST nodes, build graph (local only, no API)
|
|
7
|
-
codegraph_query —
|
|
8
|
-
codegraph_explain — look up a specific node: type, file, connections
|
|
7
|
+
codegraph_query — structural question OR single-node lookup (or both); replaces codegraph_explain
|
|
9
8
|
codegraph_report — return full CODEGRAPH_REPORT.md
|
|
10
9
|
codegraph_nodes — list nodes of a given type
|
|
11
10
|
codegraph_path — shortest path between two concepts
|
|
@@ -56,36 +55,21 @@ TOOLS = [
|
|
|
56
55
|
Tool(
|
|
57
56
|
name="codegraph_query",
|
|
58
57
|
description=(
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
58
|
+
"Ask a structural question about the codebase OR look up a specific node by name — or both in one call. "
|
|
59
|
+
"Pass `question` for natural-language traversal: what calls X, what does module Y depend on. "
|
|
60
|
+
"Pass `node` for fast single-node lookup: returns type, file, depends_on, used_by. "
|
|
61
|
+
"Pass both to get node detail + surrounding graph context together. "
|
|
62
|
+
"Returns structured text within token_budget. Use before reading any files."
|
|
64
63
|
),
|
|
65
64
|
inputSchema={
|
|
66
65
|
"type": "object",
|
|
67
66
|
"properties": {
|
|
68
67
|
"path": {"type": "string", "description": "Project root"},
|
|
69
|
-
"question": {"type": "string", "description": "Natural language question"},
|
|
68
|
+
"question": {"type": "string", "description": "Natural language question about the codebase"},
|
|
69
|
+
"node": {"type": "string", "description": "Node name or partial name to look up (type, file, deps, callers)"},
|
|
70
70
|
"token_budget": {"type": "integer", "description": "Max tokens in response (default 2000)"},
|
|
71
71
|
},
|
|
72
|
-
"required": ["path"
|
|
73
|
-
},
|
|
74
|
-
),
|
|
75
|
-
Tool(
|
|
76
|
-
name="codegraph_explain",
|
|
77
|
-
description=(
|
|
78
|
-
"Look up a specific function, class, or module by name — returns its type, file location, "
|
|
79
|
-
"and all direct connections (what it depends on, what uses it). "
|
|
80
|
-
"Use when you already know the name and want its full context in the graph."
|
|
81
|
-
),
|
|
82
|
-
inputSchema={
|
|
83
|
-
"type": "object",
|
|
84
|
-
"properties": {
|
|
85
|
-
"path": {"type": "string", "description": "Project root"},
|
|
86
|
-
"node": {"type": "string", "description": "Node name or partial name"},
|
|
87
|
-
},
|
|
88
|
-
"required": ["path", "node"],
|
|
72
|
+
"required": ["path"],
|
|
89
73
|
},
|
|
90
74
|
),
|
|
91
75
|
Tool(
|
|
@@ -143,7 +127,7 @@ async def call_tool(name: str, arguments: dict):
|
|
|
143
127
|
async def _dispatch(name: str, args: dict):
|
|
144
128
|
if name == "codegraph_build": return await _build(args)
|
|
145
129
|
if name == "codegraph_query": return await _query(args)
|
|
146
|
-
if name == "codegraph_explain": return await
|
|
130
|
+
if name == "codegraph_explain": return await _query(args)
|
|
147
131
|
if name == "codegraph_report": return await _report(args)
|
|
148
132
|
if name == "codegraph_nodes": return await _nodes(args)
|
|
149
133
|
if name == "codegraph_path": return await _path(args)
|
|
@@ -223,19 +207,8 @@ async def _build(args: dict) -> dict:
|
|
|
223
207
|
|
|
224
208
|
# ── Query / Report / Nodes / Path ─────────────────────────────────────────────
|
|
225
209
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if not graph_dict:
|
|
229
|
-
raise ValueError("No graph found. Run codegraph_build first.")
|
|
230
|
-
return graph_answer(args["question"], graph_dict, token_budget=args.get("token_budget", 2000))
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
async def _explain(args: dict) -> dict:
|
|
234
|
-
graph_dict = load_graph(args["path"])
|
|
235
|
-
if not graph_dict:
|
|
236
|
-
raise ValueError("No graph found. Run codegraph_build first.")
|
|
237
|
-
|
|
238
|
-
query = args["node"].lower()
|
|
210
|
+
def _explain_node(node_name: str, graph_dict: dict) -> dict:
|
|
211
|
+
query = node_name.lower()
|
|
239
212
|
nodes = graph_dict.get("nodes", [])
|
|
240
213
|
edges = graph_dict.get("edges", [])
|
|
241
214
|
|
|
@@ -244,8 +217,8 @@ async def _explain(args: dict) -> dict:
|
|
|
244
217
|
match = next((n for n in nodes if query in n.get("name", "").lower()), None)
|
|
245
218
|
if not match:
|
|
246
219
|
candidates = [n["name"] for n in nodes if query in n.get("id", "").lower()]
|
|
247
|
-
return {"found": False, "query":
|
|
248
|
-
"message": f"No node matching '{
|
|
220
|
+
return {"found": False, "query": node_name,
|
|
221
|
+
"message": f"No node matching '{node_name}'.",
|
|
249
222
|
"suggestions": candidates[:10]}
|
|
250
223
|
|
|
251
224
|
nid = match["id"]
|
|
@@ -254,13 +227,13 @@ async def _explain(args: dict) -> dict:
|
|
|
254
227
|
if e.get("from") == nid:
|
|
255
228
|
t = next((n for n in nodes if n.get("id") == e.get("to")), None)
|
|
256
229
|
depends_on.append({"name": t["name"] if t else e["to"],
|
|
257
|
-
"file": t.get("file","") if t else "",
|
|
258
|
-
"relation": e.get("relation","→")})
|
|
230
|
+
"file": t.get("file", "") if t else "",
|
|
231
|
+
"relation": e.get("relation", "→")})
|
|
259
232
|
elif e.get("to") == nid:
|
|
260
233
|
s = next((n for n in nodes if n.get("id") == e.get("from")), None)
|
|
261
234
|
used_by.append({"name": s["name"] if s else e["from"],
|
|
262
|
-
"file": s.get("file","") if s else "",
|
|
263
|
-
"relation": e.get("relation","→")})
|
|
235
|
+
"file": s.get("file", "") if s else "",
|
|
236
|
+
"relation": e.get("relation", "→")})
|
|
264
237
|
|
|
265
238
|
return {
|
|
266
239
|
"found": True,
|
|
@@ -270,10 +243,28 @@ async def _explain(args: dict) -> dict:
|
|
|
270
243
|
"description": match.get("description") or None,
|
|
271
244
|
"depends_on": depends_on[:20],
|
|
272
245
|
"used_by": used_by[:20],
|
|
273
|
-
"hint": None,
|
|
274
246
|
}
|
|
275
247
|
|
|
276
248
|
|
|
249
|
+
async def _query(args: dict) -> dict:
|
|
250
|
+
graph_dict = load_graph(args["path"])
|
|
251
|
+
if not graph_dict:
|
|
252
|
+
raise ValueError("No graph found. Run codegraph_build first.")
|
|
253
|
+
|
|
254
|
+
question = args.get("question")
|
|
255
|
+
node_name = args.get("node")
|
|
256
|
+
|
|
257
|
+
if not question and not node_name:
|
|
258
|
+
raise ValueError("Provide at least one of: question, node")
|
|
259
|
+
|
|
260
|
+
result = {}
|
|
261
|
+
if node_name:
|
|
262
|
+
result["node"] = _explain_node(node_name, graph_dict)
|
|
263
|
+
if question:
|
|
264
|
+
result["query"] = graph_answer(question, graph_dict, token_budget=args.get("token_budget", 2000))
|
|
265
|
+
return result
|
|
266
|
+
|
|
267
|
+
|
|
277
268
|
async def _report(args: dict) -> dict:
|
|
278
269
|
report_path = Path(args["path"]) / "codegraph-cache" / "CODEGRAPH_REPORT.md"
|
|
279
270
|
if report_path.exists():
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Persistent AI memory + codebase knowledge graph MCP server. Works across Claude Code, Cursor, Gemini CLI, Codex, Windsurf, VS Code Copilot, Claude.ai, and ChatGPT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"context-mcp": "./src/index.js",
|
|
8
8
|
"context-mcp-server": "./src/index.js",
|
|
9
9
|
"context-mcp-http": "./src/http.js",
|
|
10
|
-
"ctx": "./src/cli.js"
|
|
10
|
+
"ctx": "./src/cli.js",
|
|
11
|
+
"context": "./src/cli.js"
|
|
11
12
|
},
|
|
12
13
|
"scripts": {
|
|
13
14
|
"mcp": "node src/index.js",
|
package/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codegraph-mcp"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.7"
|
|
8
8
|
description = "Codebase knowledge graph MCP server — AST extraction, graph queries, community detection"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
Binary file
|
package/src/cli.js
CHANGED
|
@@ -107,8 +107,8 @@ function printSection(title, meta = '') {
|
|
|
107
107
|
function printUsage() {
|
|
108
108
|
printBanner();
|
|
109
109
|
|
|
110
|
-
// Terminal commands (ctx ...)
|
|
111
|
-
printSection('Terminal commands', 'run from your shell');
|
|
110
|
+
// Terminal commands (ctx / context ...)
|
|
111
|
+
printSection('Terminal commands', 'run from your shell (ctx … or context …)');
|
|
112
112
|
const cmd = (c, desc) => console.log(` ${accent(c.padEnd(40))} ${faint(desc)}`);
|
|
113
113
|
cmd('ctx', 'open interactive mode');
|
|
114
114
|
cmd('ctx list [project]', 'list entries + discussions + graphs');
|
|
@@ -131,8 +131,8 @@ function printUsage() {
|
|
|
131
131
|
cmd('ctx help', 'show this screen');
|
|
132
132
|
console.log('');
|
|
133
133
|
|
|
134
|
-
// Interactive mode commands (no
|
|
135
|
-
printSection('Interactive mode', 'type these inside
|
|
134
|
+
// Interactive mode commands (no prefix needed)
|
|
135
|
+
printSection('Interactive mode', 'type these inside the UI — no "ctx" prefix needed');
|
|
136
136
|
const icmd = (c, desc) => console.log(` ${accent(c.padEnd(40))} ${faint(desc)}`);
|
|
137
137
|
icmd('list [project]', 'list entries');
|
|
138
138
|
icmd('search <query>', 'search context');
|
|
@@ -429,7 +429,7 @@ function cmdBenchmark() {
|
|
|
429
429
|
printSection('Benchmark', 'real token savings');
|
|
430
430
|
|
|
431
431
|
const RESUME_LIMIT = 15;
|
|
432
|
-
const COMPACT_AT =
|
|
432
|
+
const COMPACT_AT = 20;
|
|
433
433
|
|
|
434
434
|
// ── Measure entry sizes from actual stored data ──────────────────────────────
|
|
435
435
|
const allEntries = getContext({ limit: 500, compact: false });
|
package/src/db.js
CHANGED
|
@@ -312,10 +312,15 @@ export function updateContext({ id, content, title, tags, type, status, files, c
|
|
|
312
312
|
* @param {Object} opts
|
|
313
313
|
* @param {boolean} opts.compact - If true, returns previews instead of full content (saves tokens)
|
|
314
314
|
*/
|
|
315
|
-
export function getContext({ project, tags, limit = 20, compact = false } = {}) {
|
|
315
|
+
export function getContext({ project, tags, limit = 20, compact = false, ids } = {}) {
|
|
316
316
|
refreshFromDisk();
|
|
317
317
|
const store = load();
|
|
318
318
|
let results = store.contexts;
|
|
319
|
+
if (ids && ids.length) {
|
|
320
|
+
const idSet = new Set(ids);
|
|
321
|
+
results = results.filter(c => idSet.has(c.id));
|
|
322
|
+
return compact ? results.map(compactEntry) : results;
|
|
323
|
+
}
|
|
319
324
|
if (project) results = results.filter(c => c.project === project || c.project === 'global');
|
|
320
325
|
if (tags && tags.length) {
|
|
321
326
|
const tagList = Array.isArray(tags) ? tags : tags.split(',').map(t => t.trim());
|
|
@@ -348,12 +353,14 @@ export function searchContext({ query, project, limit = 10, compact = false }) {
|
|
|
348
353
|
return compact ? sliced.map(compactEntry) : sliced;
|
|
349
354
|
}
|
|
350
355
|
|
|
351
|
-
export function deleteContext({ id }) {
|
|
356
|
+
export function deleteContext({ id, ids }) {
|
|
352
357
|
refreshFromDisk();
|
|
353
358
|
const store = load();
|
|
354
359
|
const before = store.contexts.length;
|
|
355
|
-
const
|
|
356
|
-
|
|
360
|
+
const idSet = new Set(ids && ids.length ? ids : (id ? [id] : []));
|
|
361
|
+
if (!idSet.size) return { deleted: 0 };
|
|
362
|
+
const removed = store.contexts.filter(c => idSet.has(c.id));
|
|
363
|
+
store.contexts = store.contexts.filter(c => !idSet.has(c.id));
|
|
357
364
|
if (store.contexts.length < before) {
|
|
358
365
|
for (const entry of removed) {
|
|
359
366
|
_deletedContextIds.add(entry.id);
|
|
@@ -89,9 +89,8 @@ Parses codebase into AST graph via tree-sitter. Extracts functions, classes, imp
|
|
|
89
89
|
|
|
90
90
|
### Query (free, instant, forever)
|
|
91
91
|
```
|
|
92
|
-
codegraph_query(path, question)
|
|
93
|
-
|
|
94
|
-
codegraph_path(path, from, to) → shortest path between two concepts
|
|
92
|
+
codegraph_query(path, question?, node?) → structural question OR single-node lookup (or both in one call)
|
|
93
|
+
codegraph_path(path, from, to) → shortest path between two concepts
|
|
95
94
|
codegraph_nodes(path, type) → list all nodes of a type
|
|
96
95
|
codegraph_report(path) → god nodes, clusters, surprises
|
|
97
96
|
```
|
package/src/tools/codegraph.js
CHANGED
|
@@ -40,92 +40,23 @@ export const definitions = [
|
|
|
40
40
|
required: ['path'],
|
|
41
41
|
},
|
|
42
42
|
},
|
|
43
|
-
{
|
|
44
|
-
name: 'codegraph_extract',
|
|
45
|
-
description:
|
|
46
|
-
'Return raw content of changed code and doc/PDF files so the AI can write descriptions. ' +
|
|
47
|
-
'Code files: lists existing AST nodes — AI writes a description for each. ' +
|
|
48
|
-
'Doc files: AI extracts new concept nodes. ' +
|
|
49
|
-
'Call after codegraph_build, then call codegraph_add_nodes with results. ' +
|
|
50
|
-
'Pass force:true to re-enrich all files (not just changed ones).',
|
|
51
|
-
inputSchema: {
|
|
52
|
-
type: 'object',
|
|
53
|
-
properties: {
|
|
54
|
-
path: { type: 'string', description: 'Project root (same as codegraph_build)' },
|
|
55
|
-
limit: { type: 'integer', description: 'Max files to return per call (default 10)' },
|
|
56
|
-
force: { type: 'boolean', description: 'Return all files, not just changed (for re-enrichment)' },
|
|
57
|
-
},
|
|
58
|
-
required: ['path'],
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: 'codegraph_add_nodes',
|
|
63
|
-
description:
|
|
64
|
-
'Add concept nodes extracted by the AI into the graph. ' +
|
|
65
|
-
'Call after reading codegraph_extract output. ' +
|
|
66
|
-
'Each node: name, type, file, and optionally description and relations.',
|
|
67
|
-
inputSchema: {
|
|
68
|
-
type: 'object',
|
|
69
|
-
properties: {
|
|
70
|
-
path: { type: 'string', description: 'Project root' },
|
|
71
|
-
nodes: {
|
|
72
|
-
type: 'array',
|
|
73
|
-
description: 'Concept nodes to add',
|
|
74
|
-
items: {
|
|
75
|
-
type: 'object',
|
|
76
|
-
properties: {
|
|
77
|
-
name: { type: 'string' },
|
|
78
|
-
type: { type: 'string', description: 'class|function|concept|service|decision|requirement' },
|
|
79
|
-
file: { type: 'string', description: 'Relative file path this concept came from' },
|
|
80
|
-
description: { type: 'string' },
|
|
81
|
-
relations: {
|
|
82
|
-
type: 'array',
|
|
83
|
-
items: {
|
|
84
|
-
type: 'object',
|
|
85
|
-
properties: {
|
|
86
|
-
name: { type: 'string' },
|
|
87
|
-
relation: { type: 'string', description: 'depends-on|uses|implements|defines|documents' },
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
required: ['name', 'type', 'file'],
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
required: ['path', 'nodes'],
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
43
|
{
|
|
100
44
|
name: 'codegraph_query',
|
|
101
45
|
description:
|
|
102
|
-
'Ask a structural
|
|
103
|
-
'
|
|
104
|
-
'
|
|
105
|
-
'
|
|
46
|
+
'Ask a structural question about the codebase OR look up a specific node by name — or both in one call. ' +
|
|
47
|
+
'Pass `question` for natural-language traversal: "what does module X depend on?", "what calls function Y?". ' +
|
|
48
|
+
'Pass `node` for fast single-node lookup: returns type, file, depends_on, used_by. ' +
|
|
49
|
+
'Pass both to get node detail + surrounding graph context together. ' +
|
|
50
|
+
'Returns structured text within token_budget. Use before reading any files.',
|
|
106
51
|
inputSchema: {
|
|
107
52
|
type: 'object',
|
|
108
53
|
properties: {
|
|
109
54
|
path: { type: 'string', description: 'Project root' },
|
|
110
|
-
question: { type: 'string', description: 'Natural language question' },
|
|
55
|
+
question: { type: 'string', description: 'Natural language question about the codebase' },
|
|
56
|
+
node: { type: 'string', description: 'Node name or partial name to look up (type, file, deps, callers)' },
|
|
111
57
|
token_budget: { type: 'integer', description: 'Max tokens in response (default 2000)' },
|
|
112
58
|
},
|
|
113
|
-
required: ['path'
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: 'codegraph_explain',
|
|
118
|
-
description:
|
|
119
|
-
'Look up a node by name — returns description, type, file, and direct neighbors (depends_on + used_by). ' +
|
|
120
|
-
'Use to understand what a specific function/class/module does and how it connects. ' +
|
|
121
|
-
'Descriptions are AI-written via codegraph_add_nodes.',
|
|
122
|
-
inputSchema: {
|
|
123
|
-
type: 'object',
|
|
124
|
-
properties: {
|
|
125
|
-
path: { type: 'string', description: 'Project root' },
|
|
126
|
-
node: { type: 'string', description: 'Node name or partial name' },
|
|
127
|
-
},
|
|
128
|
-
required: ['path', 'node'],
|
|
59
|
+
required: ['path'],
|
|
129
60
|
},
|
|
130
61
|
},
|
|
131
62
|
{
|
package/src/tools/context.js
CHANGED
|
@@ -28,9 +28,9 @@ export const definition = {
|
|
|
28
28
|
`Factual memory — record what happened, what was decided, what broke, what was built.\n` +
|
|
29
29
|
`• "resume" — START HERE every conversation. Loads recent context, active discussions, and graph status for a project.\n` +
|
|
30
30
|
`• "save" — Store a note, decision, bug, or code snippet. Auto-deduplicates.\n` +
|
|
31
|
-
`• "get" — Load
|
|
31
|
+
`• "get" — Load entries. Pass id/ids to fetch specific ones, or project/tags/limit for recent.\n` +
|
|
32
32
|
`• "update" — Edit an existing entry by id (any field).\n` +
|
|
33
|
-
`• "delete" — Remove
|
|
33
|
+
`• "delete" — Remove one entry (id) or multiple at once (ids: [...]).\n` +
|
|
34
34
|
`• "list_projects"— Show all projects and entry counts.`,
|
|
35
35
|
inputSchema: {
|
|
36
36
|
type: 'object',
|
|
@@ -50,7 +50,8 @@ export const definition = {
|
|
|
50
50
|
expiresAt: { type: 'string' },
|
|
51
51
|
limit: { type: 'number' },
|
|
52
52
|
includeArchived: { type: 'boolean' },
|
|
53
|
-
id: { type: 'string' },
|
|
53
|
+
id: { type: 'string', description: 'Single entry ID (get/update/delete)' },
|
|
54
|
+
ids: { type: 'array', items: { type: 'string' }, description: 'Multiple entry IDs — fetch or delete several at once' },
|
|
54
55
|
},
|
|
55
56
|
required: ['action'],
|
|
56
57
|
},
|
|
@@ -189,8 +190,20 @@ export async function handle(args, state) {
|
|
|
189
190
|
|
|
190
191
|
case 'get': {
|
|
191
192
|
if (!args.project && state.sessionProject) args = { ...args, project: state.sessionProject };
|
|
192
|
-
archiveExpired(args.project);
|
|
193
193
|
const includeArchived = args.includeArchived === true;
|
|
194
|
+
|
|
195
|
+
// Fetch by specific ID(s) — bypass project/tag/limit filters
|
|
196
|
+
const ids = args.ids || (args.id ? [args.id] : null);
|
|
197
|
+
if (ids) {
|
|
198
|
+
const entries = getContext({ ids, compact: false })
|
|
199
|
+
.filter(e => includeArchived || e.status !== 'archived');
|
|
200
|
+
return {
|
|
201
|
+
entries, count: entries.length,
|
|
202
|
+
message: entries.length ? `Found ${entries.length} entries.` : 'No entries found for given IDs.',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
archiveExpired(args.project);
|
|
194
207
|
let entries = getContext({ project: args.project, tags: args.tags, limit: args.limit, compact: true });
|
|
195
208
|
if (!includeArchived) entries = entries.filter(e => e.status !== 'archived');
|
|
196
209
|
const fullEntries = entries.length > 10
|
|
@@ -216,8 +229,9 @@ export async function handle(args, state) {
|
|
|
216
229
|
}
|
|
217
230
|
|
|
218
231
|
case 'delete': {
|
|
219
|
-
if (!args.id) throw new Error('id is required for delete');
|
|
220
|
-
|
|
232
|
+
if (!args.id && !args.ids) throw new Error('id or ids is required for delete');
|
|
233
|
+
const result = deleteContext(args);
|
|
234
|
+
return { ...result, message: `Deleted ${result.deleted} entr${result.deleted === 1 ? 'y' : 'ies'}.` };
|
|
221
235
|
}
|
|
222
236
|
|
|
223
237
|
case 'list_projects': {
|
package/uv.lock
CHANGED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
doc_extractor.py — extract plain text from doc and PDF files.
|
|
3
|
-
PDF extraction uses pymupdf if installed; falls back to label-only otherwise.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def extract_text(path: str) -> str:
|
|
10
|
-
"""Return text content of a doc/PDF file. Truncated at DOC_MAX_CHARS."""
|
|
11
|
-
from ..config import DOC_MAX_CHARS
|
|
12
|
-
if path.lower().endswith(".pdf"):
|
|
13
|
-
return _extract_pdf(path, DOC_MAX_CHARS)
|
|
14
|
-
try:
|
|
15
|
-
return Path(path).read_text(encoding="utf-8", errors="replace")[:DOC_MAX_CHARS]
|
|
16
|
-
except OSError:
|
|
17
|
-
return ""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _extract_pdf(path: str, max_chars: int) -> str:
|
|
21
|
-
try:
|
|
22
|
-
import pymupdf # optional dep
|
|
23
|
-
doc = pymupdf.open(path)
|
|
24
|
-
parts = []
|
|
25
|
-
for page in doc:
|
|
26
|
-
parts.append(page.get_text())
|
|
27
|
-
if sum(len(p) for p in parts) >= max_chars:
|
|
28
|
-
break
|
|
29
|
-
doc.close()
|
|
30
|
-
return "".join(parts)[:max_chars]
|
|
31
|
-
except ImportError:
|
|
32
|
-
return f"[PDF: {Path(path).name} — install pymupdf to extract text]"
|
|
33
|
-
except Exception:
|
|
34
|
-
return ""
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
image_extractor.py — encode images as base64 for AI vision.
|
|
3
|
-
No external deps — stdlib only.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import base64
|
|
7
|
-
import mimetypes
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def extract_image_b64(path: str) -> dict | None:
|
|
12
|
-
"""Return {"data": base64_str, "media_type": "image/png"} or None on failure."""
|
|
13
|
-
try:
|
|
14
|
-
data = Path(path).read_bytes()
|
|
15
|
-
media_type = mimetypes.guess_type(path)[0] or "image/png"
|
|
16
|
-
return {"data": base64.b64encode(data).decode(), "media_type": media_type}
|
|
17
|
-
except OSError:
|
|
18
|
-
return None
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def extract_svg_text(path: str) -> str:
|
|
22
|
-
"""Return SVG file as plain text (SVGs are XML — readable as-is)."""
|
|
23
|
-
try:
|
|
24
|
-
return Path(path).read_text(encoding="utf-8", errors="replace")[:4000]
|
|
25
|
-
except OSError:
|
|
26
|
-
return ""
|