context-mcp-server 1.0.1 → 1.0.3

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.
@@ -9,7 +9,7 @@ description: >
9
9
  # Context-MCP — Claude Usage Guide
10
10
 
11
11
  Persistent memory + codebase knowledge graph for Claude.
12
- Every conversation starts with `context.resume`. Every structural question uses `codegraph_query`. Files only read for bugs/logic.
12
+ Every conversation starts with `context.resume`. Every codebase question uses `codegraph_query`. Files only read for bugs/logic.
13
13
 
14
14
  ---
15
15
 
@@ -45,42 +45,31 @@ Always pass `project`. Auto-compact fires at >50 entries — oldest summarized a
45
45
 
46
46
  ## 3. CodeGraph Pipeline
47
47
 
48
- ### Step 1 — Build (once per project, free)
48
+ ### Step 1 — Build (once per project, fast, local)
49
49
  ```
50
50
  codegraph_build(path)
51
- → AST: code files → functions, classes, imports, edges
52
- → Config: .yaml .toml .sql → schema nodes
53
- → Docs: .md .txt .pdf → pending (no content yet)
54
51
  ```
55
- Saves graph to `~/.context-mcp/graphs.json`. Visible on `context.resume`.
52
+ - Parses codebase into AST graph using tree-sitter (regex fallback for unsupported languages)
53
+ - Extracts functions, classes, imports, call edges for all code files
54
+ - Build files (package.json, pyproject.toml, go.mod, Dockerfile, etc.) get a single metadata node
55
+ - Saves graph to `~/.context-mcp/graphs.json`. Visible on `context.resume`.
56
56
 
57
- ### Step 2 — Enrich (one-time cost per file)
57
+ ### Step 2 — Query (free, instant, forever)
58
58
  ```
59
- codegraph_extract(path)
60
- → returns changed code files (with existing node list) + doc files (raw text)
61
-
62
- For each code file: write description for each node listed in existing_nodes
63
- For each doc file: extract concept nodes + relationships
64
-
65
- codegraph_add_nodes(path, nodes)
66
- → stores descriptions in semantic cache (never overwritten by rebuild)
67
- ```
68
-
69
- ### Step 3 — Query (free, instant forever)
70
- ```
71
- codegraph_query(path, question) → NODE/EDGE subgraph, token_budget param (default 2000)
72
- codegraph_explain(path, node) → one node: description + depends_on + used_by
73
- codegraph_path(path, from, to) → shortest path between two concepts
74
- codegraph_nodes(path, type) → list all nodes of a type
75
- codegraph_report(path) → god nodes, clusters, surprises
59
+ codegraph_query(path, question) → fetch any details about the codebase
60
+ codegraph_explain(path, node) one node: type, file, depends_on, used_by
61
+ codegraph_path(path, from, to) → shortest path between two concepts
62
+ codegraph_nodes(path, type) → list all nodes of a type
63
+ codegraph_report(path) → god nodes, clusters, surprises
76
64
  ```
77
65
 
78
66
  ---
79
67
 
80
68
  ## 4. Graph vs File
81
69
 
82
- **Graph** — structural questions: dependencies, callers, imports, paths between concepts.
83
- **File** — bugs, logic inside a function, tracing unexpected behavior.
70
+ **Graph** — use for any question about what exists in the codebase: finding functions, classes, files, understanding what a module contains, dependencies, callers, imports, paths between concepts.
71
+
72
+ **File** — use for bugs, logic inside a specific function, tracing unexpected behavior.
84
73
 
85
74
  ---
86
75
 
@@ -91,4 +80,3 @@ codegraph_report(path) → god nodes, clusters, surprises
91
80
  3. **`search` before asking** — if user references past work, find it first
92
81
  4. **`codegraph_query` before reading files** — graph is faster and cheaper
93
82
  5. **Read files for bugs/logic** — graph is structure only, not behavior
94
- 6. **Enrich once** — run extract → add_nodes once per project; descriptions persist forever
@@ -1,7 +1,7 @@
1
1
  # Context-MCP — Gemini CLI Usage Guide
