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 CHANGED
@@ -1,31 +1,35 @@
1
- # context-mcp
1
+ <p align="center">
2
+ <img src="src/assests/main.png" alt="context-mcp" />
3
+ </p>
2
4
 
3
- Persistent memory and codebase knowledge graph for AI coding assistants — delivered as a single MCP server.
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
- One shared context store. Works 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. Your memory follows the project, not the tool.
12
+ Persistent memory and codebase knowledge graph for AI coding assistants delivered as a single MCP server.
6
13
 
7
- **6 AI platforms** · **6 IDEs** · **16 programming languages** (Python, JS, TS, Go, Rust, Java, Kotlin, C, C++, C#, Ruby, PHP, Swift, Lua, and more) · tree-sitter AST parsing with regex fallback
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. You explain the same things.
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. A codebase with 50 files means the AI either reads all of them every time (burning thousands of tokens) or misses context and gives wrong answers.
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 context-mcp Solves
26
+ ## What It Solves
20
27
 
21
- **1. You lose context between conversations.**
22
- AI assistants have no memory. Every new chat is a blank slate. context-mcp gives the AI a persistent store of decisions, bugs, notes, and architecture — loaded automatically at conversation start.
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
- **2. Context is siloed to one tool.**
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
- That's it. One command installs everything the MCP server, HTTP server, and `ctx` CLI.
42
+ Requires Node.js 18. Installs `context-mcp`, `context-mcp-http`, and the `ctx` CLI.
39
43
 
40
- Then run from your project root:
44
+ **ContextGraph requires [uv](https://docs.astral.sh/uv/)** (Python runner). Memory tools work without it.
41
45
 
42
46
  ```bash
43
- ctx install --all
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
- This writes MCP config + AI instruction files for every platform **and** automatically sets up the Python codegraph environment if [uv](https://docs.astral.sh/uv/) is installed.
54
+ ---
47
55
 
48
- > **CodeGraph requires uv.** Install it first if you want graph features:
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
- Requires Node.js 18.
58
+ Run from your project root:
56
59
 
57
- Installs three commands:
60
+ ```bash
61
+ ctx install --initial
62
+ ```
58
63
 
59
- | Command | What it runs |
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
- ## Platform Setup
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
- Run from your project root. Each command writes the MCP config file and AI instruction file for that platform, then checks for uv and sets up the Python codegraph environment.
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
- ```json
92
- {
93
- "mcpServers": {
94
- "context-mcp": {
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
- ### Cursor
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
- ```json
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
- ### VS Code Copilot
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
- `ctx install --vscode` writes:
128
- - `.vscode/mcp.json` MCP server config
129
- - `CLAUDE.md` instruction file
107
+ # Delete
108
+ ctx delete <id-prefix> # delete one entry
109
+ ctx delete project <name> # delete all entries for a project
130
110
 
131
- Manual config — add to `.vscode/mcp.json`:
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
- ```json
134
- {
135
- "servers": {
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
- ### Gemini CLI
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
- Manual config add to `.gemini/settings.json`:
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
- ### Windsurf
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
- ### Claude.ai / ChatGPT (HTTP mode)
137
+ ### Memory
207
138
 
208
- Web-based clients connect over HTTP with OAuth 2.0. Use `ctx online` to start the HTTP server.
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
- **Step 1 — Start the server:**
146
+ ### ContextGraph
211
147
 
212
- ```bash
213
- ctx online
214
- ```
148
+ > Also called **CodeGraph**. MCP tools use the `codegraph_*` prefix — both names mean the same thing.
215
149
 
216
- Starts the server in the background, shows your OAuth credentials, and prints the endpoint URL. Safe to re-run — won't start a second copy.
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
- **Step 2 Add as a remote MCP connector:**
156
+ Parses codebase via tree-sitter AST (16 languages, regex fallback). Extracts functions, classes, imports, call edges. Saved to `~/.context-mcp/`.
230
157
 
231
- 1. Go to Claude.ai Settings → Integrations → Add MCP Connector
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
- ## Path Sandboxing (Security)
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
- The root is stored permanently with the project. Any file or git operation outside that directory is rejected. This applies to all HTTP-connected clients (Claude.ai, ChatGPT) they can only access files within the registered project root.
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
- ## CLI Reference
171
+ Available to HTTP-connected clients (Claude.ai, ChatGPT). Local AI clients use their native IDE tools.
256
172
 
257
- ```bash
258
- ctx # open interactive mode
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
- # Tools
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 [options]
183
+ context-mcp [--data-dir <path>]
299
184
 
300
- Options:
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
- ### `context-mcp-http` (HTTP + OAuth)
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
- Config lives at `~/.context-mcp/contextconfig.json` — auto-created on first run:
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 — keep private |
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 shown in `ctx online` output |
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 beyond `claude.ai` and `localhost` |
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
 
@@ -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 — fetch details about any part of the codebase via natural language
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
- "Fetch details about any part of the codebase using a natural language question. "
60
- "Searches the knowledge graph instant, no API call. "
61
- "Use for: finding functions, classes, files, understanding what exists, "
62
- "what a module contains, what calls what, what imports what. "
63
- "NOT for: bug investigation or tracing unexpected behavior read the file for that."
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", "question"],
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 _explain(args)
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
- async def _query(args: dict) -> dict:
227
- graph_dict = load_graph(args["path"])
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": args["node"],
248
- "message": f"No node matching '{args['node']}'.",
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.6",
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.6"
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 ctx prefix)
135
- printSection('Interactive mode', 'type these inside ctx (no "ctx" prefix)');
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 = 50;
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 removed = store.contexts.filter(c => c.id === id);
356
- store.contexts = store.contexts.filter(c => c.id !== id);
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) find functions, classes, files, dependencies, callers
93
- codegraph_explain(path, node) one node: type, file, depends_on, used_by
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
  ```
@@ -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/dependency question about the codebase. ' +
103
- 'Pure graph traversal returns NODE/EDGE structured text truncated to token_budget. ' +
104
- 'Good for: "what does module X depend on?", "what calls function Y?", "what is the path from A to B?". ' +
105
- 'NOT for: bug investigation, logic errors, or understanding what code actually does — read the file directly for those.',
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', 'question'],
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
  {
@@ -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 recent entries (compact previews). Auto-digests when large.\n` +
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 an entry by id.\n` +
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
- return deleteContext(args);
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
@@ -168,7 +168,7 @@ wheels = [
168
168
 
169
169
  [[package]]
170
170
  name = "codegraph-mcp"
171
- version = "1.0.5"
171
+ version = "1.0.6"
172
172
  source = { editable = "." }
173
173
  dependencies = [
174
174
  { name = "mcp" },
@@ -1,8 +0,0 @@
1
- """
2
- audio_extractor.py — audio files are not supported without faster-whisper.
3
- Stub kept so imports don't break.
4
- """
5
-
6
-
7
- def transcribe(_path: str) -> str:
8
- return ""
@@ -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 ""