mcp-vector-search 0.12.0__py3-none-any.whl → 0.12.2__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 (51) hide show
  1. mcp_vector_search/__init__.py +2 -2
  2. mcp_vector_search/cli/commands/index.py +15 -24
  3. mcp_vector_search/cli/commands/install.py +502 -523
  4. mcp_vector_search/cli/commands/install_old.py +696 -0
  5. mcp_vector_search/cli/commands/status.py +7 -5
  6. mcp_vector_search/cli/commands/uninstall.py +485 -0
  7. mcp_vector_search/cli/commands/visualize.py +417 -121
  8. mcp_vector_search/cli/didyoumean.py +10 -0
  9. mcp_vector_search/cli/main.py +39 -21
  10. mcp_vector_search/core/connection_pool.py +49 -11
  11. mcp_vector_search/core/database.py +7 -9
  12. mcp_vector_search/core/directory_index.py +26 -11
  13. mcp_vector_search/core/indexer.py +89 -29
  14. mcp_vector_search/core/models.py +4 -1
  15. mcp_vector_search/core/project.py +16 -5
  16. mcp_vector_search/parsers/base.py +54 -18
  17. mcp_vector_search/parsers/javascript.py +41 -20
  18. mcp_vector_search/parsers/python.py +19 -11
  19. mcp_vector_search/parsers/registry.py +3 -2
  20. mcp_vector_search/utils/gitignore.py +3 -1
  21. mcp_vector_search/visualization/favicon-v1-1024.png +0 -0
  22. mcp_vector_search/visualization/favicon-v1-128.png +0 -0
  23. mcp_vector_search/visualization/favicon-v1-16.png +0 -0
  24. mcp_vector_search/visualization/favicon-v1-256.png +0 -0
  25. mcp_vector_search/visualization/favicon-v1-32.png +0 -0
  26. mcp_vector_search/visualization/favicon-v1-512.png +0 -0
  27. mcp_vector_search/visualization/favicon-v1-64.png +0 -0
  28. mcp_vector_search/visualization/favicon-v1.ico +0 -0
  29. mcp_vector_search/visualization/favicon-v2-1024.png +0 -0
  30. mcp_vector_search/visualization/favicon-v2-128.png +0 -0
  31. mcp_vector_search/visualization/favicon-v2-16.png +0 -0
  32. mcp_vector_search/visualization/favicon-v2-256.png +0 -0
  33. mcp_vector_search/visualization/favicon-v2-32.png +0 -0
  34. mcp_vector_search/visualization/favicon-v2-512.png +0 -0
  35. mcp_vector_search/visualization/favicon-v2-64.png +0 -0
  36. mcp_vector_search/visualization/favicon-v2.ico +0 -0
  37. mcp_vector_search/visualization/favicon-v3-1024.png +0 -0
  38. mcp_vector_search/visualization/favicon-v3-128.png +0 -0
  39. mcp_vector_search/visualization/favicon-v3-16.png +0 -0
  40. mcp_vector_search/visualization/favicon-v3-256.png +0 -0
  41. mcp_vector_search/visualization/favicon-v3-32.png +0 -0
  42. mcp_vector_search/visualization/favicon-v3-512.png +0 -0
  43. mcp_vector_search/visualization/favicon-v3-64.png +0 -0
  44. mcp_vector_search/visualization/favicon-v3.ico +0 -0
  45. mcp_vector_search/visualization/index.html +522 -172
  46. {mcp_vector_search-0.12.0.dist-info → mcp_vector_search-0.12.2.dist-info}/METADATA +148 -25
  47. mcp_vector_search-0.12.2.dist-info/RECORD +93 -0
  48. mcp_vector_search-0.12.0.dist-info/RECORD +0 -67
  49. {mcp_vector_search-0.12.0.dist-info → mcp_vector_search-0.12.2.dist-info}/WHEEL +0 -0
  50. {mcp_vector_search-0.12.0.dist-info → mcp_vector_search-0.12.2.dist-info}/entry_points.txt +0 -0
  51. {mcp_vector_search-0.12.0.dist-info → mcp_vector_search-0.12.2.dist-info}/licenses/LICENSE +0 -0
@@ -236,9 +236,12 @@ class ProjectManager:
236
236
  count += 1
237
237
  return count
238
238
 
239
- def get_project_info(self) -> ProjectInfo:
239
+ def get_project_info(self, file_count: int | None = None) -> ProjectInfo:
240
240
  """Get comprehensive project information.
241
241
 
242
+ Args:
243
+ file_count: Optional pre-computed file count (avoids expensive filesystem scan)
244
+
242
245
  Returns:
243
246
  Project information
244
247
  """