2
2
 
3
3
  Persistent memory + codebase knowledge graph.
4
- Every conversation starts with `context.resume`. Every structural question uses `codegraph_query`. Files only read for bugs/logic.
4
+ Every conversation starts with `context.resume`. Every codebase question uses `codegraph_query`. Files only read for bugs/logic.
5
5
 
6
6
  ---
7
7
 
@@ -37,21 +37,15 @@ Always pass `project`. Auto-compact fires at >50 entries.
37
37
 
38
38
  ## 3. CodeGraph Pipeline
39
39
 
40
- ### Step 1 — Build (once, free)
40
+ ### Step 1 — Build (once, fast, local)
41
41
  ```
42
42
  codegraph_build(path) → AST graph: functions, classes, imports, edges
43
43
  ```
44
44
 
45
- ### Step 2 — Enrich (one-time per file)
45
+ ### Step 2 — Query (free, instant)
46
46
  ```
47
- codegraph_extract(path) file content + node list
48
- codegraph_add_nodes(path, nodes) semantic descriptions (permanent cache)
49
- ```
50
-
51
- ### Step 3 — Query (free, instant)
52
- ```
53
- codegraph_query(path, question) → NODE/EDGE subgraph (token_budget default 2000)
54
- codegraph_explain(path, node) → single node + neighbors
47
+ codegraph_query(path, question) fetch any details about the codebase
48
+ codegraph_explain(path, node) single node: type, file, connections
55
49
  codegraph_path(path, from, to) → shortest path
56
50
  codegraph_nodes(path, type) → list nodes by type
57
51
  codegraph_report(path) → full graph analysis
@@ -61,8 +55,8 @@ codegraph_report(path) → full graph analysis
61
55
 
62
56
  ## 4. Graph vs File
63
57
 
64
- **Graph** — structural questions: dependencies, callers, imports.
65
- **File** — bugs, logic, tracing behavior.
58
+ **Graph** — use for any question about what exists: finding functions, classes, files, dependencies, callers, imports, paths between concepts.
59
+ **File** — bugs, logic, tracing unexpected behavior.
66
60
 
67
61
  ---
68
62
 
@@ -73,4 +67,3 @@ codegraph_report(path) → full graph analysis
73
67
  3. **`search` before asking** — if user references past work, find it first
74
68
  4. **`codegraph_query` before reading files** — graph is faster and cheaper
75
69
  5. **Read files for bugs/logic** — graph is structure only, not behavior
76
- 6. **Enrich once** — descriptions persist forever
@@ -1,9 +1,19 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { guardPath } from '../guard.js';
1
3
  import {
2
4
  saveContext, updateContext, getContext, deleteContext,
3
5
  listProjects, findDuplicate, archiveExpired, linkContextToDiscussion,
4
6
  listDiscussions, listGraphs, countContext, shouldCompact, compactProject,
5
7
  ensureProject, getProjectRoot,
6
8
  } from '../db.js';
9
+
10
+ function detectGitRoot() {
11
+ try {
12
+ return execFileSync('git', ['rev-parse', '--show-toplevel'], {
13
+ cwd: process.cwd(), encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
14
+ }).trim();
15
+ } catch { return null; }
16
+ }
7
17
  import { summarizeEntries } from '../summarizer.js';
8
18
  import { fireAutoLink } from '../hooks/autoLink.js';
9
19
 
@@ -71,10 +81,12 @@ export async function handle(args, state) {
71
81
  // Set project on state so autoLink works for subsequent saves
72
82
  if (proj) state.sessionProject = proj;
73
83
 
74
- // Store rootPath with project (first time only) and load it onto session state
75
- if (proj) ensureProject(proj, args.rootPath || undefined);
84
+ // Store rootPath with project (first time only) and load it onto session state.
85
+ // Auto-detect from git if neither provided nor previously stored.
76
86
  const storedRoot = proj ? getProjectRoot(proj) : null;
77
- state.projectRootPath = args.rootPath || storedRoot || null;
87
+ const resolvedRoot = args.rootPath || storedRoot || detectGitRoot() || null;
88
+ if (proj) ensureProject(proj, resolvedRoot || undefined);
89
+ state.projectRootPath = resolvedRoot;
78
90
 
79
91
  const entries = getContext({ project: proj, limit: 15, compact: true })
80
92
  .filter(e => e.status !== 'archived');
@@ -105,6 +117,9 @@ export async function handle(args, state) {
105
117
  stats: { totalEntries, projects: listProjects().length },
106
118
  message: `Loaded ${totalEntries} entries for project "${proj || 'global'}".${discussions.length === 1 ? ` Auto-linked to discussion "${discussions[0].name}".` : ''}`,
107
119
  rootPath: state.projectRootPath || undefined,
120
+ sandbox: state.projectRootPath
121
+ ? `All file and git operations are sandboxed to: ${state.projectRootPath} — do not use paths outside this root.`
122
+ : 'No project root configured — pass rootPath to restrict file/git access to a directory.',
108
123
  hint: graphStatus.built
109
124
  ? `Graph ready (${graphStatus.nodes} nodes). Use codegraph_query for structural questions.`
110
125
  : 'No graph built yet. Call codegraph_build on the project root to enable graph queries.',
@@ -114,6 +129,27 @@ export async function handle(args, state) {
114
129
  case 'save': {
115
130
  if (!args.content) throw new Error('content is required for save');
116
131
  if (!args.project && state.sessionProject) args = { ...args, project: state.sessionProject };
132
+ // Auto-detect and store project root if not yet configured
133
+ if (args.project) {
134
+ const existing = getProjectRoot(args.project);
135
+ if (!existing) {
136
+ const detected = state.projectRootPath || detectGitRoot();
137
+ if (detected) {
138
+ ensureProject(args.project, detected);
139
+ if (!state.projectRootPath) state.projectRootPath = detected;
140
+ }
141
+ }
142
+ }
143
+ // Validate file paths in files[] and codeRefs[] stay within project root
144
+ if (state.projectRootPath) {
145
+ if (Array.isArray(args.files)) {
146
+ args.files.forEach(f => { if (f.path) guardPath(f.path, state.projectRootPath); });
147
+ }
148
+ if (Array.isArray(args.codeRefs)) {
149
+ args.codeRefs.forEach(r => { if (r.file) guardPath(r.file, state.projectRootPath); });
150
+ }
151
+ }
152
+
117
153
  const dupe = findDuplicate(args.content, args.project);
118
154
  if (dupe) {
119
155
  const updated = updateContext({
@@ -20,28 +20,30 @@ function atomicWrite(filePath, data) {
20
20
  }
21
21
  }
22
22
 
23
+ const ROOT_NOTE = ' Sandboxed to project root — paths outside root are denied.';
24
+
23
25
  export const definitions = [
24
26
  {
25
27
  name: 'create_dir',
26
- description: 'Create a directory (and any missing parent directories). Safe to call if already exists.',
28
+ description: 'Create a directory (and any missing parent directories). Safe to call if already exists.' + ROOT_NOTE,
27
29
  inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
28
30
  outputSchema: { type: 'object', properties: { path: { type: 'string' }, existed: { type: 'boolean' }, message: { type: 'string' } } },
29
31
  },
30
32
  {
31
33
  name: 'list_dir',
32
- description: 'List the contents of a local directory.',
34
+ description: 'List the contents of a local directory.' + ROOT_NOTE,
33
35
  inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
34
36
  outputSchema: { type: 'object', properties: { directory: { type: 'string' }, items: { type: 'array' } } },
35
37
  },
36
38
  {
37
39
  name: 'read_file',
38
- description: 'Read the text contents of a local file.',
40
+ description: 'Read the text contents of a local file.' + ROOT_NOTE,
39
41
  inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
40
42
  outputSchema: { type: 'object', properties: { file: { type: 'string' }, content: { type: 'string' } } },
41
43
  },
42
44
  {
43
45
  name: 'write_file',
44
- description: 'Create a new file or overwrite an existing file.',
46
+ description: 'Create a new file or overwrite an existing file.' + ROOT_NOTE,
45
47
  inputSchema: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } }, required: ['path', 'content'] },
46
48
  outputSchema: { type: 'object', properties: { file: { type: 'string' }, message: { type: 'string' } } },
47
49
  },
@@ -51,7 +53,7 @@ export const definitions = [
51
53
  `Apply targeted string replacement(s) to a file.\n` +
52
54
  `Single edit: pass old_str + new_str.\n` +
53
55
  `Multi edit: pass edits:[{old_str, new_str, description?}] — atomic.\n` +
54
- `Use dry_run:true to validate without writing.`,
56
+ `Use dry_run:true to validate without writing.` + ROOT_NOTE,
55
57
  inputSchema: {
56
58
  type: 'object',
57
59
  properties: {
@@ -68,7 +70,7 @@ export const definitions = [
68
70
  },
69
71
  {
70
72
  name: 'delete_file',
71
- description: 'Delete a local file. Pass recursive:true to delete a directory and its contents.',
73
+ description: 'Delete a local file. Pass recursive:true to delete a directory and its contents.' + ROOT_NOTE,
72
74
  inputSchema: { type: 'object', properties: { path: { type: 'string' }, recursive: { type: 'boolean', description: 'Required to delete a directory. Defaults to false.' } }, required: ['path'] },
73
75
  outputSchema: { type: 'object', properties: { path: { type: 'string' }, message: { type: 'string' } } },
74
76
  },
@@ -15,69 +15,85 @@ function runGit(argArr, cwd) {
15
15
  }
16
16
  }
17
17
 
18
+ function autoDetectRoot(fromDir) {
19
+ try {
20
+ return execFileSync('git', ['rev-parse', '--show-toplevel'], {
21
+ cwd: fromDir, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
22
+ }).trim();
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
18
28
  function resolveCwd(args, state) {
19
- const raw = args.cwd ? pathResolve(args.cwd) : process.cwd();
20
- // If a project root is configured, validate cwd is within it
29
+ // Auto-detect project root on first use if not already configured.
30
+ if (!state.projectRootPath) {
31
+ const detected = autoDetectRoot(args.cwd ? pathResolve(args.cwd) : process.cwd());
32
+ if (detected) state.projectRootPath = detected;
33
+ }
34
+ const raw = args.cwd ? pathResolve(args.cwd) : (state.projectRootPath || process.cwd());
21
35
  if (state.projectRootPath) return guardPath(raw, state.projectRootPath);
22
36
  return raw;
23
37
  }
24
38
 
39
+ const ROOT_NOTE = ' All paths must be within the project root (sandboxed — access outside root is denied).';
40
+
25
41
  export const definitions = [
26
42
  {
27
43
  name: 'git_status',
28
- description: 'Show working tree status — current branch, staged, unstaged, and untracked files.',
44
+ description: 'Show working tree status — current branch, staged, unstaged, and untracked files.' + ROOT_NOTE,
29
45
  inputSchema: { type: 'object', properties: { cwd: { type: 'string' } } },
30
46
  outputSchema: { type: 'object', properties: { branch: { type: 'string' }, clean: { type: 'boolean' }, staged: { type: 'array' }, unstaged: { type: 'array' }, untracked: { type: 'array' } } },
31
47
  },
32
48
  {
33
49
  name: 'git_diff',
34
- description: 'Show file changes. Use staged:true for cached diff. Optionally scope to a path.',
50
+ description: 'Show file changes. Use staged:true for cached diff. Optionally scope to a path.' + ROOT_NOTE,
35
51
  inputSchema: { type: 'object', properties: { staged: { type: 'boolean' }, path: { type: 'string' }, cwd: { type: 'string' } } },
36
52
  outputSchema: { type: 'object', properties: { diff: { type: 'string' }, staged: { type: 'boolean' } } },
37
53
  },
38
54
  {
39
55
  name: 'git_log',
40
- description: 'Show recent commit history — hash, author, date, message.',
56
+ description: 'Show recent commit history — hash, author, date, message.' + ROOT_NOTE,
41
57
  inputSchema: { type: 'object', properties: { limit: { type: 'number' }, path: { type: 'string' }, cwd: { type: 'string' } } },
42
58
  outputSchema: { type: 'object', properties: { commits: { type: 'array' }, count: { type: 'number' } } },
43
59
  },
44
60
  {
45
61
  name: 'git_add',
46
- description: 'Stage files for commit. Pass paths:["."] to stage everything.',
62
+ description: 'Stage files for commit. Pass paths:["."] to stage everything.' + ROOT_NOTE,
47
63
  inputSchema: { type: 'object', properties: { paths: { type: 'array', items: { type: 'string' } }, cwd: { type: 'string' } }, required: ['paths'] },
48
64
  outputSchema: { type: 'object', properties: { success: { type: 'boolean' }, staged: { type: 'array' }, message: { type: 'string' } } },
49
65
  },
50
66
  {
51
67
  name: 'git_commit',
52
- description: 'Commit staged changes. Set all:true to auto-stage tracked modified files first. Auto-saves context entry.',
68
+ description: 'Commit staged changes. Set all:true to auto-stage tracked modified files first. Auto-saves context entry.' + ROOT_NOTE,
53
69
  inputSchema: { type: 'object', properties: { message: { type: 'string' }, all: { type: 'boolean' }, cwd: { type: 'string' } }, required: ['message'] },
54
70
  outputSchema: { type: 'object', properties: { success: { type: 'boolean' }, hash: { type: 'string' }, branch: { type: 'string' }, message: { type: 'string' }, files: { type: 'array' } } },
55
71
  },
56
72
  {
57
73
  name: 'git_push',
58
- description: 'Push current branch to remote.',
74
+ description: 'Push current branch to remote.' + ROOT_NOTE,
59
75
  inputSchema: { type: 'object', properties: { remote: { type: 'string' }, branch: { type: 'string' }, cwd: { type: 'string' } } },
60
76
  outputSchema: { type: 'object', properties: { success: { type: 'boolean' }, remote: { type: 'string' }, branch: { type: 'string' }, output: { type: 'string' } } },
61
77
  },
62
78
  {
63
79
  name: 'git_pull',
64
- description: 'Pull from remote and merge into current branch.',
80
+ description: 'Pull from remote and merge into current branch.' + ROOT_NOTE,
65
81
  inputSchema: { type: 'object', properties: { remote: { type: 'string' }, branch: { type: 'string' }, cwd: { type: 'string' } } },
66
82
  outputSchema: { type: 'object', properties: { success: { type: 'boolean' }, remote: { type: 'string' }, output: { type: 'string' } } },
67
83
  },
68
84
  {
69
85
  name: 'git_branch',
70
- description: 'List, create, or checkout branches.',
86
+ description: 'List, create, or checkout branches.' + ROOT_NOTE,
71
87
  inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['list', 'create', 'checkout'] }, name: { type: 'string' }, cwd: { type: 'string' } } },
72
88
  },
73
89
  {
74
90
  name: 'git_stash',
75
- description: 'Stash or restore work-in-progress changes.',
91
+ description: 'Stash or restore work-in-progress changes.' + ROOT_NOTE,
76
92
  inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['save', 'pop', 'list', 'drop'] }, message: { type: 'string' }, ref: { type: 'string' }, cwd: { type: 'string' } } },
77
93
  },
78
94
  {
79
95
  name: 'git_reset',
80
- description: 'Unstage files or reset HEAD. Use mode:file + path to restore a single file.',
96
+ description: 'Unstage files or reset HEAD. Use mode:file + path to restore a single file.' + ROOT_NOTE,
81
97
  inputSchema: { type: 'object', properties: { mode: { type: 'string', enum: ['soft', 'mixed', 'hard', 'file'] }, path: { type: 'string' }, ref: { type: 'string' }, cwd: { type: 'string' } } },
82
98
  },
83
99
  {