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.
- mcp_vector_search/__init__.py +3 -2
- mcp_vector_search/cli/commands/auto_index.py +397 -0
- mcp_vector_search/cli/commands/config.py +88 -40
- mcp_vector_search/cli/commands/index.py +198 -52
- mcp_vector_search/cli/commands/init.py +472 -58
- mcp_vector_search/cli/commands/install.py +284 -0
- mcp_vector_search/cli/commands/mcp.py +495 -0
- mcp_vector_search/cli/commands/search.py +241 -87
- mcp_vector_search/cli/commands/status.py +184 -58
- mcp_vector_search/cli/commands/watch.py +34 -35
- mcp_vector_search/cli/didyoumean.py +184 -0
- mcp_vector_search/cli/export.py +320 -0
- mcp_vector_search/cli/history.py +292 -0
- mcp_vector_search/cli/interactive.py +342 -0
- mcp_vector_search/cli/main.py +163 -26
- mcp_vector_search/cli/output.py +63 -45
- mcp_vector_search/config/defaults.py +50 -36
- mcp_vector_search/config/settings.py +49 -35
- mcp_vector_search/core/auto_indexer.py +298 -0
- mcp_vector_search/core/connection_pool.py +322 -0
- mcp_vector_search/core/database.py +335 -25
- mcp_vector_search/core/embeddings.py +73 -29
- mcp_vector_search/core/exceptions.py +19 -2
- mcp_vector_search/core/factory.py +310 -0
- mcp_vector_search/core/git_hooks.py +345 -0
- mcp_vector_search/core/indexer.py +237 -73
- mcp_vector_search/core/models.py +21 -19
- mcp_vector_search/core/project.py +73 -58
- mcp_vector_search/core/scheduler.py +330 -0
- mcp_vector_search/core/search.py +574 -86
- mcp_vector_search/core/watcher.py +48 -46
- mcp_vector_search/mcp/__init__.py +4 -0
- mcp_vector_search/mcp/__main__.py +25 -0
- mcp_vector_search/mcp/server.py +701 -0
- mcp_vector_search/parsers/base.py +30 -31
- mcp_vector_search/parsers/javascript.py +74 -48
- mcp_vector_search/parsers/python.py +57 -49
- mcp_vector_search/parsers/registry.py +47 -32
- mcp_vector_search/parsers/text.py +179 -0
- mcp_vector_search/utils/__init__.py +40 -0
- mcp_vector_search/utils/gitignore.py +229 -0
- mcp_vector_search/utils/timing.py +334 -0
- mcp_vector_search/utils/version.py +47 -0
- {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/METADATA +173 -7
- mcp_vector_search-0.4.11.dist-info/RECORD +54 -0
- mcp_vector_search-0.0.3.dist-info/RECORD +0 -35
- {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/WHEEL +0 -0
- {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/entry_points.txt +0 -0
- {mcp_vector_search-0.0.3.dist-info → mcp_vector_search-0.4.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
"""File system watcher for incremental indexing."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import List, Optional, Set, Callable, Awaitable, Union
|
|
6
|
-
from threading import Thread
|
|
7
4
|
import time
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
8
6
|
from concurrent.futures import Future
|
|
7
|
+
from pathlib import Path
|
|
9
8
|
|
|
10
9
|
from loguru import logger
|
|
10
|
+
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
|
11
11
|
from watchdog.observers import Observer
|
|
12
|
-
from watchdog.events import FileSystemEventHandler, FileSystemEvent
|
|
13
12
|
|
|
14
13
|
from ..config.settings import ProjectConfig
|
|
15
|
-
from .indexer import SemanticIndexer
|
|
16
14
|
from .database import ChromaVectorDatabase
|
|
15
|
+
from .indexer import SemanticIndexer
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
class CodeFileHandler(FileSystemEventHandler):
|
|
@@ -21,8 +20,8 @@ class CodeFileHandler(FileSystemEventHandler):
|
|
|
21
20
|
|
|
22
21
|
def __init__(
|
|
23
22
|
self,
|
|
24
|
-
file_extensions:
|
|
25
|
-
ignore_patterns:
|
|
23
|
+
file_extensions: list[str],
|
|
24
|
+
ignore_patterns: list[str],
|
|
26
25
|
callback: Callable[[str, str], Awaitable[None]],
|
|
27
26
|
loop: asyncio.AbstractEventLoop,
|
|
28
27
|
debounce_delay: float = 1.0,
|
|
@@ -42,23 +41,23 @@ class CodeFileHandler(FileSystemEventHandler):
|
|
|
42
41
|
self.callback = callback
|
|
43
42
|
self.loop = loop
|
|
44
43
|
self.debounce_delay = debounce_delay
|
|
45
|
-
self.pending_changes:
|
|
44
|
+
self.pending_changes: set[str] = set()
|
|
46
45
|
self.last_change_time: float = 0
|
|
47
|
-
self.debounce_task:
|
|
46
|
+
self.debounce_task: asyncio.Task | Future | None = None
|
|
48
47
|
|
|
49
48
|
def should_process_file(self, file_path: str) -> bool:
|
|
50
49
|
"""Check if file should be processed."""
|
|
51
50
|
path = Path(file_path)
|
|
52
|
-
|
|
51
|
+
|
|
53
52
|
# Check file extension
|
|
54
53
|
if path.suffix not in self.file_extensions:
|
|
55
54
|
return False
|
|
56
|
-
|
|
55
|
+
|
|
57
56
|
# Check ignore patterns
|
|
58
57
|
for pattern in self.ignore_patterns:
|
|
59
58
|
if pattern in str(path):
|
|
60
59
|
return False
|
|
61
|
-
|
|
60
|
+
|
|
62
61
|
return True
|
|
63
62
|
|
|
64
63
|
def on_modified(self, event: FileSystemEvent) -> None:
|
|
@@ -78,7 +77,7 @@ class CodeFileHandler(FileSystemEventHandler):
|
|
|
78
77
|
|
|
79
78
|
def on_moved(self, event: FileSystemEvent) -> None:
|
|
80
79
|
"""Handle file move/rename."""
|
|
81
|
-
if hasattr(event,
|
|
80
|
+
if hasattr(event, "dest_path"):
|
|
82
81
|
# Handle rename/move
|
|
83
82
|
if not event.is_directory:
|
|
84
83
|
if self.should_process_file(event.src_path):
|
|
@@ -96,24 +95,22 @@ class CodeFileHandler(FileSystemEventHandler):
|
|
|
96
95
|
self.debounce_task.cancel()
|
|
97
96
|
|
|
98
97
|
# Schedule new debounce task using the stored loop
|
|
99
|
-
future = asyncio.run_coroutine_threadsafe(
|
|
100
|
-
self._debounced_process(), self.loop
|
|
101
|
-
)
|
|
98
|
+
future = asyncio.run_coroutine_threadsafe(self._debounced_process(), self.loop)
|
|
102
99
|
# Store the future as our task (it has a done() method)
|
|
103
100
|
self.debounce_task = future
|
|
104
101
|
|
|
105
102
|
async def _debounced_process(self) -> None:
|
|
106
103
|
"""Process pending changes after debounce delay."""
|
|
107
104
|
await asyncio.sleep(self.debounce_delay)
|
|
108
|
-
|
|
105
|
+
|
|
109
106
|
# Check if more changes occurred during debounce
|
|
110
107
|
if time.time() - self.last_change_time < self.debounce_delay:
|
|
111
108
|
return
|
|
112
|
-
|
|
109
|
+
|
|
113
110
|
# Process all pending changes
|
|
114
111
|
changes = self.pending_changes.copy()
|
|
115
112
|
self.pending_changes.clear()
|
|
116
|
-
|
|
113
|
+
|
|
117
114
|
for change in changes:
|
|
118
115
|
change_type, file_path = change.split(":", 1)
|
|
119
116
|
try:
|
|
@@ -133,7 +130,7 @@ class FileWatcher:
|
|
|
133
130
|
database: ChromaVectorDatabase,
|
|
134
131
|
):
|
|
135
132
|
"""Initialize file watcher.
|
|
136
|
-
|
|
133
|
+
|
|
137
134
|
Args:
|
|
138
135
|
project_root: Root directory to watch
|
|
139
136
|
config: Project configuration
|
|
@@ -144,8 +141,8 @@ class FileWatcher:
|
|
|
144
141
|
self.config = config
|
|
145
142
|
self.indexer = indexer
|
|
146
143
|
self.database = database
|
|
147
|
-
self.observer:
|
|
148
|
-
self.handler:
|
|
144
|
+
self.observer: Observer | None = None
|
|
145
|
+
self.handler: CodeFileHandler | None = None
|
|
149
146
|
self.is_running = False
|
|
150
147
|
|
|
151
148
|
async def start(self) -> None:
|
|
@@ -155,7 +152,7 @@ class FileWatcher:
|
|
|
155
152
|
return
|
|
156
153
|
|
|
157
154
|
logger.info(f"Starting file watcher for {self.project_root}")
|
|
158
|
-
|
|
155
|
+
|
|
159
156
|
# Create handler
|
|
160
157
|
loop = asyncio.get_running_loop()
|
|
161
158
|
self.handler = CodeFileHandler(
|
|
@@ -168,16 +165,12 @@ class FileWatcher:
|
|
|
168
165
|
|
|
169
166
|
# Create observer
|
|
170
167
|
self.observer = Observer()
|
|
171
|
-
self.observer.schedule(
|
|
172
|
-
self.handler,
|
|
173
|
-
str(self.project_root),
|
|
174
|
-
recursive=True
|
|
175
|
-
)
|
|
168
|
+
self.observer.schedule(self.handler, str(self.project_root), recursive=True)
|
|
176
169
|
|
|
177
170
|
# Start observer in a separate thread
|
|
178
171
|
self.observer.start()
|
|
179
172
|
self.is_running = True
|
|
180
|
-
|
|
173
|
+
|
|
181
174
|
logger.info("File watcher started successfully")
|
|
182
175
|
|
|
183
176
|
async def stop(self) -> None:
|
|
@@ -186,7 +179,7 @@ class FileWatcher:
|
|
|
186
179
|
return
|
|
187
180
|
|
|
188
181
|
logger.info("Stopping file watcher")
|
|
189
|
-
|
|
182
|
+
|
|
190
183
|
if self.observer:
|
|
191
184
|
self.observer.stop()
|
|
192
185
|
self.observer.join()
|
|
@@ -194,28 +187,37 @@ class FileWatcher:
|
|
|
194
187
|
|
|
195
188
|
self.handler = None
|
|
196
189
|
self.is_running = False
|
|
197
|
-
|
|
190
|
+
|
|
198
191
|
logger.info("File watcher stopped")
|
|
199
192
|
|
|
200
|
-
def _get_ignore_patterns(self) ->
|
|
193
|
+
def _get_ignore_patterns(self) -> list[str]:
|
|
201
194
|
"""Get patterns to ignore during watching."""
|
|
202
195
|
default_patterns = [
|
|
203
|
-
".git",
|
|
204
|
-
"
|
|
205
|
-
"
|
|
206
|
-
"
|
|
207
|
-
".
|
|
208
|
-
"
|
|
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",
|
|
209
211
|
".mcp-vector-search", # Ignore our own index directory
|
|
210
212
|
]
|
|
211
|
-
|
|
213
|
+
|
|
212
214
|
# Add any custom ignore patterns from config
|
|
213
215
|
# TODO: Add custom ignore patterns to config
|
|
214
216
|
return default_patterns
|
|
215
217
|
|
|
216
218
|
async def _handle_file_change(self, file_path: str, change_type: str) -> None:
|
|
217
219
|
"""Handle a file change event.
|
|
218
|
-
|
|
220
|
+
|
|
219
221
|
Args:
|
|
220
222
|
file_path: Path to the changed file
|
|
221
223
|
change_type: Type of change (created, modified, deleted)
|
|
@@ -230,9 +232,9 @@ class FileWatcher:
|
|
|
230
232
|
elif change_type in ("created", "modified"):
|
|
231
233
|
# Re-index the file
|
|
232
234
|
await self._reindex_file(path)
|
|
233
|
-
|
|
235
|
+
|
|
234
236
|
logger.info(f"Processed {change_type} for {path.name}")
|
|
235
|
-
|
|
237
|
+
|
|
236
238
|
except Exception as e:
|
|
237
239
|
logger.error(f"Failed to process {change_type} for {path}: {e}")
|
|
238
240
|
|
|
@@ -256,7 +258,7 @@ class FileWatcher:
|
|
|
256
258
|
|
|
257
259
|
# Remove existing chunks first
|
|
258
260
|
await self._remove_file_chunks(file_path)
|
|
259
|
-
|
|
261
|
+
|
|
260
262
|
# Index the file
|
|
261
263
|
chunks_indexed = await self.indexer.index_file(file_path)
|
|
262
264
|
logger.debug(f"Re-indexed {file_path.name}: {chunks_indexed} chunks")
|
|
@@ -287,21 +289,21 @@ class WatcherManager:
|
|
|
287
289
|
) -> FileWatcher:
|
|
288
290
|
"""Start a file watcher for a project."""
|
|
289
291
|
project_key = str(project_root)
|
|
290
|
-
|
|
292
|
+
|
|
291
293
|
if project_key in self.watchers:
|
|
292
294
|
logger.warning(f"Watcher already exists for {project_root}")
|
|
293
295
|
return self.watchers[project_key]
|
|
294
296
|
|
|
295
297
|
watcher = FileWatcher(project_root, config, indexer, database)
|
|
296
298
|
await watcher.start()
|
|
297
|
-
|
|
299
|
+
|
|
298
300
|
self.watchers[project_key] = watcher
|
|
299
301
|
return watcher
|
|
300
302
|
|
|
301
303
|
async def stop_watcher(self, project_root: Path) -> None:
|
|
302
304
|
"""Stop a file watcher for a project."""
|
|
303
305
|
project_key = str(project_root)
|
|
304
|
-
|
|
306
|
+
|
|
305
307
|
if project_key not in self.watchers:
|
|
306
308
|
logger.warning(f"No watcher found for {project_root}")
|
|
307
309
|
return
|
|
@@ -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()
|