mcp-vector-search 0.7.5__py3-none-any.whl → 0.7.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.

Potentially problematic release.


This version of mcp-vector-search might be problematic. Click here for more details.

@@ -1,7 +1,7 @@
1
1
  """MCP Vector Search - CLI-first semantic code search with MCP integration."""
2
2
 
3
- __version__ = "0.7.5"
4
- __build__ = "27"
3
+ __version__ = "0.7.6"
4
+ __build__ = "28"
5
5
  __author__ = "Robert Matsuoka"
6
6
  __email__ = "bobmatnyc@gmail.com"
7
7
 
@@ -9,7 +9,7 @@ import typer
9
9
  from loguru import logger
10
10
  from rich.console import Console
11
11
 
12
- from ..output import print_error, print_info, print_success, print_warning
12
+ from ..output import print_error, print_info, print_success
13
13
 
14
14
  console = Console()
15
15
 
@@ -321,9 +321,7 @@ class UserAPI:
321
321
  console.print(" ✅ Automatic code indexing")
322
322
  if not quick:
323
323
  console.print(" ✅ Semantic code search in action")
324
- console.print(
325
- " ✅ Finding code by meaning (not just keywords)\n"
326
- )
324
+ console.print(" ✅ Finding code by meaning (not just keywords)\n")
327
325
 
328
326
  console.print("[bold cyan]Next steps to use in your project:[/bold cyan]")
329
327
  console.print(" 1. [green]cd /your/project[/green]")
@@ -13,7 +13,6 @@ from ...core.exceptions import ProjectNotFoundError
13
13
  from ...core.indexer import SemanticIndexer
14
14
  from ...core.project import ProjectManager
15
15
  from ..output import (
16
- create_progress,
17
16
  print_error,
18
17
  print_index_stats,
19
18
  print_info,
@@ -201,17 +200,138 @@ async def _run_batch_indexing(
201
200
  ) -> None:
202
201
  """Run batch indexing of all files."""
203
202
  if show_progress:
204
- with create_progress() as progress:
205
- task = progress.add_task("Indexing files...", total=None)
203
+ # Import enhanced progress utilities
204
+ from rich.layout import Layout
205
+ from rich.live import Live
206
+ from rich.panel import Panel
207
+ from rich.progress import (
208
+ BarColumn,
209
+ Progress,
210
+ SpinnerColumn,
211
+ TextColumn,
212
+ TimeRemainingColumn,
213
+ )
214
+ from rich.table import Table
215
+
216
+ from ..output import console
217
+
218
+ # Pre-scan to get total file count
219
+ console.print("[dim]Scanning for indexable files...[/dim]")
220
+ indexable_files, files_to_index = await indexer.get_files_to_index(
221
+ force_reindex=force_reindex
222
+ )
223
+ total_files = len(files_to_index)
206
224
 
207
- # Start indexing
208
- indexed_count = await indexer.index_project(
209
- force_reindex=force_reindex,
210
- show_progress=False, # We handle progress here
225
+ if total_files == 0:
226
+ console.print("[yellow]No files need indexing[/yellow]")
227
+ indexed_count = 0
228
+ else:
229
+ console.print(f"[dim]Found {total_files} files to index[/dim]\n")
230
+
231
+ # Track recently indexed files for display
232
+ recent_files = []
233
+ current_file_name = ""
234
+ indexed_count = 0
235
+ failed_count = 0
236
+
237
+ # Create layout for two-panel display
238
+ layout = Layout()
239
+ layout.split_column(
240
+ Layout(name="progress", size=4),
241
+ Layout(name="samples", size=7),
242
+ )
243
+
244
+ # Create progress bar
245
+ progress = Progress(
246
+ SpinnerColumn(),
247
+ TextColumn("[progress.description]{task.description}"),
248
+ BarColumn(bar_width=40),
249
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
250
+ TextColumn("({task.completed}/{task.total} files)"),
251
+ TimeRemainingColumn(),
252
+ console=console,
211
253
  )
212
254
 
213
- progress.update(task, completed=indexed_count, total=indexed_count)
255
+ task = progress.add_task("Indexing files...", total=total_files)
256
+
257
+ # Create live display with both panels
258
+ with Live(layout, console=console, refresh_per_second=4):
259
+ # Index files with progress updates
260
+ async for (
261
+ file_path,
262
+ chunks_added,
263
+ success,
264
+ ) in indexer.index_files_with_progress(files_to_index, force_reindex):
265
+ # Update counts
266
+ if success:
267
+ indexed_count += 1
268
+ else:
269
+ failed_count += 1
270
+
271
+ # Update progress
272
+ progress.update(task, advance=1)
273
+
274
+ # Update current file name for display
275
+ current_file_name = file_path.name
276
+
277
+ # Keep last 5 files for sampling display
278
+ try:
279
+ relative_path = str(file_path.relative_to(indexer.project_root))
280
+ except ValueError:
281
+ relative_path = str(file_path)
282
+
283
+ recent_files.append((relative_path, chunks_added, success))
284
+ if len(recent_files) > 5:
285
+ recent_files.pop(0)
286
+
287
+ # Update display layouts
288
+ layout["progress"].update(
289
+ Panel(
290
+ progress,
291
+ title="[bold]Indexing Progress[/bold]",
292
+ border_style="blue",
293
+ )
294
+ )
295
+
296
+ # Build samples panel content
297
+ samples_table = Table.grid(expand=True)
298
+ samples_table.add_column(style="dim")
299
+
300
+ if current_file_name:
301
+ samples_table.add_row(
302
+ f"[bold cyan]Currently processing:[/bold cyan] {current_file_name}"
303
+ )
304
+ samples_table.add_row("")
305
+
306
+ samples_table.add_row("[dim]Recently indexed:[/dim]")
307
+ for rel_path, chunk_count, file_success in recent_files[-5:]:
308
+ icon = "✓" if file_success else "✗"
309
+ style = "green" if file_success else "red"
310
+ chunk_info = (
311
+ f"({chunk_count} chunks)"
312
+ if chunk_count > 0
313
+ else "(no chunks)"
314
+ )
315
+ samples_table.add_row(
316
+ f" [{style}]{icon}[/{style}] [cyan]{rel_path}[/cyan] [dim]{chunk_info}[/dim]"
317
+ )
318
+
319
+ layout["samples"].update(
320
+ Panel(
321
+ samples_table,
322
+ title="[bold]File Processing[/bold]",
323
+ border_style="dim",
324
+ )
325
+ )
326
+
327
+ # Final progress summary
328
+ console.print()
329
+ if failed_count > 0:
330
+ console.print(
331
+ f"[yellow]⚠ {failed_count} files failed to index[/yellow]"
332
+ )
214
333
  else:
334
+ # Non-progress mode (fallback to original behavior)
215
335
  indexed_count = await indexer.index_project(
216
336
  force_reindex=force_reindex,
217
337
  show_progress=show_progress,
@@ -374,28 +494,8 @@ async def _reindex_entire_project(project_root: Path) -> None:
374
494
  print_info("Clearing existing index...")
375
495
  await database.reset()
376
496
 
377
- # Then reindex everything with progress
378
- with create_progress() as progress:
379
- task = progress.add_task("Reindexing files...", total=None)
380
-
381
- # Force reindex all files
382
- indexed_count = await indexer.index_project(
383
- force_reindex=True, # Force reindexing
384
- show_progress=False, # We handle progress here
385
- )
386
-
387
- progress.update(task, completed=indexed_count, total=indexed_count)
388
-
389
- # Show statistics
390
- stats = await indexer.get_indexing_stats()
391
-
392
- # Display success message with chunk count for clarity
393
- total_chunks = stats.get("total_chunks", 0)
394
- print_success(
395
- f"Processed {indexed_count} files ({total_chunks} searchable chunks created)"
396
- )
397
-
398
- print_index_stats(stats)
497
+ # Then reindex everything with enhanced progress display
498
+ await _run_batch_indexing(indexer, force_reindex=True, show_progress=True)
399
499
 
400
500
  except Exception as e:
401
501
  logger.error(f"Full reindex error: {e}")
@@ -7,7 +7,7 @@ import subprocess
7
7
  import sys
8
8
  import tomllib
9
9
  from pathlib import Path
10
- from typing import Dict, Any
10
+ from typing import Any
11
11
 
12
12
  import typer
13
13
  from rich.console import Console
@@ -49,26 +49,26 @@ SUPPORTED_TOOLS = {
49
49
  "name": "Auggie",
50
50
  "config_path": "~/.augment/settings.json",
51
51
  "format": "json",
52
- "description": "Augment Code AI assistant"
52
+ "description": "Augment Code AI assistant",
53
53
  },
54
54
  "claude-code": {
55
55
  "name": "Claude Code",
56
56
  "config_path": ".mcp.json",
57
57
  "format": "json",
58
- "description": "Claude Code (project-scoped)"
58
+ "description": "Claude Code (project-scoped)",
59
59
  },
60
60
  "codex": {
61
61
  "name": "Codex",
62
62
  "config_path": "~/.codex/config.toml",
63
63
  "format": "toml",
64
- "description": "OpenAI Codex CLI"
64
+ "description": "OpenAI Codex CLI",
65
65
  },
66
66
  "gemini": {
67
67
  "name": "Gemini",
68
68
  "config_path": "~/.gemini/mcp.json",
69
69
  "format": "json",
70
- "description": "Google Gemini CLI"
71
- }
70
+ "description": "Google Gemini CLI",
71
+ },
72
72
  }
73
73
 
74
74
 
@@ -124,15 +124,18 @@ def get_mcp_server_command(
124
124
 
125
125
 
126
126
  def get_mcp_server_config_for_tool(
127
- project_root: Path, tool_name: str, server_name: str, enable_file_watching: bool = True
128
- ) -> Dict[str, Any]:
127
+ project_root: Path,
128
+ tool_name: str,
129
+ server_name: str,
130
+ enable_file_watching: bool = True,
131
+ ) -> dict[str, Any]:
129
132
  """Generate MCP server configuration for a specific tool."""
130
133
  base_config = {
131
134
  "command": "uv",
132
135
  "args": ["run", "mcp-vector-search", "mcp"],
133
136
  "env": {
134
137
  "MCP_ENABLE_FILE_WATCHING": "true" if enable_file_watching else "false"
135
- }
138
+ },
136
139
  }
