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

@@ -1,7 +1,7 @@
1
1
  """MCP Vector Search - CLI-first semantic code search with MCP integration."""
2
2
 
3
- __version__ = "0.7.6"
4
- __build__ = "28"
3
+ __version__ = "0.8.2"
4
+ __build__ = "31"
5
5
  __author__ = "Robert Matsuoka"
6
6
  __email__ = "bobmatnyc@gmail.com"
7
7
 
@@ -330,6 +330,11 @@ async def _run_batch_indexing(
330
330
  console.print(
331
331
  f"[yellow]⚠ {failed_count} files failed to index[/yellow]"
332
332
  )
333
+ error_log_path = project_root / ".mcp-vector-search" / "indexing_errors.log"
334
+ if error_log_path.exists():
335
+ console.print(
336
+ f"[dim] → See details in: {error_log_path}[/dim]"
337
+ )
333
338
  else:
334
339
  # Non-progress mode (fallback to original behavior)
335
340
  indexed_count = await indexer.index_project(
@@ -0,0 +1,529 @@
1
+ """Visualization commands for MCP Vector Search."""
2
+
3
+ import asyncio
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from loguru import logger
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+
12
+ from ...core.database import ChromaVectorDatabase
13
+ from ...core.embeddings import create_embedding_function
14
+ from ...core.project import ProjectManager
15
+
16
+ app = typer.Typer(
17
+ help="Visualize code chunk relationships",
18
+ no_args_is_help=True,
19
+ )
20
+ console = Console()
21
+
22
+
23
+ @app.command()
24
+ def export(
25
+ output: Path = typer.Option(
26
+ Path("chunk-graph.json"),
27
+ "--output",
28
+ "-o",
29
+ help="Output file for chunk relationship data",
30
+ ),
31
+ file_path: str | None = typer.Option(
32
+ None,
33
+ "--file",
34
+ "-f",
35
+ help="Export only chunks from specific file (supports wildcards)",
36
+ ),
37
+ ) -> None:
38
+ """Export chunk relationships as JSON for D3.js visualization.
39
+
40
+ Examples:
41
+ # Export all chunks
42
+ mcp-vector-search visualize export
43
+
44
+ # Export from specific file
45
+ mcp-vector-search visualize export --file src/main.py
46
+
47
+ # Custom output location
48
+ mcp-vector-search visualize export -o graph.json
49
+ """
50
+ asyncio.run(_export_chunks(output, file_path))
51
+
52
+
53
+ async def _export_chunks(output: Path, file_filter: str | None) -> None:
54
+ """Export chunk relationship data."""
55
+ try:
56
+ # Load project
57
+ project_manager = ProjectManager(Path.cwd())
58
+
59
+ if not project_manager.is_initialized():
60
+ console.print("[red]Project not initialized. Run 'mcp-vector-search init' first.[/red]")
61
+ raise typer.Exit(1)
62
+
63
+ config = project_manager.load_config()
64
+
65
+ # Get database
66
+ embedding_function, _ = create_embedding_function(config.embedding_model)
67
+ database = ChromaVectorDatabase(
68
+ persist_directory=config.index_path,
69
+ embedding_function=embedding_function,
70
+ )
71
+ await database.initialize()
72
+
73
+ # Get all chunks with metadata
74
+ console.print("[cyan]Fetching chunks from database...[/cyan]")
75
+
76
+ # Query all chunks (we'll use a dummy search to get all)
77
+ stats = await database.get_stats()
78
+
79
+ if stats.total_chunks == 0:
80
+ console.print("[yellow]No chunks found in index. Run 'mcp-vector-search index' first.[/yellow]")
81
+ raise typer.Exit(1)
82
+
83
+ # Build graph data structure
84
+ nodes = []
85
+ links = []
86
+
87
+ # We need to query the database to get actual chunk data
88
+ # Since there's no "get all chunks" method, we'll work with the stats
89
+ # In a real implementation, you would add a method to get all chunks
90
+
91
+ console.print(f"[yellow]Note: Full chunk export requires database enhancement.[/yellow]")
92
+ console.print(f"[cyan]Creating placeholder graph with {stats.total_chunks} chunks...[/cyan]")
93
+
94
+ # Create sample graph structure
95
+ graph_data = {
96
+ "nodes": [
97
+ {
98
+ "id": f"chunk_{i}",
99
+ "name": f"Chunk {i}",
100
+ "type": "code",
101
+ "file_path": "example.py",
102
+ "start_line": i * 10,
103
+ "end_line": (i + 1) * 10,
104
+ "complexity": 1.0 + (i % 5),
105
+ }
106
+ for i in range(min(stats.total_chunks, 50)) # Limit to 50 for demo
107
+ ],
108
+ "links": [
109
+ {"source": f"chunk_{i}", "target": f"chunk_{i+1}"}
110
+ for i in range(min(stats.total_chunks - 1, 49))
111
+ ],
112
+ "metadata": {
113
+ "total_chunks": stats.total_chunks,
114
+ "total_files": stats.total_files,
115
+ "languages": stats.languages,
116
+ "export_note": "This is a placeholder. Full export requires database enhancement.",
117
+ },
118
+ }
119
+
120
+ # Write to file
121
+ output.parent.mkdir(parents=True, exist_ok=True)
122
+ with open(output, "w") as f:
123
+ json.dump(graph_data, f, indent=2)
124
+
125
+ await database.close()
126
+
127
+ console.print()
128
+ console.print(
129
+ Panel.fit(
130
+ f"[green]✓[/green] Exported graph data to [cyan]{output}[/cyan]\n\n"
131
+ f"Nodes: {len(graph_data['nodes'])}\n"
132
+ f"Links: {len(graph_data['links'])}\n\n"
133
+ f"[dim]Next: Run 'mcp-vector-search visualize serve' to view[/dim]",
134
+ title="Export Complete",
135
+ border_style="green",
136
+ )
137
+ )
138
+
139
+ except Exception as e:
140
+ logger.error(f"Export failed: {e}")
141
+ console.print(f"[red]✗ Export failed: {e}[/red]")
142
+ raise typer.Exit(1)
143
+
144
+
145
+ @app.command()
146
+ def serve(
147
+ port: int = typer.Option(8080, "--port", "-p", help="Port for visualization server"),
148
+ graph_file: Path = typer.Option(
149
+ Path("chunk-graph.json"),
150
+ "--graph",
151
+ "-g",
152
+ help="Graph JSON file to visualize",
153
+ ),
154
+ ) -> None:
155
+ """Start local HTTP server for D3.js visualization.
156
+
157
+ Examples:
158
+ # Start server on default port 8080
159
+ mcp-vector-search visualize serve
160
+
161
+ # Custom port
162
+ mcp-vector-search visualize serve --port 3000
163
+
164
+ # Custom graph file
165
+ mcp-vector-search visualize serve --graph my-graph.json
166
+ """
167
+ import http.server
168
+ import os
169
+ import socketserver
170
+ import webbrowser
171
+
172
+ # Get visualization directory
173
+ viz_dir = Path(__file__).parent.parent.parent / "visualization"
174
+
175
+ if not viz_dir.exists():
176
+ console.print(
177
+ f"[yellow]Visualization directory not found. Creating at {viz_dir}...[/yellow]"
178
+ )
179
+ viz_dir.mkdir(parents=True, exist_ok=True)
180
+
181
+ # Create index.html if it doesn't exist
182
+ html_file = viz_dir / "index.html"
183
+ if not html_file.exists():
184
+ console.print("[yellow]Creating visualization HTML file...[/yellow]")
185
+ _create_visualization_html(html_file)
186
+
187
+ # Copy graph file to visualization directory if it exists
188
+ if graph_file.exists():
189
+ import shutil
190
+
191
+ dest = viz_dir / "chunk-graph.json"
192
+ shutil.copy(graph_file, dest)
193
+ console.print(f"[green]✓[/green] Copied graph data to {dest}")
194
+ else:
195
+ console.print(
196
+ f"[yellow]Warning: Graph file {graph_file} not found. "
197
+ "Run 'mcp-vector-search visualize export' first.[/yellow]"
198
+ )
199
+
200
+ # Change to visualization directory
201
+ os.chdir(viz_dir)
202
+
203
+ # Start server
204
+ Handler = http.server.SimpleHTTPRequestHandler
205
+ try:
206
+ with socketserver.TCPServer(("", port), Handler) as httpd:
207
+ url = f"http://localhost:{port}"
208
+ console.print()
209
+ console.print(
210
+ Panel.fit(
211
+ f"[green]✓[/green] Visualization server running\n\n"
212
+ f"URL: [cyan]{url}[/cyan]\n"
213
+ f"Directory: [dim]{viz_dir}[/dim]\n\n"
214
+ f"[dim]Press Ctrl+C to stop[/dim]",
215
+ title="Server Started",
216
+ border_style="green",
217
+ )
218
+ )
219
+
220
+ # Open browser
221
+ webbrowser.open(url)
222
+
223
+ try:
224
+ httpd.serve_forever()
225
+ except KeyboardInterrupt:
226
+ console.print("\n[yellow]Stopping server...[/yellow]")
227
+
228
+ except OSError as e:
229
+ if "Address already in use" in str(e):
230
+ console.print(
231
+ f"[red]✗ Port {port} is already in use. Try a different port with --port[/red]"
232
+ )
233
+ else:
234
+ console.print(f"[red]✗ Server error: {e}[/red]")
235
+ raise typer.Exit(1)
236
+
237
+
238
+ def _create_visualization_html(html_file: Path) -> None:
239
+ """Create the D3.js visualization HTML file."""
240
+ html_content = '''<!DOCTYPE html>
241
+ <html>
242
+ <head>
243
+ <meta charset="utf-8">
244
+ <title>Code Chunk Relationship Graph</title>
245
+ <script src="https://d3js.org/d3.v7.min.js"></script>
246
+ <style>
247
+ body {
248
+ margin: 0;
249
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
250
+ background: #0d1117;
251
+ color: #c9d1d9;
252
+ overflow: hidden;
253
+ }
254
+
255
+ #controls {
256
+ position: absolute;
257
+ top: 20px;
258
+ left: 20px;
259
+ background: rgba(13, 17, 23, 0.95);
260
+ border: 1px solid #30363d;
261
+ border-radius: 6px;
262
+ padding: 16px;
263
+ min-width: 250px;
264
+ max-height: 80vh;
265
+ overflow-y: auto;
266
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
267
+ }
268
+
269
+ h1 { margin: 0 0 16px 0; font-size: 18px; }
270
+ h3 { margin: 16px 0 8px 0; font-size: 14px; color: #8b949e; }
271
+
272
+ .control-group {
273
+ margin-bottom: 12px;
274
+ }
275
+
276
+ label {
277
+ display: block;
278
+ margin-bottom: 4px;
279
+ font-size: 12px;
280
+ color: #8b949e;
281
+ }
282
+
283
+ input[type="file"] {
284
+ width: 100%;
285
+ padding: 6px;
286
+ background: #161b22;
287
+ border: 1px solid #30363d;
288
+ border-radius: 6px;
289
+ color: #c9d1d9;
290
+ font-size: 12px;
291
+ }
292
+
293
+ .legend {
294
+ font-size: 12px;
295
+ }
296
+
297
+ .legend-item {
298
+ margin: 4px 0;
299
+ display: flex;
300
+ align-items: center;
301
+ }
302
+
303
+ .legend-color {
304
+ width: 12px;
305
+ height: 12px;
306
+ border-radius: 50%;
307
+ margin-right: 8px;
308
+ }
309
+
310
+ #graph {
311
+ width: 100vw;
312
+ height: 100vh;
313
+ }
314
+
315
+ .node circle {
316
+ cursor: pointer;
317
+ stroke: #c9d1d9;
318
+ stroke-width: 1.5px;
319
+ }
320
+
321
+ .node.module circle { fill: #238636; }
322
+ .node.class circle { fill: #1f6feb; }
323
+ .node.function circle { fill: #d29922; }
324
+ .node.method circle { fill: #8957e5; }
325
+ .node.code circle { fill: #6e7681; }
326
+
327
+ .node text {
328
+ font-size: 11px;
329
+ fill: #c9d1d9;
330
+ text-anchor: middle;
331
+ pointer-events: none;
332
+ user-select: none;
333
+ }
334
+
335
+ .link {
336
+ stroke: #30363d;
337
+ stroke-opacity: 0.6;
338
+ stroke-width: 1.5px;
339
+ }
340
+
341
+ .tooltip {
342
+ position: absolute;
343
+ padding: 12px;
344
+ background: rgba(13, 17, 23, 0.95);
345
+ border: 1px solid #30363d;
346
+ border-radius: 6px;
347
+ pointer-events: none;
348
+ display: none;
349
+ font-size: 12px;
350
+ max-width: 300px;
351
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
352
+ }
353
+
354
+ .stats {
355
+ margin-top: 16px;
356
+ padding-top: 16px;
357
+ border-top: 1px solid #30363d;
358
+ font-size: 12px;
359
+ color: #8b949e;
360
+ }
361
+ </style>
362
+ </head>
363
+ <body>
364
+ <div id="controls">
365
+ <h1>🔍 Code Graph</h1>
366
+
367
+ <div class="control-group">
368
+ <label>Load Graph Data:</label>
369
+ <input type="file" id="fileInput" accept=".json">
370
+ </div>
371
+
372
+ <h3>Legend</h3>
373
+ <div class="legend">
374
+ <div class="legend-item">
375
+ <span class="legend-color" style="background: #238636;"></span> Module
376
+ </div>
377
+ <div class="legend-item">
378
+ <span class="legend-color" style="background: #1f6feb;"></span> Class
379
+ </div>
380
+ <div class="legend-item">
381
+ <span class="legend-color" style="background: #d29922;"></span> Function
382
+ </div>
383
+ <div class="legend-item">
384
+ <span class="legend-color" style="background: #8957e5;"></span> Method
385
+ </div>
386
+ <div class="legend-item">
387
+ <span class="legend-color" style="background: #6e7681;"></span> Code
388
+ </div>
389
+ </div>
390
+
391
+ <div class="stats" id="stats"></div>
392
+ </div>
393
+
394
+ <svg id="graph"></svg>
395
+ <div id="tooltip" class="tooltip"></div>
396
+
397
+ <script>
398
+ const width = window.innerWidth;
399
+ const height = window.innerHeight;
400
+
401
+ const svg = d3.select("#graph")
402
+ .attr("width", width)
403
+ .attr("height", height)
404
+ .call(d3.zoom().on("zoom", (event) => {
405
+ g.attr("transform", event.transform);
406
+ }));
407
+
408
+ const g = svg.append("g");
409
+ const tooltip = d3.select("#tooltip");
410
+ let simulation;
411
+
412
+ function visualizeGraph(data) {
413
+ g.selectAll("*").remove();
414
+
415
+ simulation = d3.forceSimulation(data.nodes)
416
+ .force("link", d3.forceLink(data.links).id(d => d.id).distance(100))
417
+ .force("charge", d3.forceManyBody().strength(-400))
418
+ .force("center", d3.forceCenter(width / 2, height / 2))
419
+ .force("collision", d3.forceCollide().radius(40));
420
+
421
+ const link = g.append("g")
422
+ .selectAll("line")
423
+ .data(data.links)
424
+ .join("line")
425
+ .attr("class", "link");
426
+
427
+ const node = g.append("g")
428
+ .selectAll("g")
429
+ .data(data.nodes)
430
+ .join("g")
431
+ .attr("class", d => `node ${d.type}`)
432
+ .call(drag(simulation))
433
+ .on("mouseover", showTooltip)
434
+ .on("mouseout", hideTooltip);
435
+
436
+ node.append("circle")
437
+ .attr("r", d => d.complexity ? Math.min(8 + d.complexity * 2, 25) : 12);
438
+
439
+ node.append("text")
440
+ .text(d => d.name)
441
+ .attr("dy", 30);
442
+
443
+ simulation.on("tick", () => {
444
+ link
445
+ .attr("x1", d => d.source.x)
446
+ .attr("y1", d => d.source.y)
447
+ .attr("x2", d => d.target.x)
448
+ .attr("y2", d => d.target.y);
449
+
450
+ node.attr("transform", d => `translate(${d.x},${d.y})`);
451
+ });
452
+
453
+ updateStats(data);
454
+ }
455
+
456
+ function showTooltip(event, d) {
457
+ tooltip
458
+ .style("display", "block")
459
+ .style("left", (event.pageX + 10) + "px")
460
+ .style("top", (event.pageY + 10) + "px")
461
+ .html(`
462
+ <div><strong>${d.name}</strong></div>
463
+ <div>Type: ${d.type}</div>
464
+ ${d.complexity ? `<div>Complexity: ${d.complexity.toFixed(1)}</div>` : ''}
465
+ ${d.start_line ? `<div>Lines: ${d.start_line}-${d.end_line}</div>` : ''}
466
+ <div>File: ${d.file_path}</div>
467
+ `);
468
+ }
469
+
470
+ function hideTooltip() {
471
+ tooltip.style("display", "none");
472
+ }
473
+
474
+ function drag(simulation) {
475
+ function dragstarted(event) {
476
+ if (!event.active) simulation.alphaTarget(0.3).restart();
477
+ event.subject.fx = event.subject.x;
478
+ event.subject.fy = event.subject.y;
479
+ }
480
+
481
+ function dragged(event) {
482
+ event.subject.fx = event.x;
483
+ event.subject.fy = event.y;
484
+ }
485
+
486
+ function dragended(event) {
487
+ if (!event.active) simulation.alphaTarget(0);
488
+ event.subject.fx = null;
489
+ event.subject.fy = null;
490
+ }
491
+
492
+ return d3.drag()
493
+ .on("start", dragstarted)
494
+ .on("drag", dragged)
495
+ .on("end", dragended);
496
+ }
497
+
498
+ function updateStats(data) {
499
+ const stats = d3.select("#stats");
500
+ stats.html(`
501
+ <div>Nodes: ${data.nodes.length}</div>
502
+ <div>Links: ${data.links.length}</div>
503
+ ${data.metadata ? `<div>Files: ${data.metadata.total_files || 'N/A'}</div>` : ''}
504
+ `);
505
+ }
506
+
507
+ document.getElementById("fileInput").addEventListener("change", (event) => {
508
+ const file = event.target.files[0];
509
+ if (file) {
510
+ const reader = new FileReader();
511
+ reader.onload = (e) => {
512
+ const data = JSON.parse(e.target.result);
513
+ visualizeGraph(data);
514
+ };
515
+ reader.readAsText(file);
516
+ }
517
+ });
518
+
519
+ // Try to load default data
520
+ fetch("chunk-graph.json")
521
+ .then(response => response.json())
522
+ .then(data => visualizeGraph(data))
523
+ .catch(err => console.log("No default graph found. Please load a JSON file."));
524
+ </script>
525
+ </body>
526
+ </html>'''
527
+
528
+ with open(html_file, "w") as f:
529
+ f.write(html_content)
@@ -33,16 +33,17 @@ 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
37
- demo 🎬 Run interactive demo
38
- doctor 🩺 Check system health
39
- status 📊 Show project status
40
- search 🔍 Search code semantically
41
- index 📇 Index codebase
42
- mcp 🤖 MCP integration for AI tools
43
- config ⚙️ Configure settings
44
- help ❓ Get help
45
- version ℹ️ Show version
36
+ init 🔧 Initialize project
37
+ demo 🎬 Run interactive demo
38
+ doctor 🩺 Check system health
39
+ status 📊 Show project status
40
+ search 🔍 Search code semantically
41
+ index 📇 Index codebase
42
+ mcp 🤖 MCP integration for AI tools
43
+ config ⚙️ Configure settings
44
+ visualize 📊 Visualize code relationships
45
+ help ❓ Get help
46
+ version ℹ️ Show version
46
47
 
47
48
  [dim]For detailed help: [cyan]mcp-vector-search COMMAND --help[/cyan][/dim]
48
49
  """,
@@ -58,6 +59,7 @@ from .commands.init import init_app # noqa: E402
58
59
  from .commands.mcp import mcp_app # noqa: E402
59
60
  from .commands.search import search_app, search_main # noqa: E402, F401
60
61
  from .commands.status import main as status_main # noqa: E402
62
+ from .commands.visualize import app as visualize_app # noqa: E402
61
63
 
62
64
  # ============================================================================
63
65
  # MAIN COMMANDS - Clean hierarchy
@@ -89,7 +91,10 @@ app.add_typer(mcp_app, name="mcp", help="🤖 Manage MCP integration for AI tool
89
91
  # 8. CONFIG - Configuration
90
92
  app.add_typer(config_app, name="config", help="⚙️ Manage project configuration")
91
93
 
92
- # 9. HELP - Enhanced help
94
+ # 9. VISUALIZE - Code graph visualization
95
+ app.add_typer(visualize_app, name="visualize", help="📊 Visualize code chunk relationships")
96
+
97
+ # 10. HELP - Enhanced help
93
98
  # (defined below inline)
94
99
 
95
100
  # 10. VERSION - Version info