context-mcp-server 1.1.2 → 1.1.4

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.
Files changed (47) hide show
  1. package/README.md +2 -3
  2. package/codegraph/__pycache__/callflow_html.cpython-313.pyc +0 -0
  3. package/codegraph/__pycache__/export.cpython-313.pyc +0 -0
  4. package/codegraph/__pycache__/report.cpython-313.pyc +0 -0
  5. package/codegraph/__pycache__/scanner.cpython-313.pyc +0 -0
  6. package/codegraph/__pycache__/server.cpython-313.pyc +0 -0
  7. package/codegraph/__pycache__/tree_html.cpython-313.pyc +0 -0
  8. package/codegraph/callflow_html.py +6 -4
  9. package/codegraph/export.py +25 -10
  10. package/codegraph/extractors/__pycache__/ast_extractor.cpython-313.pyc +0 -0
  11. package/codegraph/extractors/ast_extractor.py +37 -8
  12. package/codegraph/graph/__pycache__/builder.cpython-313.pyc +0 -0
  13. package/codegraph/graph/__pycache__/clustering.cpython-313.pyc +0 -0
  14. package/codegraph/graph/__pycache__/query.cpython-313.pyc +0 -0
  15. package/codegraph/graph/__pycache__/symbol_resolution.cpython-313.pyc +0 -0
  16. package/codegraph/graph/builder.py +27 -23
  17. package/codegraph/graph/clustering.py +5 -3
  18. package/codegraph/graph/query.py +5 -4
  19. package/codegraph/graph/symbol_resolution.py +14 -3
  20. package/codegraph/report.py +1 -1
  21. package/codegraph/scanner.py +1 -1
  22. package/codegraph/server.py +26 -5
  23. package/codegraph/tree_html.py +28 -30
  24. package/package.json +2 -2
  25. package/pyproject.toml +72 -72
  26. package/src/cli.js +12 -42
  27. package/src/db.js +26 -14
  28. package/src/http.js +1 -1
  29. package/src/search.js +4 -9
  30. package/src/server.js +16 -7
  31. package/src/templates/antigravity/GEMINI.md +18 -6
  32. package/src/templates/claude/CLAUDE.md +14 -1
  33. package/src/templates/claude/skills/SKILL.md +15 -3
  34. package/src/templates/codex/AGENTS.md +9 -2
  35. package/src/templates/cursor/cursor-rules.mdc +13 -4
  36. package/src/templates/gemini/GEMINI.md +14 -3
  37. package/src/templates/windsurf/windsurf-rules.md +14 -3
  38. package/src/tools/codegraph.js +4 -1
  39. package/src/tools/context.js +6 -6
  40. package/src/tools/errorCheck.js +3 -3
  41. package/src/tools/fileTools.js +2 -2
  42. package/src/tools/gitTools.js +1 -1
  43. package/src/tools/search.js +1 -1
  44. package/src/tools/symbolDetail.js +74 -0
  45. package/src/tools/toolRegistry.js +77 -0
  46. package/src/vector.js +7 -2
  47. package/uv.lock +3 -3
@@ -71,13 +71,24 @@ Call `search` before asking user to re-explain past work.
71
71
  codegraph_build(path) → build AST graph + auto-generate all visualizations
72
72
  codegraph_arch(path, limit?) → module map: files, exports, imports
73
73
  codegraph_query(path, question?, node?) → find symbol or answer structural question
74
- codegraph_nodes(path, type) → list all nodes of a type
75
- codegraph_report(path) → structural analysis
74
+ codegraph_nodes(path, type) → list all nodes of a type (class|function|module|file|struct|table)
75
+ codegraph_report(path) → structural analysis, god nodes, clusters
76
76
  codegraph_affected(path, node, depth?) → blast radius BFS — what breaks if X changes?
77
77
  codegraph_html(path, formats?) → regenerate visualizations on demand
78
+ get_symbol_detail(name, path) → source code for one function/class — no full file read
79
+ tool_registry() → which tools have side effects + approval requirements
80
+ safety_policy() → which actions need user confirmation
78
81
  ```
79
82
 
80
- Use `codegraph_arch` first. Never read files for structure questions.
83
+ Decision rules:
84
+ - **Unknown codebase**: `codegraph_report` first (god nodes + surprises)
85
+ - **"Where is X?"**: `codegraph_query node:"X"`
86
+ - **Before any refactor**: `codegraph_affected node:"X"` — FIRST
87
+ - **"Show me function X"**: `get_symbol_detail` — avoids full file read
88
+ - **List all classes**: `codegraph_nodes type:"class"`
89
+ - **`search`** finds past decisions. **`codegraph_query`** finds code. Different tools.
90
+
91
+ Never read files for structure questions.
81
92
 
82
93
  ---
83
94
 
@@ -66,13 +66,24 @@ Call `search` before asking user to re-explain past work.
66
66
  ```
