mcp-vector-search 0.9.3__py3-none-any.whl → 0.12.1__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.

@@ -57,6 +57,11 @@ class EnhancedDidYouMeanTyper(typer.Typer):
57
57
  # Get the underlying click group
58
58
  click_group = super().__call__(*args, **kwargs)
59
59
 
60
+ # If click_group is None (command already executed), return None
61
+ # This happens after command execution completes successfully
62
+ if click_group is None:
63
+ return None
64
+
60
65
  # Create enhanced DYM group with original group's properties
61
66
  enhanced_group = EnhancedDidYouMeanGroup(
62
67
  name=click_group.name,
@@ -161,6 +166,11 @@ def enhance_existing_typer(app: typer.Typer) -> typer.Typer:
161
166
  """Enhanced call that uses EnhancedDidYouMeanGroup."""
162
167
  click_group = original_call(*args, **kwargs)
163
168
 
169
+ # If click_group is None (command already executed), return None
170
+ # This happens after command execution completes successfully
171
+ if click_group is None:
172
+ return None
173
+
164
174
  # Create enhanced group
165
175
  enhanced_group = EnhancedDidYouMeanGroup(
166
176
  name=click_group.name,
@@ -33,13 +33,15 @@ unfamiliar codebases, finding similar patterns, and integrating with AI tools.
33
33
  3. Check status: [green]mcp-vector-search status[/green]
34
34
 
35
35
  [bold cyan]Main Commands:[/bold cyan]
36
- init 🔧 Initialize project
36
+ install 📦 Install project and MCP integrations
37
+ uninstall 🗑️ Remove MCP integrations
38
+ init 🔧 Initialize project (simple)
37
39
  demo 🎬 Run interactive demo
38
40
  doctor 🩺 Check system health
39
41
  status 📊 Show project status
40
42
  search 🔍 Search code semantically
41
43
  index 📇 Index codebase
42
- mcp 🤖 MCP integration for AI tools
44
+ mcp 🔌 MCP server operations
43
45
  config ⚙️ Configure settings
44
46
  visualize 📊 Visualize code relationships
45
47
  help ❓ Get help
@@ -56,48 +58,61 @@ from .commands.config import config_app # noqa: E402
56
58
  from .commands.demo import demo_app # noqa: E402
57
59
  from .commands.index import index_app # noqa: E402
58
60
  from .commands.init import init_app # noqa: E402
61
+ from .commands.install import install_app # noqa: E402
59
62
  from .commands.mcp import mcp_app # noqa: E402
60
63
  from .commands.search import search_app, search_main # noqa: E402, F401
61
64
  from .commands.status import main as status_main # noqa: E402
65
+ from .commands.uninstall import uninstall_app # noqa: E402
62
66
  from .commands.visualize import app as visualize_app # noqa: E402
63
67
 
64
68
  # ============================================================================
65
69
  # MAIN COMMANDS - Clean hierarchy
66
70
  # ============================================================================
67
71
 
68
- # 1. INIT - Initialize project
72
+ # 1. INSTALL - Install project and MCP integrations (NEW!)
73
+ app.add_typer(
74
+ install_app, name="install", help="📦 Install project and MCP integrations"
75
+ )
76
+
77
+ # 2. UNINSTALL - Remove MCP integrations (NEW!)
78
+ app.add_typer(uninstall_app, name="uninstall", help="🗑️ Remove MCP integrations")
79
+ app.add_typer(uninstall_app, name="remove", help="🗑️ Remove MCP integrations (alias)")
80
+
81
+ # 3. INIT - Initialize project (simplified)
69
82
  # Use Typer group for init to support both direct call and subcommands
70
83
  app.add_typer(init_app, name="init", help="🔧 Initialize project for semantic search")
71
84
 
72
- # 2. DEMO - Interactive demo
85
+ # 4. DEMO - Interactive demo
73
86
  app.add_typer(demo_app, name="demo", help="🎬 Run interactive demo with sample project")
74
87
 
75
- # 3. DOCTOR - System health check
88
+ # 5. DOCTOR - System health check
76
89
  # (defined below inline)
77
90
 
78
- # 4. STATUS - Project status
91
+ # 6. STATUS - Project status
79
92
  app.command("status", help="📊 Show project status and statistics")(status_main)
80
93
 
81
- # 5. SEARCH - Search code
94
+ # 7. SEARCH - Search code
82
95
  # Register search as both a command and a typer group
83
96
  app.add_typer(search_app, name="search", help="🔍 Search code semantically")
84
97
 
85
- # 6. INDEX - Index codebase
98
+ # 8. INDEX - Index codebase
86
99
  app.add_typer(index_app, name="index", help="📇 Index codebase for semantic search")
87
100
 
88
- # 7. MCP - MCP integration
89
- app.add_typer(mcp_app, name="mcp", help="🤖 Manage MCP integration for AI tools")
101
+ # 9. MCP - MCP server operations (RESERVED for server ops only!)
102
+ app.add_typer(mcp_app, name="mcp", help="🔌 MCP server operations")
90
103
 
91
- # 8. CONFIG - Configuration
104
+ # 10. CONFIG - Configuration
92
105
  app.add_typer(config_app, name="config", help="⚙️ Manage project configuration")
93
106
 
94
- # 9. VISUALIZE - Code graph visualization
95
- app.add_typer(visualize_app, name="visualize", help="📊 Visualize code chunk relationships")
107
+ # 11. VISUALIZE - Code graph visualization
108
+ app.add_typer(
109
+ visualize_app, name="visualize", help="📊 Visualize code chunk relationships"
110
+ )
96
111
 
97
- # 10. HELP - Enhanced help
112
+ # 12. HELP - Enhanced help
98
113
  # (defined below inline)
99
114
 
100
- # 10. VERSION - Version info
115
+ # 13. VERSION - Version info
101
116
  # (defined below inline)
102
117
 
103
118
 
@@ -120,11 +135,9 @@ def _deprecated_command(old_cmd: str, new_cmd: str):
120
135
  return wrapper
121
136
 
122
137
 
123
- # Deprecated: install -> init
124
- @app.command("install", hidden=True)
125
- def deprecated_install():
126
- """[DEPRECATED] Use 'init' instead."""
127
- _deprecated_command("install", "init")()
138
+ # NOTE: 'install' command is now the primary command for project installation
139
+ # Old 'install' was deprecated in favor of 'init' in v0.7.0
140
+ # Now 'install' is back as the hierarchical installation command in v0.13.0
128
141
 
129
142
 
130
143
  # Deprecated: find -> search
@@ -432,7 +445,12 @@ def cli_with_suggestions():
432
445
  except Exception as e:
433
446
  # For other exceptions, show error and exit if verbose logging is enabled
434
447
  # Suppress internal framework errors in normal operation
435
- if "--verbose" in sys.argv or "-v" in sys.argv:
448
+
449
+ # Suppress harmless didyoumean framework AttributeError (known issue)
450
+ # This occurs during Click/Typer cleanup after successful command completion
451
+ if isinstance(e, AttributeError) and "attribute" in str(e) and "name" in str(e):
452
+ pass # Ignore - this is a harmless framework cleanup error
453
+ elif "--verbose" in sys.argv or "-v" in sys.argv:
436
454
  click.echo(f"Unexpected error: {e}", err=True)
437
455
  sys.exit(1)
438
456
  # Otherwise, just exit silently to avoid confusing error messages
@@ -24,6 +24,16 @@ class PooledConnection:
24
24
  in_use: bool = False
25
25
  use_count: int = 0
26
26
 
27
+ @property
28
+ def age(self) -> float:
29
+ """Get the age of this connection in seconds."""
30
+ return time.time() - self.created_at
31
+
32
+ @property
33
+ def idle_time(self) -> float:
34
+ """Get the idle time of this connection in seconds."""
35
+ return time.time() - self.last_used
36
+
27
37
 
28
38
  class ChromaConnectionPool:
29
39
  """Connection pool for ChromaDB operations."""
@@ -209,18 +219,18 @@ class ChromaConnectionPool:
209
219
  logger.debug(f"Created new connection (pool size: {len(self._pool)})")
210
220
  return conn
211
221
 
212
- # Pool is full, wait for a connection to become available
213
- self._stats["pool_misses"] += 1
214
- logger.warning(
215
- "Connection pool exhausted, waiting for available connection"
216
- )
222
+ # Pool is full, wait for a connection to become available (outside lock)
223
+ self._stats["pool_misses"] += 1
224
+ logger.warning("Connection pool exhausted, waiting for available connection")
217
225
 
218
- # Wait for a connection (with timeout)
219
- timeout = 30.0 # 30 seconds
220
- start_time = time.time()
226
+ # Wait for a connection (with timeout) - release lock during wait
227
+ timeout = 30.0 # 30 seconds
228
+ start_time = time.time()
221
229
 
222
- while time.time() - start_time < timeout:
223
- await asyncio.sleep(0.1)
230
+ while time.time() - start_time < timeout:
231
+ await asyncio.sleep(0.1)
232
+ # Re-acquire lock to check for available connections
233
+ async with self._lock:
224
234
  for conn in self._pool:
225
235
  if not conn.in_use and self._is_connection_valid(conn):
226
236
  conn.in_use = True
@@ -229,7 +239,7 @@ class ChromaConnectionPool:
229
239
  self._stats["connections_reused"] += 1
230
240
  return conn
231
241
 
232
- raise DatabaseError("Connection pool timeout: no connections available")
242
+ raise DatabaseError("Connection pool timeout: no connections available")
233
243
 
234
244
  async def _release_connection(self, conn: PooledConnection) -> None:
235
245
  """Release a connection back to the pool."""
@@ -320,3 +330,31 @@ class ChromaConnectionPool:
320
330
  except Exception as e:
321
331
  logger.error(f"Connection pool health check failed: {e}")
322
332
  return False
333
+
334
+ # Backward compatibility aliases for old test API
335
+ async def cleanup(self) -> None:
336
+ """Alias for close() method (backward compatibility)."""
337
+ await self.close()
338
+
339
+ def _validate_connection(self, conn: PooledConnection) -> bool:
340
+ """Alias for _is_connection_valid() method (backward compatibility)."""
341
+ return self._is_connection_valid(conn)
342
+
343
+ async def _cleanup_idle_connections(self) -> None:
344
+ """Alias for _cleanup_expired_connections() method (backward compatibility)."""
345
+ await self._cleanup_expired_connections()
346
+
347
+ @property
348
+ def _connections(self) -> list[PooledConnection]:
349
+ """Alias for _pool attribute (backward compatibility)."""
350
+ return self._pool
351
+
352
+ @property
353
+ def _max_connections(self) -> int:
354
+ """Alias for max_connections attribute (backward compatibility)."""
355
+ return self.max_connections
356
+
357
+ @property
358
+ def _min_connections(self) -> int:
359
+ """Alias for min_connections attribute (backward compatibility)."""
360
+ return self.min_connections
@@ -1,6 +1,7 @@
1
1
  """Database abstraction and ChromaDB implementation for MCP Vector Search."""
2
2
 
3
3
  import asyncio
4
+ import json
4
5
  import shutil
5
6
  from abc import ABC, abstractmethod
6
7
  from pathlib import Path
@@ -273,16 +274,16 @@ class ChromaVectorDatabase(VectorDatabase):
273
274
  "class_name": chunk.class_name or "",
274
275
  "docstring": chunk.docstring or "",
275
276
  "complexity_score": chunk.complexity_score,
276
- # Hierarchy fields
277
+ # Hierarchy fields (convert lists to JSON strings for ChromaDB)
277
278
  "chunk_id": chunk.chunk_id or "",
278
279
  "parent_chunk_id": chunk.parent_chunk_id or "",
279
- "child_chunk_ids": chunk.child_chunk_ids or [],
280
+ "child_chunk_ids": json.dumps(chunk.child_chunk_ids or []),
280
281
  "chunk_depth": chunk.chunk_depth,
281
- # Additional metadata
282
- "decorators": chunk.decorators or [],
283
- "parameters": chunk.parameters or [],
282
+ # Additional metadata (convert lists/dicts to JSON strings)
283
+ "decorators": json.dumps(chunk.decorators or []),
284
+ "parameters": json.dumps(chunk.parameters or []),
284
285
  "return_type": chunk.return_type or "",
285
- "type_annotations": chunk.type_annotations or {},
286
+ "type_annotations": json.dumps(chunk.type_annotations or {}),
286
287
  # Monorepo support
287
288
  "subproject_name": chunk.subproject_name or "",
288
289
  "subproject_path": chunk.subproject_path or "",
@@ -500,16 +501,31 @@ class ChromaVectorDatabase(VectorDatabase):
500
501
 
501
502
  try:
502
503
  # Get all documents from collection
503
- results = self._collection.get(
504
- include=["metadatas", "documents"]
505
- )
504
+ results = self._collection.get(include=["metadatas", "documents"])
506
505
 
507
506
  chunks = []
508
507
  if results and results.get("ids"):
509
- for i, chunk_id in enumerate(results["ids"]):
508
+ for i, _chunk_id in enumerate(results["ids"]):
510
509
  metadata = results["metadatas"][i]
511
510
  content = results["documents"][i]
512
511
 
512
+ # Parse JSON strings back to lists/dicts
513
+ child_chunk_ids = metadata.get("child_chunk_ids", "[]")
514
+ if isinstance(child_chunk_ids, str):
515
+ child_chunk_ids = json.loads(child_chunk_ids)
516
+
517
+ decorators = metadata.get("decorators", "[]")
518
+ if isinstance(decorators, str):
519
+ decorators = json.loads(decorators)
520
+
521
+ parameters = metadata.get("parameters", "[]")
522
+ if isinstance(parameters, str):
523
+ parameters = json.loads(parameters)
524
+
525
+ type_annotations = metadata.get("type_annotations", "{}")
526
+ if isinstance(type_annotations, str):
527
+ type_annotations = json.loads(type_annotations)
528
+
513
529
  chunk = CodeChunk(
514
530
  content=content,
515
531
  file_path=Path(metadata["file_path"]),
@@ -524,12 +540,12 @@ class ChromaVectorDatabase(VectorDatabase):
524
540
  complexity_score=metadata.get("complexity_score", 0.0),
525
541
  chunk_id=metadata.get("chunk_id"),
526
542
  parent_chunk_id=metadata.get("parent_chunk_id"),
527
- child_chunk_ids=metadata.get("child_chunk_ids", []),
543
+ child_chunk_ids=child_chunk_ids,
528
544
  chunk_depth=metadata.get("chunk_depth", 0),
529
- decorators=metadata.get("decorators", []),
530
- parameters=metadata.get("parameters", []),
545
+ decorators=decorators,
546
+ parameters=parameters,
531
547
  return_type=metadata.get("return_type"),
532
- type_annotations=metadata.get("type_annotations", {}),
548
+ type_annotations=type_annotations,
533
549
  subproject_name=metadata.get("subproject_name"),
534
550
  subproject_path=metadata.get("subproject_path"),
535
551
  )
@@ -775,16 +791,18 @@ class PooledChromaVectorDatabase(VectorDatabase):
775
791
  "class_name": chunk.class_name or "",
776
792
  "docstring": chunk.docstring or "",
777
793
  "complexity_score": chunk.complexity_score,
778
- # Hierarchy fields
794
+ # Hierarchy fields (convert lists to JSON strings for ChromaDB)
779
795
  "chunk_id": chunk.chunk_id or "",
780
796
  "parent_chunk_id": chunk.parent_chunk_id or "",
781
- "child_chunk_ids": chunk.child_chunk_ids or [],
797
+ "child_chunk_ids": json.dumps(chunk.child_chunk_ids or []),
782
798
  "chunk_depth": chunk.chunk_depth,
783
- # Additional metadata
784
- "decorators": chunk.decorators or [],
785
- "parameters": chunk.parameters or [],
799
+ # Additional metadata (convert lists/dicts to JSON strings)
800
+ "decorators": json.dumps(chunk.decorators or []),
801
+ "parameters": json.dumps(chunk.parameters or []),
786
802
  "return_type": chunk.return_type or "",
787
- "type_annotations": chunk.type_annotations or {},
803
+ "type_annotations": json.dumps(
804
+ chunk.type_annotations or {}
805
+ ),
788
806
  # Monorepo support
789
807
  "subproject_name": chunk.subproject_name or "",
790
808
  "subproject_path": chunk.subproject_path or "",
@@ -1013,16 +1031,31 @@ class PooledChromaVectorDatabase(VectorDatabase):
1013
1031
  try:
1014
1032
  async with self._pool.get_connection() as conn:
1015
1033
  # Get all documents from collection
1016
- results = conn.collection.get(
1017
- include=["metadatas", "documents"]
1018
- )
1034
+ results = conn.collection.get(include=["metadatas", "documents"])
1019
1035
 
1020
1036
  chunks = []
1021
1037
  if results and results.get("ids"):
1022
- for i, chunk_id in enumerate(results["ids"]):
1038
+ for i, _chunk_id in enumerate(results["ids"]):
1023
1039
  metadata = results["metadatas"][i]
1024
1040
  content = results["documents"][i]
1025
1041
 
1042
+ # Parse JSON strings back to lists/dicts
1043
+ child_chunk_ids = metadata.get("child_chunk_ids", "[]")
1044
+ if isinstance(child_chunk_ids, str):
1045
+ child_chunk_ids = json.loads(child_chunk_ids)
1046
+
1047
+ decorators = metadata.get("decorators", "[]")
1048
+ if isinstance(decorators, str):
1049
+ decorators = json.loads(decorators)
1050
+
1051
+ parameters = metadata.get("parameters", "[]")
1052
+ if isinstance(parameters, str):
1053
+ parameters = json.loads(parameters)
1054
+
1055
+ type_annotations = metadata.get("type_annotations", "{}")
1056
+ if isinstance(type_annotations, str):
1057
+ type_annotations = json.loads(type_annotations)
1058
+
1026
1059
  chunk = CodeChunk(
1027
1060
  content=content,
1028
1061
  file_path=Path(metadata["file_path"]),
@@ -1037,12 +1070,12 @@ class PooledChromaVectorDatabase(VectorDatabase):
1037
1070
  complexity_score=metadata.get("complexity_score", 0.0),
1038
1071
  chunk_id=metadata.get("chunk_id"),
1039
1072
  parent_chunk_id=metadata.get("parent_chunk_id"),
1040
- child_chunk_ids=metadata.get("child_chunk_ids", []),
1073
+ child_chunk_ids=child_chunk_ids,
1041
1074
  chunk_depth=metadata.get("chunk_depth", 0),
1042
- decorators=metadata.get("decorators", []),
1043
- parameters=metadata.get("parameters", []),
1075
+ decorators=decorators,
1076
+ parameters=parameters,
1044
1077
  return_type=metadata.get("return_type"),
1045
- type_annotations=metadata.get("type_annotations", {}),
1078
+ type_annotations=type_annotations,
1046
1079
  subproject_name=metadata.get("subproject_name"),
1047
1080
  subproject_path=metadata.get("subproject_path"),
1048
1081
  )