@@ -247,13 +250,19 @@ class ProjectManager:
247
250
 
248
251
  is_initialized = self.is_initialized()
249
252
  languages = []
250
- file_count = 0
253
+ computed_file_count = 0
251
254
 
252
255
  if is_initialized:
253
256
  try:
254
257
  config = self.config
255
258
  languages = config.languages
256
- file_count = self.count_indexable_files(config.file_extensions)
259
+ # Use provided file_count if available to avoid filesystem scan
260
+ if file_count is not None:
261
+ computed_file_count = file_count
262
+ else:
263
+ computed_file_count = self.count_indexable_files(
264
+ config.file_extensions
265
+ )
257
266
  except Exception:
258
267
  # Ignore errors when getting detailed info
259
268
  pass
@@ -265,7 +274,7 @@ class ProjectManager:
265
274
  index_path=index_path,
266
275
  is_initialized=is_initialized,
267
276
  languages=languages,
268
- file_count=file_count,
277
+ file_count=computed_file_count,
269
278
  )
270
279
 
271
280
  def _iter_source_files(self) -> list[Path]:
@@ -301,7 +310,9 @@ class ProjectManager:
301
310
  """
302
311
  # First check gitignore rules if available
303
312
  # PERFORMANCE: Pass is_directory hint to avoid redundant stat() calls
304
- if self.gitignore_parser and self.gitignore_parser.is_ignored(path, is_directory=is_directory):
313
+ if self.gitignore_parser and self.gitignore_parser.is_ignored(
314
+ path, is_directory=is_directory
315
+ ):
305
316
  return True
306
317
 
307
318
  # Check if any parent directory is in ignore patterns
@@ -79,7 +79,7 @@ class BaseParser(ABC):
79
79
  if language is None:
80
80
  language = self.language
81
81
 
82
- if not hasattr(node, 'children'):
82
+ if not hasattr(node, "children"):
83
83
  return 1.0
84
84
 
85
85
  complexity = 1.0 # Base complexity
@@ -87,39 +87,75 @@ class BaseParser(ABC):
87
87
  # Language-specific decision node types
88
88
  decision_nodes = {
89
89
  "python": {
90
- "if_statement", "elif_clause", "while_statement", "for_statement",
91
- "except_clause", "with_statement", "conditional_expression",
92
- "boolean_operator" # and, or
90
+ "if_statement",
91
+ "elif_clause",
92
+ "while_statement",
93
+ "for_statement",
94
+ "except_clause",
95
+ "with_statement",
96
+ "conditional_expression",
97
+ "boolean_operator", # and, or
93
98
  },
94
99
  "javascript": {
95
- "if_statement", "while_statement", "for_statement", "for_in_statement",
96
- "switch_case", "catch_clause", "conditional_expression", "ternary_expression"
100
+ "if_statement",
101
+ "while_statement",
102
+ "for_statement",
103
+ "for_in_statement",
104
+ "switch_case",
105
+ "catch_clause",
106
+ "conditional_expression",
107
+ "ternary_expression",
97
108
  },
98
109
  "typescript": {
99
- "if_statement", "while_statement", "for_statement", "for_in_statement",
100
- "switch_case", "catch_clause", "conditional_expression", "ternary_expression"
110
+ "if_statement",
111
+ "while_statement",
112
+ "for_statement",
113
+ "for_in_statement",
114
+ "switch_case",
115
+ "catch_clause",
116
+ "conditional_expression",
117
+ "ternary_expression",
101
118
  },
102
119
  "dart": {
103
- "if_statement", "while_statement", "for_statement", "for_in_statement",
104
- "switch_case", "catch_clause", "conditional_expression"
120
+ "if_statement",
121
+ "while_statement",
122
+ "for_statement",
123
+ "for_in_statement",
124
+ "switch_case",
125
+ "catch_clause",
126
+ "conditional_expression",
105
127
  },
106
128
  "php": {
107
- "if_statement", "elseif_clause", "while_statement", "foreach_statement",
108
- "for_statement", "switch_case", "catch_clause", "ternary_expression"
129
+ "if_statement",
130
+ "elseif_clause",
131
+ "while_statement",
132
+ "foreach_statement",
133
+ "for_statement",
134
+ "switch_case",
135
+ "catch_clause",
136
+ "ternary_expression",
109
137
  },
110
138
  "ruby": {
111
- "if", "unless", "while", "until", "for", "case", "rescue",
112
- "conditional"
113
- }
139
+ "if",
140
+ "unless",
141
+ "while",
142
+ "until",
143
+ "for",
144
+ "case",
145
+ "rescue",
146
+ "conditional",
147
+ },
114
148
  }
115
149
 
116
- nodes_to_count = decision_nodes.get(language, decision_nodes.get("python", set()))
150
+ nodes_to_count = decision_nodes.get(
151
+ language, decision_nodes.get("python", set())
152
+ )
117
153
 
118
154
  def count_decision_points(n):
119
155
  nonlocal complexity
120
- if hasattr(n, 'type') and n.type in nodes_to_count:
156
+ if hasattr(n, "type") and n.type in nodes_to_count:
121
157
  complexity += 1
122
- if hasattr(n, 'children'):
158
+ if hasattr(n, "children"):
123
159
  for child in n.children:
124
160
  count_decision_points(child)
125
161
 
@@ -54,7 +54,7 @@ class JavaScriptParser(BaseParser):
54
54
 
55
55
  if self._use_tree_sitter:
56
56
  try:
57
- tree = self._parser.parse(content.encode('utf-8'))
57
+ tree = self._parser.parse(content.encode("utf-8"))
58
58
  return self._extract_chunks_from_tree(tree, content, file_path)
59
59
  except Exception as e:
60
60
  logger.warning(f"Tree-sitter parsing failed for {file_path}: {e}")
@@ -77,10 +77,14 @@ class JavaScriptParser(BaseParser):
77
77
  extracted = False
78
78
 
79
79
  if node_type == "function_declaration":
80
- chunks.extend(self._extract_function(node, lines, file_path, current_class))
80
+ chunks.extend(
81
+ self._extract_function(node, lines, file_path, current_class)
82
+ )
81
83
  extracted = True
82
84
  elif node_type == "arrow_function":
83
- chunks.extend(self._extract_arrow_function(node, lines, file_path, current_class))
85
+ chunks.extend(
86
+ self._extract_arrow_function(node, lines, file_path, current_class)
87
+ )
84
88
  extracted = True
85
89
  elif node_type == "class_declaration":
86
90
  class_chunks = self._extract_class(node, lines, file_path)
@@ -92,18 +96,22 @@ class JavaScriptParser(BaseParser):
92
96
  visit_node(child, class_name)
93
97
  extracted = True
94
98
  elif node_type == "method_definition":
95
- chunks.extend(self._extract_method(node, lines, file_path, current_class))
99
+ chunks.extend(
100
+ self._extract_method(node, lines, file_path, current_class)
101
+ )
96
102
  extracted = True
97
103
  elif node_type == "lexical_declaration":
98
104
  # const/let declarations might be arrow functions
99
- extracted_chunks = self._extract_variable_function(node, lines, file_path, current_class)
105
+ extracted_chunks = self._extract_variable_function(
106
+ node, lines, file_path, current_class
107
+ )
100
108
  if extracted_chunks:
101
109
  chunks.extend(extracted_chunks)
102
110
  extracted = True
103
111
 
104
112
  # Only recurse into children if we didn't extract this node
105
113
  # This prevents double-extraction of arrow functions in variable declarations
106
- if not extracted and hasattr(node, 'children'):
114
+ if not extracted and hasattr(node, "children"):
107
115
  for child in node.children:
108
116
  visit_node(child, current_class)
109
117
 
@@ -163,7 +171,7 @@ class JavaScriptParser(BaseParser):
163
171
  ) -> list[CodeChunk]:
164
172
  """Extract arrow function from AST."""
165
173
  # Arrow functions often don't have explicit names, try to get from parent
166
- parent = getattr(node, 'parent', None)
174
+ parent = getattr(node, "parent", None)
167
175
  function_name = None
168
176
 
169
177
  if parent and parent.type == "variable_declarator":
@@ -219,7 +227,9 @@ class JavaScriptParser(BaseParser):
219
227
  docstring = self._extract_jsdoc_from_node(child, lines)
220
228
 
221
229
  # Calculate complexity
222
- complexity = self._calculate_complexity(subchild, "javascript")
230
+ complexity = self._calculate_complexity(
231
+ subchild, "javascript"
232
+ )
223
233
 
224
234
  # Extract parameters
225
235
  parameters = self._extract_js_parameters(subchild)
@@ -319,8 +329,8 @@ class JavaScriptParser(BaseParser):
319
329
 
320
330
  def _get_node_text(self, node) -> str:
321
331
  """Get text content of a node."""
322
- if hasattr(node, 'text'):
323
- return node.text.decode('utf-8')
332
+ if hasattr(node, "text"):
333
+ return node.text.decode("utf-8")
324
334
  return ""
325
335
 
326
336
  def _extract_js_parameters(self, node) -> list[dict]:
@@ -330,12 +340,13 @@ class JavaScriptParser(BaseParser):
330
340
  for child in node.children:
331
341
  if child.type == "formal_parameters":
332
342
  for param_node in child.children:
333
- if param_node.type in ("identifier", "required_parameter", "optional_parameter", "rest_parameter"):
334
- param_info = {
335
- "name": None,
336
- "type": None,
337
- "default": None
338
- }
343
+ if param_node.type in (
344
+ "identifier",
345
+ "required_parameter",
346
+ "optional_parameter",
347
+ "rest_parameter",
348
+ ):
349
+ param_info = {"name": None, "type": None, "default": None}
339
350
 
340
351
  # Extract parameter details
341
352
  if param_node.type == "identifier":
@@ -347,10 +358,20 @@ class JavaScriptParser(BaseParser):
347
358
  param_info["name"] = self._get_node_text(subchild)
348
359
  elif subchild.type == "type_annotation":
349
360
  param_info["type"] = self._get_node_text(subchild)
350
- elif "default" in subchild.type or subchild.type == "number":
351
- param_info["default"] = self._get_node_text(subchild)
352
-
353
- if param_info["name"] and param_info["name"] not in ("(", ")", ",", "..."):
361
+ elif (
362
+ "default" in subchild.type
363
+ or subchild.type == "number"
364
+ ):
365
+ param_info["default"] = self._get_node_text(
366
+ subchild
367
+ )
368
+
369
+ if param_info["name"] and param_info["name"] not in (
370
+ "(",
371
+ ")",
372
+ ",",
373
+ "...",
374
+ ):
354
375
  # Clean up rest parameters
355
376
  if param_info["name"].startswith("..."):
356
377
  param_info["name"] = param_info["name"][3:]
@@ -452,12 +452,12 @@ class PythonParser(BaseParser):
452
452
  for child in node.children:
453
453
  if child.type == "parameters":
454
454
  for param_node in child.children:
455
- if param_node.type in ("identifier", "typed_parameter", "default_parameter"):
456
- param_info = {
457
- "name": None,
458
- "type": None,
459
- "default": None
460
- }
455
+ if param_node.type in (
456
+ "identifier",
457
+ "typed_parameter",
458
+ "default_parameter",
459
+ ):
460
+ param_info = {"name": None, "type": None, "default": None}
461
461
 
462
462
  # Extract parameter name
463
463
  if param_node.type == "identifier":
@@ -470,9 +470,17 @@ class PythonParser(BaseParser):
470
470
  elif subchild.type == "type":
471
471
  param_info["type"] = self._get_node_text(subchild)
472
472
  elif "default" in subchild.type:
473
- param_info["default"] = self._get_node_text(subchild)
474
-
475
- if param_info["name"] and param_info["name"] not in ("self", "cls", "(", ")", ","):
473
+ param_info["default"] = self._get_node_text(
474
+ subchild
475
+ )
476
+
477
+ if param_info["name"] and param_info["name"] not in (
478
+ "self",
479
+ "cls",
480
+ "(",
481
+ ")",
482
+ ",",
483
+ ):
476
484
  parameters.append(param_info)
477
485
  return parameters
478
486
 
@@ -485,8 +493,8 @@ class PythonParser(BaseParser):
485
493
 
486
494
  def _get_node_text(self, node) -> str:
487
495
  """Get text content of a node."""
488
- if hasattr(node, 'text'):
489
- return node.text.decode('utf-8')
496
+ if hasattr(node, "text"):
497
+ return node.text.decode("utf-8")
490
498
  return ""
491
499
 
492
500
  def get_supported_extensions(self) -> list[str]:
@@ -162,14 +162,15 @@ class ParserRegistry:
162
162
  info[language] = {
163
163
  "class": parser.__class__.__name__,
164
164
  "extensions": parser.get_supported_extensions(),
165
- "language": parser.language,
165
+ "language": getattr(parser, "language", None) or language,
166
166
  }
167
167
 
168
168
  # Add fallback parser info
169
+ fallback_lang = getattr(self._fallback_parser, "language", None) or "unknown"
169
170
  info["fallback"] = {
170
171
  "class": self._fallback_parser.__class__.__name__,
171
172
  "extensions": ["*"],
172
- "language": self._fallback_parser.language,
173
+ "language": fallback_lang,
173
174
  }
174
175
 
175
176
  return info
@@ -233,7 +233,9 @@ def create_gitignore_parser(project_root: Path) -> GitignoreParser:
233
233
  return GitignoreParser(project_root)
234
234
 
235
235
 
236
- def is_path_gitignored(path: Path, project_root: Path, is_directory: bool | None = None) -> bool:
236
+ def is_path_gitignored(
237
+ path: Path, project_root: Path, is_directory: bool | None = None
238
+ ) -> bool:
237
239
  """Quick function to check if a path is gitignored.
238
240
 
239
241
  Args: