context-mcp-server 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +464 -0
  2. package/codegraph/__init__.py +0 -0
  3. package/codegraph/__main__.py +24 -0
  4. package/codegraph/__pycache__/__init__.cpython-313.pyc +0 -0
  5. package/codegraph/__pycache__/__main__.cpython-313.pyc +0 -0
  6. package/codegraph/__pycache__/cache.cpython-313.pyc +0 -0
  7. package/codegraph/__pycache__/config.cpython-313.pyc +0 -0
  8. package/codegraph/__pycache__/report.cpython-313.pyc +0 -0
  9. package/codegraph/__pycache__/scanner.cpython-313.pyc +0 -0
  10. package/codegraph/__pycache__/server.cpython-313.pyc +0 -0
  11. package/codegraph/cache.py +137 -0
  12. package/codegraph/config.py +31 -0
  13. package/codegraph/extractors/__init__.py +0 -0
  14. package/codegraph/extractors/__pycache__/__init__.cpython-313.pyc +0 -0
  15. package/codegraph/extractors/__pycache__/ast_extractor.cpython-313.pyc +0 -0
  16. package/codegraph/extractors/__pycache__/audio_extractor.cpython-313.pyc +0 -0
  17. package/codegraph/extractors/__pycache__/doc_extractor.cpython-313.pyc +0 -0
  18. package/codegraph/extractors/__pycache__/image_extractor.cpython-313.pyc +0 -0
  19. package/codegraph/extractors/ast_extractor.py +222 -0
  20. package/codegraph/extractors/audio_extractor.py +8 -0
  21. package/codegraph/extractors/doc_extractor.py +34 -0
  22. package/codegraph/extractors/image_extractor.py +26 -0
  23. package/codegraph/graph/__init__.py +0 -0
  24. package/codegraph/graph/__pycache__/__init__.cpython-313.pyc +0 -0
  25. package/codegraph/graph/__pycache__/builder.cpython-313.pyc +0 -0
  26. package/codegraph/graph/__pycache__/clustering.cpython-313.pyc +0 -0
  27. package/codegraph/graph/__pycache__/query.cpython-313.pyc +0 -0
  28. package/codegraph/graph/builder.py +145 -0
  29. package/codegraph/graph/clustering.py +40 -0
  30. package/codegraph/graph/query.py +283 -0
  31. package/codegraph/report.py +115 -0
  32. package/codegraph/scanner.py +92 -0
  33. package/codegraph/server.py +514 -0
  34. package/package.json +62 -0
  35. package/src/cli.js +1010 -0
  36. package/src/config.js +89 -0
  37. package/src/db.js +786 -0
  38. package/src/guard.js +20 -0
  39. package/src/hooks/autoContext.js +17 -0
  40. package/src/hooks/autoLink.js +7 -0
  41. package/src/http.js +765 -0
  42. package/src/index.js +47 -0
  43. package/src/search.js +50 -0
  44. package/src/server.js +80 -0
  45. package/src/summarizer.js +124 -0
  46. package/src/templates/AGENTS.md +76 -0
  47. package/src/templates/CLAUDE.md +94 -0
  48. package/src/templates/GEMINI.md +76 -0
  49. package/src/templates/cursor-rules.mdc +41 -0
  50. package/src/templates/windsurf-rules.md +35 -0
  51. package/src/tools/codegraph.js +215 -0
  52. package/src/tools/context.js +188 -0
  53. package/src/tools/discussion.js +123 -0
  54. package/src/tools/errorCheck.js +65 -0
  55. package/src/tools/fileTools.js +185 -0
  56. package/src/tools/gitTools.js +259 -0
  57. package/src/tools/search.js +55 -0
  58. package/src/vector.js +153 -0
