mcp-vector-search 0.4.13__py3-none-any.whl → 0.5.0__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 (29) hide show
  1. mcp_vector_search/__init__.py +2 -2
  2. mcp_vector_search/cli/commands/index.py +73 -31
  3. mcp_vector_search/cli/commands/init.py +189 -113
  4. mcp_vector_search/cli/commands/install.py +525 -113
  5. mcp_vector_search/cli/commands/mcp.py +201 -151
  6. mcp_vector_search/cli/commands/reset.py +41 -41
  7. mcp_vector_search/cli/commands/search.py +73 -14
  8. mcp_vector_search/cli/commands/status.py +51 -17
  9. mcp_vector_search/cli/didyoumean.py +254 -246
  10. mcp_vector_search/cli/main.py +171 -52
  11. mcp_vector_search/cli/output.py +152 -0
  12. mcp_vector_search/cli/suggestions.py +246 -197
  13. mcp_vector_search/core/database.py +81 -49
  14. mcp_vector_search/core/indexer.py +10 -4
  15. mcp_vector_search/core/search.py +17 -6
  16. mcp_vector_search/mcp/__main__.py +1 -1
  17. mcp_vector_search/mcp/server.py +211 -203
  18. mcp_vector_search/parsers/__init__.py +6 -0
  19. mcp_vector_search/parsers/dart.py +605 -0
  20. mcp_vector_search/parsers/php.py +694 -0
  21. mcp_vector_search/parsers/registry.py +16 -1
  22. mcp_vector_search/parsers/ruby.py +678 -0
  23. mcp_vector_search/parsers/text.py +31 -25
  24. mcp_vector_search/utils/gitignore.py +72 -71
  25. {mcp_vector_search-0.4.13.dist-info → mcp_vector_search-0.5.0.dist-info}/METADATA +59 -2
  26. {mcp_vector_search-0.4.13.dist-info → mcp_vector_search-0.5.0.dist-info}/RECORD +29 -26
  27. {mcp_vector_search-0.4.13.dist-info → mcp_vector_search-0.5.0.dist-info}/WHEEL +0 -0
  28. {mcp_vector_search-0.4.13.dist-info → mcp_vector_search-0.5.0.dist-info}/entry_points.txt +0 -0
  29. {mcp_vector_search-0.4.13.dist-info → mcp_vector_search-0.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,25 +1,22 @@
1
1
  """MCP server implementation for MCP Vector Search."""
2
2
 
3
3
  import asyncio
4
- import json
5
4
  import os
6
5
  import sys
7
6
  from pathlib import Path
8
- from typing import Any, Dict, List, Optional
7
+ from typing import Any
9
8
 
10
9
  from loguru import logger
11
10
  from mcp.server import Server
11
+ from mcp.server.models import InitializationOptions
12
12
  from mcp.server.stdio import stdio_server
13
13
  from mcp.types import (
14
14
  CallToolRequest,
15
15
  CallToolResult,
16
- ListToolsRequest,
17
- ListToolsResult,
18
16
  ServerCapabilities,
19
17
  TextContent,
20
18
  Tool,
21
19
  )
22
- from mcp.server.models import InitializationOptions
23
20
 
24
21
  from ..core.database import ChromaVectorDatabase
25
22
  from ..core.embeddings import create_embedding_function
@@ -33,9 +30,13 @@ from ..core.watcher import FileWatcher
33
30
  class MCPVectorSearchServer:
34
31
  """MCP server for vector search functionality."""
35
32
 
36
- def __init__(self, project_root: Optional[Path] = None, enable_file_watching: Optional[bool] = None):
33
+ def __init__(
34
+ self,
35
+ project_root: Path | None = None,
36
+ enable_file_watching: bool | None = None,
37
+ ):
37
38
  """Initialize the MCP server.
38
-
39
+
39
40
  Args:
40
41
  project_root: Project root directory. If None, will auto-detect.
41
42
  enable_file_watching: Enable file watching for automatic reindexing.
@@ -43,12 +44,12 @@ class MCPVectorSearchServer:
43
44
  """
44
45
  self.project_root = project_root or Path.cwd()
45
46
  self.project_manager = ProjectManager(self.project_root)
46
- self.search_engine: Optional[SemanticSearchEngine] = None
47
- self.file_watcher: Optional[FileWatcher] = None
48
- self.indexer: Optional[SemanticIndexer] = None
49
- self.database: Optional[ChromaVectorDatabase] = None
47
+ self.search_engine: SemanticSearchEngine | None = None
48
+ self.file_watcher: FileWatcher | None = None
49
+ self.indexer: SemanticIndexer | None = None
50
+ self.database: ChromaVectorDatabase | None = None
50
51
  self._initialized = False
51
-
52
+
52
53
  # Determine if file watching should be enabled
53
54
  if enable_file_watching is None:
54
55
  # Check environment variable, default to True
@@ -65,52 +66,51 @@ class MCPVectorSearchServer:
65
66
  try:
66
67
  # Load project configuration
67
68
  config = self.project_manager.load_config()
68
-
69
+
69
70
  # Setup embedding function
70
71
  embedding_function, _ = create_embedding_function(
71
72
  model_name=config.embedding_model
72
73
  )
73
-
74
+
74
75
  # Setup database
75
76
  self.database = ChromaVectorDatabase(
76
77
  persist_directory=config.index_path,
77
78
  embedding_function=embedding_function,
78
79
  )
79
-
80
+
80
81
  # Initialize database
81
82
  await self.database.__aenter__()
82
-
83
+
83
84
  # Setup search engine
84
85
  self.search_engine = SemanticSearchEngine(
85
- database=self.database,
86
- project_root=self.project_root
86
+ database=self.database, project_root=self.project_root
87
87
  )
88
-
88
+
89
89
  # Setup indexer for file watching
90
90
  if self.enable_file_watching:
91
91
  self.indexer = SemanticIndexer(
92
92
  database=self.database,
93
93
  project_root=self.project_root,
94
- file_extensions=config.file_extensions
94
+ file_extensions=config.file_extensions,
95
95
  )
96
-
96
+
97
97
  # Setup file watcher
98
98
  self.file_watcher = FileWatcher(
99
99
  project_root=self.project_root,
100
100
  config=config,
101
101
  indexer=self.indexer,
102
- database=self.database
102
+ database=self.database,
103
103
  )
104
-
104
+
105
105
  # Start file watching
106
106
  await self.file_watcher.start()
107
- logger.info(f"File watching enabled for automatic reindexing")
107
+ logger.info("File watching enabled for automatic reindexing")
108
108
  else:
109
- logger.info(f"File watching disabled")
110
-
109
+ logger.info("File watching disabled")
110
+
111
111
  self._initialized = True
112
112
  logger.info(f"MCP server initialized for project: {self.project_root}")
113
-
113
+
114
114
  except ProjectNotFoundError:
115
115
  logger.error(f"Project not initialized at {self.project_root}")
116
116
  raise
@@ -125,19 +125,19 @@ class MCPVectorSearchServer:
125
125
  logger.info("Stopping file watcher...")
126
126
  await self.file_watcher.stop()
127
127
  self.file_watcher = None
128
-
128
+
129
129
  # Cleanup database connection
130
- if self.database and hasattr(self.database, '__aexit__'):
130
+ if self.database and hasattr(self.database, "__aexit__"):
131
131
  await self.database.__aexit__(None, None, None)
132
132
  self.database = None
133
-
133
+
134
134
  # Clear references
135
135
  self.search_engine = None
136
136
  self.indexer = None
137
137
  self._initialized = False
138
138
  logger.info("MCP server cleanup completed")
139
139
 
140
- def get_tools(self) -> List[Tool]:
140
+ def get_tools(self) -> list[Tool]:
141
141
  """Get available MCP tools."""
142
142
  tools = [
143
143
  Tool(
@@ -148,46 +148,46 @@ class MCPVectorSearchServer:
148
148
  "properties": {
149
149
  "query": {
150
150
  "type": "string",
151
- "description": "The search query to find relevant code"
151
+ "description": "The search query to find relevant code",
152
152
  },
153
153
  "limit": {
154
154
  "type": "integer",
155
155
  "description": "Maximum number of results to return",
156
156
  "default": 10,
157
157
  "minimum": 1,
158
- "maximum": 50
158
+ "maximum": 50,
159
159
  },
160
160
  "similarity_threshold": {
161
161
  "type": "number",
162
162
  "description": "Minimum similarity threshold (0.0-1.0)",
163
163
  "default": 0.3,
164
164
  "minimum": 0.0,
165
- "maximum": 1.0
165
+ "maximum": 1.0,
166
166
  },
167
167
  "file_extensions": {
168
168
  "type": "array",
169
169
  "items": {"type": "string"},
170
- "description": "Filter by file extensions (e.g., ['.py', '.js'])"
170
+ "description": "Filter by file extensions (e.g., ['.py', '.js'])",
171
171
  },
172
172
  "language": {
173
173
  "type": "string",
174
- "description": "Filter by programming language"
174
+ "description": "Filter by programming language",
175
175
  },
176
176
  "function_name": {
177
177
  "type": "string",
178
- "description": "Filter by function name"
178
+ "description": "Filter by function name",
179
179
  },
180
180
  "class_name": {
181
181
  "type": "string",
182
- "description": "Filter by class name"
182
+ "description": "Filter by class name",
183
183
  },
184
184
  "files": {
185
185
  "type": "string",
186
- "description": "Filter by file patterns (e.g., '*.py' or 'src/*.js')"
187
- }
186
+ "description": "Filter by file patterns (e.g., '*.py' or 'src/*.js')",
187
+ },
188
188
  },
189
- "required": ["query"]
190
- }
189
+ "required": ["query"],
190
+ },
191
191
  ),
