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.
- mcp_vector_search/__init__.py +10 -0
- mcp_vector_search/cli/__init__.py +1 -0
- mcp_vector_search/cli/commands/__init__.py +1 -0
- mcp_vector_search/cli/commands/auto_index.py +397 -0
- mcp_vector_search/cli/commands/chat.py +534 -0
- mcp_vector_search/cli/commands/config.py +393 -0
- mcp_vector_search/cli/commands/demo.py +358 -0
- mcp_vector_search/cli/commands/index.py +762 -0
- mcp_vector_search/cli/commands/init.py +658 -0
- mcp_vector_search/cli/commands/install.py +869 -0
- mcp_vector_search/cli/commands/install_old.py +700 -0
- mcp_vector_search/cli/commands/mcp.py +1254 -0
- mcp_vector_search/cli/commands/reset.py +393 -0
- mcp_vector_search/cli/commands/search.py +796 -0
- mcp_vector_search/cli/commands/setup.py +1133 -0
- mcp_vector_search/cli/commands/status.py +584 -0
- mcp_vector_search/cli/commands/uninstall.py +404 -0
- mcp_vector_search/cli/commands/visualize/__init__.py +39 -0
- mcp_vector_search/cli/commands/visualize/cli.py +265 -0
- mcp_vector_search/cli/commands/visualize/exporters/__init__.py +12 -0
- mcp_vector_search/cli/commands/visualize/exporters/html_exporter.py +33 -0
- mcp_vector_search/cli/commands/visualize/exporters/json_exporter.py +29 -0
- mcp_vector_search/cli/commands/visualize/graph_builder.py +709 -0
- mcp_vector_search/cli/commands/visualize/layout_engine.py +469 -0
- mcp_vector_search/cli/commands/visualize/server.py +201 -0
- mcp_vector_search/cli/commands/visualize/state_manager.py +428 -0
- mcp_vector_search/cli/commands/visualize/templates/__init__.py +16 -0
- mcp_vector_search/cli/commands/visualize/templates/base.py +218 -0
- mcp_vector_search/cli/commands/visualize/templates/scripts.py +3670 -0
- mcp_vector_search/cli/commands/visualize/templates/styles.py +779 -0
- mcp_vector_search/cli/commands/visualize.py.original +2536 -0
- mcp_vector_search/cli/commands/watch.py +287 -0
- mcp_vector_search/cli/didyoumean.py +520 -0
- mcp_vector_search/cli/export.py +320 -0
- mcp_vector_search/cli/history.py +295 -0
- mcp_vector_search/cli/interactive.py +342 -0
- mcp_vector_search/cli/main.py +484 -0
- mcp_vector_search/cli/output.py +414 -0
- mcp_vector_search/cli/suggestions.py +375 -0
- mcp_vector_search/config/__init__.py +1 -0
- mcp_vector_search/config/constants.py +24 -0
- mcp_vector_search/config/defaults.py +200 -0
- mcp_vector_search/config/settings.py +146 -0
- mcp_vector_search/core/__init__.py +1 -0
- mcp_vector_search/core/auto_indexer.py +298 -0
- mcp_vector_search/core/config_utils.py +394 -0
- mcp_vector_search/core/connection_pool.py +360 -0
- mcp_vector_search/core/database.py +1237 -0
- mcp_vector_search/core/directory_index.py +318 -0
- mcp_vector_search/core/embeddings.py +294 -0
- mcp_vector_search/core/exceptions.py +89 -0
- mcp_vector_search/core/factory.py +318 -0
- mcp_vector_search/core/git_hooks.py +345 -0
- mcp_vector_search/core/indexer.py +1002 -0
- mcp_vector_search/core/llm_client.py +453 -0
- mcp_vector_search/core/models.py +294 -0
- mcp_vector_search/core/project.py +350 -0
- mcp_vector_search/core/scheduler.py +330 -0
- mcp_vector_search/core/search.py +952 -0
- mcp_vector_search/core/watcher.py +322 -0
- mcp_vector_search/mcp/__init__.py +5 -0
- mcp_vector_search/mcp/__main__.py +25 -0
- mcp_vector_search/mcp/server.py +752 -0
- mcp_vector_search/parsers/__init__.py +8 -0
- mcp_vector_search/parsers/base.py +296 -0
- mcp_vector_search/parsers/dart.py +605 -0
- mcp_vector_search/parsers/html.py +413 -0
- mcp_vector_search/parsers/javascript.py +643 -0
- mcp_vector_search/parsers/php.py +694 -0
- mcp_vector_search/parsers/python.py +502 -0
- mcp_vector_search/parsers/registry.py +223 -0
- mcp_vector_search/parsers/ruby.py +678 -0
- mcp_vector_search/parsers/text.py +186 -0
- mcp_vector_search/parsers/utils.py +265 -0
- mcp_vector_search/py.typed +1 -0
- mcp_vector_search/utils/__init__.py +42 -0
- mcp_vector_search/utils/gitignore.py +250 -0
- mcp_vector_search/utils/gitignore_updater.py +212 -0
- mcp_vector_search/utils/monorepo.py +339 -0
- mcp_vector_search/utils/timing.py +338 -0
- mcp_vector_search/utils/version.py +47 -0
- mcp_vector_search-0.15.7.dist-info/METADATA +884 -0
- mcp_vector_search-0.15.7.dist-info/RECORD +86 -0
- mcp_vector_search-0.15.7.dist-info/WHEEL +4 -0
- mcp_vector_search-0.15.7.dist-info/entry_points.txt +3 -0
- 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()
|