mcp-vector-search 0.12.6__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.
Files changed (68) 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/config.py +393 -0
  6. mcp_vector_search/cli/commands/demo.py +358 -0
  7. mcp_vector_search/cli/commands/index.py +744 -0
  8. mcp_vector_search/cli/commands/init.py +645 -0
  9. mcp_vector_search/cli/commands/install.py +675 -0
  10. mcp_vector_search/cli/commands/install_old.py +696 -0
  11. mcp_vector_search/cli/commands/mcp.py +1182 -0
  12. mcp_vector_search/cli/commands/reset.py +393 -0
  13. mcp_vector_search/cli/commands/search.py +773 -0
  14. mcp_vector_search/cli/commands/status.py +549 -0
  15. mcp_vector_search/cli/commands/uninstall.py +485 -0
  16. mcp_vector_search/cli/commands/visualize.py +1467 -0
  17. mcp_vector_search/cli/commands/watch.py +287 -0
  18. mcp_vector_search/cli/didyoumean.py +500 -0
  19. mcp_vector_search/cli/export.py +320 -0
  20. mcp_vector_search/cli/history.py +295 -0
  21. mcp_vector_search/cli/interactive.py +342 -0
  22. mcp_vector_search/cli/main.py +461 -0
  23. mcp_vector_search/cli/output.py +412 -0
  24. mcp_vector_search/cli/suggestions.py +375 -0
  25. mcp_vector_search/config/__init__.py +1 -0
  26. mcp_vector_search/config/constants.py +24 -0
  27. mcp_vector_search/config/defaults.py +200 -0
  28. mcp_vector_search/config/settings.py +134 -0
  29. mcp_vector_search/core/__init__.py +1 -0
  30. mcp_vector_search/core/auto_indexer.py +298 -0
  31. mcp_vector_search/core/connection_pool.py +360 -0
  32. mcp_vector_search/core/database.py +1214 -0
  33. mcp_vector_search/core/directory_index.py +318 -0
  34. mcp_vector_search/core/embeddings.py +294 -0
  35. mcp_vector_search/core/exceptions.py +89 -0
  36. mcp_vector_search/core/factory.py +318 -0
  37. mcp_vector_search/core/git_hooks.py +345 -0
  38. mcp_vector_search/core/indexer.py +1002 -0
  39. mcp_vector_search/core/models.py +294 -0
  40. mcp_vector_search/core/project.py +333 -0
  41. mcp_vector_search/core/scheduler.py +330 -0
  42. mcp_vector_search/core/search.py +952 -0
  43. mcp_vector_search/core/watcher.py +322 -0
  44. mcp_vector_search/mcp/__init__.py +5 -0
  45. mcp_vector_search/mcp/__main__.py +25 -0
  46. mcp_vector_search/mcp/server.py +733 -0
  47. mcp_vector_search/parsers/__init__.py +8 -0
  48. mcp_vector_search/parsers/base.py +296 -0
  49. mcp_vector_search/parsers/dart.py +605 -0
  50. mcp_vector_search/parsers/html.py +413 -0
  51. mcp_vector_search/parsers/javascript.py +643 -0
  52. mcp_vector_search/parsers/php.py +694 -0
  53. mcp_vector_search/parsers/python.py +502 -0
  54. mcp_vector_search/parsers/registry.py +223 -0
  55. mcp_vector_search/parsers/ruby.py +678 -0
  56. mcp_vector_search/parsers/text.py +186 -0
  57. mcp_vector_search/parsers/utils.py +265 -0
  58. mcp_vector_search/py.typed +1 -0
  59. mcp_vector_search/utils/__init__.py +40 -0
  60. mcp_vector_search/utils/gitignore.py +250 -0
  61. mcp_vector_search/utils/monorepo.py +277 -0
  62. mcp_vector_search/utils/timing.py +334 -0
  63. mcp_vector_search/utils/version.py +47 -0
  64. mcp_vector_search-0.12.6.dist-info/METADATA +754 -0
  65. mcp_vector_search-0.12.6.dist-info/RECORD +68 -0
  66. mcp_vector_search-0.12.6.dist-info/WHEEL +4 -0
  67. mcp_vector_search-0.12.6.dist-info/entry_points.txt +2 -0
  68. mcp_vector_search-0.12.6.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,334 @@