192
192
  Tool(
193
193
  name="search_similar",
@@ -197,29 +197,29 @@ class MCPVectorSearchServer:
197
197
  "properties": {
198
198
  "file_path": {
199
199
  "type": "string",
200
- "description": "Path to the file to find similar code for"
200
+ "description": "Path to the file to find similar code for",
201
201
  },
202
202
  "function_name": {
203
203
  "type": "string",
204
- "description": "Optional function name within the file"
204
+ "description": "Optional function name within the file",
205
205
  },
206
206
  "limit": {
207
207
  "type": "integer",
208
208
  "description": "Maximum number of results to return",
209
209
  "default": 10,
210
210
  "minimum": 1,
211
- "maximum": 50
211
+ "maximum": 50,
212
212
  },
213
213
  "similarity_threshold": {
214
214
  "type": "number",
215
215
  "description": "Minimum similarity threshold (0.0-1.0)",
216
216
  "default": 0.3,
217
217
  "minimum": 0.0,
218
- "maximum": 1.0
219
- }
218
+ "maximum": 1.0,
219
+ },
220
220
  },
221
- "required": ["file_path"]
222
- }
221
+ "required": ["file_path"],
222
+ },
223
223
  ),
224
224
  Tool(
225
225
  name="search_context",
@@ -229,32 +229,28 @@ class MCPVectorSearchServer:
229
229
  "properties": {
230
230
  "description": {
231
231
  "type": "string",
232
- "description": "Contextual description of what you're looking for"
232
+ "description": "Contextual description of what you're looking for",
233
233
  },
234
234
  "focus_areas": {
235
235
  "type": "array",
236
236
  "items": {"type": "string"},
237
- "description": "Areas to focus on (e.g., ['security', 'authentication'])"
237
+ "description": "Areas to focus on (e.g., ['security', 'authentication'])",
238
238
  },
239
239
  "limit": {
240
240
  "type": "integer",
241
241
  "description": "Maximum number of results to return",
242
242
  "default": 10,
243
243
  "minimum": 1,
244
- "maximum": 50
245
- }
244
+ "maximum": 50,
245
+ },
246
246
  },
247
- "required": ["description"]
248
- }
247
+ "required": ["description"],
248
+ },
249
249
  ),
250
250
  Tool(
251
251
  name="get_project_status",
252
252
  description="Get project indexing status and statistics",
253
- inputSchema={
254
- "type": "object",
255
- "properties": {},
256
- "required": []
257
- }
253
+ inputSchema={"type": "object", "properties": {}, "required": []},
258
254
  ),
259
255
  Tool(
260
256
  name="index_project",
@@ -265,27 +261,24 @@ class MCPVectorSearchServer:
265
261
  "force": {
266
262
  "type": "boolean",
267
263
  "description": "Force reindexing even if index exists",
268
- "default": False
264
+ "default": False,
269
265
  },
270
266
  "file_extensions": {
271
267
  "type": "array",
272
268
  "items": {"type": "string"},
273
- "description": "File extensions to index (e.g., ['.py', '.js'])"
274
- }
269
+ "description": "File extensions to index (e.g., ['.py', '.js'])",
270
+ },
275
271
  },
276
- "required": []
277
- }
278
- )
272
+ "required": [],
273
+ },
274
+ ),
279
275
  ]
280
276
 
281
277
  return tools
282
278
 
283
279
  def get_capabilities(self) -> ServerCapabilities:
284
280
  """Get server capabilities."""