@@ -0,0 +1,514 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ codegraph/server.py — MCP server exposing codebase knowledge graph tools.
4
+
5
+ Tools:
6
+ codegraph_build — scan project, extract AST nodes, build graph (local only, no API)
7
+ codegraph_extract — return raw doc content for the AI to read and extract concepts from
8
+ codegraph_add_nodes — AI pushes extracted concept nodes back into the graph
9
+ codegraph_query — natural language question → graph traversal answer
10
+ codegraph_report — return full CODEGRAPH_REPORT.md
11
+ codegraph_nodes — list nodes of a given type
12
+ codegraph_path — shortest path between two concepts
13
+ """
14
+
15
+ import asyncio
16
+ import json
17
+ import os
18
+ import time
19
+ from pathlib import Path
20
+
21
+ from mcp.server import Server
22
+ from mcp.server.stdio import stdio_server
23
+ from mcp.types import Tool, TextContent
24
+
25
+ from .scanner import scan, classify_file
26
+ from .cache import file_hash, set_cached_nodes, save_cache, save_semantic_cache
27
+ from .extractors.ast_extractor import extract as ast_extract
28
+ from .extractors.doc_extractor import extract_text
29
+ from .graph.builder import build, to_json_dict, save_graph, load_graph
30
+ from .graph.query import answer as graph_answer, find_path
31
+ from .graph.clustering import detect_communities
32
+ from .report import generate as generate_report
33
+
34
+ app = Server("codegraph")
35
+
36
+
37
+ # ── Tool definitions ──────────────────────────────────────────────────────────
38
+
39
+ TOOLS = [
40
+ Tool(
41
+ name="codegraph_build",
42
+ description=(
43
+ "Scan a project directory and build the knowledge graph from code files. "
44
+ "Uses AST extraction (with regex fallback) for all code files. "
45
+ "Fast, local, no API key needed. "
46
+ "For docs and images, call codegraph_extract afterward — the AI reads and extracts concepts, "
47
+ "then calls codegraph_add_nodes to push them into the graph."
48
+ ),
49
+ inputSchema={
50
+ "type": "object",
51
+ "properties": {
52
+ "path": {"type": "string", "description": "Absolute path to project root"},
53
+ "cluster": {"type": "boolean", "description": "Run community detection after build (default true)"},
54
+ },
55
+ "required": ["path"],
56
+ },
57
+ ),
58
+ Tool(
59
+ name="codegraph_extract",
60
+ description=(
61
+ "Return raw content of changed doc files so the AI can read them and extract concepts. "
62
+ "Call this after codegraph_build. Read the returned files, extract key concepts and "
63
+ "relationships, then call codegraph_add_nodes with your findings. "
64
+ "Works with any AI — no API key required."
65
+ ),
66
+ inputSchema={
67
+ "type": "object",
68
+ "properties": {
69
+ "path": {"type": "string", "description": "Project root (same as codegraph_build)"},
70
+ "limit": {"type": "integer", "description": "Max files to return per call (default 10)"},
71
+ },
72
+ "required": ["path"],
73
+ },
74
+ ),
75
+ Tool(
76
+ name="codegraph_add_nodes",
77
+ description=(
78
+ "Add concept nodes extracted by the AI into the graph. "
79
+ "Call this after reading the output of codegraph_extract. "
80
+ "Each node should have: name, type, file, and optionally description and relations."
81
+ ),
82
+ inputSchema={
83
+ "type": "object",
84
+ "properties": {
85
+ "path": {"type": "string", "description": "Project root"},
86
+ "nodes": {
87
+ "type": "array",
88
+ "description": "Concept nodes to add",
89
+ "items": {
90
+ "type": "object",
91
+ "properties": {
92
+ "name": {"type": "string"},
93
+ "type": {"type": "string", "description": "class|function|concept|service|decision|requirement"},
94
+ "file": {"type": "string", "description": "Relative file path this concept came from"},
95
+ "description": {"type": "string"},
96
+ "relations": {
97
+ "type": "array",
98
+ "items": {
99
+ "type": "object",
100
+ "properties": {
101
+ "name": {"type": "string"},
102
+ "relation": {"type": "string", "description": "depends-on|uses|implements|defines|documents"},
103
+ },
104
+ },
105
+ },
106
+ },
107
+ "required": ["name", "type", "file"],
108
+ },
109
+ },
110
+ },
111
+ "required": ["path", "nodes"],
112
+ },
113
+ ),
114
+ Tool(
115
+ name="codegraph_query",
116
+ description=(
117
+ "Ask a structural question about the codebase. Pure graph traversal — instant, no API call. "
118
+ "Returns structured NODE/EDGE text truncated to token_budget. "
119
+ "Good for: dependencies, callers, module relationships. "
120
+ "NOT for: bug investigation or understanding code logic — read the file for that."
121
+ ),
122
+ inputSchema={
123
+ "type": "object",
124
+ "properties": {
125
+ "path": {"type": "string", "description": "Project root"},
126
+ "question": {"type": "string", "description": "Natural language question"},
127
+ "token_budget": {"type": "integer", "description": "Max tokens in response (default 2000)"},
128
+ },
129
+ "required": ["path", "question"],
130
+ },
131
+ ),
132
+ Tool(
133
+ name="codegraph_explain",
134
+ description=(
135
+ "Look up a node by name — returns description, type, file, and direct neighbors. "
136
+ "Use to understand what a specific function/class/module does and how it connects. "
137
+ "Descriptions are AI-written via codegraph_add_nodes."
138
+ ),
139
+ inputSchema={
140
+ "type": "object",
141
+ "properties": {
142
+ "path": {"type": "string", "description": "Project root"},
143
+ "node": {"type": "string", "description": "Node name or partial name"},
144
+ },
145
+ "required": ["path", "node"],
146
+ },
147
+ ),
148
+ Tool(
149
+ name="codegraph_report",
150
+ description="Return CODEGRAPH_REPORT.md — god nodes, clusters, surprising connections, suggested questions.",
151
+ inputSchema={
152
+ "type": "object",
153
+ "properties": {"path": {"type": "string"}},
154
+ "required": ["path"],
155
+ },
156
+ ),
157
+ Tool(
158
+ name="codegraph_nodes",
159
+ description="List all nodes of a given type in the graph.",
160
+ inputSchema={
161
+ "type": "object",
162
+ "properties": {
163
+ "path": {"type": "string"},
164
+ "type": {"type": "string", "enum": ["class", "function", "module", "concept", "service", "file", "struct", "table"]},
165
+ "limit": {"type": "integer", "description": "Max results (default 50)"},
166
+ },
167
+ "required": ["path", "type"],
168
+ },
169
+ ),
170
+ Tool(
171
+ name="codegraph_path",
172
+ description="Find the shortest relationship path between two concepts in the graph.",
173
+ inputSchema={
174
+ "type": "object",
175
+ "properties": {
176
+ "path": {"type": "string"},
177
+ "from": {"type": "string"},
178
+ "to": {"type": "string"},
179
+ },
180
+ "required": ["path", "from", "to"],
181
+ },
182
+ ),
183
+ ]
184
+
185
+
186
+ @app.list_tools()
187
+ async def list_tools():
188
+ return TOOLS
189
+
190
+
191
+ @app.call_tool()
192
+ async def call_tool(name: str, arguments: dict):
193
+ try:
194
+ result = await _dispatch(name, arguments)
195
+ return [TextContent(type="text", text=json.dumps(result, indent=2))]
196
+ except Exception as e:
197
+ return [TextContent(type="text", text=json.dumps({"error": str(e)}))]
198
+
199
+
200
+ async def _dispatch(name: str, args: dict):
201
+ if name == "codegraph_build": return await _build(args)
202
+ if name == "codegraph_extract": return await _extract(args)
203
+ if name == "codegraph_add_nodes": return await _add_nodes(args)
204
+ if name == "codegraph_query": return await _query(args)
205
+ if name == "codegraph_explain": return await _explain(args)
206
+ if name == "codegraph_report": return await _report(args)
207
+ if name == "codegraph_nodes": return await _nodes(args)
208
+ if name == "codegraph_path": return await _path(args)
209
+ raise ValueError(f"Unknown tool: {name}")
210
+
211
+
212
+ # ── Build ─────────────────────────────────────────────────────────────────────
213
+
214
+ async def _build(args: dict) -> dict:
215
+ root = args["path"]
216
+ do_cluster = args.get("cluster", True)
217
+ t0 = time.time()
218
+
219
+ scan_result = scan(root)
220
+ cache = scan_result["cache"]
221
+ cached = scan_result["cached"]
222
+ changed = scan_result["changed"]
223
+ deleted = scan_result["deleted"]
224
+
225
+ # Local AST extraction — code/sql/config files only
226
+ all_nodes: list[dict] = []
227
+ pending_docs: list[str] = [] # rel_paths of changed docs/images for codegraph_extract
228
+
229
+ for nodes in cached.values():
230
+ all_nodes.extend(nodes)
231
+
232
+ for rel_path, abs_path in changed.items():
233
+ cat = classify_file(abs_path)
234
+ if cat in ("code", "sql"):
235
+ nodes = ast_extract(abs_path, rel_path)
236
+ set_cached_nodes(cache, rel_path, file_hash(abs_path), nodes)
237
+ all_nodes.extend(nodes)
238
+ elif cat == "config":
239
+ # Label config files as a single node — don't decompose every key
240
+ node = {"id": f"{rel_path}::file::{Path(rel_path).name}",
241
+ "name": Path(rel_path).name, "type": "file", "file": rel_path}
242
+ set_cached_nodes(cache, rel_path, file_hash(abs_path), [node])
243
+ all_nodes.append(node)
244
+ elif cat in ("doc", "pdf"):
245
+ pending_docs.append(rel_path)
246
+ elif cat in ("image", "audio", "video"):
247
+ # Label-only — node in graph so AI can reference the file, no content extraction
248
+ node = {"id": f"{rel_path}::file::{Path(rel_path).name}",
249
+ "name": Path(rel_path).name, "type": "file", "file": rel_path}
250
+ set_cached_nodes(cache, rel_path, file_hash(abs_path), [node])
251
+ all_nodes.append(node)
252
+
253
+ G = build(all_nodes)
254
+ communities = []
255
+ if do_cluster:
256
+ try:
257
+ communities = detect_communities(G)
258
+ except Exception:
259
+ pass
260
+
261
+ graph_dict = to_json_dict(G)
262
+ save_graph(root, graph_dict)
263
+ generate_report(graph_dict, root)
264
+ save_cache(root, cache)
265
+
266
+ elapsed_ms = int((time.time() - t0) * 1000)
267
+ result = {
268
+ "success": True,
269
+ "nodes": len(graph_dict.get("nodes", [])),
270
+ "edges": len(graph_dict.get("edges", [])),
271
+ "communities": len(communities),
272
+ "cached": len(cached),
273
+ "changed": len(changed),
274
+ "deleted": len(deleted),
275
+ "time_ms": elapsed_ms,
276
+ "summary": f"Built graph: {len(graph_dict.get('nodes', []))} nodes from code files.",
277
+ }
278
+
279
+ if pending_docs:
280
+ result["pending_docs"] = len(pending_docs)
281
+ result["hint"] = (
282
+ f"{len(pending_docs)} doc/image file(s) need concept extraction. "
283
+ "Call codegraph_extract to get their content, then codegraph_add_nodes with your findings."
284
+ )
285
+
286
+ return result
287
+
288
+
289
+ # ── Extract (return raw content for AI to read) ───────────────────────────────
290
+
291
+ async def _extract(args: dict) -> dict:
292
+ root = args["path"]
293
+ limit = args.get("limit", 10)
294
+ force = args.get("force", False)
295
+
296
+ scan_result = scan(root)
297
+ cache = scan_result["cache"]
298
+ scan_root = scan_result["root"]
299
+ # force=True: return all files; otherwise only changed
300
+ if force:
301
+ from codegraph.scanner import walk_files, classify_file
302
+ candidates = {
303
+ os.path.relpath(p, scan_root).replace("\\", "/"): p
304
+ for p in walk_files(scan_root)
305
+ if classify_file(p) in ("doc", "code")
306
+ }
307
+ else:
308
+ candidates = scan_result["changed"]
309
+
310
+ files = []
311
+ for rel_path, abs_path in list(candidates.items()):
312
+ if len(files) >= limit:
313
+ break
314
+ cat = classify_file(abs_path)
315
+ if cat in ("doc", "code"):
316
+ text = extract_text(abs_path)
317
+ if not text:
318
+ continue
319
+ entry: dict = {"rel_path": rel_path, "type": cat, "content": text}
320
+ # For code files, include existing AST nodes so AI knows what to describe
321
+ if cat == "code":
322
+ cached_entry = cache.get(rel_path, {})
323
+ existing_nodes = cached_entry.get("nodes", [])
324
+ entry["existing_nodes"] = [
325
+ {"name": n.get("name"), "type": n.get("type")}
326
+ for n in existing_nodes
327
+ ]
328
+ files.append(entry)
329
+
330
+ remaining = max(0, len(candidates) - limit)
331
+ return {
332
+ "files": files,
333
+ "returned": len(files),
334
+ "remaining": remaining,
335
+ "instruction": (
336
+ "For each file: read the content. "
337
+ "For code files, write a description for each existing_node (name + type listed). "
338
+ "For doc files, extract key concepts, decisions, and relationships as new nodes. "
339
+ "Then call codegraph_add_nodes with all nodes (include description field)."
340
+ ),
341
+ }
342
+
343
+
344
+ # ── Add nodes (AI pushes extracted concepts in) ───────────────────────────────
345
+
346
+ async def _add_nodes(args: dict) -> dict:
347
+ root = args["path"]
348
+ nodes = args.get("nodes", [])
349
+
350
+ if not nodes:
351
+ return {"success": False, "message": "No nodes provided."}
352
+
353
+ graph_dict = load_graph(root) or {"nodes": [], "edges": [], "communities": [], "god_nodes": []}
354
+
355
+ # Assign IDs and merge; update description on existing nodes
356
+ existing_map = {n["id"]: n for n in graph_dict["nodes"]}
357
+ added = 0
358
+ updated = 0
359
+ for node in nodes:
360
+ nid = f"{node['file']}::concept::{node['name']}"
361
+ desc = node.get("description", "")
362
+ if nid in existing_map:
363
+ if desc and desc != existing_map[nid].get("description", ""):
364
+ existing_map[nid]["description"] = desc
365
+ updated += 1
366
+ # Still add edges below
367
+ else:
368
+ new_node = {
369
+ "id": nid,
370
+ "name": node["name"],
371
+ "type": node.get("type", "concept"),
372
+ "file": node["file"],
373
+ "description": desc,
374
+ }
375
+ graph_dict["nodes"].append(new_node)
376
+ existing_map[nid] = new_node
377
+ added += 1
378
+
379
+ # Also try to enrich AST nodes (different ID pattern: file::type::name)
380
+ for id_pattern in [
381
+ f"{node['file']}::{node.get('type','function')}::{node['name']}",
382
+ f"{node['file']}::function::{node['name']}",
383
+ f"{node['file']}::class::{node['name']}",
384
+ f"{node['file']}::module::{node['name']}",
385
+ ]:
386
+ if id_pattern in existing_map and desc:
387
+ if not existing_map[id_pattern].get("description"):
388
+ existing_map[id_pattern]["description"] = desc
389
+ updated += 1
390
+
391
+ # Add relation edges
392
+ for rel in node.get("relations", []):
393
+ graph_dict["edges"].append({
394
+ "from": nid,
395
+ "to": rel["name"],
396
+ "relation": rel.get("relation", "relates-to"),
397
+ "confidence": "EXTRACTED",
398
+ })
399
+
400
+ # Persist descriptions to semantic cache (never overwritten by rebuild)
401
+ by_file: dict = {}
402
+ for node in nodes:
403
+ if node.get("description"):
404
+ by_file.setdefault(node["file"], []).append(node)
405
+ if by_file:
406
+ save_semantic_cache(root, by_file)
407
+
408
+ save_graph(root, graph_dict)
409
+ generate_report(graph_dict, root)
410
+
411
+ return {
412
+ "success": True,
413
+ "nodes_added": added,
414
+ "nodes_updated": updated,
415
+ "total_nodes": len(graph_dict["nodes"]),
416
+ "message": f"Added {added}, updated {updated} node description(s).",
417
+ }
418
+
419
+
420
+ # ── Query / Report / Nodes / Path ─────────────────────────────────────────────
421
+
422
+ async def _query(args: dict) -> dict:
423
+ graph_dict = load_graph(args["path"])
424
+ if not graph_dict:
425
+ raise ValueError("No graph found. Run codegraph_build first.")
426
+ return graph_answer(args["question"], graph_dict, token_budget=args.get("token_budget", 2000))
427
+
428
+
429
+ async def _explain(args: dict) -> dict:
430
+ graph_dict = load_graph(args["path"])
431
+ if not graph_dict:
432
+ raise ValueError("No graph found. Run codegraph_build first.")
433
+
434
+ query = args["node"].lower()
435
+ nodes = graph_dict.get("nodes", [])
436
+ edges = graph_dict.get("edges", [])
437
+
438
+ match = next((n for n in nodes if n.get("name", "").lower() == query), None)
439
+ if not match:
440
+ match = next((n for n in nodes if query in n.get("name", "").lower()), None)
441
+ if not match:
442
+ candidates = [n["name"] for n in nodes if query in n.get("id", "").lower()]
443
+ return {"found": False, "query": args["node"],
444
+ "message": f"No node matching '{args['node']}'.",
445
+ "suggestions": candidates[:10]}
446
+
447
+ nid = match["id"]
448
+ depends_on, used_by = [], []
449
+ for e in edges:
450
+ if e.get("from") == nid:
451
+ t = next((n for n in nodes if n.get("id") == e.get("to")), None)
452
+ depends_on.append({"name": t["name"] if t else e["to"],
453
+ "file": t.get("file","") if t else "",
454
+ "relation": e.get("relation","→")})
455
+ elif e.get("to") == nid:
456
+ s = next((n for n in nodes if n.get("id") == e.get("from")), None)
457
+ used_by.append({"name": s["name"] if s else e["from"],
458
+ "file": s.get("file","") if s else "",
459
+ "relation": e.get("relation","→")})
460
+
461
+ return {
462
+ "found": True,
463
+ "name": match.get("name"),
464
+ "type": match.get("type"),
465
+ "file": match.get("file"),
466
+ "description": match.get("description") or None,
467
+ "depends_on": depends_on[:20],
468
+ "used_by": used_by[:20],
469
+ "hint": None if match.get("description") else
470
+ "No description yet. Call codegraph_extract → codegraph_add_nodes to enrich.",
471
+ }
472
+
473
+
474
+ async def _report(args: dict) -> dict:
475
+ report_path = Path(args["path"]) / "codegraph-cache" / "CODEGRAPH_REPORT.md"
476
+ if report_path.exists():
477
+ return {"content": report_path.read_text(encoding="utf-8")}
478
+ graph_dict = load_graph(args["path"])
479
+ if not graph_dict:
480
+ raise ValueError("No graph found. Run codegraph_build first.")
481
+ return {"content": generate_report(graph_dict, args["path"])}
482
+
483
+
484
+ async def _nodes(args: dict) -> dict:
485
+ graph_dict = load_graph(args["path"])
486
+ if not graph_dict:
487
+ raise ValueError("No graph found. Run codegraph_build first.")
488
+ node_type = args["type"]
489
+ limit = args.get("limit", 50)
490
+ matched = [n for n in graph_dict.get("nodes", []) if n.get("type") == node_type]
491
+ return {"type": node_type, "count": len(matched), "nodes": matched[:limit]}
492
+
493
+
494
+ async def _path(args: dict) -> dict:
495
+ graph_dict = load_graph(args["path"])
496
+ if not graph_dict:
497
+ raise ValueError("No graph found. Run codegraph_build first.")
498
+ return find_path(args["from"], args["to"], graph_dict)
499
+
500
+
501
+ # ── Entry point ───────────────────────────────────────────────────────────────
502
+
503
+ async def _async_main():
504
+ async with stdio_server() as (read_stream, write_stream):
505
+ await app.run(read_stream, write_stream, app.create_initialization_options())
506
+
507
+
508
+ def main():
509
+ """Sync entry point — required by pyproject.toml [project.scripts]."""
510
+ asyncio.run(_async_main())
511
+
512
+
513
+ if __name__ == "__main__":
514
+ main()
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "context-mcp-server",
3
+ "version": "1.0.1",
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
+ "type": "module",
6
+ "bin": {
7
+ "context-mcp": "./src/index.js",
8
+ "context-mcp-http": "./src/http.js",
9
+ "ctx": "./src/cli.js"
10
+ },
11
+ "scripts": {
12
+ "mcp": "node src/index.js",
13
+ "mcp-server": "node src/http.js",
14
+ "cli": "node src/cli.js",
15
+ "check": "node --check src/index.js && node --check src/server.js && node --check src/db.js && node --check src/vector.js && node --check src/summarizer.js && node --check src/search.js && node --check src/config.js && node --check src/cli.js && node --check src/http.js && node --check src/tools/context.js && node --check src/tools/search.js && node --check src/tools/discussion.js && node --check src/tools/errorCheck.js && node --check src/tools/fileTools.js && node --check src/tools/gitTools.js && node --check src/tools/codegraph.js && node --check src/hooks/autoLink.js && node --check src/hooks/autoContext.js",
16
+ "check-mcp": "npm run check",
17
+ "test": "node --test",
18
+ "prepublishOnly": "npm run check"
19
+ },
20
+ "files": [
21
+ "src/",
22
+ "codegraph/",
23
+ "README.md"
24
+ ],
25
+ "keywords": [
26
+ "mcp",
27
+ "ai",
28
+ "memory",
29
+ "context",
30
+ "claude",
31
+ "cursor",
32
+ "gemini",
33
+ "codex",
34
+ "windsurf",
35
+ "knowledge-graph",
36
+ "codegraph",
37
+ "modelcontextprotocol",
38
+ "persistent-memory",
39
+ "ai-tools"
40
+ ],
41
+ "author": "Vibhas Dutta",
42
+ "license": "MIT",
43
+ "homepage": "https://github.com/vibhasdutta/context-mcp#readme",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/vibhasdutta/context-mcp.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/vibhasdutta/context-mcp/issues"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ },
54
+ "dependencies": {
55
+ "@modelcontextprotocol/sdk": "^1.0.0"
56
+ },
57
+ "optionalDependencies": {
58
+ "keytar": "^7.9.0",
59
+ "ws": "^8.18.0"
60
+ },
61
+ "devDependencies": {}
62
+ }