mcp-code-indexer 1.1.2__py3-none-any.whl → 1.1.5__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.
- mcp_code_indexer/__main__.py +11 -0
- mcp_code_indexer/main.py +265 -0
- mcp_code_indexer/server/mcp_server.py +126 -21
- {mcp_code_indexer-1.1.2.dist-info → mcp_code_indexer-1.1.5.dist-info}/METADATA +1 -1
- {mcp_code_indexer-1.1.2.dist-info → mcp_code_indexer-1.1.5.dist-info}/RECORD +9 -8
- {mcp_code_indexer-1.1.2.dist-info → mcp_code_indexer-1.1.5.dist-info}/WHEEL +0 -0
- {mcp_code_indexer-1.1.2.dist-info → mcp_code_indexer-1.1.5.dist-info}/entry_points.txt +0 -0
- {mcp_code_indexer-1.1.2.dist-info → mcp_code_indexer-1.1.5.dist-info}/licenses/LICENSE +0 -0
- {mcp_code_indexer-1.1.2.dist-info → mcp_code_indexer-1.1.5.dist-info}/top_level.txt +0 -0
mcp_code_indexer/main.py
CHANGED
@@ -7,9 +7,11 @@ Entry point for the mcp-code-indexer package when installed via pip.
|
|
7
7
|
|
8
8
|
import argparse
|
9
9
|
import asyncio
|
10
|
+
import json
|
10
11
|
import logging
|
11
12
|
import sys
|
12
13
|
from pathlib import Path
|
14
|
+
from typing import Any, Dict
|
13
15
|
|
14
16
|
from . import __version__
|
15
17
|
from .logging_config import setup_logging
|
@@ -58,13 +60,276 @@ def parse_arguments() -> argparse.Namespace:
|
|
58
60
|
help="Logging level (default: INFO)"
|
59
61
|
)
|
60
62
|
|
63
|
+
# Utility commands
|
64
|
+
parser.add_argument(
|
65
|
+
"--getprojects",
|
66
|
+
action="store_true",
|
67
|
+
help="List all projects with IDs, branches, and description counts"
|
68
|
+
)
|
69
|
+
|
70
|
+
parser.add_argument(
|
71
|
+
"--runcommand",
|
72
|
+
type=str,
|
73
|
+
help="Execute a command using JSON in MCP format (single or multi-line)"
|
74
|
+
)
|
75
|
+
|
76
|
+
parser.add_argument(
|
77
|
+
"--dumpdescriptions",
|
78
|
+
nargs="+",
|
79
|
+
metavar=("PROJECT_ID", "BRANCH"),
|
80
|
+
help="Export descriptions for a project. Usage: --dumpdescriptions PROJECT_ID [BRANCH]"
|
81
|
+
)
|
82
|
+
|
61
83
|
return parser.parse_args()
|
62
84
|
|
63
85
|
|
86
|
+
async def handle_getprojects(args: argparse.Namespace) -> None:
|
87
|
+
"""Handle --getprojects command."""
|
88
|
+
try:
|
89
|
+
from .database.database import DatabaseManager
|
90
|
+
|
91
|
+
# Initialize database
|
92
|
+
db_path = Path(args.db_path).expanduser()
|
93
|
+
db_manager = DatabaseManager(db_path)
|
94
|
+
await db_manager.initialize()
|
95
|
+
|
96
|
+
# Get all projects
|
97
|
+
projects = await db_manager.get_all_projects()
|
98
|
+
|
99
|
+
if not projects:
|
100
|
+
print("No projects found.")
|
101
|
+
return
|
102
|
+
|
103
|
+
print("Projects:")
|
104
|
+
print("-" * 80)
|
105
|
+
|
106
|
+
for project in projects:
|
107
|
+
print(f"ID: {project.id}")
|
108
|
+
print(f"Name: {project.name}")
|
109
|
+
print(f"Remote Origin: {project.remote_origin or 'N/A'}")
|
110
|
+
print(f"Upstream Origin: {project.upstream_origin or 'N/A'}")
|
111
|
+
|
112
|
+
# Get branch information
|
113
|
+
try:
|
114
|
+
branch_counts = await db_manager.get_branch_file_counts(project.id)
|
115
|
+
if branch_counts:
|
116
|
+
print("Branches:")
|
117
|
+
for branch, count in branch_counts.items():
|
118
|
+
print(f" - {branch}: {count} descriptions")
|
119
|
+
else:
|
120
|
+
print("Branches: No descriptions found")
|
121
|
+
except Exception as e:
|
122
|
+
print(f"Branches: Error loading branch info - {e}")
|
123
|
+
|
124
|
+
print("-" * 80)
|
125
|
+
|
126
|
+
except Exception as e:
|
127
|
+
print(f"Error: {e}", file=sys.stderr)
|
128
|
+
sys.exit(1)
|
129
|
+
|
130
|
+
|
131
|
+
async def handle_runcommand(args: argparse.Namespace) -> None:
|
132
|
+
"""Handle --runcommand command."""
|
133
|
+
from .server.mcp_server import MCPCodeIndexServer
|
134
|
+
|
135
|
+
try:
|
136
|
+
# Parse JSON (handle both single-line and multi-line)
|
137
|
+
json_data = json.loads(args.runcommand)
|
138
|
+
except json.JSONDecodeError as e:
|
139
|
+
print(f"Initial JSON parse failed: {e}", file=sys.stderr)
|
140
|
+
|
141
|
+
# Try to repair the JSON
|
142
|
+
try:
|
143
|
+
import re
|
144
|
+
repaired = args.runcommand
|
145
|
+
|
146
|
+
# Fix common issues
|
147
|
+
# Quote unquoted URLs and paths
|
148
|
+
url_pattern = r'("[\w]+"):\s*([a-zA-Z][a-zA-Z0-9+.-]*://[^\s,}]+|/[^\s,}]*)'
|
149
|
+
repaired = re.sub(url_pattern, r'\1: "\2"', repaired)
|
150
|
+
|
151
|
+
# Quote unquoted values
|
152
|
+
unquoted_pattern = r'("[\w]+"):\s*([a-zA-Z0-9_-]+)(?=\s*[,}])'
|
153
|
+
repaired = re.sub(unquoted_pattern, r'\1: "\2"', repaired)
|
154
|
+
|
155
|
+
# Remove trailing commas
|
156
|
+
repaired = re.sub(r',(\s*[}\]])', r'\1', repaired)
|
157
|
+
|
158
|
+
json_data = json.loads(repaired)
|
159
|
+
print(f"JSON repaired successfully", file=sys.stderr)
|
160
|
+
print(f"Original: {args.runcommand}", file=sys.stderr)
|
161
|
+
print(f"Repaired: {repaired}", file=sys.stderr)
|
162
|
+
except json.JSONDecodeError as repair_error:
|
163
|
+
print(f"JSON repair also failed: {repair_error}", file=sys.stderr)
|
164
|
+
print(f"Original JSON: {args.runcommand}", file=sys.stderr)
|
165
|
+
sys.exit(1)
|
166
|
+
|
167
|
+
# Initialize server
|
168
|
+
db_path = Path(args.db_path).expanduser()
|
169
|
+
cache_dir = Path(args.cache_dir).expanduser()
|
170
|
+
|
171
|
+
server = MCPCodeIndexServer(
|
172
|
+
token_limit=args.token_limit,
|
173
|
+
db_path=db_path,
|
174
|
+
cache_dir=cache_dir
|
175
|
+
)
|
176
|
+
await server.initialize()
|
177
|
+
|
178
|
+
# Extract the tool call information from the JSON
|
179
|
+
if "method" in json_data and json_data["method"] == "tools/call":
|
180
|
+
tool_name = json_data["params"]["name"]
|
181
|
+
tool_arguments = json_data["params"]["arguments"]
|
182
|
+
elif "projectName" in json_data and "folderPath" in json_data:
|
183
|
+
# Auto-detect: user provided just arguments, try to infer the tool
|
184
|
+
if "filePath" in json_data and "description" in json_data:
|
185
|
+
tool_name = "update_file_description"
|
186
|
+
tool_arguments = json_data
|
187
|
+
print("Auto-detected tool: update_file_description", file=sys.stderr)
|
188
|
+
elif "branch" in json_data:
|
189
|
+
tool_name = "check_codebase_size"
|
190
|
+
tool_arguments = json_data
|
191
|
+
print("Auto-detected tool: check_codebase_size", file=sys.stderr)
|
192
|
+
else:
|
193
|
+
print("Error: Could not auto-detect tool from arguments. Please use full MCP format:", file=sys.stderr)
|
194
|
+
print('{"method": "tools/call", "params": {"name": "TOOL_NAME", "arguments": {...}}}', file=sys.stderr)
|
195
|
+
sys.exit(1)
|
196
|
+
|
197
|
+
try:
|
198
|
+
# Map tool names to handler methods
|
199
|
+
tool_handlers = {
|
200
|
+
"get_file_description": server._handle_get_file_description,
|
201
|
+
"update_file_description": server._handle_update_file_description,
|
202
|
+
"check_codebase_size": server._handle_check_codebase_size,
|
203
|
+
"find_missing_descriptions": server._handle_find_missing_descriptions,
|
204
|
+
"search_descriptions": server._handle_search_descriptions,
|
205
|
+
"get_codebase_overview": server._handle_get_codebase_overview,
|
206
|
+
"merge_branch_descriptions": server._handle_merge_branch_descriptions,
|
207
|
+
}
|
208
|
+
|
209
|
+
if tool_name not in tool_handlers:
|
210
|
+
error_result = {
|
211
|
+
"error": {
|
212
|
+
"code": -32601,
|
213
|
+
"message": f"Unknown tool: {tool_name}"
|
214
|
+
}
|
215
|
+
}
|
216
|
+
print(json.dumps(error_result, indent=2))
|
217
|
+
return
|
218
|
+
|
219
|
+
# Clean HTML entities from arguments before execution
|
220
|
+
def clean_html_entities(text: str) -> str:
|
221
|
+
if not text:
|
222
|
+
return text
|
223
|
+
import html
|
224
|
+
return html.unescape(text)
|
225
|
+
|
226
|
+
def clean_arguments(arguments: dict) -> dict:
|
227
|
+
cleaned = {}
|
228
|
+
for key, value in arguments.items():
|
229
|
+
if isinstance(value, str):
|
230
|
+
cleaned[key] = clean_html_entities(value)
|
231
|
+
elif isinstance(value, list):
|
232
|
+
cleaned[key] = [
|
233
|
+
clean_html_entities(item) if isinstance(item, str) else item
|
234
|
+
for item in value
|
235
|
+
]
|
236
|
+
elif isinstance(value, dict):
|
237
|
+
cleaned[key] = clean_arguments(value)
|
238
|
+
else:
|
239
|
+
cleaned[key] = value
|
240
|
+
return cleaned
|
241
|
+
|
242
|
+
cleaned_tool_arguments = clean_arguments(tool_arguments)
|
243
|
+
|
244
|
+
# Execute the tool handler directly
|
245
|
+
result = await tool_handlers[tool_name](cleaned_tool_arguments)
|
246
|
+
print(json.dumps(result, indent=2, default=str))
|
247
|
+
except Exception as e:
|
248
|
+
error_result = {
|
249
|
+
"error": {
|
250
|
+
"code": -32603,
|
251
|
+
"message": str(e)
|
252
|
+
}
|
253
|
+
}
|
254
|
+
print(json.dumps(error_result, indent=2))
|
255
|
+
else:
|
256
|
+
print("Error: JSON must contain a valid MCP tool call", file=sys.stderr)
|
257
|
+
sys.exit(1)
|
258
|
+
|
259
|
+
|
260
|
+
async def handle_dumpdescriptions(args: argparse.Namespace) -> None:
|
261
|
+
"""Handle --dumpdescriptions command."""
|
262
|
+
from .database.database import DatabaseManager
|
263
|
+
from .token_counter import TokenCounter
|
264
|
+
|
265
|
+
if len(args.dumpdescriptions) < 1:
|
266
|
+
print("Error: Project ID is required", file=sys.stderr)
|
267
|
+
sys.exit(1)
|
268
|
+
|
269
|
+
project_id = args.dumpdescriptions[0]
|
270
|
+
branch = args.dumpdescriptions[1] if len(args.dumpdescriptions) > 1 else None
|
271
|
+
|
272
|
+
# Initialize database and token counter
|
273
|
+
db_path = Path(args.db_path).expanduser()
|
274
|
+
db_manager = DatabaseManager(db_path)
|
275
|
+
await db_manager.initialize()
|
276
|
+
|
277
|
+
token_counter = TokenCounter(args.token_limit)
|
278
|
+
|
279
|
+
# Get file descriptions
|
280
|
+
if branch:
|
281
|
+
file_descriptions = await db_manager.get_all_file_descriptions(
|
282
|
+
project_id=project_id,
|
283
|
+
branch=branch
|
284
|
+
)
|
285
|
+
print(f"File descriptions for project {project_id}, branch {branch}:")
|
286
|
+
else:
|
287
|
+
file_descriptions = await db_manager.get_all_file_descriptions(
|
288
|
+
project_id=project_id
|
289
|
+
)
|
290
|
+
print(f"File descriptions for project {project_id} (all branches):")
|
291
|
+
|
292
|
+
print("=" * 80)
|
293
|
+
|
294
|
+
if not file_descriptions:
|
295
|
+
print("No descriptions found.")
|
296
|
+
total_tokens = 0
|
297
|
+
else:
|
298
|
+
total_tokens = 0
|
299
|
+
for desc in file_descriptions:
|
300
|
+
print(f"File: {desc.file_path}")
|
301
|
+
if branch is None:
|
302
|
+
print(f"Branch: {desc.branch}")
|
303
|
+
print(f"Description: {desc.description}")
|
304
|
+
print("-" * 40)
|
305
|
+
|
306
|
+
# Count tokens for this description
|
307
|
+
desc_tokens = token_counter.count_file_description_tokens(desc)
|
308
|
+
total_tokens += desc_tokens
|
309
|
+
|
310
|
+
print("=" * 80)
|
311
|
+
print(f"Total descriptions: {len(file_descriptions)}")
|
312
|
+
print(f"Total tokens: {total_tokens}")
|
313
|
+
|
314
|
+
|
315
|
+
|
64
316
|
async def main() -> None:
|
65
317
|
"""Main entry point for the MCP server."""
|
66
318
|
args = parse_arguments()
|
67
319
|
|
320
|
+
# Handle utility commands
|
321
|
+
if args.getprojects:
|
322
|
+
await handle_getprojects(args)
|
323
|
+
return
|
324
|
+
|
325
|
+
if args.runcommand:
|
326
|
+
await handle_runcommand(args)
|
327
|
+
return
|
328
|
+
|
329
|
+
if args.dumpdescriptions:
|
330
|
+
await handle_dumpdescriptions(args)
|
331
|
+
return
|
332
|
+
|
68
333
|
# Setup structured logging
|
69
334
|
log_file = Path(args.cache_dir).expanduser() / "server.log" if args.cache_dir else None
|
70
335
|
logger = setup_logging(
|
@@ -7,8 +7,10 @@ for file description management tools.
|
|
7
7
|
|
8
8
|
import asyncio
|
9
9
|
import hashlib
|
10
|
+
import html
|
10
11
|
import json
|
11
12
|
import logging
|
13
|
+
import re
|
12
14
|
import uuid
|
13
15
|
from datetime import datetime
|
14
16
|
from pathlib import Path
|
@@ -85,6 +87,102 @@ class MCPCodeIndexServer:
|
|
85
87
|
extra={"structured_data": {"initialization": {"token_limit": token_limit}}}
|
86
88
|
)
|
87
89
|
|
90
|
+
def _clean_html_entities(self, text: str) -> str:
|
91
|
+
"""
|
92
|
+
Clean HTML entities from text to prevent encoding issues.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
text: Text that may contain HTML entities
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
Text with HTML entities decoded to proper characters
|
99
|
+
"""
|
100
|
+
if not text:
|
101
|
+
return text
|
102
|
+
|
103
|
+
# Decode HTML entities like < > & etc.
|
104
|
+
return html.unescape(text)
|
105
|
+
|
106
|
+
def _clean_arguments(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
107
|
+
"""
|
108
|
+
Clean HTML entities from all text arguments.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
arguments: Dictionary of arguments to clean
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
Dictionary with HTML entities decoded in all string values
|
115
|
+
"""
|
116
|
+
cleaned = {}
|
117
|
+
|
118
|
+
for key, value in arguments.items():
|
119
|
+
if isinstance(value, str):
|
120
|
+
cleaned[key] = self._clean_html_entities(value)
|
121
|
+
elif isinstance(value, list):
|
122
|
+
# Clean strings in lists (like conflict resolutions)
|
123
|
+
cleaned[key] = [
|
124
|
+
self._clean_html_entities(item) if isinstance(item, str) else item
|
125
|
+
for item in value
|
126
|
+
]
|
127
|
+
elif isinstance(value, dict):
|
128
|
+
# Recursively clean nested dictionaries
|
129
|
+
cleaned[key] = self._clean_arguments(value)
|
130
|
+
else:
|
131
|
+
# Pass through other types unchanged
|
132
|
+
cleaned[key] = value
|
133
|
+
|
134
|
+
return cleaned
|
135
|
+
|
136
|
+
def _parse_json_robust(self, json_str: str) -> Dict[str, Any]:
|
137
|
+
"""
|
138
|
+
Parse JSON with automatic repair for common issues.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
json_str: JSON string that may have formatting issues
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
Parsed JSON dictionary
|
145
|
+
|
146
|
+
Raises:
|
147
|
+
ValueError: If JSON cannot be parsed even after repair attempts
|
148
|
+
"""
|
149
|
+
# First try normal parsing
|
150
|
+
try:
|
151
|
+
return json.loads(json_str)
|
152
|
+
except json.JSONDecodeError as original_error:
|
153
|
+
logger.warning(f"Initial JSON parse failed: {original_error}")
|
154
|
+
|
155
|
+
# Try to repair common issues
|
156
|
+
repaired = json_str
|
157
|
+
|
158
|
+
# Fix 1: Quote unquoted URLs and paths
|
159
|
+
# Look for patterns like: "key": http://... or "key": /path/...
|
160
|
+
url_pattern = r'("[\w]+"):\s*([a-zA-Z][a-zA-Z0-9+.-]*://[^\s,}]+|/[^\s,}]*)'
|
161
|
+
repaired = re.sub(url_pattern, r'\1: "\2"', repaired)
|
162
|
+
|
163
|
+
# Fix 2: Quote unquoted boolean-like strings
|
164
|
+
# Look for: "key": true-ish-string or "key": false-ish-string
|
165
|
+
bool_pattern = r'("[\w]+"):\s*([a-zA-Z][a-zA-Z0-9_-]*[a-zA-Z0-9])(?=\s*[,}])'
|
166
|
+
repaired = re.sub(bool_pattern, r'\1: "\2"', repaired)
|
167
|
+
|
168
|
+
# Fix 3: Remove trailing commas
|
169
|
+
repaired = re.sub(r',(\s*[}\]])', r'\1', repaired)
|
170
|
+
|
171
|
+
# Fix 4: Ensure proper string quoting for common unquoted values
|
172
|
+
# Handle cases like: "key": value (where value should be "value")
|
173
|
+
unquoted_pattern = r'("[\w]+"):\s*([a-zA-Z0-9_-]+)(?=\s*[,}])'
|
174
|
+
repaired = re.sub(unquoted_pattern, r'\1: "\2"', repaired)
|
175
|
+
|
176
|
+
try:
|
177
|
+
result = json.loads(repaired)
|
178
|
+
logger.info(f"Successfully repaired JSON. Original: {json_str[:100]}...")
|
179
|
+
logger.info(f"Repaired: {repaired[:100]}...")
|
180
|
+
return result
|
181
|
+
except json.JSONDecodeError as repair_error:
|
182
|
+
logger.error(f"JSON repair failed. Original: {json_str}")
|
183
|
+
logger.error(f"Repaired attempt: {repaired}")
|
184
|
+
raise ValueError(f"Could not parse JSON even after repair attempts. Original error: {original_error}, Repair error: {repair_error}")
|
185
|
+
|
88
186
|
async def initialize(self) -> None:
|
89
187
|
"""Initialize database and other resources."""
|
90
188
|
await self.db_manager.initialize()
|
@@ -116,11 +214,11 @@ class MCPCodeIndexServer:
|
|
116
214
|
"description": "Git branch name (e.g., 'main', 'develop')"
|
117
215
|
},
|
118
216
|
"remoteOrigin": {
|
119
|
-
"type":
|
217
|
+
"type": "string",
|
120
218
|
"description": "Git remote origin URL if available"
|
121
219
|
},
|
122
220
|
"upstreamOrigin": {
|
123
|
-
"type":
|
221
|
+
"type": "string",
|
124
222
|
"description": "Upstream repository URL if this is a fork"
|
125
223
|
},
|
126
224
|
"filePath": {
|
@@ -140,11 +238,11 @@ class MCPCodeIndexServer:
|
|
140
238
|
"projectName": {"type": "string", "description": "The name of the project"},
|
141
239
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
142
240
|
"branch": {"type": "string", "description": "Git branch name"},
|
143
|
-
"remoteOrigin": {"type":
|
144
|
-
"upstreamOrigin": {"type":
|
241
|
+
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
242
|
+
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
145
243
|
"filePath": {"type": "string", "description": "Relative path to the file from project root"},
|
146
244
|
"description": {"type": "string", "description": "Detailed description of the file's contents"},
|
147
|
-
"fileHash": {"type":
|
245
|
+
"fileHash": {"type": "string", "description": "SHA-256 hash of the file contents (optional)"}
|
148
246
|
},
|
149
247
|
"required": ["projectName", "folderPath", "branch", "filePath", "description"]
|
150
248
|
}
|
@@ -158,8 +256,9 @@ class MCPCodeIndexServer:
|
|
158
256
|
"projectName": {"type": "string", "description": "The name of the project"},
|
159
257
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
160
258
|
"branch": {"type": "string", "description": "Git branch name"},
|
161
|
-
"remoteOrigin": {"type":
|
162
|
-
"upstreamOrigin": {"type":
|
259
|
+
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
260
|
+
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
261
|
+
"tokenLimit": {"type": "integer", "description": "Optional token limit override (defaults to server configuration)"}
|
163
262
|
},
|
164
263
|
"required": ["projectName", "folderPath", "branch"]
|
165
264
|
}
|
@@ -173,8 +272,8 @@ class MCPCodeIndexServer:
|
|
173
272
|
"projectName": {"type": "string", "description": "The name of the project"},
|
174
273
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
175
274
|
"branch": {"type": "string", "description": "Git branch name"},
|
176
|
-
"remoteOrigin": {"type":
|
177
|
-
"upstreamOrigin": {"type":
|
275
|
+
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
276
|
+
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
178
277
|
"limit": {"type": "integer", "description": "Maximum number of missing files to return (optional)"}
|
179
278
|
},
|
180
279
|
"required": ["projectName", "folderPath", "branch"]
|
@@ -189,8 +288,8 @@ class MCPCodeIndexServer:
|
|
189
288
|
"projectName": {"type": "string", "description": "The name of the project"},
|
190
289
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
191
290
|
"branch": {"type": "string", "description": "Git branch to search in"},
|
192
|
-
"remoteOrigin": {"type":
|
193
|
-
"upstreamOrigin": {"type":
|
291
|
+
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
292
|
+
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
194
293
|
"query": {"type": "string", "description": "Search query (e.g., 'authentication middleware', 'database models')"},
|
195
294
|
"maxResults": {"type": "integer", "default": 20, "description": "Maximum number of results to return"}
|
196
295
|
},
|
@@ -206,8 +305,8 @@ class MCPCodeIndexServer:
|
|
206
305
|
"projectName": {"type": "string", "description": "The name of the project"},
|
207
306
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder on disk"},
|
208
307
|
"branch": {"type": "string", "description": "Git branch name"},
|
209
|
-
"remoteOrigin": {"type":
|
210
|
-
"upstreamOrigin": {"type":
|
308
|
+
"remoteOrigin": {"type": "string", "description": "Git remote origin URL if available"},
|
309
|
+
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"}
|
211
310
|
},
|
212
311
|
"required": ["projectName", "folderPath", "branch"]
|
213
312
|
}
|
@@ -220,8 +319,8 @@ class MCPCodeIndexServer:
|
|
220
319
|
"properties": {
|
221
320
|
"projectName": {"type": "string", "description": "The name of the project"},
|
222
321
|
"folderPath": {"type": "string", "description": "Absolute path to the project folder"},
|
223
|
-
"remoteOrigin": {"type":
|
224
|
-
"upstreamOrigin": {"type":
|
322
|
+
"remoteOrigin": {"type": "string", "description": "Git remote origin URL"},
|
323
|
+
"upstreamOrigin": {"type": "string", "description": "Upstream repository URL if this is a fork"},
|
225
324
|
"sourceBranch": {"type": "string", "description": "Branch to merge from (e.g., 'feature/new-ui')"},
|
226
325
|
"targetBranch": {"type": "string", "description": "Branch to merge into (e.g., 'main')"},
|
227
326
|
"conflictResolutions": {
|
@@ -269,7 +368,10 @@ class MCPCodeIndexServer:
|
|
269
368
|
|
270
369
|
async def _execute_tool_handler(self, handler, arguments: Dict[str, Any]) -> List[types.TextContent]:
|
271
370
|
"""Execute a tool handler and format the result."""
|
272
|
-
|
371
|
+
# Clean HTML entities from all arguments before processing
|
372
|
+
cleaned_arguments = self._clean_arguments(arguments)
|
373
|
+
|
374
|
+
result = await handler(cleaned_arguments)
|
273
375
|
|
274
376
|
return [types.TextContent(
|
275
377
|
type="text",
|
@@ -409,8 +511,8 @@ class MCPCodeIndexServer:
|
|
409
511
|
if not scanner.is_valid_project_directory():
|
410
512
|
return False
|
411
513
|
|
412
|
-
current_files = scanner.
|
413
|
-
current_basenames = {
|
514
|
+
current_files = scanner.scan_directory()
|
515
|
+
current_basenames = {f.name for f in current_files}
|
414
516
|
|
415
517
|
if not current_basenames:
|
416
518
|
return False
|
@@ -584,16 +686,19 @@ class MCPCodeIndexServer:
|
|
584
686
|
branch=resolved_branch
|
585
687
|
)
|
586
688
|
|
689
|
+
# Use provided token limit or fall back to server default
|
690
|
+
token_limit = arguments.get("tokenLimit", self.token_limit)
|
691
|
+
|
587
692
|
# Calculate total tokens
|
588
693
|
total_tokens = self.token_counter.calculate_codebase_tokens(file_descriptions)
|
589
|
-
is_large =
|
590
|
-
recommendation =
|
694
|
+
is_large = total_tokens > token_limit
|
695
|
+
recommendation = "use_search" if is_large else "use_overview"
|
591
696
|
|
592
697
|
return {
|
593
698
|
"totalTokens": total_tokens,
|
594
699
|
"isLarge": is_large,
|
595
700
|
"recommendation": recommendation,
|
596
|
-
"tokenLimit":
|
701
|
+
"tokenLimit": token_limit,
|
597
702
|
"totalFiles": len(file_descriptions)
|
598
703
|
}
|
599
704
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-code-indexer
|
3
|
-
Version: 1.1.
|
3
|
+
Version: 1.1.5
|
4
4
|
Summary: MCP server that tracks file descriptions across codebases, enabling AI agents to efficiently navigate and understand code through searchable summaries and token-aware overviews.
|
5
5
|
Author: MCP Code Indexer Contributors
|
6
6
|
Maintainer: MCP Code Indexer Contributors
|
@@ -1,8 +1,9 @@
|
|
1
1
|
mcp_code_indexer/__init__.py,sha256=PUkiM7VGRk7n2B_Ma0fzZWC0wmHCjyE15wxsvU9I54E,473
|
2
|
+
mcp_code_indexer/__main__.py,sha256=4Edinoe0ug43hobuLYcjTmGp2YJnlFYN4_8iKvUBJ0Q,213
|
2
3
|
mcp_code_indexer/error_handler.py,sha256=cNSUFFrGBMLDv4qa78c7495L1wSl_dXCRbzCJOidx-Q,11590
|
3
4
|
mcp_code_indexer/file_scanner.py,sha256=ctXeZMROgDThEtjzsANTK9TbK-fhTScMBd4iyuleBT4,11734
|
4
5
|
mcp_code_indexer/logging_config.py,sha256=5L1cYIG8IAX91yCjc5pzkbO_KPt0bvm_ABHB53LBZjI,5184
|
5
|
-
mcp_code_indexer/main.py,sha256=
|
6
|
+
mcp_code_indexer/main.py,sha256=eRc0Vl3DVDGS5XtuPCDBArgmqcBIi92O97LbE8HYGGA,13601
|
6
7
|
mcp_code_indexer/merge_handler.py,sha256=lJR8eVq2qSrF6MW9mR3Fy8UzrNAaQ7RsI2FMNXne3vQ,14692
|
7
8
|
mcp_code_indexer/token_counter.py,sha256=WrifOkbF99nWWHlRlhCHAB2KN7qr83GOHl7apE-hJcE,8460
|
8
9
|
mcp_code_indexer/database/__init__.py,sha256=aPq_aaRp0aSwOBIq9GkuMNjmLxA411zg2vhdrAuHm-w,38
|
@@ -11,12 +12,12 @@ mcp_code_indexer/database/models.py,sha256=3wOxHKb6j3zKPWFSwB5g1TLpI507vLNZcqsxZ
|
|
11
12
|
mcp_code_indexer/middleware/__init__.py,sha256=p-mP0pMsfiU2yajCPvokCUxUEkh_lu4XJP1LyyMW2ug,220
|
12
13
|
mcp_code_indexer/middleware/error_middleware.py,sha256=v6jaHmPxf3qerYdb85X1tHIXLxgcbybpitKVakFLQTA,10109
|
13
14
|
mcp_code_indexer/server/__init__.py,sha256=16xMcuriUOBlawRqWNBk6niwrvtv_JD5xvI36X1Vsmk,41
|
14
|
-
mcp_code_indexer/server/mcp_server.py,sha256=
|
15
|
+
mcp_code_indexer/server/mcp_server.py,sha256=DtfoeoOF72kdWO7fCYU9qvhg3LUyJU12KpjCCqw_Uxw,50589
|
15
16
|
mcp_code_indexer/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4,sha256=Ijkht27pm96ZW3_3OFE-7xAPtR0YyTWXoRO8_-hlsqc,1681126
|
16
17
|
mcp_code_indexer/tools/__init__.py,sha256=m01mxML2UdD7y5rih_XNhNSCMzQTz7WQ_T1TeOcYlnE,49
|
17
|
-
mcp_code_indexer-1.1.
|
18
|
-
mcp_code_indexer-1.1.
|
19
|
-
mcp_code_indexer-1.1.
|
20
|
-
mcp_code_indexer-1.1.
|
21
|
-
mcp_code_indexer-1.1.
|
22
|
-
mcp_code_indexer-1.1.
|
18
|
+
mcp_code_indexer-1.1.5.dist-info/licenses/LICENSE,sha256=JN9dyPPgYwH9C-UjYM7FLNZjQ6BF7kAzpF3_4PwY4rY,1086
|
19
|
+
mcp_code_indexer-1.1.5.dist-info/METADATA,sha256=anW6T4WSZBQjIgvFaViNibdh3mgIeLW35vxiaj3wrjM,11930
|
20
|
+
mcp_code_indexer-1.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
21
|
+
mcp_code_indexer-1.1.5.dist-info/entry_points.txt,sha256=8HqWOw1Is7jOP1bvIgaSwouvT9z_Boe-9hd4NzyJOhY,68
|
22
|
+
mcp_code_indexer-1.1.5.dist-info/top_level.txt,sha256=yKYCM-gMGt-cnupGfAhnZaoEsROLB6DQ1KFUuyKx4rw,17
|
23
|
+
mcp_code_indexer-1.1.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|