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,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,5 @@
1
+ """MCP server integration for MCP Vector Search."""
2
+
3
+ from .server import create_mcp_server, run_mcp_server
4
+
5
+ __all__ = ["create_mcp_server", "run_mcp_server"]
@@ -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()