@sanderkooger/mcp-server-ragdocs
Version:
An MCP server for semantic documentation search and retrieval using vector databases to augment LLM capabilities.
151 lines (150 loc) • 6 kB
JavaScript
import { QdrantClient } from '@qdrant/js-client-rest';
import OpenAI from 'openai';
import { Ollama } from 'ollama';
import { chromium } from 'playwright';
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
// Environment variables for configuration
const OPENAI_API_KEY = process.env['OPENAI_API_KEY'];
const QDRANT_URL = process.env['QDRANT_URL'];
if (!QDRANT_URL)
throw new Error('QDRANT_URL environment variable required');
const QDRANT_API_KEY = process.env['QDRANT_API_KEY'];
const EMBEDDINGS_PROVIDER = process.env['EMBEDDINGS_PROVIDER'] || 'ollama';
const OLLAMA_BASE_URL = process.env['OLLAMA_BASE_URL'];
if (!QDRANT_URL) {
throw new Error('QDRANT_URL environment variable is required');
}
/**
* Client for managing connections to various AI service providers and vector database
* @class
*/
export class ApiClient {
/** Qdrant vector database client instance */
qdrantClient;
/** OpenAI client instance (if configured) */
openaiClient;
/** Ollama client instance (if configured) */
ollamaClient;
/** Headless browser instance for web interactions */
browser;
/**
* Initializes API clients based on environment configuration
* @constructor
* @throws {Error} If QDRANT_URL environment variable is missing
*/
constructor() {
// Initialize Qdrant client with cloud configuration
this.qdrantClient = new QdrantClient({
url: QDRANT_URL,
...(QDRANT_API_KEY ? { apiKey: QDRANT_API_KEY } : {})
});
// Initialize OpenAI client if API key is provided
if (EMBEDDINGS_PROVIDER === 'openai' && OPENAI_API_KEY) {
this.openaiClient = new OpenAI({
apiKey: OPENAI_API_KEY
});
}
// Initialize OpenAI client if API key is provided
if (EMBEDDINGS_PROVIDER === 'ollama') {
// FIX TO CREATE OLLAMA CLIENT PROPPER
this.ollamaClient = new Ollama({
host: OLLAMA_BASE_URL || 'http://127.0.0.1:11434'
});
}
}
/**
* Initializes a headless browser instance for web scraping/interactions
* @async
* @returns {Promise<void>}
*/
async initBrowser() {
if (!this.browser) {
this.browser = await chromium.launch();
}
}
/**
* Cleans up resources and closes browser instance
* @async
* @returns {Promise<void>}
*/
async cleanup() {
if (this.browser) {
await this.browser.close();
}
}
async getEmbeddings(text) {
if (EMBEDDINGS_PROVIDER == 'openai' && !this.openaiClient) {
throw new McpError(ErrorCode.InvalidRequest, 'OpenAI API key not configured');
}
if (EMBEDDINGS_PROVIDER == 'ollama' && !this.ollamaClient) {
throw new McpError(ErrorCode.InvalidRequest, 'ollama URL not configured, or ollama is not running');
}
// get embeddings using OpenAI Client
if (this.openaiClient) {
try {
const response = await this.openaiClient.embeddings.create({
model: 'text-embedding-ada-002',
input: text
});
return response.data?.[0]?.embedding || [];
}
catch (error) {
throw new McpError(ErrorCode.InternalError, `Failed to generate embeddingsusing openai: ${error}`);
}
}
// get embeddings using Ollama
if (this.ollamaClient) {
try {
const response = await this.ollamaClient.embeddings({
model: 'nomic-embed-text',
prompt: text
});
return response.embedding;
}
catch (error) {
throw new McpError(ErrorCode.InternalError, `Failed to generate embeddings using ollama: ${error}`);
}
}
// Handle unexpected case
throw new McpError(ErrorCode.InternalError, 'No valid embeddings provider configured');
}
/**
* Initializes Qdrant vector database collection with optimal configuration
* @async
* @param {string} COLLECTION_NAME - Name of the collection to initialize
* @returns {Promise<void>}
* @throws {McpError} If collection creation fails due to authentication or connection issues
*/
async initCollection(COLLECTION_NAME) {
try {
const collections = await this.qdrantClient.getCollections();
const exists = collections.collections.some((c) => c.name === COLLECTION_NAME);
if (!exists) {
await this.qdrantClient.createCollection(COLLECTION_NAME, {
vectors: {
size: EMBEDDINGS_PROVIDER === 'openai' ? 1536 : 768, // OpenAI ada-002 (1536) or Ollama nomic-embed-text (768)
distance: 'Cosine'
},
// Add optimized settings for cloud deployment
optimizers_config: {
default_segment_number: 2,
memmap_threshold: 20000
},
replication_factor: 2
});
}
}
catch (error) {
if (error instanceof Error) {
if (error.message.includes('unauthorized')) {
throw new McpError(ErrorCode.InvalidRequest, 'Failed to authenticate with Qdrant cloud. Please check your API key.');
}
else if (error.message.includes('ECONNREFUSED') ||
error.message.includes('ETIMEDOUT')) {
throw new McpError(ErrorCode.InternalError, 'Failed to connect to Qdrant cloud. Please check your QDRANT_URL.');
}
}
throw new McpError(ErrorCode.InternalError, `Failed to initialize Qdrant cloud collection: ${error}`);
}
}
}