mcp-code-indexer 1.0.8__tar.gz → 1.1.0__tar.gz
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-1.0.8/src/mcp_code_indexer.egg-info → mcp_code_indexer-1.1.0}/PKG-INFO +1 -1
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/pyproject.toml +1 -1
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/database/database.py +24 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/server/mcp_server.py +170 -35
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0/src/mcp_code_indexer.egg-info}/PKG-INFO +1 -1
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/LICENSE +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/MANIFEST.in +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/README.md +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/docs/api-reference.md +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/docs/architecture.md +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/docs/configuration.md +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/docs/contributing.md +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/migrations/001_initial.sql +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/migrations/002_performance_indexes.sql +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/requirements.txt +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/setup.cfg +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/setup.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/__init__.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/database/__init__.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/database/models.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/error_handler.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/file_scanner.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/logging_config.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/main.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/merge_handler.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/middleware/__init__.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/middleware/error_middleware.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/server/__init__.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4 +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/token_counter.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/tools/__init__.py +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer.egg-info/SOURCES.txt +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer.egg-info/dependency_links.txt +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer.egg-info/entry_points.txt +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer.egg-info/requires.txt +0 -0
- {mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-code-indexer
|
3
|
-
Version: 1.0
|
3
|
+
Version: 1.1.0
|
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
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "mcp-code-indexer"
|
7
|
-
version = "1.0
|
7
|
+
version = "1.1.0"
|
8
8
|
description = "MCP server that tracks file descriptions across codebases, enabling AI agents to efficiently navigate and understand code through searchable summaries and token-aware overviews."
|
9
9
|
readme = "README.md"
|
10
10
|
license = {text = "MIT"}
|
@@ -210,6 +210,30 @@ class DatabaseManager:
|
|
210
210
|
await db.commit()
|
211
211
|
logger.debug(f"Updated project: {project.id}")
|
212
212
|
|
213
|
+
async def get_all_projects(self) -> List[Project]:
|
214
|
+
"""Get all projects in the database."""
|
215
|
+
async with self.get_connection() as db:
|
216
|
+
cursor = await db.execute(
|
217
|
+
"SELECT id, name, remote_origin, upstream_origin, aliases, created, last_accessed FROM projects"
|
218
|
+
)
|
219
|
+
rows = await cursor.fetchall()
|
220
|
+
|
221
|
+
projects = []
|
222
|
+
for row in rows:
|
223
|
+
aliases = json.loads(row[4]) if row[4] else []
|
224
|
+
project = Project(
|
225
|
+
id=row[0],
|
226
|
+
name=row[1],
|
227
|
+
remote_origin=row[2],
|
228
|
+
upstream_origin=row[3],
|
229
|
+
aliases=aliases,
|
230
|
+
created=row[5],
|
231
|
+
last_accessed=row[6]
|
232
|
+
)
|
233
|
+
projects.append(project)
|
234
|
+
|
235
|
+
return projects
|
236
|
+
|
213
237
|
# File description operations
|
214
238
|
|
215
239
|
async def create_file_description(self, file_desc: FileDescription) -> None:
|
@@ -9,9 +9,10 @@ import asyncio
|
|
9
9
|
import hashlib
|
10
10
|
import json
|
11
11
|
import logging
|
12
|
+
import uuid
|
12
13
|
from datetime import datetime
|
13
14
|
from pathlib import Path
|
14
|
-
from typing import Any, Dict, List, Optional
|
15
|
+
from typing import Any, Dict, List, Optional, Set
|
15
16
|
|
16
17
|
from mcp import types
|
17
18
|
from mcp.server import Server
|
@@ -276,24 +277,48 @@ class MCPCodeIndexServer:
|
|
276
277
|
)]
|
277
278
|
|
278
279
|
async def _get_or_create_project_id(self, arguments: Dict[str, Any]) -> str:
|
279
|
-
"""
|
280
|
+
"""
|
281
|
+
Get or create a project ID using intelligent matching.
|
282
|
+
|
283
|
+
Matches projects based on 2+ out of 4 identification factors:
|
284
|
+
1. Project name (normalized, case-insensitive)
|
285
|
+
2. Remote origin URL
|
286
|
+
3. Upstream origin URL
|
287
|
+
4. Any folder path in aliases
|
288
|
+
|
289
|
+
If only 1 factor matches, uses file similarity to determine if it's the same project.
|
290
|
+
"""
|
280
291
|
project_name = arguments["projectName"]
|
281
292
|
remote_origin = arguments.get("remoteOrigin")
|
282
293
|
upstream_origin = arguments.get("upstreamOrigin")
|
283
294
|
folder_path = arguments["folderPath"]
|
284
295
|
branch = arguments.get("branch", "main")
|
285
296
|
|
286
|
-
#
|
287
|
-
|
288
|
-
id_source = f"{project_name}:{folder_path}"
|
289
|
-
project_id = hashlib.sha256(id_source.encode()).hexdigest()[:16]
|
297
|
+
# Normalize project name for case-insensitive matching
|
298
|
+
normalized_name = project_name.lower()
|
290
299
|
|
291
|
-
#
|
292
|
-
project = await self.
|
293
|
-
|
300
|
+
# Find potential project matches
|
301
|
+
project = await self._find_matching_project(
|
302
|
+
normalized_name, remote_origin, upstream_origin, folder_path
|
303
|
+
)
|
304
|
+
if project:
|
305
|
+
# Update project metadata and aliases
|
306
|
+
await self._update_existing_project(project, normalized_name, remote_origin, upstream_origin, folder_path)
|
307
|
+
|
308
|
+
# Check if upstream inheritance is needed
|
309
|
+
if upstream_origin and await self.db_manager.check_upstream_inheritance_needed(project):
|
310
|
+
try:
|
311
|
+
inherited_count = await self.db_manager.inherit_from_upstream(project, branch)
|
312
|
+
if inherited_count > 0:
|
313
|
+
logger.info(f"Auto-inherited {inherited_count} descriptions from upstream for {normalized_name}")
|
314
|
+
except Exception as e:
|
315
|
+
logger.warning(f"Failed to inherit from upstream: {e}")
|
316
|
+
else:
|
317
|
+
# Create new project with UUID
|
318
|
+
project_id = str(uuid.uuid4())
|
294
319
|
project = Project(
|
295
320
|
id=project_id,
|
296
|
-
name=
|
321
|
+
name=normalized_name,
|
297
322
|
remote_origin=remote_origin,
|
298
323
|
upstream_origin=upstream_origin,
|
299
324
|
aliases=[folder_path],
|
@@ -301,42 +326,152 @@ class MCPCodeIndexServer:
|
|
301
326
|
last_accessed=datetime.utcnow()
|
302
327
|
)
|
303
328
|
await self.db_manager.create_project(project)
|
329
|
+
logger.info(f"Created new project: {normalized_name} ({project_id})")
|
304
330
|
|
305
331
|
# Auto-inherit from upstream if needed
|
306
332
|
if upstream_origin:
|
307
333
|
try:
|
308
334
|
inherited_count = await self.db_manager.inherit_from_upstream(project, branch)
|
309
335
|
if inherited_count > 0:
|
310
|
-
logger.info(f"Auto-inherited {inherited_count} descriptions from upstream for {
|
336
|
+
logger.info(f"Auto-inherited {inherited_count} descriptions from upstream for {normalized_name}")
|
311
337
|
except Exception as e:
|
312
338
|
logger.warning(f"Failed to inherit from upstream: {e}")
|
313
|
-
|
314
|
-
|
315
|
-
|
339
|
+
|
340
|
+
return project.id
|
341
|
+
|
342
|
+
async def _find_matching_project(
|
343
|
+
self,
|
344
|
+
normalized_name: str,
|
345
|
+
remote_origin: Optional[str],
|
346
|
+
upstream_origin: Optional[str],
|
347
|
+
folder_path: str
|
348
|
+
) -> Optional[Project]:
|
349
|
+
"""
|
350
|
+
Find a matching project using intelligent 2-out-of-4 matching logic.
|
351
|
+
|
352
|
+
Returns the best matching project or None if no sufficient match is found.
|
353
|
+
"""
|
354
|
+
all_projects = await self.db_manager.get_all_projects()
|
355
|
+
|
356
|
+
best_match = None
|
357
|
+
best_score = 0
|
358
|
+
|
359
|
+
for project in all_projects:
|
360
|
+
score = 0
|
361
|
+
match_factors = []
|
362
|
+
|
363
|
+
# Factor 1: Project name match
|
364
|
+
if project.name.lower() == normalized_name:
|
365
|
+
score += 1
|
366
|
+
match_factors.append("name")
|
367
|
+
|
368
|
+
# Factor 2: Remote origin match
|
369
|
+
if remote_origin and project.remote_origin == remote_origin:
|
370
|
+
score += 1
|
371
|
+
match_factors.append("remote_origin")
|
372
|
+
|
373
|
+
# Factor 3: Upstream origin match
|
374
|
+
if upstream_origin and project.upstream_origin == upstream_origin:
|
375
|
+
score += 1
|
376
|
+
match_factors.append("upstream_origin")
|
377
|
+
|
378
|
+
# Factor 4: Folder path in aliases
|
379
|
+
project_aliases = json.loads(project.aliases) if isinstance(project.aliases, str) else project.aliases
|
380
|
+
if folder_path in project_aliases:
|
381
|
+
score += 1
|
382
|
+
match_factors.append("folder_path")
|
316
383
|
|
317
|
-
#
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
project.upstream_origin = upstream_origin
|
324
|
-
should_update = True
|
384
|
+
# If we have 2+ matches, this is a strong candidate
|
385
|
+
if score >= 2:
|
386
|
+
if score > best_score:
|
387
|
+
best_score = score
|
388
|
+
best_match = project
|
389
|
+
logger.info(f"Strong match for project {project.name} (score: {score}, factors: {match_factors})")
|
325
390
|
|
326
|
-
|
327
|
-
|
328
|
-
|
391
|
+
# If only 1 match, check file similarity for potential matches
|
392
|
+
elif score == 1:
|
393
|
+
if await self._check_file_similarity(project, folder_path):
|
394
|
+
logger.info(f"File similarity match for project {project.name} (factor: {match_factors[0]})")
|
395
|
+
if score > best_score:
|
396
|
+
best_score = score
|
397
|
+
best_match = project
|
398
|
+
|
399
|
+
return best_match
|
400
|
+
|
401
|
+
async def _check_file_similarity(self, project: Project, folder_path: str) -> bool:
|
402
|
+
"""
|
403
|
+
Check if the files in the folder are similar to files already indexed for this project.
|
404
|
+
Returns True if 80%+ of files match.
|
405
|
+
"""
|
406
|
+
try:
|
407
|
+
# Get files currently in the folder
|
408
|
+
scanner = FileScanner(Path(folder_path))
|
409
|
+
if not scanner.is_valid_project_directory():
|
410
|
+
return False
|
329
411
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
412
|
+
current_files = scanner.scan_files()
|
413
|
+
current_basenames = {Path(f).name for f in current_files}
|
414
|
+
|
415
|
+
if not current_basenames:
|
416
|
+
return False
|
417
|
+
|
418
|
+
# Get files already indexed for this project
|
419
|
+
indexed_files = await self.db_manager.get_all_file_descriptions(project.id, "main")
|
420
|
+
indexed_basenames = {Path(fd.file_path).name for fd in indexed_files}
|
421
|
+
|
422
|
+
if not indexed_basenames:
|
423
|
+
return False
|
424
|
+
|
425
|
+
# Calculate similarity
|
426
|
+
intersection = current_basenames & indexed_basenames
|
427
|
+
similarity = len(intersection) / len(current_basenames)
|
428
|
+
|
429
|
+
logger.debug(f"File similarity for {project.name}: {similarity:.2%} ({len(intersection)}/{len(current_basenames)} files match)")
|
430
|
+
|
431
|
+
return similarity >= 0.8
|
432
|
+
except Exception as e:
|
433
|
+
logger.warning(f"Error checking file similarity: {e}")
|
434
|
+
return False
|
435
|
+
|
436
|
+
async def _update_existing_project(
|
437
|
+
self,
|
438
|
+
project: Project,
|
439
|
+
normalized_name: str,
|
440
|
+
remote_origin: Optional[str],
|
441
|
+
upstream_origin: Optional[str],
|
442
|
+
folder_path: str
|
443
|
+
) -> None:
|
444
|
+
"""Update an existing project with new metadata and folder alias."""
|
445
|
+
# Update last accessed time
|
446
|
+
await self.db_manager.update_project_access_time(project.id)
|
447
|
+
|
448
|
+
should_update = False
|
449
|
+
|
450
|
+
# Update name if different
|
451
|
+
if project.name != normalized_name:
|
452
|
+
project.name = normalized_name
|
453
|
+
should_update = True
|
454
|
+
|
455
|
+
# Update remote/upstream origins if provided and different
|
456
|
+
if remote_origin and project.remote_origin != remote_origin:
|
457
|
+
project.remote_origin = remote_origin
|
458
|
+
should_update = True
|
459
|
+
|
460
|
+
if upstream_origin and project.upstream_origin != upstream_origin:
|
461
|
+
project.upstream_origin = upstream_origin
|
462
|
+
should_update = True
|
463
|
+
|
464
|
+
# Add folder path to aliases if not already present
|
465
|
+
project_aliases = json.loads(project.aliases) if isinstance(project.aliases, str) else project.aliases
|
466
|
+
if folder_path not in project_aliases:
|
467
|
+
project_aliases.append(folder_path)
|
468
|
+
project.aliases = project_aliases
|
469
|
+
should_update = True
|
470
|
+
logger.info(f"Added new folder alias to project {project.name}: {folder_path}")
|
471
|
+
|
472
|
+
if should_update:
|
473
|
+
await self.db_manager.update_project(project)
|
474
|
+
logger.debug(f"Updated project metadata for {project.name}")
|
340
475
|
|
341
476
|
async def _handle_get_file_description(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
342
477
|
"""Handle get_file_description tool calls."""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-code-indexer
|
3
|
-
Version: 1.0
|
3
|
+
Version: 1.1.0
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer/middleware/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer.egg-info/dependency_links.txt
RENAMED
File without changes
|
{mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer.egg-info/entry_points.txt
RENAMED
File without changes
|
{mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer.egg-info/requires.txt
RENAMED
File without changes
|
{mcp_code_indexer-1.0.8 → mcp_code_indexer-1.1.0}/src/mcp_code_indexer.egg-info/top_level.txt
RENAMED
File without changes
|