285
- return ServerCapabilities(
286
- tools={"listChanged": True},
287
- logging={}
288
- )
281
+ return ServerCapabilities(tools={"listChanged": True}, logging={})
289
282
 
290
283
  async def call_tool(self, request: CallToolRequest) -> CallToolResult:
291
284
  """Handle tool calls."""
@@ -305,23 +298,23 @@ class MCPVectorSearchServer:
305
298
  return await self._index_project(request.params.arguments)
306
299
  else:
307
300
  return CallToolResult(
308
- content=[TextContent(
309
- type="text",
310
- text=f"Unknown tool: {request.params.name}"
311
- )],
312
- isError=True
301
+ content=[
302
+ TextContent(
303
+ type="text", text=f"Unknown tool: {request.params.name}"
304
+ )
305
+ ],
306
+ isError=True,
313
307
  )
314
308
  except Exception as e:
315
309
  logger.error(f"Tool call failed: {e}")
316
310
  return CallToolResult(
317
- content=[TextContent(
318
- type="text",
319
- text=f"Tool execution failed: {str(e)}"
320
- )],
321
- isError=True
311
+ content=[
312
+ TextContent(type="text", text=f"Tool execution failed: {str(e)}")
313
+ ],
314
+ isError=True,
322
315
  )
323
316
 
324
- async def _search_code(self, args: Dict[str, Any]) -> CallToolResult:
317
+ async def _search_code(self, args: dict[str, Any]) -> CallToolResult:
325
318
  """Handle search_code tool call."""
326
319
  query = args.get("query", "")
327
320
  limit = args.get("limit", 10)
@@ -334,11 +327,8 @@ class MCPVectorSearchServer:
334
327
 
335
328
  if not query:
336
329
  return CallToolResult(
337
- content=[TextContent(
338
- type="text",
339
- text="Query parameter is required"
340
- )],
341
- isError=True
330
+ content=[TextContent(type="text", text="Query parameter is required")],
331
+ isError=True,
342
332
  )
343
333
 
344
334
  # Build filters
@@ -360,7 +350,7 @@ class MCPVectorSearchServer:
360
350
  query=query,
361
351
  limit=limit,
362
352
  similarity_threshold=similarity_threshold,
363
- filters=filters
353
+ filters=filters,
364
354
  )
365
355
 
366
356
  # Format results
@@ -368,35 +358,37 @@ class MCPVectorSearchServer:
368
358
  response_text = f"No results found for query: '{query}'"
369
359
  else:
370
360
  response_lines = [f"Found {len(results)} results for query: '{query}'\n"]
371
-
361
+
372
362
  for i, result in enumerate(results, 1):
373
- response_lines.append(f"## Result {i} (Score: {result.similarity_score:.3f})")
363
+ response_lines.append(
364
+ f"## Result {i} (Score: {result.similarity_score:.3f})"
365
+ )
374
366
  response_lines.append(f"**File:** {result.file_path}")
375
367
  if result.function_name:
376
368
  response_lines.append(f"**Function:** {result.function_name}")
377
369
  if result.class_name:
378
370
  response_lines.append(f"**Class:** {result.class_name}")
379
- response_lines.append(f"**Lines:** {result.start_line}-{result.end_line}")
371
+ response_lines.append(
372
+ f"**Lines:** {result.start_line}-{result.end_line}"
373
+ )
380
374
  response_lines.append("**Code:**")
381
375
  response_lines.append("```" + (result.language or ""))
382
376
  response_lines.append(result.content)
383
377
  response_lines.append("```\n")
384
-
378
+
385
379
  response_text = "\n".join(response_lines)
386
380
 