67
67
  codegraph_arch(path) → module map (files, exports, imports)
68
68
  codegraph_query(path, ...) → find specific function/class/file
69
- codegraph_nodes(path, type) → list all nodes of a type
70
- codegraph_report(path) → structural analysis
69
+ codegraph_nodes(path, type) → list all nodes of a type (class|function|module|file|struct|table)
70
+ codegraph_report(path) → structural analysis, god nodes, clusters
71
71
  codegraph_affected(path, node, depth?) → blast radius — what breaks if X changes?
72
72
  codegraph_html(path, formats?) → regenerate visualizations (auto on every build)
73
+ get_symbol_detail(name, path) → source code for one function/class — no full file read
74
+ tool_registry() → which tools have side effects + approval requirements
75
+ safety_policy() → which actions need user confirmation
73
76
  ```
74
77
 
75
- Use `codegraph_arch` first. Never read files for structure questions.
78
+ Decision rules:
79
+ - **Unknown codebase**: `codegraph_report` first
80
+ - **"Where is X?"**: `codegraph_query node:"X"`
81
+ - **Before any refactor**: `codegraph_affected node:"X"` — FIRST
82
+ - **"Show me function X"**: `get_symbol_detail` — avoids full file read
83
+ - **List all classes**: `codegraph_nodes type:"class"`
84
+ - **`search`** finds past decisions. **`codegraph_query`** finds code. Different tools.
85
+
86
+ Never read files for structure questions.
76
87
 
77
88
  ---
78
89
 
@@ -71,7 +71,10 @@ export const definitions = [
71
71
  },
72
72
  {
73
73
  name: 'codegraph_nodes',
74
- description: 'List all nodes of a given type in the graph.',
74
+ description:
75
+ 'List all nodes of a given type in the graph. ' +
76
+ 'type must be one of: class, function, module, concept, service, file, struct, table. ' +
77
+ 'Use to enumerate all classes before refactoring, all functions in a module, or all files of a type.',
75
78
  inputSchema: {
76
79
  type: 'object',
77
80
  properties: {
@@ -89,15 +89,15 @@ export async function handle(args, state) {
89
89
 
90
90
  const rawEntries = getContext({ project: proj, limit: 15, compact: false })
91
91
  .filter(e => e.status !== 'archived');
92
- // Newest 5 get full content; older entries get a lightweight preview
92
+ // Full content only for: newest 2, or high-signal entries (why+outcome+files)
93
+ // ponytail: avoids dumping 5 full auto-entries (graph builds, etc.) every resume
93
94
  const entries = rawEntries.map((e, i) => {
94
- if (i < 5) return e;
95
+ const isHighSignal = e.why && e.outcome && Array.isArray(e.files) && e.files.length > 0;
96
+ if (i < 2 || isHighSignal) return e;
95
97
  return {
96
98
  id: e.id, project: e.project, title: e.title, type: e.type,
97
99
  status: e.status, tags: e.tags, source: e.source,
98
100
  createdAt: e.createdAt, updatedAt: e.updatedAt,
99
- ...(e.why ? { why: e.why } : {}),
100
- ...(e.outcome ? { outcome: e.outcome } : {}),
101
101
  preview: (e.content || '').slice(0, 200),
102
102
  };
103
103
  });
@@ -116,12 +116,12 @@ export async function handle(args, state) {
116
116
 
117
117
  if (discussions.length === 1) state.discussionId = discussions[0].id;
118
118
 
119
- const digest = totalEntries > 10
119
+ const digest = totalEntries > 25
120
120
  ? autoDigest(getContext({ project: proj, limit: 30 }), proj)
121
121
  : null;
122
122
 
123
123
  const graphStatus = graph
124
- ? { built: true, path: graph.path, nodes: graph.nodes, edges: graph.edges, communities: graph.communities, builtAt: graph.builtAt }
124
+ ? { built: true, path: graph.path, nodes: graph.nodes, edges: graph.edges, builtAt: graph.builtAt }
125
125
  : { built: false };
126
126
 
127
127
  return {
@@ -35,7 +35,7 @@ export async function handle(args, state) {
35
35
 
36
36
  if (action === 'check') {
37
37
  const results = unifiedSearch({ mode: 'semantic', query: errorMessage, project, limit: 5 })
38
- .filter(r => r.type === 'bug');
38
+ .filter(r => Array.isArray(r.tags) && r.tags.includes('error-log'));
39
39
  if (results.length > 0 && results[0].similarity > 0.4) {
40
40
  return {
41
41
  success: true, found: true,
@@ -53,8 +53,8 @@ export async function handle(args, state) {
53
53
  sessionId: state.sessionId || null,
54
54
  title: `Error: ${errorMessage.split('\n')[0].slice(0, 60)}`,
55
55
  content: `Command: ${command || 'unknown'}\n\nError:\n${errorMessage}\n\nSolution:\n${solution}`,
56
- type: 'bug',
57
- status: 'done',
56
+ type: 'note',
57
+ status: 'active',
58
58
  tags: ['error-log', command].filter(Boolean),
59
59
  });
60
60
  fireAutoLink(entry.id, state);
@@ -107,7 +107,7 @@ export async function handle(name, args, state) {
107
107
  saveAutoContext({
108
108
  title: `wrote ${filePath.split(/[\\/]/).pop()}`,
109
109
  content: `write_file: created/overwrote ${filePath}`,
110
- type: 'code',
110
+ type: 'note',
111
111
  files: [{ path: filePath, action: 'modified' }],
112
112
  tags: ['file-write'],
113
113
  state,
@@ -159,7 +159,7 @@ export async function handle(name, args, state) {
159
159
  title: `patched ${filePath.split(/[\\/]/).pop()}${sorted.length > 1 ? ` (${sorted.length} edits)` : ''}`,
160
160
  content: `patch_file: ${sorted.length} edit(s) in ${filePath}\n` +
161
161
  sorted.map(e => ` ${e.description}: -${e.old_str.split('\n').length} +${e.new_str.split('\n').length} lines`).join('\n'),
162
- type: 'code',
162
+ type: 'note',
163
163
  files: [{ path: filePath, action: 'modified' }],
164
164
  tags: ['file-patch'],
165
165
  state,
@@ -175,7 +175,7 @@ export async function handle(name, args, state) {
175
175
  saveAutoContext({
176
176
  title: `git commit: ${args.message.slice(0, 57)}${args.message.length > 57 ? '...' : ''}`,
177
177
  content: `hash: ${hash} | branch: ${branch}\nmessage: ${args.message}\nfiles: ${stagedFiles.map(f => f.path).join(', ')}`,
178
- type: 'decision',
178
+ type: 'note',
179
179
  files: stagedFiles,
180
180
  tags: ['git', 'commit', branch],
181
181
  state,
@@ -44,7 +44,7 @@ export async function handle(args, _state) {
44
44
  id: e.id, project: e.project, title: e.title || '',
45
45
  tags: e.tags, createdAt: e.createdAt,
46
46
  similarity: e.similarity,
47
- preview: (e.content || '').slice(0, 200),
47
+ // ponytail: preview omitted — call context.get id:[...] for full content
48
48
  }));
49
49
  return {
50
50
  matches, count: matches.length, mode,
@@ -0,0 +1,74 @@
1
+ /**
2
+ * get_symbol_detail — return source code for a single function/class by name.
3
+ * Uses codegraph_query to locate the symbol, then reads only the relevant lines.
4
+ */
5
+
6
+ import { readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { handle as codegraphHandle } from './codegraph.js';
9
+
10
+ export const definition = {
11
+ name: 'get_symbol_detail',
12
+ description:
13
+ 'Return the source code and location for a single function, class, or method by name. ' +
14
+ 'Use instead of reading the whole file — much cheaper. Requires codegraph to be built. ' +
15
+ 'Pass file to narrow when multiple symbols share the same name.',
16
+ inputSchema: {
17
+ type: 'object',
18
+ required: ['name'],
19
+ properties: {
20
+ name: { type: 'string', description: 'Symbol name (function, class, method)' },
21
+ file: { type: 'string', description: 'Optional: narrow by file path (partial match ok)' },
22
+ path: { type: 'string', description: 'Project root path (required if codegraph was built for a specific path)' },
23
+ context_lines: { type: 'number', description: 'Extra lines to include above/below (default 3)' },
24
+ },
25
+ },
26
+ };
27
+
28
+ export async function handle(args, state) {
29
+ const { name, file, context_lines = 3 } = args;
30
+ const rootPath = args.path || state.projectRootPath;
31
+ if (!rootPath) throw new Error('path or projectRootPath required');
32
+
33
+ // Ask codegraph for the node location
34
+ const queryResult = codegraphHandle('codegraph_query', { path: rootPath, node: name }, state);
35
+
36
+ // Find the node matching name (and optionally file)
37
+ const nodes = queryResult?.nodes || queryResult?.results || [];
38
+ let match = nodes.find(n =>
39
+ n.name === name && (!file || (n.file || '').includes(file))
40
+ );
41
+ if (!match && nodes.length === 1) match = nodes[0];
42
+ if (!match) {
43
+ return {
44
+ found: false,
45
+ message: `Symbol "${name}" not found in graph. Run codegraph_build first, or check spelling.`,
46
+ candidates: nodes.slice(0, 5).map(n => ({ name: n.name, file: n.file, type: n.type })),
47
+ };
48
+ }
49
+
50
+ // Read the source file around the symbol's line
51
+ const absFile = join(rootPath, match.file);
52
+ let source;
53
+ try {
54
+ source = readFileSync(absFile, 'utf8');
55
+ } catch {
56
+ return { found: true, ...match, error: `Could not read file: ${absFile}` };
57
+ }
58
+
59
+ const lines = source.split('\n');
60
+ const startLine = Math.max(0, (match.line || 1) - 1 - context_lines);
61
+ // Heuristic: read up to 80 lines or until we find the closing brace/dedent
62
+ const endLine = Math.min(lines.length, startLine + 80 + context_lines);
63
+ const snippet = lines.slice(startLine, endLine).join('\n');
64
+
65
+ return {
66
+ found: true,
67
+ name: match.name,
68
+ type: match.type,
69
+ file: match.file,
70
+ line: match.line,
71
+ source: snippet,
72
+ hint: `Lines ${startLine + 1}–${endLine} of ${match.file}. Use read_file for the full file.`,
73
+ };
74
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * tool_registry + safety_policy — static metadata about every MCP tool.
3
+ * No DB queries. Pure JSON describing side-effects and approval requirements.
4
+ */
5
+
6
+ const REGISTRY = [
7
+ // Core memory tools
8
+ { name: 'context', side_effects: 'Writes to ~/.context-mcp on save/update/delete', requires_approval: false },
9
+ { name: 'search', side_effects: 'none', requires_approval: false },
10
+ { name: 'plan', side_effects: 'Writes plan files to planDir on save/update', requires_approval: false },
11
+ { name: 'error_check', side_effects: 'Writes to ~/.context-mcp on save', requires_approval: false },
12
+ // Graph tools
13
+ { name: 'codegraph_build', side_effects: 'Writes codegraph-cache/ in project root', requires_approval: false },
14
+ { name: 'codegraph_query', side_effects: 'none', requires_approval: false },
15
+ { name: 'codegraph_arch', side_effects: 'none', requires_approval: false },
16
+ { name: 'codegraph_nodes', side_effects: 'none', requires_approval: false },
17
+ { name: 'codegraph_report', side_effects: 'Writes CODEGRAPH_REPORT.md in project root', requires_approval: false },
18
+ { name: 'codegraph_affected',side_effects: 'none', requires_approval: false },
19
+ { name: 'codegraph_html', side_effects: 'Writes HTML/GraphML files to codegraph-cache/', requires_approval: false },
20
+ { name: 'get_symbol_detail', side_effects: 'none', requires_approval: false },
21
+ // File tools (HTTP mode only)
22
+ { name: 'read_file', side_effects: 'none', requires_approval: false },
23
+ { name: 'list_dir', side_effects: 'none', requires_approval: false },
24
+ { name: 'write_file', side_effects: 'Creates or overwrites a file', requires_approval: true },
25
+ { name: 'patch_file', side_effects: 'Modifies an existing file', requires_approval: true },
26
+ { name: 'create_dir', side_effects: 'Creates a directory', requires_approval: false },
27
+ { name: 'delete_file', side_effects: 'Permanently deletes a file or directory', requires_approval: true },
28
+ // Git tools (ACCESS_GIT=true only)
29
+ { name: 'git_status', side_effects: 'none', requires_approval: false },
30
+ { name: 'git_diff', side_effects: 'none', requires_approval: false },
31
+ { name: 'git_log', side_effects: 'none', requires_approval: false },
32
+ { name: 'git_show', side_effects: 'none', requires_approval: false },
33
+ { name: 'git_add', side_effects: 'Stages files for commit', requires_approval: false },
34
+ { name: 'git_commit', side_effects: 'Creates a git commit', requires_approval: true },
35
+ { name: 'git_push', side_effects: 'Pushes commits to remote — IRREVERSIBLE', requires_approval: true },
36
+ { name: 'git_pull', side_effects: 'Modifies working tree', requires_approval: true },
37
+ { name: 'git_branch', side_effects: 'May create or switch branches', requires_approval: false },
38
+ { name: 'git_stash', side_effects: 'Modifies stash stack', requires_approval: false },
39
+ { name: 'git_reset', side_effects: 'Modifies staging area or HEAD — use with care', requires_approval: true },
40
+ ];
41
+
42
+ const SAFETY_POLICY = {
43
+ description: 'Actions that require explicit user confirmation before execution.',
44
+ requires_confirmation: REGISTRY.filter(t => t.requires_approval).map(t => t.name),
45
+ rules: [
46
+ 'Always confirm before git_push — pushes affect the remote and are hard to undo.',
47
+ 'Always confirm before delete_file — no recycle bin.',
48
+ 'Always confirm before git_reset if mode is hard — discards uncommitted work.',
49
+ 'Always confirm before write_file on files that look like credentials or config.',
50
+ ],
51
+ };
52
+
53
+ export const definitions = [
54
+ {
55
+ name: 'tool_registry',
56
+ description:
57
+ 'Lists every MCP tool with its side effects and whether it requires user approval. ' +
58
+ 'Read this before calling any destructive tool to understand the risk.',
59
+ inputSchema: { type: 'object', properties: {} },
60
+ },
61
+ {
62
+ name: 'safety_policy',
63
+ description:
64
+ 'Lists which operations require explicit user confirmation before execution ' +
65
+ '(git_push, git_reset, delete_file, write_file, git_commit, patch_file). ' +
66
+ 'Read before performing any irreversible action.',
67
+ inputSchema: { type: 'object', properties: {} },
68
+ },
69
+ ];
70
+
71
+ export const TOOL_NAMES = new Set(definitions.map(d => d.name));
72
+
73
+ export function handle(name) {
74
+ if (name === 'tool_registry') return { tools: REGISTRY, count: REGISTRY.length };
75
+ if (name === 'safety_policy') return SAFETY_POLICY;
76
+ throw new Error(`Unknown tool: ${name}`);
77
+ }
package/src/vector.js CHANGED
@@ -64,15 +64,20 @@ function buildIDF(docs) {
64
64
  return idf;
65
65
  }
66
66
 
67
+ let _idfFingerprint = null;
68
+
67
69
  function getCachedIDF(corpus) {
68
70
  const gen = getGeneration();
69
- // Cache hit: same generation (no mutations) and same corpus size
70
- if (_idfCache && _idfGeneration === gen && _idfCorpusLen === corpus.length) {
71
+ // Fingerprint first 20 IDs to detect content change at same size (e.g. delete+add)
72
+ const fingerprint = corpus.slice(0, 20).map(e => e.id).join(',');
73
+ if (_idfCache && _idfGeneration === gen && _idfCorpusLen === corpus.length
74
+ && _idfFingerprint === fingerprint) {
71
75
  return _idfCache;
72
76
  }
73
77
  _idfCache = buildIDF(corpus);
74
78
  _idfGeneration = gen;
75
79
  _idfCorpusLen = corpus.length;
80
+ _idfFingerprint = fingerprint;
76
81
  return _idfCache;
77
82
  }
78
83
 
package/uv.lock CHANGED
@@ -201,7 +201,7 @@ wheels = [
201
201
 
202
202
  [[package]]
203
203
  name = "codegraph-mcp"
204
- version = "1.1.0"
204
+ version = "1.1.3"
205
205
  source = { editable = "." }
206
206
  dependencies = [
207
207
  { name = "mcp" },
@@ -2308,8 +2308,8 @@ name = "uvicorn"
2308
2308
  version = "0.46.0"
2309
2309
  source = { registry = "https://pypi.org/simple" }
2310
2310
  dependencies = [
2311
- { name = "click" },
2312
- { name = "h11" },
2311
+ { name = "click", marker = "python_full_version < '3.15' or sys_platform != 'emscripten'" },
2312
+ { name = "h11", marker = "python_full_version < '3.15' or sys_platform != 'emscripten'" },
2313
2313
  ]
2314
2314
  sdist = { url = "https://files.pythonhosted.org/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" }
2315
2315
  wheels = [