local-codebase-indexer 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,136 @@
1
+ """Minimal Model Context Protocol server (JSON-RPC 2.0 over stdio, newline-delimited)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+
8
+ from . import __version__
9
+ from .query import CodebaseTools, render_context, render_data_access, render_relations, render_search, render_status
10
+
11
+ PROTOCOL_VERSION = "2024-11-05"
12
+
13
+ TOOLS = [
14
+ {
15
+ "name": "codebase_semantic_search",
16
+ "description": "Find code components from a natural language description (hybrid keyword + symbol + vector search with dependency expansion).",
17
+ "inputSchema": {
18
+ "type": "object",
19
+ "properties": {
20
+ "query": {"type": "string", "description": "Semantic explanation of what you are looking for."},
21
+ "limit": {"type": "integer", "description": "Maximum chunks to return (default 5).", "default": 5},
22
+ },
23
+ "required": ["query"],
24
+ },
25
+ },
26
+ {
27
+ "name": "codebase_get_symbol_relations",
28
+ "description": "Trace inheritance, usages and cross-file dependencies of a class, interface or method.",
29
+ "inputSchema": {
30
+ "type": "object",
31
+ "properties": {
32
+ "symbol_name": {"type": "string", "description": 'Name of the class, interface or variable, e.g. "IExistOpenHandleService".'},
33
+ "relation_direction": {"type": "string", "enum": ["upstream", "downstream", "both"]},
34
+ },
35
+ "required": ["symbol_name", "relation_direction"],
36
+ },
37
+ },
38
+ {
39
+ "name": "codebase_get_optimized_context",
40
+ "description": "Return only the relevant lines around focus lines of a file to minimise token usage.",
41
+ "inputSchema": {
42
+ "type": "object",
43
+ "properties": {
44
+ "file_path": {"type": "string", "description": "Target source file (relative to project root)."},
45
+ "focus_lines": {"type": "array", "items": {"type": "integer"}, "description": "Target lines identified by search tools."},
46
+ "padding_lines": {"type": "integer", "description": "Surrounding lines to extract (default 15).", "default": 15},
47
+ },
48
+ "required": ["file_path", "focus_lines"],
49
+ },
50
+ },
51
+ {
52
+ "name": "codebase_get_data_access",
53
+ "description": "Who reads, writes or executes a database table / stored procedure / view (SQL files and C# SQL strings, Dapper, ADO.NET, EF mappings), including callers of procedures that write it, and what a procedure itself touches. Dynamic SQL is reported as uncertain.",
54
+ "inputSchema": {
55
+ "type": "object",
56
+ "properties": {
57
+ "object_name": {"type": "string", "description": 'Table, view or procedure name, optionally schema qualified, e.g. "MES_Lot" or "dbo.usp_Lot_UpdateStatus".'},
58
+ },
59
+ "required": ["object_name"],
60
+ },
61
+ },
62
+ {
63
+ "name": "codebase_index_status",
64
+ "description": "Report whether the index can be trusted: freshness, git commit it was built from, files that failed to index, and consistency checks. Does not refresh the index.",
65
+ "inputSchema": {"type": "object", "properties": {}},
66
+ },
67
+ ]
68
+
69
+
70
+ def call_tool(tools: CodebaseTools, name: str, args: dict) -> str:
71
+ if name == "codebase_semantic_search":
72
+ return render_search(tools.semantic_search(args["query"], args.get("limit", 5)))
73
+ if name == "codebase_get_symbol_relations":
74
+ return render_relations(tools.symbol_relations(args["symbol_name"], args.get("relation_direction", "both")))
75
+ if name == "codebase_get_optimized_context":
76
+ return render_context(tools.optimized_context(args["file_path"], args["focus_lines"], args.get("padding_lines", 15)))
77
+ if name == "codebase_get_data_access":
78
+ return render_data_access(tools.data_access(args["object_name"]))
79
+ if name == "codebase_index_status":
80
+ return render_status(tools.index_status())
81
+ raise KeyError(f"unknown tool: {name}")
82
+
83
+
84
+ def handle(tools: CodebaseTools, msg: dict) -> dict | None:
85
+ method, mid = msg.get("method"), msg.get("id")
86
+ params = msg.get("params") or {}
87
+
88
+ def ok(result):
89
+ return {"jsonrpc": "2.0", "id": mid, "result": result}
90
+
91
+ def err(code, message):
92
+ return {"jsonrpc": "2.0", "id": mid, "error": {"code": code, "message": message}}
93
+
94
+ if method == "initialize":
95
+ return ok({
96
+ "protocolVersion": PROTOCOL_VERSION,
97
+ "capabilities": {"tools": {}},
98
+ "serverInfo": {"name": "local-codebase-indexer", "version": __version__},
99
+ })
100
+ if method == "ping":
101
+ return ok({})
102
+ if method == "tools/list":
103
+ return ok({"tools": TOOLS})
104
+ if method == "tools/call":
105
+ try:
106
+ text = call_tool(tools, params.get("name", ""), params.get("arguments") or {})
107
+ return ok({"content": [{"type": "text", "text": text}], "isError": False})
108
+ except KeyError as exc:
109
+ if str(exc).startswith("'unknown tool"):
110
+ return err(-32602, str(exc).strip("'\""))
111
+ return ok({"content": [{"type": "text", "text": f"missing argument: {exc}"}], "isError": True})
112
+ except (ValueError, FileNotFoundError) as exc:
113
+ return ok({"content": [{"type": "text", "text": f"error: {exc}"}], "isError": True})
114
+ if mid is None: # notification (e.g. notifications/initialized): no response
115
+ return None
116
+ return err(-32601, f"method not found: {method}")
117
+
118
+
119
+ def serve(root: str) -> int:
120
+ tools = CodebaseTools(root)
121
+ for raw in sys.stdin:
122
+ raw = raw.strip()
123
+ if not raw:
124
+ continue
125
+ try:
126
+ msg = json.loads(raw)
127
+ except json.JSONDecodeError:
128
+ sys.stdout.write(json.dumps({"jsonrpc": "2.0", "id": None,
129
+ "error": {"code": -32700, "message": "parse error"}}) + "\n")
130
+ sys.stdout.flush()
131
+ continue
132
+ resp = handle(tools, msg)
133
+ if resp is not None:
134
+ sys.stdout.write(json.dumps(resp, ensure_ascii=False) + "\n")
135
+ sys.stdout.flush()
136
+ return 0