mcp-vector-search 0.0.3__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 (35) hide show
  1. mcp_vector_search/__init__.py +9 -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/config.py +303 -0
  5. mcp_vector_search/cli/commands/index.py +304 -0
  6. mcp_vector_search/cli/commands/init.py +212 -0
  7. mcp_vector_search/cli/commands/search.py +395 -0
  8. mcp_vector_search/cli/commands/status.py +340 -0
  9. mcp_vector_search/cli/commands/watch.py +288 -0
  10. mcp_vector_search/cli/main.py +117 -0
  11. mcp_vector_search/cli/output.py +242 -0
  12. mcp_vector_search/config/__init__.py +1 -0
  13. mcp_vector_search/config/defaults.py +175 -0
  14. mcp_vector_search/config/settings.py +108 -0
  15. mcp_vector_search/core/__init__.py +1 -0
  16. mcp_vector_search/core/database.py +431 -0
  17. mcp_vector_search/core/embeddings.py +250 -0
  18. mcp_vector_search/core/exceptions.py +66 -0
  19. mcp_vector_search/core/indexer.py +310 -0
  20. mcp_vector_search/core/models.py +174 -0
  21. mcp_vector_search/core/project.py +304 -0
  22. mcp_vector_search/core/search.py +324 -0
  23. mcp_vector_search/core/watcher.py +320 -0
  24. mcp_vector_search/mcp/__init__.py +1 -0
  25. mcp_vector_search/parsers/__init__.py +1 -0
  26. mcp_vector_search/parsers/base.py +180 -0
  27. mcp_vector_search/parsers/javascript.py +238 -0
  28. mcp_vector_search/parsers/python.py +407 -0
  29. mcp_vector_search/parsers/registry.py +187 -0
  30. mcp_vector_search/py.typed +1 -0
  31. mcp_vector_search-0.0.3.dist-info/METADATA +333 -0
  32. mcp_vector_search-0.0.3.dist-info/RECORD +35 -0
  33. mcp_vector_search-0.0.3.dist-info/WHEEL +4 -0
  34. mcp_vector_search-0.0.3.dist-info/entry_points.txt +2 -0
  35. mcp_vector_search-0.0.3.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,340 @@