387
- return CallToolResult(
388
- content=[TextContent(type="text", text=response_text)]
389
- )
381
+ return CallToolResult(content=[TextContent(type="text", text=response_text)])
390
382
 
391
- async def _get_project_status(self, args: Dict[str, Any]) -> CallToolResult:
383
+ async def _get_project_status(self, args: dict[str, Any]) -> CallToolResult:
392
384
  """Handle get_project_status tool call."""
393
385
  try:
394
386
  config = self.project_manager.load_config()
395
-
387
+
396
388
  # Get database stats
397
389
  if self.search_engine:
398
390
  stats = await self.search_engine.database.get_stats()
399
-
391
+
400
392
  status_info = {
401
393
  "project_root": str(config.project_root),
402
394
  "index_path": str(config.index_path),
@@ -405,7 +397,9 @@ class MCPVectorSearchServer:
405
397
  "languages": config.languages,
406
398
  "total_chunks": stats.total_chunks,
407
399
  "total_files": stats.total_files,
408
- "index_size": f"{stats.index_size_mb:.2f} MB" if hasattr(stats, 'index_size_mb') else "Unknown"
400
+ "index_size": f"{stats.index_size_mb:.2f} MB"
401
+ if hasattr(stats, "index_size_mb")
402
+ else "Unknown",
409
403
  }
410
404
  else:
411
405
  status_info = {
@@ -414,74 +408,76 @@ class MCPVectorSearchServer:
414
408
  "file_extensions": config.file_extensions,
415
409
  "embedding_model": config.embedding_model,
416
410
  "languages": config.languages,
417
- "status": "Not indexed"
411
+ "status": "Not indexed",
418
412
  }
419
-
420
- response_text = f"# Project Status\n\n"
413
+
414
+ response_text = "# Project Status\n\n"
421
415
  response_text += f"**Project Root:** {status_info['project_root']}\n"
422
416
  response_text += f"**Index Path:** {status_info['index_path']}\n"
423
- response_text += f"**File Extensions:** {', '.join(status_info['file_extensions'])}\n"
417
+ response_text += (
418
+ f"**File Extensions:** {', '.join(status_info['file_extensions'])}\n"
419
+ )
424
420
  response_text += f"**Embedding Model:** {status_info['embedding_model']}\n"
425
421
  response_text += f"**Languages:** {', '.join(status_info['languages'])}\n"
426
-
422
+
427
423
  if "total_chunks" in status_info:
428
424
  response_text += f"**Total Chunks:** {status_info['total_chunks']}\n"
429
425
  response_text += f"**Total Files:** {status_info['total_files']}\n"
430
426
  response_text += f"**Index Size:** {status_info['index_size']}\n"
431
427
  else:
432
428
  response_text += f"**Status:** {status_info['status']}\n"
433
-
429
+
434
430
  return CallToolResult(
435
431
  content=[TextContent(type="text", text=response_text)]
436
432
  )
437
-
433
+
438
434
  except ProjectNotFoundError:
439
435
  return CallToolResult(
440
- content=[TextContent(
441
- type="text",
442
- text=f"Project not initialized at {self.project_root}. Run 'mcp-vector-search init' first."
443
- )],
444
- isError=True
436
+ content=[
437
+ TextContent(
438
+ type="text",
439
+ text=f"Project not initialized at {self.project_root}. Run 'mcp-vector-search init' first.",
440
+ )
441
+ ],
442
+ isError=True,
445
443
  )
446
444
 
447
- async def _index_project(self, args: Dict[str, Any]) -> CallToolResult:
445
+ async def _index_project(self, args: dict[str, Any]) -> CallToolResult:
448
446
  """Handle index_project tool call."""
449
447
  force = args.get("force", False)
450
448
  file_extensions = args.get("file_extensions")
451
-
449
+
452
450
  try:
453
451
  # Import indexing functionality
454
452
  from ..cli.commands.index import run_indexing
455
-
453
+
456
454
  # Run indexing
457
455
  await run_indexing(
458
456
  project_root=self.project_root,
459
457
  force_reindex=force,
460
458
  extensions=file_extensions,
461
- show_progress=False # Disable progress for MCP
459
+ show_progress=False, # Disable progress for MCP
462
460
  )
