mcp-vector-search 0.12.6__py3-none-any.whl → 1.0.3__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.
Files changed (65) hide show
  1. mcp_vector_search/__init__.py +2 -2
  2. mcp_vector_search/analysis/__init__.py +64 -0
  3. mcp_vector_search/analysis/collectors/__init__.py +39 -0
  4. mcp_vector_search/analysis/collectors/base.py +164 -0
  5. mcp_vector_search/analysis/collectors/complexity.py +743 -0
  6. mcp_vector_search/analysis/metrics.py +341 -0
  7. mcp_vector_search/analysis/reporters/__init__.py +5 -0
  8. mcp_vector_search/analysis/reporters/console.py +222 -0
  9. mcp_vector_search/cli/commands/analyze.py +408 -0
  10. mcp_vector_search/cli/commands/chat.py +1262 -0
  11. mcp_vector_search/cli/commands/index.py +21 -3
  12. mcp_vector_search/cli/commands/init.py +13 -0
  13. mcp_vector_search/cli/commands/install.py +597 -335
  14. mcp_vector_search/cli/commands/install_old.py +8 -4
  15. mcp_vector_search/cli/commands/mcp.py +78 -6
  16. mcp_vector_search/cli/commands/reset.py +68 -26
  17. mcp_vector_search/cli/commands/search.py +30 -7
  18. mcp_vector_search/cli/commands/setup.py +1133 -0
  19. mcp_vector_search/cli/commands/status.py +37 -2
  20. mcp_vector_search/cli/commands/uninstall.py +276 -357
  21. mcp_vector_search/cli/commands/visualize/__init__.py +39 -0
  22. mcp_vector_search/cli/commands/visualize/cli.py +276 -0
  23. mcp_vector_search/cli/commands/visualize/exporters/__init__.py +12 -0
  24. mcp_vector_search/cli/commands/visualize/exporters/html_exporter.py +33 -0
  25. mcp_vector_search/cli/commands/visualize/exporters/json_exporter.py +29 -0
  26. mcp_vector_search/cli/commands/visualize/graph_builder.py +714 -0
  27. mcp_vector_search/cli/commands/visualize/layout_engine.py +469 -0
  28. mcp_vector_search/cli/commands/visualize/server.py +311 -0
  29. mcp_vector_search/cli/commands/visualize/state_manager.py +428 -0
  30. mcp_vector_search/cli/commands/visualize/templates/__init__.py +16 -0
  31. mcp_vector_search/cli/commands/visualize/templates/base.py +180 -0
  32. mcp_vector_search/cli/commands/visualize/templates/scripts.py +2507 -0
  33. mcp_vector_search/cli/commands/visualize/templates/styles.py +1313 -0
  34. mcp_vector_search/cli/commands/visualize.py.original +2536 -0
  35. mcp_vector_search/cli/didyoumean.py +22 -2
  36. mcp_vector_search/cli/main.py +115 -159
  37. mcp_vector_search/cli/output.py +24 -8
  38. mcp_vector_search/config/__init__.py +4 -0
  39. mcp_vector_search/config/default_thresholds.yaml +52 -0
  40. mcp_vector_search/config/settings.py +12 -0
  41. mcp_vector_search/config/thresholds.py +185 -0
  42. mcp_vector_search/core/auto_indexer.py +3 -3
  43. mcp_vector_search/core/boilerplate.py +186 -0
  44. mcp_vector_search/core/config_utils.py +394 -0
  45. mcp_vector_search/core/database.py +369 -94
  46. mcp_vector_search/core/exceptions.py +11 -0
  47. mcp_vector_search/core/git_hooks.py +4 -4
  48. mcp_vector_search/core/indexer.py +221 -4
  49. mcp_vector_search/core/llm_client.py +751 -0
  50. mcp_vector_search/core/models.py +3 -0
  51. mcp_vector_search/core/project.py +17 -0
  52. mcp_vector_search/core/scheduler.py +11 -11
  53. mcp_vector_search/core/search.py +179 -29
  54. mcp_vector_search/mcp/server.py +24 -5
  55. mcp_vector_search/utils/__init__.py +2 -0
  56. mcp_vector_search/utils/gitignore_updater.py +212 -0
  57. mcp_vector_search/utils/monorepo.py +66 -4
  58. mcp_vector_search/utils/timing.py +10 -6
  59. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.0.3.dist-info}/METADATA +182 -52
  60. mcp_vector_search-1.0.3.dist-info/RECORD +97 -0
  61. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.0.3.dist-info}/WHEEL +1 -1
  62. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.0.3.dist-info}/entry_points.txt +1 -0
  63. mcp_vector_search/cli/commands/visualize.py +0 -1467
  64. mcp_vector_search-0.12.6.dist-info/RECORD +0 -68
  65. {mcp_vector_search-0.12.6.dist-info → mcp_vector_search-1.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -578,7 +578,8 @@ def demo(
578
578
  demo_dir.mkdir()
579
579
 
580
580
  # Create sample files
581
- (demo_dir / "main.py").write_text("""
581
+ (demo_dir / "main.py").write_text(
582
+ """
582
583
  def main():
583
584
  '''Main entry point for the application.'''
584
585
  print("Hello, World!")
@@ -600,9 +601,11 @@ class UserService:
600
601
 
601
602
  if __name__ == "__main__":
602
603
  main()
603
- """)
604
+ """
605
+ )
604
606
 
605
- (demo_dir / "utils.py").write_text("""
607
+ (demo_dir / "utils.py").write_text(
608
+ """
606
609
  import json
607
610
  from typing import Dict, Any
608
611
 
@@ -619,7 +622,8 @@ def hash_password(password: str) -> str:
619
622
  '''Hash password for secure storage.'''
620
623
  import hashlib
621
624
  return hashlib.sha256(password.encode()).hexdigest()
622
- """)
625
+ """
626
+ )
623
627
 
624
628
  console.print(
625
629
  f"\n[bold blue]📁 Created demo project at:[/bold blue] {demo_dir}"
@@ -38,11 +38,50 @@ Each tool has its own configuration format and location.
38
38
  3. Test setup: [green]mcp-vector-search mcp test[/green]
39
39
 
40
40
  [dim]Use --force to overwrite existing configurations[/dim]
41
- """
41
+ """,
42
+ no_args_is_help=False, # Allow running without subcommand
43
+ invoke_without_command=True, # Call callback even without subcommand
42
44
  )
43
45
 
44
46
  console = Console()
45
47
 
48
+
49
+ @mcp_app.callback()
50
+ def mcp_callback(ctx: typer.Context):
51
+ """MCP server management.
52
+
53
+ When invoked without a subcommand, starts the MCP server over stdio.
54
+ Use subcommands to configure MCP integration for different AI tools.
55
+ """
56
+ # Store context for subcommands
57
+ if not ctx.obj:
58
+ ctx.obj = {}
59
+
60
+ # If a subcommand was invoked, let it handle things (check this FIRST)
61
+ if ctx.invoked_subcommand is not None:
62
+ return
63
+
64
+ # No subcommand - start the MCP server
65
+ import asyncio
66
+ from pathlib import Path
67
+
68
+ from ...mcp.server import run_mcp_server
69
+
70
+ project_root = ctx.obj.get("project_root") if ctx.obj else None
71
+ if project_root is None:
72
+ project_root = Path.cwd()
73
+
74
+ # Start the MCP server over stdio
75
+ try:
76
+ asyncio.run(run_mcp_server(project_root))
77
+ raise typer.Exit(0)
78
+ except KeyboardInterrupt:
79
+ raise typer.Exit(0)
80
+ except Exception as e:
81
+ print(f"MCP server error: {e}", file=sys.stderr)
82
+ raise typer.Exit(1)
83
+
84
+
46
85
  # Supported AI tools and their configuration details
47
86
  SUPPORTED_TOOLS = {
48
87
  "auggie": {
@@ -123,6 +162,29 @@ def get_mcp_server_command(
123
162
  return f"{python_exe} -m mcp_vector_search.mcp.server{watch_flag} {project_root}"
124
163
 
125
164
 
165
+ def detect_install_method() -> tuple[str, list[str]]:
166
+ """Detect how mcp-vector-search is installed and return appropriate command.
167
+
168
+ Returns:
169
+ Tuple of (command, args) for running mcp-vector-search mcp
170
+ """
171
+ # Check if we're in a uv-managed environment
172
+ # uv sets UV_PROJECT_ENVIRONMENT or has .venv structure
173
+ if os.environ.get("VIRTUAL_ENV") and ".venv" in os.environ.get("VIRTUAL_ENV", ""):
174
+ # Likely uv project environment
175
+ if shutil.which("uv"):
176
+ return ("uv", ["run", "mcp-vector-search", "mcp"])
177
+
178
+ # Check if mcp-vector-search is directly available in PATH
179
+ mcp_cmd = shutil.which("mcp-vector-search")
180
+ if mcp_cmd:
181
+ # Installed via pipx or pip - use direct command
182
+ return ("mcp-vector-search", ["mcp"])
183
+
184
+ # Fallback to uv run (development mode)
185
+ return ("uv", ["run", "mcp-vector-search", "mcp"])
186
+
187
+
126
188
  def get_mcp_server_config_for_tool(
127
189
  project_root: Path,
128
190
  tool_name: str,
@@ -130,9 +192,11 @@ def get_mcp_server_config_for_tool(
130
192
  enable_file_watching: bool = True,
131
193
  ) -> dict[str, Any]:
132
194
  """Generate MCP server configuration for a specific tool."""
195
+ command, args = detect_install_method()
196
+
133
197
  base_config = {
134
- "command": "uv",
135
- "args": ["run", "mcp-vector-search", "mcp"],
198
+ "command": command,
199
+ "args": args,
136
200
  "env": {
137
201
  "MCP_ENABLE_FILE_WATCHING": "true" if enable_file_watching else "false"
138
202
  },
@@ -183,11 +247,12 @@ def create_project_claude_config(
183
247
  if "mcpServers" not in config:
184
248
  config["mcpServers"] = {}
185
249
 
186
- # Use uv for better compatibility, with proper args structure
250
+ # Detect installation method and use appropriate command
251
+ command, args = detect_install_method()
187
252
  config["mcpServers"][server_name] = {
188
253
  "type": "stdio",
189
- "command": "uv",
190
- "args": ["run", "mcp-vector-search", "mcp"],
254
+ "command": command,
255
+ "args": args,
191
256
  "env": {
192
257
  "MCP_ENABLE_FILE_WATCHING": "true" if enable_file_watching else "false"
193
258
  },
@@ -198,6 +263,13 @@ def create_project_claude_config(
198
263
  json.dump(config, f, indent=2)
199
264
 
200
265
  print_success("Created project-level .mcp.json with MCP server configuration")
266
+
267
+ # Show which command will be used
268
+ if command == "uv":
269
+ print_info(f"Using uv: {command} {' '.join(args)}")
270
+ else:
271
+ print_info(f"Using direct command: {command} {' '.join(args)}")
272
+
201
273
  if enable_file_watching:
202
274
  print_info("File watching is enabled for automatic reindexing")
203
275
  else:
@@ -82,27 +82,58 @@ def reset_index(
82
82
  console.print("[yellow]Reset cancelled[/yellow]")
83
83
  raise typer.Exit(0)
84
84
 
85
- # Get the database directory
86
- project_manager.load_config()
87
- db_path = root / ".mcp_vector_search" / "db"
85
+ # Get the database directory from config
86
+ config = project_manager.load_config()
87
+ db_path = Path(config.index_path)
88
+
89
+ # Check if index exists (look for chroma.sqlite3 or collection directories)
90
+ has_index = (db_path / "chroma.sqlite3").exists()
88
91
 
89
- if not db_path.exists():
92
+ if not has_index:
90
93
  print_warning("No index found. Nothing to reset.")
91
94
  raise typer.Exit(0)
92
95
 
96
+ # Files/dirs to remove (index data)
97
+ index_files = [
98
+ "chroma.sqlite3",
99
+ "cache",
100
+ "indexing_errors.log",
101
+ "index_metadata.json",
102
+ "directory_index.json",
103
+ ]
104
+
105
+ # Also remove any UUID-named directories (ChromaDB collections)
106
+ if db_path.exists():
107
+ for item in db_path.iterdir():
108
+ if item.is_dir() and len(item.name) == 36 and "-" in item.name:
109
+ # Looks like a UUID directory
110
+ index_files.append(item.name)
111
+
93
112
  # Create backup if requested
94
113
  if backup:
95
- backup_dir = root / ".mcp_vector_search" / "backups"
114
+ backup_dir = db_path / "backups"
96
115
  backup_dir.mkdir(exist_ok=True)
97
116
 
98
117
  import time
99
118
 
100
119
  timestamp = int(time.time())
101
- backup_path = backup_dir / f"db_backup_{timestamp}"
120
+ backup_path = backup_dir / f"index_backup_{timestamp}"
121
+ backup_path.mkdir(exist_ok=True)
102
122
 
103
123
  try:
104
- shutil.copytree(db_path, backup_path)
105
- print_success(f"Created backup at: {backup_path.relative_to(root)}")
124
+ backed_up = []
125
+ for file in index_files:
126
+ src = db_path / file
127
+ if src.exists():
128
+ dest = backup_path / file
129
+ if src.is_dir():
130
+ shutil.copytree(src, dest)
131
+ else:
132
+ shutil.copy2(src, dest)
133
+ backed_up.append(file)
134
+
135
+ if backed_up:
136
+ print_success(f"Created backup at: {backup_path.relative_to(root)}")
106
137
  except Exception as e:
107
138
  print_warning(f"Could not create backup: {e}")
108
139
  if not force:
@@ -110,12 +141,25 @@ def reset_index(
110
141
  console.print("[yellow]Reset cancelled[/yellow]")
111
142
  raise typer.Exit(0)
112
143
 
113
- # Clear the index
144
+ # Clear the index files
114
145
  console.print("[cyan]Clearing index...[/cyan]")
146
+ removed_count = 0
115
147
  try:
116
- shutil.rmtree(db_path)
117
- db_path.mkdir(parents=True, exist_ok=True)
118
- print_success("Index cleared successfully!")
148
+ for file in index_files:
149
+ path = db_path / file
150
+ if path.exists():
151
+ if path.is_dir():
152
+ shutil.rmtree(path)
153
+ else:
154
+ path.unlink()
155
+ removed_count += 1
156
+
157
+ if removed_count > 0:
158
+ print_success(
159
+ f"Index cleared successfully! ({removed_count} items removed)"
160
+ )
161
+ else:
162
+ print_warning("No index files found to remove.")
119
163
  except Exception as e:
120
164
  print_error(f"Failed to clear index: {e}")
121
165
  raise typer.Exit(1)
@@ -215,19 +259,9 @@ def reset_all(
215
259
  raise typer.Exit(1)
216
260
 
217
261
 
218
- @reset_app.command("health")
219
262
  async def check_health(
220
- project_root: Path = typer.Option(
221
- None,
222
- "--project-root",
223
- "-p",
224
- help="Project root directory",
225
- ),
226
- fix: bool = typer.Option(
227
- False,
228
- "--fix",
229
- help="Attempt to fix issues if found",
230
- ),
263
+ project_root: Path,
264
+ fix: bool,
231
265
  ) -> None:
232
266
  """Check the health of the search index.
233
267
 
@@ -254,7 +288,7 @@ async def check_health(
254
288
  from ...core.embeddings import create_embedding_function
255
289
 
256
290
  config = project_manager.load_config()
257
- db_path = root / ".mcp_vector_search" / "db"
291
+ db_path = Path(config.index_path)
258
292
 
259
293
  # Setup embedding function and cache
260
294
  cache_dir = get_default_cache_path(root) if config.cache_embeddings else None
@@ -376,6 +410,7 @@ main = reset_main
376
410
 
377
411
 
378
412
  # Make health check synchronous for CLI
413
+ @reset_app.command("health")
379
414
  def health_main(
380
415
  project_root: Path = typer.Option(
381
416
  None,
@@ -389,5 +424,12 @@ def health_main(
389
424
  help="Attempt to fix issues if found",
390
425
  ),
391
426
  ) -> None:
392
- """Check the health of the search index (sync wrapper)."""
427
+ """Check the health of the search index.
428
+
429
+ This command will:
430
+ - Verify database connectivity
431
+ - Check for index corruption
432
+ - Validate collection integrity
433
+ - Optionally attempt repairs with --fix
434
+ """
393
435
  asyncio.run(check_health(project_root, fix))
@@ -1,6 +1,8 @@
1
1
  """Search command for MCP Vector Search CLI."""
2
2
 
3
3
  import asyncio
4
+ import os
5
+ from fnmatch import fnmatch
4
6
  from pathlib import Path
5
7
 
6
8
  import typer
@@ -62,7 +64,7 @@ def search_main(
62
64
  None,
63
65
  "--files",
64
66
  "-f",
65
- help="Filter by file patterns (e.g., '*.py' or 'src/*.js')",
67
+ help="Filter by file glob patterns (e.g., '*.py', 'src/*.js', 'tests/*.ts'). Matches basename or relative path.",
66
68
  rich_help_panel="🔍 Filters",
67
69
  ),
68
70
  language: str | None = typer.Option(
@@ -153,8 +155,10 @@ def search_main(
153
155
 
154
156
  [bold cyan]Advanced Search:[/bold cyan]
155
157
 
156
- [green]Filter by file pattern:[/green]
157
- $ mcp-vector-search search "validation" --files "src/*.py"
158
+ [green]Filter by file pattern (glob):[/green]
159
+ $ mcp-vector-search search "validation" --files "*.py"
160
+ $ mcp-vector-search search "component" --files "src/*.tsx"
161
+ $ mcp-vector-search search "test utils" --files "tests/*.ts"
158
162
 
159
163
  [green]Find similar code:[/green]
160
164
  $ mcp-vector-search search "src/auth.py" --similar
@@ -326,7 +330,7 @@ async def run_search(
326
330
  similarity_threshold=similarity_threshold or config.similarity_threshold,
327
331
  )
328
332
 
329
- # Build filters
333
+ # Build filters (exclude file_path - will be handled with post-filtering)
330
334
  filters = {}
331
335
  if language:
332
336
  filters["language"] = language
@@ -334,9 +338,6 @@ async def run_search(
334
338
  filters["function_name"] = function_name
335
339
  if class_name:
336
340
  filters["class_name"] = class_name
337
- if files:
338
- # Simple file pattern matching (could be enhanced)
339
- filters["file_path"] = files
340
341
 
341
342
  try:
342
343
  async with database:
@@ -348,6 +349,28 @@ async def run_search(
348
349
  include_context=show_content,
349
350
  )
350
351
 
352
+ # Post-filter results by file pattern if specified
353
+ if files and results:
354
+ filtered_results = []
355
+ for result in results:
356
+ # Get relative path from project root
357
+ try:
358
+ rel_path = str(result.file_path.relative_to(project_root))
359
+ except ValueError:
360
+ # If file is outside project root, use absolute path
361
+ rel_path = str(result.file_path)
362
+
363
+ # Match against glob pattern (both full path and basename)
364
+ if fnmatch(rel_path, files) or fnmatch(
365
+ os.path.basename(rel_path), files
366
+ ):
367
+ filtered_results.append(result)
368
+
369
+ results = filtered_results
370
+ logger.debug(
371
+ f"File pattern '{files}' filtered results to {len(results)} matches"
372
+ )
373
+
351
374
  # Handle export if requested
352
375
  if export_format:
353
376
  from ..export import SearchResultExporter, get_export_path