1
+ """Status command for MCP Vector Search CLI."""
2
+
3
+ import asyncio
4
+ from pathlib import Path
5
+ from typing import Dict, Any
6
+
7
+ import typer
8
+ from loguru import logger
9
+
10
+ from ...core.database import ChromaVectorDatabase
11
+ from ...core.embeddings import create_embedding_function
12
+ from ...core.exceptions import ProjectNotFoundError
13
+ from ...core.indexer import SemanticIndexer
14
+ from ...core.project import ProjectManager
15
+ from ..output import (
16
+ console,
17
+ print_config,
18
+ print_dependency_status,
19
+ print_error,
20
+ print_index_stats,
21
+ print_info,
22
+ print_json,
23
+ print_project_info,
24
+ )
25
+
26
+ # Create status subcommand app
27
+ status_app = typer.Typer(help="Show project status and statistics")
28
+
29
+
30
+ @status_app.command()
31
+ def main(
32
+ ctx: typer.Context,
33
+ verbose: bool = typer.Option(
34
+ False,
35
+ "--verbose",
36
+ "-v",
37
+ help="Show detailed information",
38
+ ),
39
+ health_check: bool = typer.Option(
40
+ False,
41
+ "--health-check",
42
+ help="Perform health check of all components",
43
+ ),
44
+ json_output: bool = typer.Option(
45
+ False,
46
+ "--json",
47
+ help="Output status in JSON format",
48
+ ),
49
+ ) -> None:
50
+ """Show project status and indexing statistics.
51
+
52
+ This command displays comprehensive information about your MCP Vector Search
53
+ project including configuration, indexing status, and system health.
54
+
55
+ Examples:
56
+ mcp-vector-search status
57
+ mcp-vector-search status --verbose
58
+ mcp-vector-search status --health-check --json
59
+ """
60
+ try:
61
+ project_root = ctx.obj.get("project_root") or Path.cwd()
62
+
63
+ asyncio.run(show_status(
64
+ project_root=project_root,
65
+ verbose=verbose,
66
+ health_check=health_check,
67
+ json_output=json_output,
68
+ ))
69
+
70
+ except Exception as e:
71
+ logger.error(f"Status check failed: {e}")
72
+ print_error(f"Status check failed: {e}")
73
+ raise typer.Exit(1)
74
+
75
+
76
+ async def show_status(
77
+ project_root: Path,
78
+ verbose: bool = False,
79
+ health_check: bool = False,
80
+ json_output: bool = False,
81
+ ) -> None:
82
+ """Show comprehensive project status."""
83
+ status_data = {}
84
+
85
+ try:
86
+ # Check if project is initialized
87
+ project_manager = ProjectManager(project_root)
88
+
89
+ if not project_manager.is_initialized():
90
+ if json_output:
91
+ status_data = {
92
+ "initialized": False,
93
+ "project_root": str(project_root),
94
+ "error": "Project not initialized"
95
+ }
96
+ print_json(status_data)
97
+ else:
98
+ print_error(f"Project not initialized at {project_root}")
99
+ print_info("Run 'mcp-vector-search init' to initialize the project")
100
+ return
101
+
102
+ # Get project information
103
+ project_info = project_manager.get_project_info()
104
+ config = project_manager.load_config()
105
+
106
+ # Get indexing statistics
107
+ embedding_function, _ = create_embedding_function(config.embedding_model)
108
+ database = ChromaVectorDatabase(
109
+ persist_directory=config.index_path,
110
+ embedding_function=embedding_function,
111
+ )
112
+
113
+ indexer = SemanticIndexer(
114
+ database=database,
115
+ project_root=project_root,
116
+ file_extensions=config.file_extensions,
117
+ )
118
+
119
+ async with database:
120
+ index_stats = await indexer.get_indexing_stats()
121
+ db_stats = await database.get_stats()
122
+
123
+ # Compile status data
124
+ status_data = {
125
+ "project": {
126
+ "name": project_info.name,
127
+ "root_path": str(project_info.root_path),
128
+ "initialized": project_info.is_initialized,
129
+ "languages": project_info.languages,
130
+ "file_count": project_info.file_count,
131
+ },
132
+ "configuration": {
133
+ "embedding_model": config.embedding_model,
134
+ "similarity_threshold": config.similarity_threshold,
135
+ "file_extensions": config.file_extensions,
136
+ "max_chunk_size": config.max_chunk_size,
137
+ "cache_embeddings": config.cache_embeddings,
138
+ "watch_files": config.watch_files,
139
+ },
140
+ "index": {
141
+ "total_files": index_stats.get("total_indexable_files", 0),
142
+ "indexed_files": index_stats.get("indexed_files", 0),
143
+ "total_chunks": index_stats.get("total_chunks", 0),
144
+ "languages": index_stats.get("languages", {}),
145
+ "index_size_mb": db_stats.index_size_mb,
146
+ "last_updated": db_stats.last_updated,
147
+ },
148
+ }
149
+
150
+ # Add health check if requested
151
+ if health_check:
152
+ health_status = await perform_health_check(project_root, config)
153
+ status_data["health"] = health_status
154
+
155
+ # Add verbose information
156
+ if verbose:
157
+ status_data["verbose"] = {
158
+ "config_path": str(project_info.config_path),
159
+ "index_path": str(project_info.index_path),
160
+ "ignore_patterns": list(indexer.get_ignore_patterns()),
161
+ "parser_info": index_stats.get("parser_info", {}),
162
+ }
163
+
164
+ # Output results
165
+ if json_output:
166
+ print_json(status_data)
167
+ else:
168
+ _display_status(status_data, verbose)
169
+
170
+ except ProjectNotFoundError:
171
+ if json_output:
172
+ print_json({"initialized": False, "error": "Project not initialized"})
173
+ else:
174
+ print_error("Project not initialized")
175
+ print_info("Run 'mcp-vector-search init' to initialize the project")
176
+ except Exception as e:
177
+ if json_output:
178
+ print_json({"error": str(e)})
179
+ else:
180
+ print_error(f"Failed to get status: {e}")
181
+ raise
182
+
183
+
184
+ def _display_status(status_data: Dict[str, Any], verbose: bool) -> None:
185
+ """Display status in human-readable format."""
186
+ project_data = status_data["project"]
187
+ config_data = status_data["configuration"]
188
+ index_data = status_data["index"]
189
+
190
+ # Project information
191
+ console.print("[bold blue]Project Information[/bold blue]")
192
+ console.print(f" Name: {project_data['name']}")
193
+ console.print(f" Root: {project_data['root_path']}")
194
+ console.print(f" Languages: {', '.join(project_data['languages']) if project_data['languages'] else 'None detected'}")
195
+ console.print(f" Indexable Files: {project_data['file_count']}")
196
+ console.print()
197
+
198
+ # Configuration
199
+ console.print("[bold blue]Configuration[/bold blue]")
200
+ console.print(f" Embedding Model: {config_data['embedding_model']}")
201
+ console.print(f" Similarity Threshold: {config_data['similarity_threshold']}")
202
+ console.print(f" File Extensions: {', '.join(config_data['file_extensions'])}")
203
+ console.print(f" Cache Embeddings: {'āœ“' if config_data['cache_embeddings'] else 'āœ—'}")
204
+ console.print()
205
+
206
+ # Index statistics
207
+ console.print("[bold blue]Index Statistics[/bold blue]")
208
+ console.print(f" Indexed Files: {index_data['indexed_files']}/{index_data['total_files']}")
209
+ console.print(f" Total Chunks: {index_data['total_chunks']}")
210
+ console.print(f" Index Size: {index_data['index_size_mb']:.2f} MB")
211
+
212
+ if index_data['languages']:
213
+ console.print(" Language Distribution:")
214
+ for lang, count in index_data['languages'].items():
215
+ console.print(f" {lang}: {count} chunks")
216
+ console.print()
217
+
218
+ # Health check results
219
+ if "health" in status_data:
220
+ health_data = status_data["health"]
221
+ console.print("[bold blue]Health Check[/bold blue]")
222
+
223
+ overall_health = health_data.get("overall", "unknown")
224
+ if overall_health == "healthy":
225
+ console.print("[green]āœ“ System is healthy[/green]")
226
+ elif overall_health == "warning":
227
+ console.print("[yellow]⚠ System has warnings[/yellow]")
228
+ else:
229
+ console.print("[red]āœ— System has issues[/red]")
230
+
231
+ for component, status in health_data.get("components", {}).items():
232
+ if status == "ok":
233
+ console.print(f" [green]āœ“[/green] {component}")
234
+ elif status == "warning":
235
+ console.print(f" [yellow]⚠[/yellow] {component}")
236
+ else:
237
+ console.print(f" [red]āœ—[/red] {component}")
238
+ console.print()
239
+
240
+ # Verbose information
241
+ if verbose and "verbose" in status_data:
242
+ verbose_data = status_data["verbose"]
243
+ console.print("[bold blue]Detailed Information[/bold blue]")
244
+ console.print(f" Config Path: {verbose_data['config_path']}")
245
+ console.print(f" Index Path: {verbose_data['index_path']}")
246
+ console.print(f" Ignore Patterns: {', '.join(verbose_data['ignore_patterns'])}")
247
+
248
+
249
+ async def perform_health_check(project_root: Path, config) -> Dict[str, Any]:
250
+ """Perform comprehensive health check."""
251
+ health_status = {
252
+ "overall": "healthy",
253
+ "components": {},
254
+ "issues": [],
255
+ }
256
+
257
+ try:
258
+ # Check dependencies
259
+ deps_ok = check_dependencies()
260
+ health_status["components"]["dependencies"] = "ok" if deps_ok else "error"
261
+ if not deps_ok:
262
+ health_status["issues"].append("Missing dependencies")
263
+
264
+ # Check configuration
265
+ config_ok = True
266
+ try:
267
+ # Validate embedding model
268
+ embedding_function, _ = create_embedding_function(config.embedding_model)
269
+ health_status["components"]["embedding_model"] = "ok"
270
+ except Exception as e:
271
+ config_ok = False
272
+ health_status["components"]["embedding_model"] = "error"
273
+ health_status["issues"].append(f"Embedding model error: {e}")
274
+
275
+ # Check database
276
+ try:
277
+ database = ChromaVectorDatabase(
278
+ persist_directory=config.index_path,
279
+ embedding_function=embedding_function,
280
+ )
281
+ async with database:
282
+ await database.get_stats()
283
+ health_status["components"]["database"] = "ok"
284
+ except Exception as e:
285
+ health_status["components"]["database"] = "error"
286
+ health_status["issues"].append(f"Database error: {e}")
287
+
288
+ # Check file system permissions
289
+ try:
290
+ config.index_path.mkdir(parents=True, exist_ok=True)
291
+ test_file = config.index_path / ".test_write"
292
+ test_file.write_text("test")
293
+ test_file.unlink()
294
+ health_status["components"]["file_permissions"] = "ok"
295
+ except Exception as e:
296
+ health_status["components"]["file_permissions"] = "error"
297
+ health_status["issues"].append(f"File permission error: {e}")
298
+
299
+ # Determine overall health
300
+ if any(status == "error" for status in health_status["components"].values()):
301
+ health_status["overall"] = "error"
302
+ elif any(status == "warning" for status in health_status["components"].values()):
303
+ health_status["overall"] = "warning"
304
+
305
+ except Exception as e:
306
+ health_status["overall"] = "error"
307
+ health_status["issues"].append(f"Health check failed: {e}")
308
+
309
+ return health_status
310
+
311
+
312
+ def check_dependencies() -> bool:
313
+ """Check if all required dependencies are available."""
314
+ dependencies = [
315
+ ("chromadb", "ChromaDB"),
316
+ ("sentence_transformers", "Sentence Transformers"),
317
+ ("tree_sitter", "Tree-sitter"),
318
+ ("tree_sitter_languages", "Tree-sitter Languages"),
319
+ ("typer", "Typer"),
320
+ ("rich", "Rich"),
321
+ ("pydantic", "Pydantic"),
322
+ ("watchdog", "Watchdog"),
323
+ ("loguru", "Loguru"),
324
+ ]
325
+
326
+ all_available = True
327
+
328
+ for module_name, display_name in dependencies:
329
+ try:
330
+ __import__(module_name)
331
+ print_dependency_status(display_name, True)
332
+ except ImportError:
333
+ print_dependency_status(display_name, False)
334
+ all_available = False
335
+
336
+ return all_available
337
+
338
+
339
+ if __name__ == "__main__":
340
+ status_app()
@@ -0,0 +1,288 @@
1
+ """Watch command for MCP Vector Search CLI."""
2
+
3
+ import asyncio
4
+ import signal
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ import typer
10
+ from loguru import logger
11
+
12
+ from ...core.database import ChromaVectorDatabase
13
+ from ...core.embeddings import create_embedding_function
14
+ from ...core.exceptions import ProjectNotFoundError
15
+ from ...core.indexer import SemanticIndexer
16
+ from ...core.project import ProjectManager
17
+ from ...core.watcher import FileWatcher
18
+ from ..output import (
19
+ console,
20
+ print_error,
21
+ print_info,
22
+ print_success,
23
+ print_warning,
24
+ )
25
+
26
+ app = typer.Typer(name="watch", help="Watch for file changes and update index")
27
+
28
+
29
+ @app.command("main")
30
+ def watch_main(
31
+ project_root: Path = typer.Argument(
32
+ Path.cwd(),
33
+ help="Project root directory to watch",
34
+ exists=True,
35
+ file_okay=False,
36
+ dir_okay=True,
37
+ readable=True,
38
+ ),
39
+ config: Optional[Path] = typer.Option(
40
+ None,
41
+ "--config",
42
+ "-c",
43
+ help="Configuration file to use",
44
+ exists=True,
45
+ file_okay=True,
46
+ dir_okay=False,
47
+ readable=True,
48
+ ),
49
+ verbose: bool = typer.Option(
50
+ False,
51
+ "--verbose",
52
+ "-v",
53
+ help="Enable verbose output",
54
+ ),
55
+ ) -> None:
56
+ """Watch for file changes and automatically update the search index.
57
+
58
+ This command starts a file watcher that monitors your project directory
59
+ for changes to code files. When files are created, modified, or deleted,
60
+ the search index is automatically updated to reflect the changes.
61
+
62
+ The watcher will:
63
+ - Monitor all files with configured extensions
64
+ - Debounce rapid changes to avoid excessive indexing
65
+ - Update the index incrementally for better performance
66
+ - Ignore common build/cache directories
67
+
68
+ Press Ctrl+C to stop watching.
69
+
70
+ Examples:
71
+ mcp-vector-search watch
72
+ mcp-vector-search watch /path/to/project --verbose
73
+ mcp-vector-search watch --config custom-config.json
74
+ """
75
+ if verbose:
76
+ logger.remove()
77
+ logger.add(sys.stderr, level="DEBUG")
78
+
79
+ try:
80
+ asyncio.run(_watch_async(project_root, config))
81
+ except KeyboardInterrupt:
82
+ print_info("Watch stopped by user")
83
+ except Exception as e:
84
+ print_error(f"Watch failed: {e}")
85
+ raise typer.Exit(1)
86
+
87
+
88
+ async def _watch_async(project_root: Path, config_path: Optional[Path]) -> None:
89
+ """Async implementation of watch command."""
90
+ # Load project configuration
91
+ try:
92
+ project_manager = ProjectManager(project_root)
93
+ if not project_manager.is_initialized():
94
+ print_error(
95
+ f"Project not initialized at {project_root}. "
96
+ "Run 'mcp-vector-search init' first."
97
+ )
98
+ raise typer.Exit(1)
99
+
100
+ config = project_manager.load_config()
101
+ print_info(f"Loaded configuration from {project_root}")
102
+
103
+ except ProjectNotFoundError:
104
+ print_error(
105
+ f"No MCP Vector Search project found at {project_root}. "
106
+ "Run 'mcp-vector-search init' to initialize."
107
+ )
108
+ raise typer.Exit(1)
109
+
110
+ # Setup database and indexer
111
+ try:
112
+ embedding_function, _ = create_embedding_function(config.embedding_model)
113
+ database = ChromaVectorDatabase(
114
+ persist_directory=config.index_path,
115
+ embedding_function=embedding_function,
116
+ )
117
+
118
+ indexer = SemanticIndexer(
119
+ database=database,
120
+ project_root=project_root,
121
+ file_extensions=config.file_extensions,
122
+ )
123
+
124
+ print_info(f"Initialized database at {config.index_path}")
125
+
126
+ except Exception as e:
127
+ print_error(f"Failed to initialize database: {e}")
128
+ raise typer.Exit(1)
129
+
130
+ # Start watching
131
+ try:
132
+ async with database:
133
+ watcher = FileWatcher(
134
+ project_root=project_root,
135
+ config=config,
136
+ indexer=indexer,
137
+ database=database,
138
+ )
139
+
140
+ print_success("šŸ” Starting file watcher...")
141
+ print_info(f"šŸ“ Watching: {project_root}")
142
+ print_info(f"šŸ“„ Extensions: {', '.join(config.file_extensions)}")
143
+ print_info("Press Ctrl+C to stop watching")
144
+
145
+ async with watcher:
146
+ # Set up signal handlers for graceful shutdown
147
+ stop_event = asyncio.Event()
148
+
149
+ def signal_handler():
150
+ print_info("\nā¹ļø Stopping file watcher...")
151
+ stop_event.set()
152
+
153
+ # Handle SIGINT (Ctrl+C) and SIGTERM
154
+ if sys.platform != "win32":
155
+ loop = asyncio.get_running_loop()
156
+ for sig in (signal.SIGINT, signal.SIGTERM):
157
+ loop.add_signal_handler(sig, signal_handler)
158
+
159
+ try:
160
+ # Wait for stop signal
161
+ await stop_event.wait()
162
+ except KeyboardInterrupt:
163
+ signal_handler()
164
+
165
+ print_success("āœ… File watcher stopped")
166
+
167
+ except Exception as e:
168
+ print_error(f"File watching failed: {e}")
169
+ raise typer.Exit(1)
170
+
171
+
172
+ @app.command("status")
173
+ def watch_status(
174
+ project_root: Path = typer.Argument(
175
+ Path.cwd(),
176
+ help="Project root directory",
177
+ exists=True,
178
+ file_okay=False,
179
+ dir_okay=True,
180
+ readable=True,
181
+ ),
182
+ ) -> None:
183
+ """Check if file watching is enabled for a project.
184
+
185
+ This command checks the project configuration to see if file watching
186
+ is enabled and provides information about the watch settings.
187
+ """
188
+ try:
189
+ project_manager = ProjectManager(project_root)
190
+ if not project_manager.is_initialized():
191
+ print_error(f"Project not initialized at {project_root}")
192
+ raise typer.Exit(1)
193
+
194
+ config = project_manager.load_config()
195
+
196
+ console.print("\n[bold]File Watch Status[/bold]")
197
+ console.print(f"Project: {project_root}")
198
+ console.print(f"Watch Files: {'āœ“' if config.watch_files else 'āœ—'}")
199
+
200
+ if config.watch_files:
201
+ console.print(f"Extensions: {', '.join(config.file_extensions)}")
202
+ print_info("File watching is enabled for this project")
203
+ else:
204
+ print_warning("File watching is disabled for this project")
205
+ print_info("Enable with: mcp-vector-search config set watch_files true")
206
+
207
+ except ProjectNotFoundError:
208
+ print_error(f"No MCP Vector Search project found at {project_root}")
209
+ raise typer.Exit(1)
210
+ except Exception as e:
211
+ print_error(f"Failed to check watch status: {e}")
212
+ raise typer.Exit(1)
213
+
214
+
215
+ @app.command("enable")
216
+ def watch_enable(
217
+ project_root: Path = typer.Argument(
218
+ Path.cwd(),
219
+ help="Project root directory",
220
+ exists=True,
221
+ file_okay=False,
222
+ dir_okay=True,
223
+ readable=True,
224
+ ),
225
+ ) -> None:
226
+ """Enable file watching for a project.
227
+
228
+ This command enables the watch_files setting in the project configuration.
229
+ After enabling, you can use 'mcp-vector-search watch' to start monitoring.
230
+ """
231
+ try:
232
+ project_manager = ProjectManager(project_root)
233
+ if not project_manager.is_initialized():
234
+ print_error(f"Project not initialized at {project_root}")
235
+ raise typer.Exit(1)
236
+
237
+ config = project_manager.load_config()
238
+ config.watch_files = True
239
+ project_manager.save_config(config)
240
+
241
+ print_success("āœ… File watching enabled")
242
+ print_info("Start watching with: mcp-vector-search watch")
243
+
244
+ except ProjectNotFoundError:
245
+ print_error(f"No MCP Vector Search project found at {project_root}")
246
+ raise typer.Exit(1)
247
+ except Exception as e:
248
+ print_error(f"Failed to enable file watching: {e}")
249
+ raise typer.Exit(1)
250
+
251
+
252
+ @app.command("disable")
253
+ def watch_disable(
254
+ project_root: Path = typer.Argument(
255
+ Path.cwd(),
256
+ help="Project root directory",
257
+ exists=True,
258
+ file_okay=False,
259
+ dir_okay=True,
260
+ readable=True,
261
+ ),
262
+ ) -> None:
263
+ """Disable file watching for a project.
264
+
265
+ This command disables the watch_files setting in the project configuration.
266
+ """
267
+ try:
268
+ project_manager = ProjectManager(project_root)
269
+ if not project_manager.is_initialized():
270
+ print_error(f"Project not initialized at {project_root}")
271
+ raise typer.Exit(1)
272
+
273
+ config = project_manager.load_config()
274
+ config.watch_files = False
275
+ project_manager.save_config(config)
276
+
277
+ print_success("āœ… File watching disabled")
278
+
279
+ except ProjectNotFoundError:
280
+ print_error(f"No MCP Vector Search project found at {project_root}")
281
+ raise typer.Exit(1)
282
+ except Exception as e:
283
+ print_error(f"Failed to disable file watching: {e}")
284
+ raise typer.Exit(1)
285
+
286
+
287
+ if __name__ == "__main__":
288
+ app()