463
-
461
+
464
462
  # Reinitialize search engine after indexing
465
463
  await self.cleanup()
466
464
  await self.initialize()
467
-
465
+
468
466
  return CallToolResult(
469
- content=[TextContent(
470
- type="text",
471
- text="Project indexing completed successfully!"
472
- )]
467
+ content=[
468
+ TextContent(
469
+ type="text", text="Project indexing completed successfully!"
470
+ )
471
+ ]
473
472
  )
474
-
473
+
475
474
  except Exception as e:
476
475
  return CallToolResult(
477
- content=[TextContent(
478
- type="text",
479
- text=f"Indexing failed: {str(e)}"
480
- )],
481
- isError=True
476
+ content=[TextContent(type="text", text=f"Indexing failed: {str(e)}")],
477
+ isError=True,
482
478
  )
483
479
 
484
- async def _search_similar(self, args: Dict[str, Any]) -> CallToolResult:
480
+ async def _search_similar(self, args: dict[str, Any]) -> CallToolResult:
485
481
  """Handle search_similar tool call."""
486
482
  file_path = args.get("file_path", "")
487
483
  function_name = args.get("function_name")
@@ -490,11 +486,10 @@ class MCPVectorSearchServer:
490
486
 
491
487
  if not file_path:
492
488
  return CallToolResult(
493
- content=[TextContent(
494
- type="text",
495
- text="file_path parameter is required"
496
- )],
497
- isError=True
489
+ content=[
490
+ TextContent(type="text", text="file_path parameter is required")
491
+ ],
492
+ isError=True,
498
493
  )
499
494
 
500
495
  try:
@@ -507,11 +502,10 @@ class MCPVectorSearchServer:
507
502
 
508
503
  if not file_path_obj.exists():
509
504
  return CallToolResult(
510
- content=[TextContent(
511
- type="text",
512
- text=f"File not found: {file_path}"
513
- )],
514
- isError=True
505
+ content=[
506
+ TextContent(type="text", text=f"File not found: {file_path}")
507
+ ],
508
+ isError=True,
515
509
  )
516
510
 
517
511
  # Run similar search
@@ -519,54 +513,61 @@ class MCPVectorSearchServer:
519
513
  file_path=file_path_obj,
520
514
  function_name=function_name,
521
515
  limit=limit,
522
- similarity_threshold=similarity_threshold
516
+ similarity_threshold=similarity_threshold,
523
517
  )
524
518
 
525
519
  # Format results
526
520
  if not results:
527
521
  return CallToolResult(
528
- content=[TextContent(
529
- type="text",
530
- text=f"No similar code found for {file_path}"
531
- )]
522
+ content=[
523
+ TextContent(
524
+ type="text", text=f"No similar code found for {file_path}"
525
+ )
526
+ ]
532
527
  )
533
528
 
534
- response_lines = [f"Found {len(results)} similar code snippets for {file_path}\n"]
535
-
529
+ response_lines = [
530
+ f"Found {len(results)} similar code snippets for {file_path}\n"
531
+ ]
532
+
536
533
  for i, result in enumerate(results, 1):
537
- response_lines.append(f"## Result {i} (Score: {result.similarity_score:.3f})")
534
+ response_lines.append(
535
+ f"## Result {i} (Score: {result.similarity_score:.3f})"
536
+ )
538
537
  response_lines.append(f"**File:** {result.file_path}")
539
538
  if result.function_name:
540
539
  response_lines.append(f"**Function:** {result.function_name}")
541
540
  if result.class_name:
542
541
  response_lines.append(f"**Class:** {result.class_name}")
543
- response_lines.append(f"**Lines:** {result.start_line}-{result.end_line}")
542
+ response_lines.append(
543
+ f"**Lines:** {result.start_line}-{result.end_line}"
544
+ )
544
545
  response_lines.append("**Code:**")
545
546
  response_lines.append("```" + (result.language or ""))
546
547
  # Show more of the content for similar search
547
- content_preview = result.content[:500] if len(result.content) > 500 else result.content
548
- response_lines.append(content_preview + ("..." if len(result.content) > 500 else ""))
548
+ content_preview = (
549
+ result.content[:500]
550
+ if len(result.content) > 500
551
+ else result.content
552
+ )
553
+ response_lines.append(
554
+ content_preview + ("..." if len(result.content) > 500 else "")
555
+ )
549
556
  response_lines.append("```\n")
550
-
557
+
551
558
  result_text = "\n".join(response_lines)
552
559
 
553
- return CallToolResult(
554
- content=[TextContent(
555
- type="text",
556
- text=result_text
557
- )]
558
- )
560
+ return CallToolResult(content=[TextContent(type="text", text=result_text)])
559
561
 
560
562
  except Exception as e:
561
563
  return CallToolResult(
562
- content=[TextContent(
563
- type="text",
564
- text=f"Similar search failed: {str(e)}"
565
- )],
566
- isError=True
564
+ content=[
565
+ TextContent(type="text", text=f"Similar search failed: {str(e)}")
566
+ ],
567
+ isError=True,
567
568
  )
568
569
 
569
- async def _search_context(self, args: Dict[str, Any]) -> CallToolResult:
570
+ async def _search_context(self, args: dict[str, Any]) -> CallToolResult:
570
571
  """Handle search_context tool call."""
571
572
  description = args.get("description", "")
572
573
  focus_areas = args.get("focus_areas")
@@ -574,72 +575,79 @@ class MCPVectorSearchServer:
574
575
 
575
576
  if not description:
576
577
  return CallToolResult(
577
- content=[TextContent(
578
- type="text",
579
- text="description parameter is required"
580
- )],
581
- isError=True
578
+ content=[
579
+ TextContent(type="text", text="description parameter is required")
580
+ ],
581
+ isError=True,
582
582
  )
583
583
 
584
584
  try:
585
585
  # Perform context search
586
586
  results = await self.search_engine.search_by_context(
587
- context_description=description,
588
- focus_areas=focus_areas,
589
- limit=limit
587
+ context_description=description, focus_areas=focus_areas, limit=limit
590
588
  )
591
589
 
592
590
  # Format results
593
591
  if not results:
594
592
  return CallToolResult(
595
- content=[TextContent(
596
- type="text",
597
- text=f"No contextually relevant code found for: {description}"
598
- )]
593
+ content=[
594
+ TextContent(
595
+ type="text",
596
+ text=f"No contextually relevant code found for: {description}",
597
+ )
598
+ ]
599
599
  )
600
600
 
601
- response_lines = [f"Found {len(results)} contextually relevant code snippets"]
601
+ response_lines = [
602
+ f"Found {len(results)} contextually relevant code snippets"
603
+ ]
602
604
  if focus_areas:
603
605
  response_lines[0] += f" (focus: {', '.join(focus_areas)})"
604
606
  response_lines[0] += f" for: {description}\n"
605
-
607
+
606
608
  for i, result in enumerate(results, 1):
607
- response_lines.append(f"## Result {i} (Score: {result.similarity_score:.3f})")
609
+ response_lines.append(
610
+ f"## Result {i} (Score: {result.similarity_score:.3f})"
611
+ )
608
612
  response_lines.append(f"**File:** {result.file_path}")
609
613
  if result.function_name:
610
614
  response_lines.append(f"**Function:** {result.function_name}")
611
615
  if result.class_name:
612
616
  response_lines.append(f"**Class:** {result.class_name}")
613
- response_lines.append(f"**Lines:** {result.start_line}-{result.end_line}")
617
+ response_lines.append(
618
+ f"**Lines:** {result.start_line}-{result.end_line}"
619
+ )
614
620
  response_lines.append("**Code:**")
615
621
  response_lines.append("```" + (result.language or ""))
616
622
  # Show more of the content for context search
617
- content_preview = result.content[:500] if len(result.content) > 500 else result.content
618
- response_lines.append(content_preview + ("..." if len(result.content) > 500 else ""))
623
+ content_preview = (
624
+ result.content[:500]
625
+ if len(result.content) > 500
626
+ else result.content
627
+ )
628
+ response_lines.append(
629
+ content_preview + ("..." if len(result.content) > 500 else "")
630
+ )
619
631
  response_lines.append("```\n")
620
-
632
+
621
633
  result_text = "\n".join(response_lines)
622
634
 
623
- return CallToolResult(
624
- content=[TextContent(
625
- type="text",
626
- text=result_text
627
- )]
628
- )
635
+ return CallToolResult(content=[TextContent(type="text", text=result_text)])
629
636
 
