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,322 @@
|
|
|
1
|
+
"""File system watcher for incremental indexing."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import time
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from concurrent.futures import Future
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from loguru import logger
|
|
10
|
+
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
|
11
|
+
from watchdog.observers import Observer
|
|
12
|
+
|
|
13
|
+
from ..config.settings import ProjectConfig
|
|
14
|
+
from .database import ChromaVectorDatabase
|
|
15
|
+
from .indexer import SemanticIndexer
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CodeFileHandler(FileSystemEventHandler):
|
|
19
|
+
"""Handler for code file changes."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
file_extensions: list[str],
|
|
24
|
+
ignore_patterns: list[str],
|
|
25
|
+
callback: Callable[[str, str], Awaitable[None]],
|
|
26
|
+
loop: asyncio.AbstractEventLoop,
|
|
27
|
+
debounce_delay: float = 1.0,
|
|
28
|
+
):
|
|
29
|
+
"""Initialize file handler.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
file_extensions: List of file extensions to watch
|
|
33
|
+
ignore_patterns: List of patterns to ignore
|
|
34
|
+
callback: Async callback function for file changes
|
|
35
|
+
loop: Event loop to schedule tasks on
|
|
36
|
+
debounce_delay: Delay in seconds to debounce rapid changes
|
|
37
|
+
"""
|
|
38
|
+
super().__init__()
|
|
39
|
+
self.file_extensions = set(file_extensions)
|
|
40
|
+
self.ignore_patterns = ignore_patterns
|
|
41
|
+
self.callback = callback
|
|
42
|
+
self.loop = loop
|
|
43
|
+
self.debounce_delay = debounce_delay
|
|
44
|
+
self.pending_changes: set[str] = set()
|
|
45
|
+
self.last_change_time: float = 0
|
|
46
|
+
self.debounce_task: asyncio.Task | Future | None = None
|
|
47
|
+
|
|
48
|
+
def should_process_file(self, file_path: str) -> bool:
|
|
49
|
+
"""Check if file should be processed."""
|
|
50
|
+
path = Path(file_path)
|
|
51
|
+
|
|
52
|
+
# Check file extension
|
|
53
|
+
if path.suffix not in self.file_extensions:
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
# Check ignore patterns
|
|
57
|
+
for pattern in self.ignore_patterns:
|
|
58
|
+
if pattern in str(path):
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
def on_modified(self, event: FileSystemEvent) -> None:
|
|
64
|
+
"""Handle file modification."""
|
|
65
|
+
if not event.is_directory and self.should_process_file(event.src_path):
|
|
66
|
+
self._schedule_change(event.src_path, "modified")
|
|
67
|
+
|
|
68
|
+
def on_created(self, event: FileSystemEvent) -> None:
|
|
69
|
+
"""Handle file creation."""
|
|
70
|
+
if not event.is_directory and self.should_process_file(event.src_path):
|
|
71
|
+
self._schedule_change(event.src_path, "created")
|
|
72
|
+
|
|
73
|
+
def on_deleted(self, event: FileSystemEvent) -> None:
|
|
74
|
+
"""Handle file deletion."""
|
|
75
|
+
if not event.is_directory and self.should_process_file(event.src_path):
|
|
76
|
+
self._schedule_change(event.src_path, "deleted")
|
|
77
|
+
|
|
78
|
+
def on_moved(self, event: FileSystemEvent) -> None:
|
|
79
|
+
"""Handle file move/rename."""
|
|
80
|
+
if hasattr(event, "dest_path"):
|
|
81
|
+
# Handle rename/move
|
|
82
|
+
if not event.is_directory:
|
|
83
|
+
if self.should_process_file(event.src_path):
|
|
84
|
+
self._schedule_change(event.src_path, "deleted")
|
|
85
|
+
if self.should_process_file(event.dest_path):
|
|
86
|
+
self._schedule_change(event.dest_path, "created")
|
|
87
|
+
|
|
88
|
+
def _schedule_change(self, file_path: str, change_type: str) -> None:
|
|
89
|
+
"""Schedule a file change for processing with debouncing."""
|
|
90
|
+
self.pending_changes.add(f"{change_type}:{file_path}")
|
|
91
|
+
self.last_change_time = time.time()
|
|
92
|
+
|
|
93
|
+
# Cancel existing debounce task
|
|
94
|
+
if self.debounce_task and not self.debounce_task.done():
|
|
95
|
+
self.debounce_task.cancel()
|
|
96
|
+
|
|
97
|
+
# Schedule new debounce task using the stored loop
|
|
98
|
+
future = asyncio.run_coroutine_threadsafe(self._debounced_process(), self.loop)
|
|
99
|
+
# Store the future as our task (it has a done() method)
|
|
100
|
+
self.debounce_task = future
|
|
101
|
+
|
|
102
|
+
async def _debounced_process(self) -> None:
|
|
103
|
+
"""Process pending changes after debounce delay."""
|
|
104
|
+
await asyncio.sleep(self.debounce_delay)
|
|
105
|
+
|
|
106
|
+
# Check if more changes occurred during debounce
|
|
107
|
+
if time.time() - self.last_change_time < self.debounce_delay:
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
# Process all pending changes
|
|
111
|
+
changes = self.pending_changes.copy()
|
|
112
|
+
self.pending_changes.clear()
|
|
113
|
+
|
|
114
|
+
for change in changes:
|
|
115
|
+
change_type, file_path = change.split(":", 1)
|
|
116
|
+
try:
|
|
117
|
+
await self.callback(file_path, change_type)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.error(f"Error processing file change {file_path}: {e}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class FileWatcher:
|
|
123
|
+
"""File system watcher for incremental indexing."""
|
|
124
|
+
|
|
125
|
+
def __init__(
|
|
126
|
+
self,
|
|
127
|
+
project_root: Path,
|
|
128
|
+
config: ProjectConfig,
|
|
129
|
+
indexer: SemanticIndexer,
|
|
130
|
+
database: ChromaVectorDatabase,
|
|
131
|
+
):
|
|
132
|
+
"""Initialize file watcher.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
project_root: Root directory to watch
|
|
136
|
+
config: Project configuration
|
|
137
|
+
indexer: Semantic indexer instance
|
|
138
|
+
database: Vector database instance
|
|
139
|
+
"""
|
|
140
|
+
self.project_root = project_root
|
|
141
|
+
self.config = config
|
|
142
|
+
self.indexer = indexer
|
|
143
|
+
self.database = database
|
|
144
|
+
self.observer: Observer | None = None
|
|
145
|
+
self.handler: CodeFileHandler | None = None
|
|
146
|
+
self.is_running = False
|
|
147
|
+
|
|
148
|
+
async def start(self) -> None:
|
|
149
|
+
"""Start watching for file changes."""
|
|
150
|
+
if self.is_running:
|
|
151
|
+
logger.warning("File watcher is already running")
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
logger.info(f"Starting file watcher for {self.project_root}")
|
|
155
|
+
|
|
156
|
+
# Create handler
|
|
157
|
+
loop = asyncio.get_running_loop()
|
|
158
|
+
self.handler = CodeFileHandler(
|
|
159
|
+
file_extensions=self.config.file_extensions,
|
|
160
|
+
ignore_patterns=self._get_ignore_patterns(),
|
|
161
|
+
callback=self._handle_file_change,
|
|
162
|
+
loop=loop,
|
|
163
|
+
debounce_delay=1.0,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Create observer
|
|
167
|
+
self.observer = Observer()
|
|
168
|
+
self.observer.schedule(self.handler, str(self.project_root), recursive=True)
|
|
169
|
+
|
|
170
|
+
# Start observer in a separate thread
|
|
171
|
+
self.observer.start()
|
|
172
|
+
self.is_running = True
|
|
173
|
+
|
|
174
|
+
logger.info("File watcher started successfully")
|
|
175
|
+
|
|
176
|
+
async def stop(self) -> None:
|
|
177
|
+
"""Stop watching for file changes."""
|
|
178
|
+
if not self.is_running:
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
logger.info("Stopping file watcher")
|
|
182
|
+
|
|
183
|
+
if self.observer:
|
|
184
|
+
self.observer.stop()
|
|
185
|
+
self.observer.join()
|
|
186
|
+
self.observer = None
|
|
187
|
+
|
|
188
|
+
self.handler = None
|
|
189
|
+
self.is_running = False
|
|
190
|
+
|
|
191
|
+
logger.info("File watcher stopped")
|
|
192
|
+
|
|
193
|
+
def _get_ignore_patterns(self) -> list[str]:
|
|
194
|
+
"""Get patterns to ignore during watching."""
|
|
195
|
+
default_patterns = [
|
|
196
|
+
".git",
|
|
197
|
+
".svn",
|
|
198
|
+
".hg",
|
|
199
|
+
"__pycache__",
|
|
200
|
+
".pytest_cache",
|
|
201
|
+
"node_modules",
|
|
202
|
+
".venv",
|
|
203
|
+
"venv",
|
|
204
|
+
".DS_Store",
|
|
205
|
+
"Thumbs.db",
|
|
206
|
+
".idea",
|
|
207
|
+
".vscode",
|
|
208
|
+
"build",
|
|
209
|
+
"dist",
|
|
210
|
+
"target",
|
|
211
|
+
".mcp-vector-search", # Ignore our own index directory
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
# Add any custom ignore patterns from config
|
|
215
|
+
# TODO: Add custom ignore patterns to config
|
|
216
|
+
return default_patterns
|
|
217
|
+
|
|
218
|
+
async def _handle_file_change(self, file_path: str, change_type: str) -> None:
|
|
219
|
+
"""Handle a file change event.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
file_path: Path to the changed file
|
|
223
|
+
change_type: Type of change (created, modified, deleted)
|
|
224
|
+
"""
|
|
225
|
+
path = Path(file_path)
|
|
226
|
+
logger.debug(f"Processing file change: {change_type} {path}")
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
if change_type == "deleted":
|
|
230
|
+
# Remove chunks for deleted file
|
|
231
|
+
await self._remove_file_chunks(path)
|
|
232
|
+
elif change_type in ("created", "modified"):
|
|
233
|
+
# Re-index the file
|
|
234
|
+
await self._reindex_file(path)
|
|
235
|
+
|
|
236
|
+
logger.info(f"Processed {change_type} for {path.name}")
|
|
237
|
+
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.error(f"Failed to process {change_type} for {path}: {e}")
|
|
240
|
+
|
|
241
|
+
async def _remove_file_chunks(self, file_path: Path) -> None:
|
|
242
|
+
"""Remove all chunks for a deleted file."""
|
|
243
|
+
# Get relative path for consistent IDs
|
|
244
|
+
try:
|
|
245
|
+
relative_path = file_path.relative_to(self.project_root)
|
|
246
|
+
except ValueError:
|
|
247
|
+
relative_path = file_path
|
|
248
|
+
|
|
249
|
+
# Remove chunks from database
|
|
250
|
+
await self.database.remove_file_chunks(str(relative_path))
|
|
251
|
+
logger.debug(f"Removed chunks for deleted file: {relative_path}")
|
|
252
|
+
|
|
253
|
+
async def _reindex_file(self, file_path: Path) -> None:
|
|
254
|
+
"""Re-index a single file."""
|
|
255
|
+
if not file_path.exists():
|
|
256
|
+
logger.warning(f"File no longer exists: {file_path}")
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
# Remove existing chunks first
|
|
260
|
+
await self._remove_file_chunks(file_path)
|
|
261
|
+
|
|
262
|
+
# Index the file
|
|
263
|
+
chunks_indexed = await self.indexer.index_file(file_path)
|
|
264
|
+
logger.debug(f"Re-indexed {file_path.name}: {chunks_indexed} chunks")
|
|
265
|
+
|
|
266
|
+
async def __aenter__(self):
|
|
267
|
+
"""Async context manager entry."""
|
|
268
|
+
await self.start()
|
|
269
|
+
return self
|
|
270
|
+
|
|
271
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
272
|
+
"""Async context manager exit."""
|
|
273
|
+
await self.stop()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class WatcherManager:
|
|
277
|
+
"""Manager for file watchers across multiple projects."""
|
|
278
|
+
|
|
279
|
+
def __init__(self):
|
|
280
|
+
"""Initialize watcher manager."""
|
|
281
|
+
self.watchers: dict[str, FileWatcher] = {}
|
|
282
|
+
|
|
283
|
+
async def start_watcher(
|
|
284
|
+
self,
|
|
285
|
+
project_root: Path,
|
|
286
|
+
config: ProjectConfig,
|
|
287
|
+
indexer: SemanticIndexer,
|
|
288
|
+
database: ChromaVectorDatabase,
|
|
289
|
+
) -> FileWatcher:
|
|
290
|
+
"""Start a file watcher for a project."""
|
|
291
|
+
project_key = str(project_root)
|
|
292
|
+
|
|
293
|
+
if project_key in self.watchers:
|
|
294
|
+
logger.warning(f"Watcher already exists for {project_root}")
|
|
295
|
+
return self.watchers[project_key]
|
|
296
|
+
|
|
297
|
+
watcher = FileWatcher(project_root, config, indexer, database)
|
|
298
|
+
await watcher.start()
|
|
299
|
+
|
|
300
|
+
self.watchers[project_key] = watcher
|
|
301
|
+
return watcher
|
|
302
|
+
|
|
303
|
+
async def stop_watcher(self, project_root: Path) -> None:
|
|
304
|
+
"""Stop a file watcher for a project."""
|
|
305
|
+
project_key = str(project_root)
|
|
306
|
+
|
|
307
|
+
if project_key not in self.watchers:
|
|
308
|
+
logger.warning(f"No watcher found for {project_root}")
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
watcher = self.watchers.pop(project_key)
|
|
312
|
+
await watcher.stop()
|
|
313
|
+
|
|
314
|
+
async def stop_all(self) -> None:
|
|
315
|
+
"""Stop all file watchers."""
|
|
316
|
+
for watcher in list(self.watchers.values()):
|
|
317
|
+
await watcher.stop()
|
|
318
|
+
self.watchers.clear()
|
|
319
|
+
|
|
320
|
+
def is_watching(self, project_root: Path) -> bool:
|
|
321
|
+
"""Check if a project is being watched."""
|
|
322
|
+
return str(project_root) in self.watchers
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Entry point for running the MCP server."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .server import run_mcp_server
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
"""Main entry point for the MCP server."""
|
|
12
|
+
# Allow specifying project root as command line argument
|
|
13
|
+
project_root = Path(sys.argv[1]) if len(sys.argv) > 1 else None
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
asyncio.run(run_mcp_server(project_root))
|
|
17
|
+
except KeyboardInterrupt:
|
|
18
|
+
pass
|
|
19
|
+
except Exception as e:
|
|
20
|
+
print(f"MCP server error: {e}", file=sys.stderr)
|
|
21
|
+
sys.exit(1)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
main()
|