mcp-vector-search 0.0.3__py3-none-any.whl → 0.4.11__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.

Potentially problematic release.


This version of mcp-vector-search might be problematic. Click here for more details.

Files changed (49) hide show
  1. mcp_vector_search/__init__.py +3 -2
  2. mcp_vector_search/cli/commands/auto_index.py +397 -0
  3. mcp_vector_search/cli/commands/config.py +88 -40
  4. mcp_vector_search/cli/commands/index.py +198 -52
  5. mcp_vector_search/cli/commands/init.py +472 -58
  6. mcp_vector_search/cli/commands/install.py +284 -0
  7. mcp_vector_search/cli/commands/mcp.py +495 -0
  8. mcp_vector_search/cli/commands/search.py +241 -87
  9. mcp_vector_search/cli/commands/status.py +184 -58
  10. mcp_vector_search/cli/commands/watch.py +34 -35
  11. mcp_vector_search/cli/didyoumean.py +184 -0
  12. mcp_vector_search/cli/export.py +320 -0
  13. mcp_vector_search/cli/history.py +292 -0
  14. mcp_vector_search/cli/interactive.py +342 -0
  15. mcp_vector_search/cli/main.py +163 -26
  16. mcp_vector_search/cli/output.py +63 -45
  17. mcp_vector_search/config/defaults.py +50 -36
  18. mcp_vector_search/config/settings.py +49 -35
  19. mcp_vector_search/core/auto_indexer.py +298 -0
  20. mcp_vector_search/core/connection_pool.py +322 -0
  21. mcp_vector_search/core/database.py +335 -25
  22. mcp_vector_search/core/embeddings.py +73 -29
  23. mcp_vector_search/core/exceptions.py +19 -2
  24. mcp_vector_search/core/factory.py +310 -0
  25. mcp_vector_search/core/git_hooks.py +345 -0
  26. mcp_vector_search/core/indexer.py +237 -73
  27. mcp_vector_search/core/models.py +21 -19
  28. mcp_vector_search/core/project.py +73 -58
  29. mcp_vector_search/core/scheduler.py +330 -0
  30. mcp_vector_search/core/search.py +574 -86
  31. mcp_vector_search/core/watcher.py +48 -46
  32. mcp_vector_search/mcp/__init__.py +4 -0
  33. mcp_vector_search/mcp/__main__.py +25 -0
  34. mcp_vector_search/mcp/server.py +701 -0
  35. mcp_vector_search/parsers/base.py +30 -31
  36. mcp_vector_search/parsers/javascript.py +74 -48
  37. mcp_vector_search/parsers/python.py +57 -49
  38. mcp_vector_search/parsers/registry.py +47 -32
  39. mcp_vector_search/parsers/text.py +179 -0
  40. mcp_vector_search/utils/__init__.py +40 -0
  41. mcp_vector_search/utils/gitignore.py +229 -0
  42. mcp_vector_search/utils/timing.py +334 -0
  43. mcp_vector_search/utils/version.py +47 -0
  44. {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/METADATA +173 -7
  45. mcp_vector_search-0.4.11.dist-info/RECORD +54 -0
  46. mcp_vector_search-0.0.3.dist-info/RECORD +0 -35
  47. {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/WHEEL +0 -0
  48. {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/entry_points.txt +0 -0
  49. {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/licenses/LICENSE +0 -0
@@ -2,7 +2,6 @@
2
2
 
3
3
  import asyncio
4
4
  from pathlib import Path
5
- from typing import List, Optional
6
5
 
7
6
  import typer
8
7
  from loguru import logger
@@ -12,22 +11,29 @@ from ...core.embeddings import create_embedding_function
12
11
  from ...core.exceptions import ProjectNotFoundError
13
12
  from ...core.project import ProjectManager
14
13
  from ...core.search import SemanticSearchEngine
14
+ from ..didyoumean import create_enhanced_typer
15
15
  from ..output import (
16
- console,
17
16
  print_error,
18
- print_info,
19
17
  print_search_results,
20
- print_warning,
21
18
  )
22
19
 
23
- # Create search subcommand app
24
- search_app = typer.Typer(help="Search code semantically")
20
+ # Create search subcommand app with "did you mean" functionality (kept for backward compatibility)
21
+ search_app = create_enhanced_typer(help="Search code semantically")
25
22
 
26
23
 
27
- @search_app.command()
28
- def main(
24
+ def search_main(
29
25
  ctx: typer.Context,
30
26
  query: str = typer.Argument(..., help="Search query"),
27
+ project_root: Path | None = typer.Option(
28
+ None,
29
+ "--project-root",
30
+ "-p",
31
+ help="Project root directory (auto-detected if not specified)",
32
+ exists=True,
33
+ file_okay=False,
34
+ dir_okay=True,
35
+ readable=True,
36
+ ),
31
37
  limit: int = typer.Option(
32
38
  10,
33
39
  "--limit",
@@ -36,28 +42,28 @@ def main(
36
42
  min=1,
37
43
  max=100,
38
44
  ),
39
- files: Optional[str] = typer.Option(
45
+ files: str | None = typer.Option(
40
46
  None,
41
47
  "--files",
42
48
  "-f",
43
49
  help="Filter by file patterns (e.g., '*.py' or 'src/*.js')",
44
50
  ),
45
- language: Optional[str] = typer.Option(
51
+ language: str | None = typer.Option(
46
52
  None,
47
53
  "--language",
48
54
  help="Filter by programming language",
49
55
  ),
50
- function_name: Optional[str] = typer.Option(
56
+ function_name: str | None = typer.Option(
51
57
  None,
52
58
  "--function",
53
59
  help="Filter by function name",
54
60
  ),
55
- class_name: Optional[str] = typer.Option(
61
+ class_name: str | None = typer.Option(
56
62
  None,
57
63
  "--class",
58
64
  help="Filter by class name",
59
65
  ),
60
- similarity_threshold: Optional[float] = typer.Option(
66
+ similarity_threshold: float | None = typer.Option(
61
67
  None,
62
68
  "--threshold",
63
69
  "-t",
@@ -65,6 +71,21 @@ def main(
65
71
  min=0.0,
66
72
  max=1.0,
67
73
  ),
74
+ similar: bool = typer.Option(
75
+ False,
76
+ "--similar",
77
+ help="Find code similar to the query (treats query as file path)",
78
+ ),
79
+ context: bool = typer.Option(
80
+ False,
81
+ "--context",
82
+ help="Search for code based on contextual description",
83
+ ),
84
+ focus: str | None = typer.Option(
85
+ None,
86
+ "--focus",
87
+ help="Focus areas for context search (comma-separated)",
88
+ ),
68
89
  no_content: bool = typer.Option(
69
90
  False,
70
91
  "--no-content",
@@ -75,35 +96,92 @@ def main(
75
96
  "--json",
76
97
  help="Output results in JSON format",
77
98
  ),
99
+ export_format: str | None = typer.Option(
100
+ None,
101
+ "--export",
102
+ help="Export results to file (json, csv, markdown, summary)",
103
+ ),
104
+ export_path: Path | None = typer.Option(
105
+ None,
106
+ "--export-path",
107
+ help="Custom export file path",
108
+ ),
78
109
  ) -> None:
79
110
  """Search your codebase semantically.
80
-
111
+
81
112
  This command performs semantic search across your indexed codebase,
82
113
  finding code that is conceptually similar to your query even if it
83
114
  doesn't contain the exact keywords.
84
-
115
+
85
116
  Examples:
86
117
  mcp-vector-search search "authentication middleware"
87
118
  mcp-vector-search search "database connection" --language python
88
119
  mcp-vector-search search "error handling" --files "*.js" --limit 5
89
120
  mcp-vector-search search "user validation" --function validate --json
121
+ mcp-vector-search search "src/auth.py" --similar
122
+ mcp-vector-search search "implement rate limiting" --context
123
+ mcp-vector-search search "user authentication" --context --focus security,middleware
90
124
  """
91
125
  try:
92
- project_root = ctx.obj.get("project_root") or Path.cwd()
93
-
94
- asyncio.run(run_search(
95
- project_root=project_root,
96
- query=query,
97
- limit=limit,
98
- files=files,
99
- language=language,
100
- function_name=function_name,
101
- class_name=class_name,
102
- similarity_threshold=similarity_threshold,
103
- show_content=not no_content,
104
- json_output=json_output,
105
- ))
106
-
126
+ project_root = project_root or ctx.obj.get("project_root") or Path.cwd()
127
+
128
+ # Validate mutually exclusive options
129
+ if similar and context:
130
+ print_error("Cannot use both --similar and --context flags together")
131
+ raise typer.Exit(1)
132
+
133
+ # Route to appropriate search function
134
+ if similar:
135
+ # Similar search - treat query as file path
136
+ file_path = Path(query)
137
+ if not file_path.exists():
138
+ print_error(f"File not found: {query}")
139
+ raise typer.Exit(1)
140
+
141
+ asyncio.run(
142
+ run_similar_search(
143
+ project_root=project_root,
144
+ file_path=file_path,
145
+ function_name=function_name,
146
+ limit=limit,
147
+ similarity_threshold=similarity_threshold,
148
+ json_output=json_output,
149
+ )
150
+ )
151
+ elif context:
152
+ # Context search
153
+ focus_areas = None
154
+ if focus:
155
+ focus_areas = [area.strip() for area in focus.split(",")]
156
+
157
+ asyncio.run(
158
+ run_context_search(
159
+ project_root=project_root,
160
+ description=query,
161
+ focus_areas=focus_areas,
162
+ limit=limit,
163
+ json_output=json_output,
164
+ )
165
+ )
166
+ else:
167
+ # Default semantic search
168
+ asyncio.run(
169
+ run_search(
170
+ project_root=project_root,
171
+ query=query,
172
+ limit=limit,
173
+ files=files,
174
+ language=language,
175
+ function_name=function_name,
176
+ class_name=class_name,
177
+ similarity_threshold=similarity_threshold,
178
+ show_content=not no_content,
179
+ json_output=json_output,
180
+ export_format=export_format,
181
+ export_path=export_path,
182
+ )
183
+ )
184
+
107
185
  except Exception as e:
108
186
  logger.error(f"Search failed: {e}")
109
187
  print_error(f"Search failed: {e}")
@@ -114,38 +192,40 @@ async def run_search(
114
192
  project_root: Path,
115
193
  query: str,
116
194
  limit: int = 10,
117
- files: Optional[str] = None,
118
- language: Optional[str] = None,
119
- function_name: Optional[str] = None,
120
- class_name: Optional[str] = None,
121
- similarity_threshold: Optional[float] = None,
195
+ files: str | None = None,
196
+ language: str | None = None,
197
+ function_name: str | None = None,
198
+ class_name: str | None = None,
199
+ similarity_threshold: float | None = None,
122
200
  show_content: bool = True,
123
201
  json_output: bool = False,
202
+ export_format: str | None = None,
203
+ export_path: Path | None = None,
124
204
  ) -> None:
125
205
  """Run semantic search."""
126
206
  # Load project configuration
127
207
  project_manager = ProjectManager(project_root)
128
-
208
+
129
209
  if not project_manager.is_initialized():
130
210
  raise ProjectNotFoundError(
131
211
  f"Project not initialized at {project_root}. Run 'mcp-vector-search init' first."
132
212
  )
133
-
213
+
134
214
  config = project_manager.load_config()
135
-
215
+
136
216
  # Setup database and search engine
137
217
  embedding_function, _ = create_embedding_function(config.embedding_model)
138
218
  database = ChromaVectorDatabase(
139
219
  persist_directory=config.index_path,
140
220
  embedding_function=embedding_function,
141
221
  )
142
-
222
+
143
223
  search_engine = SemanticSearchEngine(
144
224
  database=database,
145
225
  project_root=project_root,
146
226
  similarity_threshold=similarity_threshold or config.similarity_threshold,
147
227
  )
148
-
228
+
149
229
  # Build filters
150
230
  filters = {}
151
231
  if language:
@@ -157,7 +237,7 @@ async def run_search(
157
237
  if files:
158
238
  # Simple file pattern matching (could be enhanced)
159
239
  filters["file_path"] = files
160
-
240
+
161
241
  try:
162
242
  async with database:
163
243
  results = await search_engine.search(
@@ -167,9 +247,53 @@ async def run_search(
167
247
  similarity_threshold=similarity_threshold,
168
248
  include_context=show_content,
169
249
  )
170
-
250
+
251
+ # Handle export if requested
252
+ if export_format:
253
+ from ..export import SearchResultExporter, get_export_path
254
+
255
+ exporter = SearchResultExporter()
256
+
257
+ # Determine export path
258
+ if export_path:
259
+ output_path = export_path
260
+ else:
261
+ output_path = get_export_path(export_format, query, project_root)
262
+
263
+ # Export based on format
264
+ success = False
265
+ if export_format == "json":
266
+ success = exporter.export_to_json(results, output_path, query)
267
+ elif export_format == "csv":
268
+ success = exporter.export_to_csv(results, output_path, query)
269
+ elif export_format == "markdown":
270
+ success = exporter.export_to_markdown(
271
+ results, output_path, query, show_content
272
+ )
273
+ elif export_format == "summary":
274
+ success = exporter.export_summary_table(results, output_path, query)
275
+ else:
276
+ from ..output import print_error
277
+
278
+ print_error(f"Unsupported export format: {export_format}")
279
+
280
+ if not success:
281
+ return
282
+
283
+ # Save to search history
284
+ from ..history import SearchHistory
285
+
286
+ history_manager = SearchHistory(project_root)
287
+ history_manager.add_search(
288
+ query=query,
289
+ results_count=len(results),
290
+ filters=filters if filters else None,
291
+ )
292
+
293
+ # Display results
171
294
  if json_output:
172
295
  from ..output import print_json
296
+
173
297
  results_data = [result.to_dict() for result in results]
174
298
  print_json(results_data, title="Search Results")
175
299
  else:
@@ -178,14 +302,13 @@ async def run_search(
178
302
  query=query,
179
303
  show_content=show_content,
180
304
  )
181
-
305
+
182
306
  except Exception as e:
183
307
  logger.error(f"Search execution failed: {e}")
184
308
  raise
185
309
 
186
310
 
187
- @search_app.command("similar")
188
- def search_similar(
311
+ def search_similar_cmd(
189
312
  ctx: typer.Context,
190
313
  file_path: Path = typer.Argument(
191
314
  ...,
@@ -195,7 +318,17 @@ def search_similar(
195
318
  dir_okay=False,
196
319
  readable=True,
197
320
  ),
198
- function_name: Optional[str] = typer.Option(
321
+ project_root: Path | None = typer.Option(
322
+ None,
323
+ "--project-root",
324
+ "-p",
325
+ help="Project root directory (auto-detected if not specified)",
326
+ exists=True,
327
+ file_okay=False,
328
+ dir_okay=True,
329
+ readable=True,
330
+ ),
331
+ function_name: str | None = typer.Option(
199
332
  None,
200
333
  "--function",
201
334
  "-f",
@@ -209,7 +342,7 @@ def search_similar(
209
342
  min=1,
210
343
  max=100,
211
344
  ),
212
- similarity_threshold: Optional[float] = typer.Option(
345
+ similarity_threshold: float | None = typer.Option(
213
346
  None,
214
347
  "--threshold",
215
348
  "-t",
@@ -224,23 +357,25 @@ def search_similar(
224
357
  ),
225
358
  ) -> None:
226
359
  """Find code similar to a specific file or function.
227
-
360
+
228
361
  Examples:
229
362
  mcp-vector-search search similar src/auth.py
230
363
  mcp-vector-search search similar src/utils.py --function validate_email
231
364
  """
232
365
  try:
233
- project_root = ctx.obj.get("project_root") or Path.cwd()
234
-
235
- asyncio.run(run_similar_search(
236
- project_root=project_root,
237
- file_path=file_path,
238
- function_name=function_name,
239
- limit=limit,
240
- similarity_threshold=similarity_threshold,
241
- json_output=json_output,
242
- ))
243
-
366
+ project_root = project_root or ctx.obj.get("project_root") or Path.cwd()
367
+
368
+ asyncio.run(
369
+ run_similar_search(
370
+ project_root=project_root,
371
+ file_path=file_path,
372
+ function_name=function_name,
373
+ limit=limit,
374
+ similarity_threshold=similarity_threshold,
375
+ json_output=json_output,
376
+ )
377
+ )
378
+
244
379
  except Exception as e:
245
380
  logger.error(f"Similar search failed: {e}")
246
381
  print_error(f"Similar search failed: {e}")
@@ -250,27 +385,27 @@ def search_similar(
250
385
  async def run_similar_search(
251
386
  project_root: Path,
252
387
  file_path: Path,
253
- function_name: Optional[str] = None,
388
+ function_name: str | None = None,
254
389
  limit: int = 10,
255
- similarity_threshold: Optional[float] = None,
390
+ similarity_threshold: float | None = None,
256
391
  json_output: bool = False,
257
392
  ) -> None:
258
393
  """Run similar code search."""
259
394
  project_manager = ProjectManager(project_root)
260
395
  config = project_manager.load_config()
261
-
396
+
262
397
  embedding_function, _ = create_embedding_function(config.embedding_model)
263
398
  database = ChromaVectorDatabase(
264
399
  persist_directory=config.index_path,
265
400
  embedding_function=embedding_function,
266
401
  )
267
-
402
+
268
403
  search_engine = SemanticSearchEngine(
269
404
  database=database,
270
405
  project_root=project_root,
271
406
  similarity_threshold=similarity_threshold or config.similarity_threshold,
272
407
  )
273
-
408
+
274
409
  async with database:
275
410
  results = await search_engine.search_similar(
276
411
  file_path=file_path,
@@ -278,16 +413,17 @@ async def run_similar_search(
278
413
  limit=limit,
279
414
  similarity_threshold=similarity_threshold,
280
415
  )
281
-
416
+
282
417
  if json_output:
283
418
  from ..output import print_json
419
+
284
420
  results_data = [result.to_dict() for result in results]
285
421
  print_json(results_data, title="Similar Code Results")
286
422
  else:
287
423
  query_desc = f"{file_path}"
288
424
  if function_name:
289
425
  query_desc += f" → {function_name}()"
290
-
426
+
291
427
  print_search_results(
292
428
  results=results,
293
429
  query=f"Similar to: {query_desc}",
@@ -295,11 +431,20 @@ async def run_similar_search(
295
431
  )
296
432
 
297
433
 
298
- @search_app.command("context")
299
- def search_context(
434
+ def search_context_cmd(
300
435
  ctx: typer.Context,
301
436
  description: str = typer.Argument(..., help="Context description"),
302
- focus: Optional[str] = typer.Option(
437
+ project_root: Path | None = typer.Option(
438
+ None,
439
+ "--project-root",
440
+ "-p",
441
+ help="Project root directory (auto-detected if not specified)",
442
+ exists=True,
443
+ file_okay=False,
444
+ dir_okay=True,
445
+ readable=True,
446
+ ),
447
+ focus: str | None = typer.Option(
303
448
  None,
304
449
  "--focus",
305
450
  help="Comma-separated focus areas (e.g., 'security,authentication')",
@@ -319,26 +464,28 @@ def search_context(
319
464
  ),
320
465
  ) -> None:
321
466
  """Search for code based on contextual description.
322
-
467
+
323
468
  Examples:
324
469
  mcp-vector-search search context "implement rate limiting"
325
470
  mcp-vector-search search context "user authentication" --focus security,middleware
326
471
  """
327
472
  try:
328
- project_root = ctx.obj.get("project_root") or Path.cwd()
329
-
473
+ project_root = project_root or ctx.obj.get("project_root") or Path.cwd()
474
+
330
475
  focus_areas = None
331
476
  if focus:
332
477
  focus_areas = [area.strip() for area in focus.split(",")]
333
-
334
- asyncio.run(run_context_search(
335
- project_root=project_root,
336
- description=description,
337
- focus_areas=focus_areas,
338
- limit=limit,
339
- json_output=json_output,
340
- ))
341
-
478
+
479
+ asyncio.run(
480
+ run_context_search(
481
+ project_root=project_root,
482
+ description=description,
483
+ focus_areas=focus_areas,
484
+ limit=limit,
485
+ json_output=json_output,
486
+ )
487
+ )
488
+
342
489
  except Exception as e:
343
490
  logger.error(f"Context search failed: {e}")
344
491
  print_error(f"Context search failed: {e}")
@@ -348,42 +495,43 @@ def search_context(
348
495
  async def run_context_search(
349
496
  project_root: Path,
350
497
  description: str,
351
- focus_areas: Optional[List[str]] = None,
498
+ focus_areas: list[str] | None = None,
352
499
  limit: int = 10,
353
500
  json_output: bool = False,
354
501
  ) -> None:
355
502
  """Run contextual search."""
356
503
  project_manager = ProjectManager(project_root)
357
504
  config = project_manager.load_config()
358
-
505
+
359
506
  embedding_function, _ = create_embedding_function(config.embedding_model)
360
507
  database = ChromaVectorDatabase(
361
508
  persist_directory=config.index_path,
362
509
  embedding_function=embedding_function,
363
510
  )
364
-
511
+
365
512
  search_engine = SemanticSearchEngine(
366
513
  database=database,
367
514
  project_root=project_root,
368
515
  similarity_threshold=config.similarity_threshold,
369
516
  )
370
-
517
+
371
518
  async with database:
372
519
  results = await search_engine.search_by_context(
373
520
  context_description=description,
374
521
  focus_areas=focus_areas,
375
522
  limit=limit,
376
523
  )
377
-
524
+
378
525
  if json_output:
379
526
  from ..output import print_json
527
+
380
528
  results_data = [result.to_dict() for result in results]
381
529
  print_json(results_data, title="Context Search Results")
382
530
  else:
383
531
  query_desc = description
384
532
  if focus_areas:
385
533
  query_desc += f" (focus: {', '.join(focus_areas)})"
386
-
534
+
387
535
  print_search_results(
388
536
  results=results,
389
537
  query=query_desc,
@@ -391,5 +539,11 @@ async def run_context_search(
391
539
  )
392
540
 
393
541
 
542
+ # Add commands to search_app for backward compatibility
543
+ search_app.command("main")(search_main)
544
+ search_app.command("similar")(search_similar_cmd)
545
+ search_app.command("context")(search_context_cmd)
546
+
547
+
394
548
  if __name__ == "__main__":
395
549
  search_app()