630
637
  except Exception as e:
631
638
  return CallToolResult(
632
- content=[TextContent(
633
- type="text",
634
- text=f"Context search failed: {str(e)}"
635
- )],
636
- isError=True
639
+ content=[
640
+ TextContent(type="text", text=f"Context search failed: {str(e)}")
641
+ ],
642
+ isError=True,
637
643
  )
638
644
 
639
645
 
640
- def create_mcp_server(project_root: Optional[Path] = None, enable_file_watching: Optional[bool] = None) -> Server:
646
+ def create_mcp_server(
647
+ project_root: Path | None = None, enable_file_watching: bool | None = None
648
+ ) -> Server:
641
649
  """Create and configure the MCP server.
642
-
650
+
643
651
  Args:
644
652
  project_root: Project root directory. If None, will auto-detect.
645
653
  enable_file_watching: Enable file watching for automatic reindexing.
@@ -649,7 +657,7 @@ def create_mcp_server(project_root: Optional[Path] = None, enable_file_watching:
649
657
  mcp_server = MCPVectorSearchServer(project_root, enable_file_watching)
650
658
 
651
659
  @server.list_tools()
652
- async def handle_list_tools() -> List[Tool]:
660
+ async def handle_list_tools() -> list[Tool]:
653
661
  """List available tools."""
654
662
  return mcp_server.get_tools()
655
663
 
@@ -658,6 +666,7 @@ def create_mcp_server(project_root: Optional[Path] = None, enable_file_watching:
658
666
  """Handle tool calls."""
659
667
  # Create a mock request object for compatibility
660
668
  from types import SimpleNamespace
669
+
661
670
  mock_request = SimpleNamespace()
662
671
  mock_request.params = SimpleNamespace()
663
672
  mock_request.params.name = name
@@ -674,9 +683,11 @@ def create_mcp_server(project_root: Optional[Path] = None, enable_file_watching:
674
683
  return server
675
684
 
676
685
 
677
- async def run_mcp_server(project_root: Optional[Path] = None, enable_file_watching: Optional[bool] = None) -> None:
686
+ async def run_mcp_server(
687
+ project_root: Path | None = None, enable_file_watching: bool | None = None
688
+ ) -> None:
678
689
  """Run the MCP server using stdio transport.
679
-
690
+
680
691
  Args:
681
692
  project_root: Project root directory. If None, will auto-detect.
682
693
  enable_file_watching: Enable file watching for automatic reindexing.
@@ -688,10 +699,7 @@ async def run_mcp_server(project_root: Optional[Path] = None, enable_file_watchi
688
699
  init_options = InitializationOptions(
689
700
  server_name="mcp-vector-search",
690
701
  server_version="0.4.0",
691
- capabilities=ServerCapabilities(
692
- tools={"listChanged": True},
693
- logging={}
694
- )
702
+ capabilities=ServerCapabilities(tools={"listChanged": True}, logging={}),
695
703
  )
696
704
 
697
705
  try:
@@ -704,7 +712,7 @@ async def run_mcp_server(project_root: Optional[Path] = None, enable_file_watchi
704
712
  raise
705
713
  finally:
706
714
  # Cleanup
707
- if hasattr(server, '_mcp_server'):
715
+ if hasattr(server, "_mcp_server"):
708
716
  logger.info("Performing server cleanup...")
709
717
  await server._mcp_server.cleanup()
710
718
 
@@ -712,7 +720,7 @@ async def run_mcp_server(project_root: Optional[Path] = None, enable_file_watchi
712
720
  if __name__ == "__main__":
713
721
  # Allow specifying project root as command line argument
714
722
  project_root = Path(sys.argv[1]) if len(sys.argv) > 1 else None
715
-
723
+
716
724
  # Check for file watching flag in command line args
717
725
  enable_file_watching = None
718
726
  if "--no-watch" in sys.argv:
@@ -721,5 +729,5 @@ if __name__ == "__main__":
721
729
  elif "--watch" in sys.argv:
722
730
  enable_file_watching = True
723
731
  sys.argv.remove("--watch")
724
-
732
+
725
733
  asyncio.run(run_mcp_server(project_root, enable_file_watching))