@sanderkooger/mcp-server-ragdocs
by sanderkooger
Verified
- src
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: QdrantClient
/** OpenAI client instance (if configured) */
openaiClient?: OpenAI
/** Ollama client instance (if configured) */
ollamaClient?: Ollama
/** Headless browser instance for web interactions */
browser: any
/**
* 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: string): Promise<number[]> {
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: string) {
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}`
)
}
}
}