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.
- mcp_vector_search/__init__.py +2 -2
- mcp_vector_search/cli/commands/index.py +73 -31
- mcp_vector_search/cli/commands/init.py +189 -113
- mcp_vector_search/cli/commands/install.py +525 -113
- mcp_vector_search/cli/commands/mcp.py +201 -151
- mcp_vector_search/cli/commands/reset.py +41 -41
- mcp_vector_search/cli/commands/search.py +73 -14
- mcp_vector_search/cli/commands/status.py +51 -17
- mcp_vector_search/cli/didyoumean.py +254 -246
- mcp_vector_search/cli/main.py +171 -52
- mcp_vector_search/cli/output.py +152 -0
- mcp_vector_search/cli/suggestions.py +246 -197
- mcp_vector_search/core/database.py +81 -49
- mcp_vector_search/core/indexer.py +10 -4
- mcp_vector_search/core/search.py +17 -6
- mcp_vector_search/mcp/__main__.py +1 -1
- mcp_vector_search/mcp/server.py +211 -203
- mcp_vector_search/parsers/__init__.py +6 -0
- mcp_vector_search/parsers/dart.py +605 -0
- mcp_vector_search/parsers/php.py +694 -0
- mcp_vector_search/parsers/registry.py +16 -1
- mcp_vector_search/parsers/ruby.py +678 -0
- mcp_vector_search/parsers/text.py +31 -25
- mcp_vector_search/utils/gitignore.py +72 -71
- {mcp_vector_search-0.4.13.dist-info → mcp_vector_search-0.5.0.dist-info}/METADATA +59 -2
- {mcp_vector_search-0.4.13.dist-info → mcp_vector_search-0.5.0.dist-info}/RECORD +29 -26
- {mcp_vector_search-0.4.13.dist-info → mcp_vector_search-0.5.0.dist-info}/WHEEL +0 -0
- {mcp_vector_search-0.4.13.dist-info → mcp_vector_search-0.5.0.dist-info}/entry_points.txt +0 -0
- {mcp_vector_search-0.4.13.dist-info → mcp_vector_search-0.5.0.dist-info}/licenses/LICENSE +0 -0
mcp_vector_search/mcp/server.py
CHANGED
|
@@ -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
|
|
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__(
|
|
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:
|
|
47
|
-
self.file_watcher:
|
|
48
|
-
self.indexer:
|
|
49
|
-
self.database:
|
|
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(
|
|
107
|
+
logger.info("File watching enabled for automatic reindexing")
|
|
108
108
|
else:
|
|
109
|
-
logger.info(
|
|
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,
|
|
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) ->
|
|
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=[
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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=[
|
|
318
|
-
type="text",
|
|
319
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
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"
|
|
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 =
|
|
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 +=
|
|
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=[
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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:
|
|
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=[
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
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:
|
|
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=[
|
|
494
|
-
type="text",
|
|
495
|
-
|
|
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=[
|
|
511
|
-
type="text",
|
|
512
|
-
|
|
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=[
|
|
529
|
-
|
|
530
|
-
|
|
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 = [
|
|
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(
|
|
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(
|
|
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 =
|
|
548
|
-
|
|
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=[
|
|
563
|
-
type="text",
|
|
564
|
-
|
|
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:
|
|
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=[
|
|
578
|
-
type="text",
|
|
579
|
-
|
|
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=[
|
|
596
|
-
|
|
597
|
-
|
|
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 = [
|
|
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(
|
|
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(
|
|
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 =
|
|
618
|
-
|
|
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=[
|
|
633
|
-
type="text",
|
|
634
|
-
|
|
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(
|
|
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() ->
|
|
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(
|
|
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,
|
|
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))
|