mcp-vector-search 0.15.7__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 (86) hide show
  1. mcp_vector_search/__init__.py +10 -0
  2. mcp_vector_search/cli/__init__.py +1 -0
  3. mcp_vector_search/cli/commands/__init__.py +1 -0
  4. mcp_vector_search/cli/commands/auto_index.py +397 -0
  5. mcp_vector_search/cli/commands/chat.py +534 -0
  6. mcp_vector_search/cli/commands/config.py +393 -0
  7. mcp_vector_search/cli/commands/demo.py +358 -0
  8. mcp_vector_search/cli/commands/index.py +762 -0
  9. mcp_vector_search/cli/commands/init.py +658 -0
  10. mcp_vector_search/cli/commands/install.py +869 -0
  11. mcp_vector_search/cli/commands/install_old.py +700 -0
  12. mcp_vector_search/cli/commands/mcp.py +1254 -0
  13. mcp_vector_search/cli/commands/reset.py +393 -0
  14. mcp_vector_search/cli/commands/search.py +796 -0
  15. mcp_vector_search/cli/commands/setup.py +1133 -0
  16. mcp_vector_search/cli/commands/status.py +584 -0
  17. mcp_vector_search/cli/commands/uninstall.py +404 -0
  18. mcp_vector_search/cli/commands/visualize/__init__.py +39 -0
  19. mcp_vector_search/cli/commands/visualize/cli.py +265 -0
  20. mcp_vector_search/cli/commands/visualize/exporters/__init__.py +12 -0
  21. mcp_vector_search/cli/commands/visualize/exporters/html_exporter.py +33 -0
  22. mcp_vector_search/cli/commands/visualize/exporters/json_exporter.py +29 -0
  23. mcp_vector_search/cli/commands/visualize/graph_builder.py +709 -0
  24. mcp_vector_search/cli/commands/visualize/layout_engine.py +469 -0
  25. mcp_vector_search/cli/commands/visualize/server.py +201 -0
  26. mcp_vector_search/cli/commands/visualize/state_manager.py +428 -0
  27. mcp_vector_search/cli/commands/visualize/templates/__init__.py +16 -0
  28. mcp_vector_search/cli/commands/visualize/templates/base.py +218 -0
  29. mcp_vector_search/cli/commands/visualize/templates/scripts.py +3670 -0
  30. mcp_vector_search/cli/commands/visualize/templates/styles.py +779 -0
  31. mcp_vector_search/cli/commands/visualize.py.original +2536 -0
  32. mcp_vector_search/cli/commands/watch.py +287 -0
  33. mcp_vector_search/cli/didyoumean.py +520 -0
  34. mcp_vector_search/cli/export.py +320 -0
  35. mcp_vector_search/cli/history.py +295 -0
  36. mcp_vector_search/cli/interactive.py +342 -0
  37. mcp_vector_search/cli/main.py +484 -0
  38. mcp_vector_search/cli/output.py +414 -0
  39. mcp_vector_search/cli/suggestions.py +375 -0
  40. mcp_vector_search/config/__init__.py +1 -0
  41. mcp_vector_search/config/constants.py +24 -0
  42. mcp_vector_search/config/defaults.py +200 -0
  43. mcp_vector_search/config/settings.py +146 -0
  44. mcp_vector_search/core/__init__.py +1 -0
  45. mcp_vector_search/core/auto_indexer.py +298 -0
  46. mcp_vector_search/core/config_utils.py +394 -0
  47. mcp_vector_search/core/connection_pool.py +360 -0
  48. mcp_vector_search/core/database.py +1237 -0
  49. mcp_vector_search/core/directory_index.py +318 -0
  50. mcp_vector_search/core/embeddings.py +294 -0
  51. mcp_vector_search/core/exceptions.py +89 -0
  52. mcp_vector_search/core/factory.py +318 -0
  53. mcp_vector_search/core/git_hooks.py +345 -0
  54. mcp_vector_search/core/indexer.py +1002 -0
  55. mcp_vector_search/core/llm_client.py +453 -0
  56. mcp_vector_search/core/models.py +294 -0
  57. mcp_vector_search/core/project.py +350 -0
  58. mcp_vector_search/core/scheduler.py +330 -0
  59. mcp_vector_search/core/search.py +952 -0
  60. mcp_vector_search/core/watcher.py +322 -0
  61. mcp_vector_search/mcp/__init__.py +5 -0
  62. mcp_vector_search/mcp/__main__.py +25 -0
  63. mcp_vector_search/mcp/server.py +752 -0
  64. mcp_vector_search/parsers/__init__.py +8 -0
  65. mcp_vector_search/parsers/base.py +296 -0
  66. mcp_vector_search/parsers/dart.py +605 -0
  67. mcp_vector_search/parsers/html.py +413 -0
  68. mcp_vector_search/parsers/javascript.py +643 -0
  69. mcp_vector_search/parsers/php.py +694 -0
  70. mcp_vector_search/parsers/python.py +502 -0
  71. mcp_vector_search/parsers/registry.py +223 -0
  72. mcp_vector_search/parsers/ruby.py +678 -0
  73. mcp_vector_search/parsers/text.py +186 -0
  74. mcp_vector_search/parsers/utils.py +265 -0
  75. mcp_vector_search/py.typed +1 -0
  76. mcp_vector_search/utils/__init__.py +42 -0
  77. mcp_vector_search/utils/gitignore.py +250 -0
  78. mcp_vector_search/utils/gitignore_updater.py +212 -0
  79. mcp_vector_search/utils/monorepo.py +339 -0
  80. mcp_vector_search/utils/timing.py +338 -0
  81. mcp_vector_search/utils/version.py +47 -0
  82. mcp_vector_search-0.15.7.dist-info/METADATA +884 -0
  83. mcp_vector_search-0.15.7.dist-info/RECORD +86 -0
  84. mcp_vector_search-0.15.7.dist-info/WHEEL +4 -0
  85. mcp_vector_search-0.15.7.dist-info/entry_points.txt +3 -0
  86. mcp_vector_search-0.15.7.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,342 @@