1
+ """Timing utilities for performance measurement and optimization."""
2
+
3
+ import asyncio
4
+ import json
5
+ import statistics
6
+ import time
7
+ from collections.abc import Callable
8
+ from contextlib import asynccontextmanager, contextmanager
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from loguru import logger
14
+
15
+
16
+ @dataclass
17
+ class TimingResult:
18
+ """Result of a timing measurement."""
19
+
20
+ operation: str
21
+ duration: float # in seconds
22
+ timestamp: float
23
+ metadata: dict[str, Any] = field(default_factory=dict)
24
+
25
+ @property
26
+ def duration_ms(self) -> float:
27
+ """Duration in milliseconds."""
28
+ return self.duration * 1000
29
+
30
+ @property
31
+ def duration_us(self) -> float:
32
+ """Duration in microseconds."""
33
+ return self.duration * 1_000_000
34
+
35
+
36
+ class PerformanceProfiler:
37
+ """Performance profiler for measuring and analyzing operation timings."""
38
+
39
+ def __init__(self, name: str = "default"):
40
+ self.name = name
41
+ self.results: list[TimingResult] = []
42
+ self._active_timers: dict[str, float] = {}
43
+ self._nested_level = 0
44
+
45
+ def start_timer(self, operation: str) -> None:
46
+ """Start timing an operation."""
47
+ if operation in self._active_timers:
48
+ logger.warning(f"Timer '{operation}' already active, overwriting")
49
+ self._active_timers[operation] = time.perf_counter()
50
+
51
+ def stop_timer(
52
+ self, operation: str, metadata: dict[str, Any] | None = None
53
+ ) -> TimingResult:
54
+ """Stop timing an operation and record the result."""
55
+ if operation not in self._active_timers:
56
+ raise ValueError(f"Timer '{operation}' not found or not started")
57
+
58
+ start_time = self._active_timers.pop(operation)
59
+ duration = time.perf_counter() - start_time
60
+
61
+ result = TimingResult(
62
+ operation=operation,
63
+ duration=duration,
64
+ timestamp=time.time(),
65
+ metadata=metadata or {},
66
+ )
67
+
68
+ self.results.append(result)
69
+ return result
70
+
71
+ @contextmanager
72
+ def time_operation(self, operation: str, metadata: dict[str, Any] | None = None):
73
+ """Context manager for timing an operation."""
74
+ indent = " " * self._nested_level
75
+ logger.debug(f"{indent}⏱️ Starting: {operation}")
76
+
77
+ self._nested_level += 1
78
+ start_time = time.perf_counter()
79
+
80
+ try:
81
+ yield
82
+ finally:
83
+ duration = time.perf_counter() - start_time
84
+ self._nested_level -= 1
85
+
86
+ result = TimingResult(
87
+ operation=operation,
88
+ duration=duration,
89
+ timestamp=time.time(),
90
+ metadata=metadata or {},
91
+ )
92
+
93
+ self.results.append(result)
94
+
95
+ indent = " " * self._nested_level
96
+ logger.debug(f"{indent}✅ Completed: {operation} ({duration * 1000:.2f}ms)")
97
+
98
+ @asynccontextmanager
99
+ async def time_async_operation(
100
+ self, operation: str, metadata: dict[str, Any] | None = None
101
+ ):
102
+ """Async context manager for timing an operation."""
103
+ indent = " " * self._nested_level
104
+ logger.debug(f"{indent}⏱️ Starting: {operation}")
105
+
106
+ self._nested_level += 1
107
+ start_time = time.perf_counter()
108
+
109
+ try:
110
+ yield
111
+ finally:
112
+ duration = time.perf_counter() - start_time
113
+ self._nested_level -= 1
114
+
115
+ result = TimingResult(
116
+ operation=operation,
117
+ duration=duration,
118
+ timestamp=time.time(),
119
+ metadata=metadata or {},
120
+ )
121
+
122
+ self.results.append(result)
123
+
124
+ indent = " " * self._nested_level
125
+ logger.debug(f"{indent}✅ Completed: {operation} ({duration * 1000:.2f}ms)")
126
+
127
+ def get_stats(self, operation: str | None = None) -> dict[str, Any]:
128
+ """Get timing statistics for operations."""
129
+ if operation:
130
+ durations = [r.duration for r in self.results if r.operation == operation]
131
+ else:
132
+ durations = [r.duration for r in self.results]
133
+
134
+ if not durations:
135
+ return {}
136
+
137
+ return {
138
+ "count": len(durations),
139
+ "total": sum(durations),
140
+ "mean": statistics.mean(durations),
141
+ "median": statistics.median(durations),
142
+ "min": min(durations),
143
+ "max": max(durations),
144
+ "std_dev": statistics.stdev(durations) if len(durations) > 1 else 0.0,
145
+ "p95": statistics.quantiles(durations, n=20)[18]
146
+ if len(durations) >= 20
147
+ else max(durations),
148
+ "p99": statistics.quantiles(durations, n=100)[98]
149
+ if len(durations) >= 100
150
+ else max(durations),
151
+ }
152
+
153
+ def get_operation_breakdown(self) -> dict[str, dict[str, Any]]:
154
+ """Get breakdown of all operations."""
155
+ operations = {r.operation for r in self.results}
156
+ return {op: self.get_stats(op) for op in operations}
157
+
158
+ def print_report(self, show_individual: bool = False, min_duration_ms: float = 0.0):
159
+ """Print a detailed performance report."""
160
+ if not self.results:
161
+ print("No timing results recorded.")
162
+ return
163
+
164
+ print(f"\n{'=' * 60}")
165
+ print(f"PERFORMANCE REPORT: {self.name}")
166
+ print(f"{'=' * 60}")
167
+
168
+ # Overall stats
169
+ overall_stats = self.get_stats()
170
+ print("\nOVERALL STATISTICS:")
171
+ print(f" Total operations: {overall_stats['count']}")
172
+ print(f" Total time: {overall_stats['total'] * 1000:.2f}ms")
173
+ print(f" Average: {overall_stats['mean'] * 1000:.2f}ms")
174
+ print(f" Median: {overall_stats['median'] * 1000:.2f}ms")
175
+ print(f" Min: {overall_stats['min'] * 1000:.2f}ms")
176
+ print(f" Max: {overall_stats['max'] * 1000:.2f}ms")
177
+
178
+ # Per-operation breakdown
179
+ breakdown = self.get_operation_breakdown()
180
+ print("\nPER-OPERATION BREAKDOWN:")
181
+
182
+ for operation, stats in sorted(
183
+ breakdown.items(), key=lambda x: x[1]["total"], reverse=True
184
+ ):
185
+ print(f"\n {operation}:")
186
+ print(f" Count: {stats['count']}")
187
+ print(
188
+ f" Total: {stats['total'] * 1000:.2f}ms ({stats['total'] / overall_stats['total'] * 100:.1f}%)"
189
+ )
190
+ print(f" Average: {stats['mean'] * 1000:.2f}ms")
191
+ print(
192
+ f" Min/Max: {stats['min'] * 1000:.2f}ms / {stats['max'] * 1000:.2f}ms"
193
+ )
194
+ if stats["count"] > 1:
195
+ print(f" StdDev: {stats['std_dev'] * 1000:.2f}ms")
196
+
197
+ # Individual results if requested
198
+ if show_individual:
199
+ print("\nINDIVIDUAL RESULTS:")
200
+ for result in self.results:
201
+ if result.duration_ms >= min_duration_ms:
202
+ print(f" {result.operation}: {result.duration_ms:.2f}ms")
203
+ if result.metadata:
204
+ print(f" Metadata: {result.metadata}")
205
+
206
+ def save_results(self, file_path: Path):
207
+ """Save timing results to a JSON file."""
208
+ data = {
209
+ "profiler_name": self.name,
210
+ "timestamp": time.time(),
211
+ "results": [
212
+ {
213
+ "operation": r.operation,
214
+ "duration": r.duration,
215
+ "timestamp": r.timestamp,
216
+ "metadata": r.metadata,
217
+ }
218
+ for r in self.results
219
+ ],
220
+ "stats": self.get_operation_breakdown(),
221
+ }
222
+
223
+ with open(file_path, "w") as f:
224
+ json.dump(data, f, indent=2)
225
+
226
+ def clear(self):
227
+ """Clear all timing results."""
228
+ self.results.clear()
229
+ self._active_timers.clear()
230
+ self._nested_level = 0
231
+
232
+
233
+ # Global profiler instance
234
+ _global_profiler = PerformanceProfiler("global")
235
+
236
+
237
+ def time_function(
238
+ operation_name: str | None = None, metadata: dict[str, Any] | None = None
239
+ ):
240
+ """Decorator for timing function execution."""
241
+
242
+ def decorator(func: Callable) -> Callable:
243
+ name = operation_name or f"{func.__module__}.{func.__name__}"
244
+
245
+ if asyncio.iscoroutinefunction(func):
246
+
247
+ async def async_wrapper(*args, **kwargs):
248
+ async with _global_profiler.time_async_operation(name, metadata):
249
+ return await func(*args, **kwargs)
250
+
251
+ return async_wrapper
252
+ else:
253
+
254
+ def sync_wrapper(*args, **kwargs):
255
+ with _global_profiler.time_operation(name, metadata):
256
+ return func(*args, **kwargs)
257
+
258
+ return sync_wrapper
259
+
260
+ return decorator
261
+
262
+
263
+ @contextmanager
264
+ def time_block(operation: str, metadata: dict[str, Any] | None = None):
265
+ """Context manager for timing a block of code using the global profiler."""
266
+ with _global_profiler.time_operation(operation, metadata):
267
+ yield
268
+
269
+
270
+ @asynccontextmanager
271
+ async def time_async_block(operation: str, metadata: dict[str, Any] | None = None):
272
+ """Async context manager for timing a block of code using the global profiler."""
273
+ async with _global_profiler.time_async_operation(operation, metadata):
274
+ yield
275
+
276
+
277
+ def get_global_profiler() -> PerformanceProfiler:
278
+ """Get the global profiler instance."""
279
+ return _global_profiler
280
+
281
+
282
+ def print_global_report(**kwargs):
283
+ """Print report from the global profiler."""
284
+ _global_profiler.print_report(**kwargs)
285
+
286
+
287
+ def clear_global_profiler():
288
+ """Clear the global profiler."""
289
+ _global_profiler.clear()
290
+
291
+
292
+ class SearchProfiler(PerformanceProfiler):
293
+ """Specialized profiler for search operations."""
294
+
295
+ def __init__(self):
296
+ super().__init__("search_profiler")
297
+
298
+ async def profile_search(
299
+ self, search_func: Callable, query: str, **search_kwargs
300
+ ) -> tuple[Any, dict[str, float]]:
301
+ """Profile a complete search operation with detailed breakdown."""
302
+
303
+ async with self.time_async_operation(
304
+ "total_search", {"query": query, "kwargs": search_kwargs}
305
+ ):
306
+ # Time the actual search
307
+ async with self.time_async_operation("search_execution", {"query": query}):
308
+ result = await search_func(query, **search_kwargs)
309
+
310
+ # Time result processing if we can measure it
311
+ async with self.time_async_operation(
312
+ "result_processing",
313
+ {"result_count": len(result) if hasattr(result, "__len__") else 0},
314
+ ):
315
+ # Simulate any post-processing that might happen
316
+ await asyncio.sleep(0) # Placeholder for actual processing
317
+
318
+ # Return results and timing breakdown
319
+ timing_breakdown = {
320
+ op: self.get_stats(op)["mean"] * 1000 # Convert to ms
321
+ for op in ["total_search", "search_execution", "result_processing"]
322
+ if self.get_stats(op)
323
+ }
324
+
325
+ return result, timing_breakdown
326
+
327
+
328
+ # Convenience function for quick search profiling
329
+ async def profile_search_operation(
330
+ search_func: Callable, query: str, **kwargs
331
+ ) -> tuple[Any, dict[str, float]]:
332
+ """Quick function to profile a search operation."""
333
+ profiler = SearchProfiler()
334
+ return await profiler.profile_search(search_func, query, **kwargs)
@@ -0,0 +1,47 @@
1
+ """Version utilities for MCP Vector Search.
2
+
3
+ This module provides utilities for accessing and formatting version information.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from .. import __author__, __build__, __email__, __version__
9
+
10
+
11
+ def get_version_info() -> dict[str, Any]:
12
+ """Get complete version information.
13
+
14
+ Returns:
15
+ Dictionary containing version, build, and package metadata
16
+ """
17
+ return {
18
+ "version": __version__,
19
+ "build": __build__,
20
+ "author": __author__,
21
+ "email": __email__,
22
+ "package": "mcp-vector-search",
23
+ "version_string": f"{__version__} (build {__build__})",
24
+ }
25
+
26
+
27
+ def get_version_string(include_build: bool = True) -> str:
28
+ """Get formatted version string.
29
+
30
+ Args:
31
+ include_build: Whether to include build number
32
+
33
+ Returns:
34
+ Formatted version string
35
+ """
36
+ if include_build:
37
+ return f"{__version__} (build {__build__})"
38
+ return __version__
39
+
40
+
41
+ def get_user_agent() -> str:
42
+ """Get user agent string for HTTP requests.
43
+
44
+ Returns:
45
+ User agent string including version
46
+ """
47
+ return f"mcp-vector-search/{__version__}"