@sanderkooger/mcp-server-ragdocs
Version:
An MCP server for semantic documentation search and retrieval using vector databases to augment LLM capabilities.
59 lines (58 loc) • 2.46 kB
JavaScript
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { BaseHandler } from './base-handler.js';
import { isDocumentPayload } from '../types.js';
const COLLECTION_NAME = 'documentation';
export class SearchDocumentationHandler extends BaseHandler {
async handle(args) {
if (!args.query || typeof args.query !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Query is required');
}
const limit = args.limit || 5;
try {
const queryEmbedding = await this.apiClient.getEmbeddings(args.query);
const searchResults = await this.apiClient.qdrantClient.search(COLLECTION_NAME, {
vector: queryEmbedding,
limit,
with_payload: true,
// with_vector: false, // Optimize network transfer by not retrieving vectors
score_threshold: 0.6, // Temporary lower threshold for testing
});
const formattedResults = searchResults
.map((result) => {
if (!isDocumentPayload(result.payload)) {
throw new Error('Invalid payload type');
}
return `[${result.payload.title}](${result.payload.url})\nScore: ${result.score.toFixed(3)}\nContent: ${result.payload.text}\n`;
})
.join('\n---\n');
return {
content: [
{
type: 'text',
text: formattedResults || 'No results found matching the query.'
}
]
};
}
catch (error) {
if (error instanceof Error) {
if (error.message.includes('unauthorized')) {
throw new McpError(ErrorCode.InvalidRequest, 'Failed to authenticate with Qdrant cloud while searching');
}
else if (error.message.includes('ECONNREFUSED') ||
error.message.includes('ETIMEDOUT')) {
throw new McpError(ErrorCode.InternalError, 'Connection to Qdrant cloud failed while searching');
}
}
return {
content: [
{
type: 'text',
text: `Search failed: ${error}`
}
],
isError: true
};
}
}
}