import os import logging import chromadb from chromadb.utils import embedding_functions from mcp.server.fastmcp import FastMCP # Log ayarları: Sadece kendi loglarımızı yazalım, diğer kütüphaneler (httpx vb.) karışmasın. SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) logger = logging.getLogger("DocsRAGServer") logger.setLevel(logging.INFO) # Sadece bu logger için bir dosya işleyicisi (handler) ekleyelim file_handler = logging.FileHandler(os.path.join(SCRIPT_DIR, 'mcp_server.log')) file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) logger.addHandler(file_handler) # Propagate'i kapatalım ki root logger'a gitmesin logger.propagate = False # Dış kütüphanelerin gereksiz loglarını susturmak için: logging.getLogger("httpx").setLevel(logging.WARNING) logging.getLogger("sentence_transformers").setLevel(logging.WARNING) logger.info("DocsRAGServer başlatılıyor...") # MCP Server'ı oluştur # FastMCP, standart stdio/SSE vb. ayarlarını otomatik halleder. mcp = FastMCP("DocsRAGServer") # Çalışma dizinine göre ChromaDB klasörünün yolunu bul MAIN_DIR = os.path.dirname(SCRIPT_DIR) CHROMA_PATH = os.path.join(MAIN_DIR, "chroma_db") # ChromaDB'ye bağlan # Not: Sunucu ayağa kalktığında sadece bir kez bağlanır. try: client = chromadb.PersistentClient(path=CHROMA_PATH) sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2") collection = client.get_or_create_collection(name="docs", embedding_function=sentence_transformer_ef) except Exception as e: error_msg = f"UYARI: ChromaDB'ye bağlanırken hata oluştu. Henüz ingest.py çalıştırılmamış olabilir mi? Hata: {e}" print(error_msg) logger.error(error_msg) @mcp.tool() def search_documentation(query: str, num_results: int = 5) -> str: """ Kullanıcının sorusuna en uygun dokümantasyon parçalarını vektör veritabanından bulup döndürür. Bu araç (tool) sistemin belleği gibi davranır ve AI'ın kod/kavram araması yapmasını sağlar. Args: query: Aranacak soru veya anahtar kelimeler (Örn: 'app router data fetching' veya 'next-auth credentials provider'). num_results: Döndürülecek maksimum sonuç sayısı (varsayılan: 5). """ logger.info(f"Arama isteği alındı: query='{query}', num_results={num_results}") try: # Sorguyu vektöre çevirip en benzer metinleri (semantic search) getir results = collection.query( query_texts=[query], n_results=num_results ) if not results['documents'] or not results['documents'][0]: logger.info("Sorgu için ilgili bir doküman bulunamadı.") return "İlgili bir doküman bulunamadı." docs = results['documents'][0] metadatas = results['metadatas'][0] distances = results['distances'][0] response_parts = [f"Arama Sorgusu: '{query}'\nBulunan En İyi Eşleşmeler:\n"] for i, (doc, meta, dist) in enumerate(zip(docs, metadatas, distances)): source = meta.get("source", "Bilinmeyen Kaynak") # dist (distance) değeri ChromaDB için varsayılan olarak L2 mesafesidir, küçük olan daha benzerdir. response_parts.append(f"--- Sonuç {i+1} (Kaynak: {source}, Uzaklık: {dist:.4f}) ---\n{doc}\n") logger.info(f"Sorgu başarılı, {len(docs)} sonuç bulundu.") return "\n".join(response_parts) except Exception as e: logger.error(f"Arama sırasında hata oluştu: {str(e)}") return f"Arama sırasında hata oluştu: {str(e)}" if __name__ == "__main__": # Server'ı stdio üzerinden çalıştır. (Claude veya diğer MCP istemcileri bu şekilde bağlanır) mcp.run()