Skip to main content
Glama
Skynotdie

MCP Localization Project

by Skynotdie
context7_main.py21.3 kB
#!/usr/bin/env python3 """ Context7 MCP 로컬화 - 메인 통합 클래스 Context7의 모든 기능을 통합하여 MCP 인터페이스 제공 주요 기능: - Context7MCP: 통합 MCP 클래스 - resolve_library_id와 get_library_docs 통합 제공 - 로컬 데이터베이스 관리 - 웹 데이터 소스 통합 - 캐싱 및 최적화 """ import asyncio import json import time import logging from typing import Dict, List, Optional, Any, Union from dataclasses import dataclass, asdict from pathlib import Path from context7_base import Context7Database, LibraryInfo, LibraryDocs, Context7Utils from context7_resolver import LibraryResolver, ResolveResult from context7_docs import LibraryDocsProvider, DocResult logger = logging.getLogger(__name__) @dataclass class MCPResponse: """MCP 응답 형식""" success: bool data: Any message: str metadata: Dict[str, Any] class Context7MCP: """Context7 MCP 통합 클래스""" def __init__(self, db_path: str = "context7_mcp.db"): """ Context7 MCP 초기화 Args: db_path: 데이터베이스 파일 경로 """ self.db_path = db_path self.db = Context7Database(db_path) self.resolver = LibraryResolver(self.db) self.docs_provider = LibraryDocsProvider(self.db) # 통계 정보 self.stats = { "resolve_calls": 0, "docs_calls": 0, "cache_hits": 0, "web_fetches": 0, "errors": 0 } logger.info(f"🚀 Context7 MCP 초기화 완료 (DB: {db_path})") async def resolve_library_id(self, library_name: str) -> MCPResponse: """ 라이브러리 이름을 Context7 호환 ID로 해결 Args: library_name: 검색할 라이브러리 이름 Returns: MCPResponse: 해결 결과 """ try: self.stats["resolve_calls"] += 1 start_time = time.time() logger.info(f"🔍 resolve_library_id 호출: '{library_name}'") # 입력 검증 if not library_name or not library_name.strip(): return MCPResponse( success=False, data=None, message="Library name cannot be empty", metadata={"error_type": "validation_error"} ) # 라이브러리 ID 해결 result = await self.resolver.resolve_library_id(library_name.strip()) # 응답 데이터 구성 response_data = { "selected_library_id": result.selected_library_id, "explanation": result.explanation, "confidence": result.confidence, "matches": [ { "library_id": match.library_id, "name": match.name, "description": match.description, "version": match.version, "trust_score": match.trust_score, "code_snippets": match.code_snippets } for match in result.matches ] } # 성공 여부 판단 success = bool(result.selected_library_id) message = "Library ID resolved successfully" if success else "No matching library found" if not success: self.stats["errors"] += 1 execution_time = time.time() - start_time return MCPResponse( success=success, data=response_data, message=message, metadata={ "execution_time": round(execution_time, 3), "matches_count": len(result.matches), "confidence": result.confidence } ) except Exception as e: self.stats["errors"] += 1 logger.error(f"❌ resolve_library_id 오류: {e}") return MCPResponse( success=False, data=None, message=f"Error resolving library ID: {str(e)}", metadata={"error_type": "internal_error", "error": str(e)} ) async def get_library_docs( self, library_id: str, topic: str = "", tokens: int = 10000 ) -> MCPResponse: """ 라이브러리 문서 가져오기 Args: library_id: Context7 호환 라이브러리 ID topic: 특정 주제 (선택사항) tokens: 최대 토큰 수 Returns: MCPResponse: 문서 검색 결과 """ try: self.stats["docs_calls"] += 1 start_time = time.time() logger.info(f"📚 get_library_docs 호출: {library_id} (topic: {topic})") # 입력 검증 if not library_id or not library_id.strip(): return MCPResponse( success=False, data=None, message="Library ID cannot be empty", metadata={"error_type": "validation_error"} ) # 토큰 수 검증 if tokens <= 0 or tokens > 50000: tokens = 10000 # 기본값으로 설정 # 캐시 확인 cached_docs = await self.db.get_cached_docs(library_id, topic) if cached_docs: self.stats["cache_hits"] += 1 logger.info(f"💾 캐시에서 문서 반환: {library_id}") else: self.stats["web_fetches"] += 1 # 라이브러리 문서 가져오기 result = await self.docs_provider.get_library_docs(library_id, topic, tokens) # 응답 데이터 구성 response_data = { "library_id": result.library_id, "content": result.content, "topic": result.topic, "tokens": result.tokens, "source": result.source, "metadata": result.metadata } execution_time = time.time() - start_time if not result.success: self.stats["errors"] += 1 return MCPResponse( success=result.success, data=response_data, message="Documentation retrieved successfully" if result.success else "Failed to retrieve documentation", metadata={ "execution_time": round(execution_time, 3), "content_length": len(result.content), "tokens_used": result.tokens, "source_type": result.source, "from_cache": bool(cached_docs) } ) except Exception as e: self.stats["errors"] += 1 logger.error(f"❌ get_library_docs 오류: {e}") return MCPResponse( success=False, data=None, message=f"Error retrieving documentation: {str(e)}", metadata={"error_type": "internal_error", "error": str(e)} ) async def get_library_suggestions(self, partial_name: str, limit: int = 5) -> MCPResponse: """ 부분 이름으로 라이브러리 제안 Args: partial_name: 부분 라이브러리 이름 limit: 반환할 제안 수 Returns: MCPResponse: 제안 결과 """ try: start_time = time.time() logger.info(f"💡 get_library_suggestions 호출: '{partial_name}'") suggestions = await self.resolver.get_library_suggestions(partial_name, limit) response_data = { "suggestions": [ { "library_id": lib.library_id, "name": lib.name, "description": lib.description, "trust_score": lib.trust_score } for lib in suggestions ] } execution_time = time.time() - start_time return MCPResponse( success=True, data=response_data, message=f"Found {len(suggestions)} suggestions", metadata={ "execution_time": round(execution_time, 3), "suggestions_count": len(suggestions) } ) except Exception as e: logger.error(f"❌ get_library_suggestions 오류: {e}") return MCPResponse( success=False, data=None, message=f"Error getting suggestions: {str(e)}", metadata={"error_type": "internal_error", "error": str(e)} ) async def add_library(self, library_info: Dict[str, Any]) -> MCPResponse: """ 새로운 라이브러리 정보 추가 Args: library_info: 라이브러리 정보 딕셔너리 Returns: MCPResponse: 추가 결과 """ try: logger.info(f"➕ add_library 호출: {library_info.get('library_id')}") # 필수 필드 검증 required_fields = ["library_id", "name"] for field in required_fields: if field not in library_info: return MCPResponse( success=False, data=None, message=f"Missing required field: {field}", metadata={"error_type": "validation_error"} ) # LibraryInfo 객체 생성 lib_info = LibraryInfo( library_id=library_info["library_id"], name=library_info["name"], description=library_info.get("description", ""), version=library_info.get("version", ""), trust_score=library_info.get("trust_score", 7.0), code_snippets=library_info.get("code_snippets", 0), last_updated=library_info.get("last_updated", "") ) # 데이터베이스에 추가 success = await self.db.add_library(lib_info) return MCPResponse( success=success, data={"library_id": lib_info.library_id}, message="Library added successfully" if success else "Failed to add library", metadata={"operation": "add_library"} ) except Exception as e: logger.error(f"❌ add_library 오류: {e}") return MCPResponse( success=False, data=None, message=f"Error adding library: {str(e)}", metadata={"error_type": "internal_error", "error": str(e)} ) async def get_stats(self) -> MCPResponse: """ Context7 통계 정보 가져오기 Returns: MCPResponse: 통계 정보 """ try: # 데이터베이스 통계 추가 with self.db.db_path and True: # 간단한 연결 확인 # 라이브러리 수 조회 import sqlite3 with sqlite3.connect(self.db.db_path) as conn: cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM libraries") library_count = cursor.fetchone()[0] cursor.execute("SELECT COUNT(*) FROM docs_cache") cache_count = cursor.fetchone()[0] stats_data = { "runtime_stats": self.stats, "database_stats": { "total_libraries": library_count, "cached_docs": cache_count }, "performance": { "cache_hit_rate": round( self.stats["cache_hits"] / max(self.stats["docs_calls"], 1) * 100, 2 ), "error_rate": round( self.stats["errors"] / max( self.stats["resolve_calls"] + self.stats["docs_calls"], 1 ) * 100, 2 ) } } return MCPResponse( success=True, data=stats_data, message="Statistics retrieved successfully", metadata={"timestamp": time.time()} ) except Exception as e: logger.error(f"❌ get_stats 오류: {e}") return MCPResponse( success=False, data=None, message=f"Error retrieving statistics: {str(e)}", metadata={"error_type": "internal_error", "error": str(e)} ) async def clear_cache(self, library_id: str = "", older_than_hours: int = 24) -> MCPResponse: """ 캐시 정리 Args: library_id: 특정 라이브러리 캐시만 정리 (빈 문자열이면 전체) older_than_hours: 지정된 시간보다 오래된 캐시 정리 Returns: MCPResponse: 정리 결과 """ try: import sqlite3 from datetime import datetime, timedelta cutoff_time = datetime.now() - timedelta(hours=older_than_hours) with sqlite3.connect(self.db.db_path) as conn: cursor = conn.cursor() if library_id: # 특정 라이브러리 캐시 정리 cursor.execute( "DELETE FROM docs_cache WHERE library_id = ? AND expires_at < ?", (library_id, cutoff_time) ) else: # 오래된 캐시 전체 정리 cursor.execute( "DELETE FROM docs_cache WHERE expires_at < ?", (cutoff_time,) ) deleted_count = cursor.rowcount conn.commit() return MCPResponse( success=True, data={"deleted_count": deleted_count}, message=f"Cleared {deleted_count} cache entries", metadata={"operation": "clear_cache", "library_id": library_id} ) except Exception as e: logger.error(f"❌ clear_cache 오류: {e}") return MCPResponse( success=False, data=None, message=f"Error clearing cache: {str(e)}", metadata={"error_type": "internal_error", "error": str(e)} ) async def close(self): """리소스 정리""" try: await self.docs_provider.close_session() logger.info("🔚 Context7 MCP 리소스 정리 완료") except Exception as e: logger.error(f"❌ 리소스 정리 오류: {e}") # Context7MCPServer 클래스 (MCP 서버 호환) class Context7MCPServer: """Context7 MCP 서버 래퍼""" def __init__(self, db_path: str = "context7_mcp.db"): self.context7 = Context7MCP(db_path) async def resolve_library_id(self, library_name: str) -> Dict[str, Any]: """MCP 도구 호환 인터페이스""" response = await self.context7.resolve_library_id(library_name) return asdict(response) async def get_library_docs( self, context7_compatible_library_id: str, topic: str = "", tokens: int = 10000 ) -> Dict[str, Any]: """MCP 도구 호환 인터페이스""" response = await self.context7.get_library_docs( context7_compatible_library_id, topic, tokens ) return asdict(response) async def close(self): """리소스 정리""" await self.context7.close() # 테스트 함수 async def test_context7_mcp(): """Context7 MCP 통합 테스트""" print("🧪 Context7 MCP 통합 테스트 시작...") # Context7 MCP 초기화 context7 = Context7MCP("test_context7_mcp.db") # 1. resolve_library_id 테스트 print("\n🔍 resolve_library_id 테스트...") test_libraries = ["axios", "react", "express", "nonexistent"] for lib_name in test_libraries: print(f"\n테스트: '{lib_name}'") result = await context7.resolve_library_id(lib_name) print(f" ✅ 성공: {result.success}") print(f" 📝 메시지: {result.message}") if result.success and result.data: print(f" 🆔 선택된 ID: {result.data['selected_library_id']}") print(f" 📊 신뢰도: {result.data['confidence']:.2f}") print(f" 🔢 매치 수: {len(result.data['matches'])}") # 2. get_library_docs 테스트 print("\n📚 get_library_docs 테스트...") test_docs = [ ("/axios/axios", ""), ("/axios/axios", "usage"), ("/facebook/react", ""), ("/nonexistent/lib", "") ] for lib_id, topic in test_docs: print(f"\n테스트: {lib_id} (topic: {topic or 'none'})") result = await context7.get_library_docs(lib_id, topic, tokens=5000) print(f" ✅ 성공: {result.success}") print(f" 📝 메시지: {result.message}") if result.success and result.data: print(f" 📄 소스: {result.data['source']}") print(f" 🔢 토큰: {result.data['tokens']}") print(f" 📏 길이: {len(result.data['content'])} 문자") if result.metadata.get("from_cache"): print(f" 💾 캐시에서 로드됨") # 3. 제안 기능 테스트 print("\n💡 get_library_suggestions 테스트...") suggestion_result = await context7.get_library_suggestions("ax", limit=3) print(f"✅ 성공: {suggestion_result.success}") if suggestion_result.success: suggestions = suggestion_result.data["suggestions"] print(f"📝 제안 수: {len(suggestions)}") for suggestion in suggestions: print(f" - {suggestion['name']}: {suggestion['library_id']}") # 4. 통계 정보 테스트 print("\n📊 get_stats 테스트...") stats_result = await context7.get_stats() print(f"✅ 성공: {stats_result.success}") if stats_result.success: stats = stats_result.data print(f"📞 resolve 호출: {stats['runtime_stats']['resolve_calls']}") print(f"📚 docs 호출: {stats['runtime_stats']['docs_calls']}") print(f"💾 캐시 히트: {stats['runtime_stats']['cache_hits']}") print(f"📈 캐시 히트율: {stats['performance']['cache_hit_rate']}%") # 5. MCP 서버 인터페이스 테스트 print("\n🖥️ MCP 서버 인터페이스 테스트...") server = Context7MCPServer("test_server.db") # resolve_library_id 테스트 resolve_result = await server.resolve_library_id("axios") print(f"🔍 MCP resolve 테스트: {resolve_result['success']}") # get_library_docs 테스트 docs_result = await server.get_library_docs("/axios/axios", "usage", 5000) print(f"📚 MCP docs 테스트: {docs_result['success']}") # 리소스 정리 await context7.close() await server.close() print("\n🎯 Context7 MCP 통합 테스트 완료!") # MCP 도구 래퍼 클래스 (tool_wrappers.py와 호환) class Context7Wrapper: """Context7 MCP 도구 래퍼""" def __init__(self, db_path: str = "context7.db"): self.server = Context7MCPServer(db_path) async def resolve_library_id(self, library_name: str) -> Dict[str, Any]: """라이브러리 ID 해결""" return await self.server.resolve_library_id(library_name) async def get_library_docs( self, library_id: str, topic: str = "", tokens: int = 10000 ) -> Dict[str, Any]: """라이브러리 문서 가져오기""" return await self.server.get_library_docs(library_id, topic, tokens) async def close(self): """리소스 정리""" await self.server.close() if __name__ == "__main__": asyncio.run(test_context7_mcp())

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Skynotdie/mky'

If you have feedback or need assistance with the MCP directory API, please join our Discord server