137
140
 
138
141
  if tool_name == "auggie":
@@ -140,29 +143,20 @@ def get_mcp_server_config_for_tool(
140
143
  return base_config
141
144
  elif tool_name == "claude-code":
142
145
  # Claude Code requires type: stdio and no cwd
143
- return {
144
- "type": "stdio",
145
- **base_config
146
- }
146
+ return {"type": "stdio", **base_config}
147
147
  elif tool_name == "codex":
148
148
  # Codex uses TOML format with different structure
149
149
  return {
150
150
  "command": base_config["command"],
151
151
  "args": base_config["args"],
152
- "env": base_config["env"]
152
+ "env": base_config["env"],
153
153
  }
154
154
  elif tool_name == "gemini":
155
155
  # Gemini uses standard format with cwd
156
- return {
157
- **base_config,
158
- "cwd": str(project_root.absolute())
159
- }
156
+ return {**base_config, "cwd": str(project_root.absolute())}
160
157
  else:
161
158
  # Default configuration
162
- return {
163
- **base_config,
164
- "cwd": str(project_root.absolute())
165
- }
159
+ return {**base_config, "cwd": str(project_root.absolute())}
166
160
 
167
161
 
168
162
  def create_project_claude_config(
@@ -215,7 +209,7 @@ def configure_tool_mcp(
215
209
  project_root: Path,
216
210
  server_name: str = "mcp-vector-search",
217
211
  enable_file_watching: bool = True,
218
- force: bool = False
212
+ force: bool = False,
219
213
  ) -> bool:
220
214
  """Configure MCP integration for a specific AI tool."""
221
215
  if tool_name not in SUPPORTED_TOOLS:
@@ -236,13 +230,21 @@ def configure_tool_mcp(
236
230
 
237
231
  try:
238
232
  if tool_name == "auggie":
239
- return configure_auggie_mcp(config_path, project_root, server_name, enable_file_watching, force)
233
+ return configure_auggie_mcp(
234
+ config_path, project_root, server_name, enable_file_watching, force
235
+ )
240
236
  elif tool_name == "claude-code":
241
- return configure_claude_code_mcp(config_path, project_root, server_name, enable_file_watching, force)
237
+ return configure_claude_code_mcp(
238
+ config_path, project_root, server_name, enable_file_watching, force
239
+ )
242
240
  elif tool_name == "codex":
243
- return configure_codex_mcp(config_path, project_root, server_name, enable_file_watching, force)
241
+ return configure_codex_mcp(
242
+ config_path, project_root, server_name, enable_file_watching, force
243
+ )
244
244
  elif tool_name == "gemini":
245
- return configure_gemini_mcp(config_path, project_root, server_name, enable_file_watching, force)
245
+ return configure_gemini_mcp(
246
+ config_path, project_root, server_name, enable_file_watching, force
247
+ )
246
248
  else:
247
249
  print_error(f"Configuration for {tool_name} not implemented yet")
248
250
  return False
@@ -256,7 +258,7 @@ def configure_auggie_mcp(
256
258
  project_root: Path,
257
259
  server_name: str,
258
260
  enable_file_watching: bool,
259
- force: bool
261
+ force: bool,
260
262
  ) -> bool:
261
263
  """Configure Auggie MCP integration."""
262
264
  # Create backup if file exists
@@ -268,7 +270,9 @@ def configure_auggie_mcp(
268
270
  with open(config_path) as f:
269
271
  config = json.load(f)
270
272
  if config.get("mcpServers", {}).get(server_name):
271
- print_warning(f"MCP server '{server_name}' already exists in Auggie config")
273
+ print_warning(
274
+ f"MCP server '{server_name}' already exists in Auggie config"
275
+ )
272
276
  print_info("Use --force to overwrite")
273
277
  return False
274
278
  shutil.copy2(config_path, backup_path)
@@ -283,7 +287,9 @@ def configure_auggie_mcp(
283
287
  config["mcpServers"] = {}
284
288
 
285
289
  # Get server configuration
286
- server_config = get_mcp_server_config_for_tool(project_root, "auggie", server_name, enable_file_watching)
290
+ server_config = get_mcp_server_config_for_tool(
291
+ project_root, "auggie", server_name, enable_file_watching
292
+ )
287
293
  config["mcpServers"][server_name] = server_config
288
294
 
289
295
  # Write updated config
@@ -299,7 +305,7 @@ def configure_claude_code_mcp(
299
305
  project_root: Path,
300
306
  server_name: str,
301
307
  enable_file_watching: bool,
302
- force: bool
308
+ force: bool,
303
309
  ) -> bool:
304
310
  """Configure Claude Code MCP integration."""
305
311
  # Use existing function for Claude Code
@@ -307,7 +313,9 @@ def configure_claude_code_mcp(
307
313
  with open(config_path) as f:
308
314
  config = json.load(f)
309
315
  if config.get("mcpServers", {}).get(server_name):
310
- print_warning(f"MCP server '{server_name}' already exists in Claude Code config")
316
+ print_warning(
317
+ f"MCP server '{server_name}' already exists in Claude Code config"
318
+ )
311
319
  print_info("Use --force to overwrite")
312
320
  return False
313
321
 
@@ -321,7 +329,7 @@ def configure_codex_mcp(
321
329
  project_root: Path,
322
330
  server_name: str,
323
331
  enable_file_watching: bool,
324
- force: bool
332
+ force: bool,
325
333
  ) -> bool:
326
334
  """Configure Codex MCP integration."""
327
335
  # Create backup if file exists
@@ -334,7 +342,9 @@ def configure_codex_mcp(
334
342
  with open(config_path, "rb") as f:
335
343
  config = tomllib.load(f)
336
344
  if config.get("mcp_servers", {}).get(server_name):
337
- print_warning(f"MCP server '{server_name}' already exists in Codex config")
345
+ print_warning(
346
+ f"MCP server '{server_name}' already exists in Codex config"
347
+ )
338
348
  print_info("Use --force to overwrite")
339
349
  return False
340
350
  except Exception as e:
@@ -342,19 +352,21 @@ def configure_codex_mcp(
342
352
 
343
353
  shutil.copy2(config_path, backup_path)
344
354
  # Read as text to preserve existing content
345
- with open(config_path, "r") as f:
355
+ with open(config_path) as f:
346
356
  config_text = f.read()
347
357
  else:
348
358
  config_path.parent.mkdir(parents=True, exist_ok=True)
349
359
  config_text = ""
350
360
 
351
361
  # Get server configuration
352
- server_config = get_mcp_server_config_for_tool(project_root, "codex", server_name, enable_file_watching)
362
+ server_config = get_mcp_server_config_for_tool(
363
+ project_root, "codex", server_name, enable_file_watching
364
+ )
353
365
 
354
366
  # Generate TOML section for the server
355
367
  toml_section = f"\n[mcp_servers.{server_name}]\n"
356
368
  toml_section += f'command = "{server_config["command"]}"\n'
357
- toml_section += f'args = {server_config["args"]}\n'
369
+ toml_section += f"args = {server_config['args']}\n"
358
370
 
359
371
  if server_config.get("env"):
360
372
  toml_section += f"\n[mcp_servers.{server_name}.env]\n"
@@ -364,7 +376,7 @@ def configure_codex_mcp(
364
376
  # Append or replace the section
365
377
  if f"[mcp_servers.{server_name}]" in config_text:
366
378
  # Replace existing section (simple approach)
367
- lines = config_text.split('\n')
379
+ lines = config_text.split("\n")
368
380
  new_lines = []
369
381
  skip_section = False
370
382
 
@@ -378,7 +390,7 @@ def configure_codex_mcp(
378
390
  elif not skip_section:
379
391
  new_lines.append(line)
380
392
 
381
- config_text = '\n'.join(new_lines) + toml_section
393
+ config_text = "\n".join(new_lines) + toml_section
382
394
  else:
383
395
  config_text += toml_section
384
396
 
@@ -395,7 +407,7 @@ def configure_gemini_mcp(
395
407
  project_root: Path,
396
408
  server_name: str,
397
409
  enable_file_watching: bool,
398
- force: bool
410
+ force: bool,
399
411
  ) -> bool:
400
412
  """Configure Gemini MCP integration."""
401
413
  # Create backup if file exists
@@ -407,7 +419,9 @@ def configure_gemini_mcp(
407
419
  with open(config_path) as f:
408
420
  config = json.load(f)
409
421
  if config.get("mcpServers", {}).get(server_name):
410
- print_warning(f"MCP server '{server_name}' already exists in Gemini config")
422
+ print_warning(
423
+ f"MCP server '{server_name}' already exists in Gemini config"
424
+ )
411
425
  print_info("Use --force to overwrite")
412
426
  return False
413
427
  shutil.copy2(config_path, backup_path)
@@ -422,7 +436,9 @@ def configure_gemini_mcp(
422
436
  config["mcpServers"] = {}
423
437
 
424
438
  # Get server configuration
425
- server_config = get_mcp_server_config_for_tool(project_root, "gemini", server_name, enable_file_watching)
439
+ server_config = get_mcp_server_config_for_tool(
440
+ project_root, "gemini", server_name, enable_file_watching
441
+ )
426
442
  config["mcpServers"][server_name] = server_config
427
443
 
428
444
  # Write updated config
@@ -481,7 +497,9 @@ def configure_auggie(
481
497
  raise typer.Exit(1)
482
498
 
483
499
  enable_file_watching = not no_watch
484
- success = configure_tool_mcp("auggie", project_root, server_name, enable_file_watching, force)
500
+ success = configure_tool_mcp(
501
+ "auggie", project_root, server_name, enable_file_watching, force
502
+ )
485
503
 
486
504
  if success:
487
505
  print_info("Auggie will automatically detect the server when restarted")
@@ -540,10 +558,14 @@ def configure_claude_code(
540
558
  raise typer.Exit(1)
541
559
 
542
560
  enable_file_watching = not no_watch
543
- success = configure_tool_mcp("claude-code", project_root, server_name, enable_file_watching, force)
561
+ success = configure_tool_mcp(
562
+ "claude-code", project_root, server_name, enable_file_watching, force
563
+ )
544
564
 
545
565
  if success:
546
- print_info("Claude Code will automatically detect the server when you open this project")
566
+ print_info(
567
+ "Claude Code will automatically detect the server when you open this project"
568
+ )
547
569
  else:
548
570
  raise typer.Exit(1)
549
571
 
@@ -599,7 +621,9 @@ def configure_codex(
599
621
  raise typer.Exit(1)
600
622
 
601
623
  enable_file_watching = not no_watch
602
- success = configure_tool_mcp("codex", project_root, server_name, enable_file_watching, force)
624
+ success = configure_tool_mcp(
625
+ "codex", project_root, server_name, enable_file_watching, force
626
+ )
603
627
 
604
628
  if success:
605
629
  print_info("Codex will automatically detect the server when restarted")
@@ -658,7 +682,9 @@ def configure_gemini(
658
682
  raise typer.Exit(1)
659
683
 
660
684
  enable_file_watching = not no_watch
661
- success = configure_tool_mcp("gemini", project_root, server_name, enable_file_watching, force)
685
+ success = configure_tool_mcp(
686
+ "gemini", project_root, server_name, enable_file_watching, force
687
+ )
662
688
 
663
689
  if success:
664
690
  print_info("Gemini will automatically detect the server when restarted")
@@ -730,10 +756,14 @@ def install_mcp_integration(
730
756
  raise typer.Exit(1)
731
757
 
732
758
  enable_file_watching = not no_watch
733
- success = configure_tool_mcp("claude-code", project_root, server_name, enable_file_watching, force)
759
+ success = configure_tool_mcp(
760
+ "claude-code", project_root, server_name, enable_file_watching, force
761
+ )
734
762
 
735
763
  if success:
736
- print_info("Claude Code will automatically detect the server when you open this project")
764
+ print_info(
765
+ "Claude Code will automatically detect the server when you open this project"
766
+ )
737
767
 
738
768
  # Test the server (using project_root for the server command)
739
769
  print_info("Testing server startup...")
@@ -870,15 +900,12 @@ def list_tools() -> None:
870
900
  except Exception:
871
901
  status = "❓ Unknown"
872
902
 
873
- table.add_row(
874
- tool_id,
875
- tool_info["name"],
876
- str(config_path),
877
- status
878
- )
903
+ table.add_row(tool_id, tool_info["name"], str(config_path), status)
879
904
 
880
905
  console.print(table)
881
- console.print("\n[dim]💡 Use 'mcp-vector-search mcp <tool>' to configure a specific tool[/dim]")
906
+ console.print(
907
+ "\n[dim]💡 Use 'mcp-vector-search mcp <tool>' to configure a specific tool[/dim]"
908
+ )
882
909
 
883
910
 
884
911
  @mcp_app.command("tools")
@@ -102,15 +102,28 @@ def main(
102
102
  if project_root is None:
103
103
  project_root = Path.cwd()
104
104
 
105
- asyncio.run(
106
- show_status(
107
- project_root=project_root,
108
- verbose=verbose,
109
- health_check=health_check,
110
- mcp=mcp,
111
- json_output=json_output,
112
- )
113
- )
105
+ async def run_status_with_timeout():
106
+ """Run status command with timeout protection."""
107
+ try:
108
+ await asyncio.wait_for(
109
+ show_status(
110
+ project_root=project_root,
111
+ verbose=verbose,
112
+ health_check=health_check,
113
+ mcp=mcp,
114
+ json_output=json_output,
115
+ ),
116
+ timeout=30.0, # 30 second timeout
117
+ )
118
+ except TimeoutError:
119
+ logger.error("Status check timed out after 30 seconds")
120
+ print_error(
121
+ "Status check timed out after 30 seconds. "
122
+ "Try running with --verbose for more details."
123
+ )
124
+ raise typer.Exit(1)
125
+
126
+ asyncio.run(run_status_with_timeout())
114
127
 
115
128
  except Exception as e:
116
129
  logger.error(f"Status check failed: {e}")
@@ -162,6 +175,7 @@ async def show_status(
162
175
  file_extensions=config.file_extensions,
163
176
  )
164
177
 
178
+ # Get indexing stats (runs async file scanning in thread pool)
165
179
  async with database:
166
180
  index_stats = await indexer.get_indexing_stats()
167
181
  db_stats = await database.get_stats()
@@ -122,8 +122,6 @@ def deprecated_install():
122
122
  _deprecated_command("install", "init")()
123
123
 
124
124
 
125
-
126
-
127
125
  # Deprecated: find -> search
128
126
  @app.command("find", hidden=True)
129
127
  def deprecated_find():