1
+ """Interactive search features for MCP Vector Search."""
2
+
3
+ from pathlib import Path
4
+
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.prompt import Prompt
8
+ from rich.table import Table
9
+
10
+ from ..core.database import ChromaVectorDatabase
11
+ from ..core.embeddings import create_embedding_function
12
+ from ..core.exceptions import ProjectNotFoundError
13
+ from ..core.models import SearchResult
14
+ from ..core.project import ProjectManager
15
+ from ..core.search import SemanticSearchEngine
16
+ from .output import print_error, print_info, print_search_results, print_warning
17
+
18
+ console = Console()
19
+
20
+
21
+ class InteractiveSearchSession:
22
+ """Interactive search session with filtering and refinement."""
23
+
24
+ def __init__(self, project_root: Path):
25
+ """Initialize interactive search session."""
26
+ self.project_root = project_root
27
+ self.project_manager = ProjectManager(project_root)
28
+ self.search_engine: SemanticSearchEngine | None = None
29
+ self.database: ChromaVectorDatabase | None = None
30
+ self.last_results: list[SearchResult] = []
31
+ self.search_history: list[str] = []
32
+
33
+ async def start(self) -> None:
34
+ """Start interactive search session."""
35
+ if not self.project_manager.is_initialized():
36
+ raise ProjectNotFoundError(
37
+ f"Project not initialized at {self.project_root}. Run 'mcp-vector-search init' first."
38
+ )
39
+
40
+ config = self.project_manager.load_config()
41
+
42
+ # Setup database and search engine
43
+ embedding_function, _ = create_embedding_function(config.embedding_model)
44
+ self.database = ChromaVectorDatabase(
45
+ persist_directory=config.index_path,
46
+ embedding_function=embedding_function,
47
+ )
48
+
49
+ self.search_engine = SemanticSearchEngine(
50
+ database=self.database,
51
+ project_root=self.project_root,
52
+ similarity_threshold=config.similarity_threshold,
53
+ )
54
+
55
+ await self.database.initialize()
56
+
57
+ # Show welcome message
58
+ self._show_welcome()
59
+
60
+ # Main interactive loop
61
+ try:
62
+ await self._interactive_loop()
63
+ finally:
64
+ await self.database.close()
65
+
66
+ def _show_welcome(self) -> None:
67
+ """Show welcome message and help."""
68
+ welcome_text = """
69
+ [bold blue]Interactive Search Session[/bold blue]
70
+
71
+ Available commands:
72
+ • [cyan]search <query>[/cyan] - Perform semantic search
73
+ • [cyan]filter[/cyan] - Filter current results
74
+ • [cyan]refine[/cyan] - Refine last search
75
+ • [cyan]history[/cyan] - Show search history
76
+ • [cyan]stats[/cyan] - Show result statistics
77
+ • [cyan]help[/cyan] - Show this help
78
+ • [cyan]quit[/cyan] - Exit session
79
+
80
+ Type your search query or command:
81
+ """
82
+ console.print(Panel(welcome_text.strip(), border_style="blue"))
83
+
84
+ async def _interactive_loop(self) -> None:
85
+ """Main interactive loop."""
86
+ while True:
87
+ try:
88
+ # Get user input
89
+ user_input = Prompt.ask("\n[bold cyan]Search[/bold cyan]").strip()
90
+
91
+ if not user_input:
92
+ continue
93
+
94
+ # Handle commands
95
+ if user_input.lower() in ["quit", "exit", "q"]:
96
+ console.print("[yellow]Goodbye![/yellow]")
97
+ break
98
+ elif user_input.lower() == "help":
99
+ self._show_welcome()
100
+ elif user_input.lower() == "history":
101
+ self._show_history()
102
+ elif user_input.lower() == "stats":
103
+ self._show_stats()
104
+ elif user_input.lower() == "filter":
105
+ await self._filter_results()
106
+ elif user_input.lower() == "refine":
107
+ await self._refine_search()
108
+ elif user_input.startswith("search "):
109
+ query = user_input[7:].strip()
110
+ await self._perform_search(query)
111
+ else:
112
+ # Treat as search query
113
+ await self._perform_search(user_input)
114
+
115
+ except KeyboardInterrupt:
116
+ console.print("\n[yellow]Use 'quit' to exit[/yellow]")
117
+ except Exception as e:
118
+ print_error(f"Error: {e}")
119
+
120
+ async def _perform_search(self, query: str) -> None:
121
+ """Perform a search and display results."""
122
+ if not query:
123
+ print_warning("Please provide a search query")
124
+ return
125
+
126
+ try:
127
+ console.print(f"\n[dim]Searching for: {query}[/dim]")
128
+
129
+ results = await self.search_engine.search(
130
+ query=query,
131
+ limit=20, # Get more results for filtering
132
+ include_context=True,
133
+ )
134
+
135
+ self.last_results = results
136
+ self.search_history.append(query)
137
+
138
+ if results:
139
+ print_search_results(results[:10], query, show_content=False)
140
+
141
+ if len(results) > 10:
142
+ console.print(
143
+ f"\n[dim]Showing top 10 of {len(results)} results. Use 'filter' to refine.[/dim]"
144
+ )
145
+
146
+ # Show quick actions
147
+ self._show_quick_actions()
148
+ else:
149
+ print_warning(f"No results found for: {query}")
150
+ self._suggest_alternatives(query)
151
+
152
+ except Exception as e:
153
+ print_error(f"Search failed: {e}")
154
+
155
+ def _show_quick_actions(self) -> None:
156
+ """Show quick action options."""
157
+ actions = [
158
+ "[cyan]filter[/cyan] - Filter results",
159
+ "[cyan]refine[/cyan] - Refine search",
160
+ "[cyan]stats[/cyan] - Show statistics",
161
+ ]
162
+ console.print(f"\n[dim]Quick actions: {' | '.join(actions)}[/dim]")
163
+
164
+ async def _filter_results(self) -> None:
165
+ """Interactive result filtering."""
166
+ if not self.last_results:
167
+ print_warning("No results to filter. Perform a search first.")
168
+ return
169
+
170
+ console.print(f"\n[bold]Filtering {len(self.last_results)} results[/bold]")
171
+
172
+ # Show available filter options
173
+ available_languages = {r.language for r in self.last_results}
174
+ available_files = {r.file_path.name for r in self.last_results}
175
+ available_functions = {
176
+ r.function_name for r in self.last_results if r.function_name
177
+ }
178
+ {r.class_name for r in self.last_results if r.class_name}
179
+
180
+ # Language filter
181
+ if len(available_languages) > 1:
182
+ lang_choice = Prompt.ask(
183
+ f"Filter by language? ({', '.join(sorted(available_languages))})",
184
+ default="",
185
+ show_default=False,
186
+ )
187
+ if lang_choice and lang_choice in available_languages:
188
+ self.last_results = [
189
+ r for r in self.last_results if r.language == lang_choice
190
+ ]
191
+ console.print(
192
+ f"[green]Filtered to {len(self.last_results)} results with language: {lang_choice}[/green]"
193
+ )
194
+
195
+ # File filter
196
+ if len(available_files) > 1 and len(self.last_results) > 1:
197
+ file_pattern = Prompt.ask(
198
+ "Filter by file name pattern (partial match)",
199
+ default="",
200
+ show_default=False,
201
+ )
202
+ if file_pattern:
203
+ self.last_results = [
204
+ r
205
+ for r in self.last_results
206
+ if file_pattern.lower() in r.file_path.name.lower()
207
+ ]
208
+ console.print(
209
+ f"[green]Filtered to {len(self.last_results)} results matching: {file_pattern}[/green]"
210
+ )
211
+
212
+ # Function filter
213
+ if available_functions and len(self.last_results) > 1:
214
+ func_pattern = Prompt.ask(
215
+ "Filter by function name pattern (partial match)",
216
+ default="",
217
+ show_default=False,
218
+ )
219
+ if func_pattern:
220
+ self.last_results = [
221
+ r
222
+ for r in self.last_results
223
+ if r.function_name
224
+ and func_pattern.lower() in r.function_name.lower()
225
+ ]
226
+ console.print(
227
+ f"[green]Filtered to {len(self.last_results)} results with function matching: {func_pattern}[/green]"
228
+ )
229
+
230
+ # Similarity threshold filter
231
+ min_similarity = Prompt.ask(
232
+ "Minimum similarity threshold (0.0-1.0)", default="", show_default=False
233
+ )
234
+ if min_similarity:
235
+ try:
236
+ threshold = float(min_similarity)
237
+ if 0.0 <= threshold <= 1.0:
238
+ self.last_results = [
239
+ r for r in self.last_results if r.similarity_score >= threshold
240
+ ]
241
+ console.print(
242
+ f"[green]Filtered to {len(self.last_results)} results with similarity >= {threshold}[/green]"
243
+ )
244
+ except ValueError:
245
+ print_warning("Invalid similarity threshold")
246
+
247
+ # Show filtered results
248
+ if self.last_results:
249
+ print_search_results(
250
+ self.last_results[:10], "Filtered Results", show_content=False
251
+ )
252
+ else:
253
+ print_warning("No results match the filters")
254
+
255
+ async def _refine_search(self) -> None:
256
+ """Refine the last search with additional terms."""
257
+ if not self.search_history:
258
+ print_warning("No previous search to refine")
259
+ return
260
+
261
+ last_query = self.search_history[-1]
262
+ console.print(f"[dim]Last search: {last_query}[/dim]")
263
+
264
+ additional_terms = Prompt.ask("Add terms to refine search", default="")
265
+ if additional_terms:
266
+ refined_query = f"{last_query} {additional_terms}"
267
+ await self._perform_search(refined_query)
268
+
269
+ def _show_history(self) -> None:
270
+ """Show search history."""
271
+ if not self.search_history:
272
+ print_info("No search history")
273
+ return
274
+
275
+ table = Table(title="Search History", show_header=True)
276
+ table.add_column("#", style="cyan", width=3)
277
+ table.add_column("Query", style="white")
278
+
279
+ for i, query in enumerate(self.search_history[-10:], 1):
280
+ table.add_row(str(i), query)
281
+
282
+ console.print(table)
283
+
284
+ def _show_stats(self) -> None:
285
+ """Show statistics for current results."""
286
+ if not self.last_results:
287
+ print_info("No results to analyze")
288
+ return
289
+
290
+ # Calculate statistics
291
+ languages = {}
292
+ files = {}
293
+ avg_similarity = sum(r.similarity_score for r in self.last_results) / len(
294
+ self.last_results
295
+ )
296
+
297
+ for result in self.last_results:
298
+ languages[result.language] = languages.get(result.language, 0) + 1
299
+ files[result.file_path.name] = files.get(result.file_path.name, 0) + 1
300
+
301
+ # Create statistics table
302
+ table = Table(title="Result Statistics", show_header=False)
303
+ table.add_column("Metric", style="cyan")
304
+ table.add_column("Value", style="white")
305
+
306
+ table.add_row("Total Results", str(len(self.last_results)))
307
+ table.add_row("Average Similarity", f"{avg_similarity:.1%}")
308
+ table.add_row(
309
+ "Languages",
310
+ ", ".join(f"{lang}({count})" for lang, count in languages.items()),
311
+ )
312
+ table.add_row("Unique Files", str(len(files)))
313
+
314
+ console.print(table)
315
+
316
+ def _suggest_alternatives(self, query: str) -> None:
317
+ """Suggest alternative search terms."""
318
+ suggestions = []
319
+
320
+ # Simple suggestions based on common patterns
321
+ words = query.lower().split()
322
+ for word in words:
323
+ if word in ["auth", "authentication"]:
324
+ suggestions.extend(["login", "user", "session", "token"])
325
+ elif word in ["db", "database"]:
326
+ suggestions.extend(["query", "model", "connection", "storage"])
327
+ elif word in ["api"]:
328
+ suggestions.extend(["endpoint", "request", "response", "handler"])
329
+ elif word in ["test", "testing"]:
330
+ suggestions.extend(["mock", "assert", "spec", "unit"])
331
+
332
+ if suggestions:
333
+ unique_suggestions = list(set(suggestions))[:5]
334
+ console.print(
335
+ f"[dim]Try these terms: {', '.join(unique_suggestions)}[/dim]"
336
+ )
337
+
338
+
339
+ async def start_interactive_search(project_root: Path) -> None:
340
+ """Start an interactive search session."""
341
+ session = InteractiveSearchSession(project_root)
342
+ await session.start()