Skip to main content
Glama
Skynotdie

MCP Localization Project

by Skynotdie
context7_resolver.py16.7 kB
#!/usr/bin/env python3 """ Context7 MCP 로컬화 - 라이브러리 ID 해결자 라이브러리 이름을 Context7 호환 ID로 변환하는 핵심 기능 주요 기능: - resolve_library_id: 라이브러리 이름 → Context7 호환 ID 변환 - 지능형 매칭 시스템 - 다중 검색 전략 - 결과 우선순위 지정 """ import asyncio import re import json import aiohttp from typing import Dict, List, Optional, Any, Tuple from dataclasses import dataclass from pathlib import Path import logging from context7_base import Context7Database, LibraryInfo, SearchResult, Context7Utils logger = logging.getLogger(__name__) @dataclass class ResolveResult: """라이브러리 ID 해결 결과""" selected_library_id: str explanation: str matches: List[LibraryInfo] confidence: float class LibraryResolver: """라이브러리 ID 해결자""" def __init__(self, db: Context7Database): """ 라이브러리 해결자 초기화 Args: db: Context7 데이터베이스 인스턴스 """ self.db = db self.known_libraries = self._load_known_libraries() def _load_known_libraries(self) -> Dict[str, LibraryInfo]: """알려진 라이브러리 목록 로드""" # 인기 있는 라이브러리들의 기본 데이터베이스 known = { # JavaScript/TypeScript 라이브러리 "axios": LibraryInfo("/axios/axios", "axios", "Promise based HTTP client for the browser and node.js", "1.6.2", 9.5, 200), "react": LibraryInfo("/facebook/react", "react", "A declarative, efficient, and flexible JavaScript library for building user interfaces", "18.2.0", 10.0, 500), "vue": LibraryInfo("/vuejs/vue", "vue", "Vue.js is a progressive framework for building user interfaces", "3.4.0", 9.8, 300), "angular": LibraryInfo("/angular/angular", "angular", "The modern web developer's platform", "17.0.0", 9.7, 400), "express": LibraryInfo("/expressjs/express", "express", "Fast, unopinionated, minimalist web framework for Node.js", "4.18.2", 9.6, 250), "lodash": LibraryInfo("/lodash/lodash", "lodash", "A modern JavaScript utility library delivering modularity, performance & extras", "4.17.21", 9.4, 180), "jquery": LibraryInfo("/jquery/jquery", "jquery", "Fast, small, and feature-rich JavaScript library", "3.7.1", 9.2, 150), "moment": LibraryInfo("/moment/moment", "moment", "Parse, validate, manipulate, and display dates in javascript", "2.29.4", 8.8, 120), "webpack": LibraryInfo("/webpack/webpack", "webpack", "A bundler for javascript and friends", "5.89.0", 9.1, 220), "typescript": LibraryInfo("/microsoft/typescript", "typescript", "TypeScript is a superset of JavaScript that compiles to clean JavaScript output", "5.3.3", 9.9, 350), # Python 라이브러리 "requests": LibraryInfo("/psf/requests", "requests", "Python HTTP for Humans", "2.31.0", 9.8, 180), "numpy": LibraryInfo("/numpy/numpy", "numpy", "Fundamental package for scientific computing with Python", "1.24.3", 9.9, 300), "pandas": LibraryInfo("/pandas-dev/pandas", "pandas", "Powerful data structures for data analysis, time series, and statistics", "2.0.3", 9.7, 400), "flask": LibraryInfo("/pallets/flask", "flask", "The Python micro framework for building web applications", "2.3.3", 9.5, 250), "django": LibraryInfo("/django/django", "django", "The web framework for perfectionists with deadlines", "4.2.7", 9.6, 350), "scikit-learn": LibraryInfo("/scikit-learn/scikit-learn", "scikit-learn", "Machine learning library for Python", "1.3.0", 9.4, 280), "tensorflow": LibraryInfo("/tensorflow/tensorflow", "tensorflow", "An open source machine learning framework", "2.13.0", 9.8, 450), "pytorch": LibraryInfo("/pytorch/pytorch", "pytorch", "Tensors and dynamic neural networks in Python", "2.1.0", 9.7, 400), # Go 라이브러리 "gin": LibraryInfo("/gin-gonic/gin", "gin", "HTTP web framework written in Go", "1.9.1", 9.3, 160), "echo": LibraryInfo("/labstack/echo", "echo", "High performance, minimalist Go web framework", "4.11.2", 9.2, 140), "gorm": LibraryInfo("/go-gorm/gorm", "gorm", "The fantastic ORM library for Golang", "1.25.5", 9.1, 200), # Rust 라이브러리 "serde": LibraryInfo("/serde-rs/serde", "serde", "Serialization framework for Rust", "1.0.192", 9.5, 180), "tokio": LibraryInfo("/tokio-rs/tokio", "tokio", "A runtime for writing reliable asynchronous applications with Rust", "1.35.0", 9.4, 220), "actix-web": LibraryInfo("/actix/actix-web", "actix-web", "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust", "4.4.0", 9.2, 150), } return known async def resolve_library_id(self, library_name: str) -> ResolveResult: """ 라이브러리 이름을 Context7 호환 ID로 해결 Args: library_name: 검색할 라이브러리 이름 Returns: ResolveResult: 해결 결과 """ logger.info(f"🔍 라이브러리 ID 해결 시작: '{library_name}'") # 1. 정확한 이름 매칭 (알려진 라이브러리에서) exact_match = self._find_exact_match(library_name) if exact_match: return ResolveResult( selected_library_id=exact_match.library_id, explanation=f"Exact match found for '{library_name}'. This is a well-known library with high trust score ({exact_match.trust_score}/10).", matches=[exact_match], confidence=0.95 ) # 2. 데이터베이스에서 검색 db_matches = await self.db.search_libraries(library_name, limit=10) # 3. 알려진 라이브러리에서 유사한 이름 검색 similar_matches = self._find_similar_matches(library_name) # 4. 모든 매칭 결과 통합 및 점수 계산 all_matches = self._combine_and_score_matches(library_name, db_matches, similar_matches) if not all_matches: return ResolveResult( selected_library_id="", explanation=f"No matches found for '{library_name}'. Please verify the library name or provide more specific information.", matches=[], confidence=0.0 ) # 5. 최적의 매치 선택 best_match = all_matches[0] # 이미 점수 순으로 정렬됨 # 6. 선택 이유 설명 생성 explanation = self._generate_explanation(library_name, best_match, all_matches) # 7. 신뢰도 계산 confidence = self._calculate_confidence(best_match, all_matches) # 8. 선택된 라이브러리를 데이터베이스에 추가 (캐시) await self.db.add_library(best_match) return ResolveResult( selected_library_id=best_match.library_id, explanation=explanation, matches=all_matches[:5], # 상위 5개만 반환 confidence=confidence ) def _find_exact_match(self, library_name: str) -> Optional[LibraryInfo]: """정확한 이름 매칭 검색""" name_lower = library_name.lower().strip() for known_name, library_info in self.known_libraries.items(): if known_name.lower() == name_lower: logger.info(f"✅ 정확한 매칭 발견: {library_info.library_id}") return library_info return None def _find_similar_matches(self, library_name: str) -> List[LibraryInfo]: """유사한 이름 매칭 검색""" similar_matches = [] for known_name, library_info in self.known_libraries.items(): similarity = Context7Utils.calculate_name_similarity(library_name, known_name) if similarity > 0.5: # 50% 이상 유사도 similar_matches.append(library_info) return similar_matches def _combine_and_score_matches( self, query: str, db_matches: List[LibraryInfo], similar_matches: List[LibraryInfo] ) -> List[LibraryInfo]: """매칭 결과들을 통합하고 점수를 계산하여 정렬""" all_matches = {} # library_id를 키로 사용하여 중복 제거 # 데이터베이스 매치 추가 for match in db_matches: all_matches[match.library_id] = match # 유사한 매치 추가 for match in similar_matches: if match.library_id not in all_matches: all_matches[match.library_id] = match # 점수 계산 및 정렬 scored_matches = [] for library_info in all_matches.values(): name_similarity = Context7Utils.calculate_name_similarity(query, library_info.name) description_match = Context7Utils.calculate_description_match(query, library_info.description) relevance_score = Context7Utils.calculate_relevance_score( name_similarity, description_match, library_info.trust_score, library_info.code_snippets ) # 관련성 점수를 라이브러리 정보에 임시로 저장 (정렬을 위해) library_info_copy = LibraryInfo( library_info.library_id, library_info.name, library_info.description, library_info.version, library_info.trust_score, library_info.code_snippets, library_info.last_updated ) scored_matches.append((relevance_score, library_info_copy)) # 점수 순으로 정렬 (높은 점수부터) scored_matches.sort(key=lambda x: x[0], reverse=True) return [match[1] for match in scored_matches] def _generate_explanation( self, query: str, selected: LibraryInfo, all_matches: List[LibraryInfo] ) -> str: """선택 이유 설명 생성""" name_similarity = Context7Utils.calculate_name_similarity(query, selected.name) explanation_parts = [] # 선택된 라이브러리 정보 explanation_parts.append(f"Selected '{selected.library_id}' for query '{query}'.") # 매칭 이유 if name_similarity >= 0.9: explanation_parts.append("Reason: Exact or near-exact name match.") elif name_similarity >= 0.7: explanation_parts.append("Reason: High name similarity.") else: explanation_parts.append("Reason: Best available match based on name similarity and description relevance.") # 신뢰도 정보 if selected.trust_score >= 9.0: explanation_parts.append(f"This library has a high trust score ({selected.trust_score}/10).") elif selected.trust_score >= 7.0: explanation_parts.append(f"This library has a good trust score ({selected.trust_score}/10).") # 코드 스니펫 정보 if selected.code_snippets > 100: explanation_parts.append(f"Rich documentation with {selected.code_snippets} code snippets available.") # 대안 정보 if len(all_matches) > 1: explanation_parts.append(f"Alternative matches were considered but this was the most relevant.") return " ".join(explanation_parts) def _calculate_confidence(self, selected: LibraryInfo, all_matches: List[LibraryInfo]) -> float: """신뢰도 계산""" if not all_matches: return 0.0 if len(all_matches) == 1: # 유일한 매치인 경우 return 0.9 if selected.trust_score >= 8.0 else 0.7 # 여러 매치가 있는 경우, 상위 2개의 점수 차이를 고려 first_score = Context7Utils.calculate_relevance_score( Context7Utils.calculate_name_similarity("", selected.name), 0, selected.trust_score, selected.code_snippets ) if len(all_matches) >= 2: second = all_matches[1] second_score = Context7Utils.calculate_relevance_score( Context7Utils.calculate_name_similarity("", second.name), 0, second.trust_score, second.code_snippets ) score_difference = first_score - second_score # 점수 차이가 클수록 높은 신뢰도 if score_difference >= 0.3: return 0.9 elif score_difference >= 0.2: return 0.8 elif score_difference >= 0.1: return 0.7 else: return 0.6 return 0.8 async def get_library_suggestions(self, partial_name: str, limit: int = 5) -> List[LibraryInfo]: """부분 이름으로 라이브러리 제안""" suggestions = [] # 알려진 라이브러리에서 검색 for known_name, library_info in self.known_libraries.items(): if partial_name.lower() in known_name.lower(): suggestions.append(library_info) # 데이터베이스에서 추가 검색 db_suggestions = await self.db.search_libraries(partial_name, limit=limit*2) # 중복 제거하고 통합 seen_ids = {lib.library_id for lib in suggestions} for db_lib in db_suggestions: if db_lib.library_id not in seen_ids and len(suggestions) < limit: suggestions.append(db_lib) seen_ids.add(db_lib.library_id) # 신뢰도 순으로 정렬 suggestions.sort(key=lambda x: x.trust_score, reverse=True) return suggestions[:limit] # 테스트 함수 async def test_library_resolver(): """라이브러리 해결자 테스트""" print("🧪 라이브러리 해결자 테스트 시작...") # 데이터베이스 및 해결자 초기화 db = Context7Database("test_resolver.db") resolver = LibraryResolver(db) # 테스트 케이스들 test_cases = [ "axios", # 정확한 매칭 "react", # 정확한 매칭 "express", # 정확한 매칭 "axio", # 유사한 이름 "reactjs", # 유사한 이름 "nonexistent", # 존재하지 않는 라이브러리 ] for test_case in test_cases: print(f"\n🔍 테스트: '{test_case}'") result = await resolver.resolve_library_id(test_case) print(f"✅ 선택된 ID: {result.selected_library_id}") print(f"📝 설명: {result.explanation}") print(f"📊 신뢰도: {result.confidence:.2f}") print(f"🔢 매치 수: {len(result.matches)}") if result.matches: print("📋 매치 목록:") for i, match in enumerate(result.matches[:3]): print(f" {i+1}. {match.library_id} - {match.name} (trust: {match.trust_score})") # 제안 기능 테스트 print(f"\n🔍 제안 테스트: 'ax'") suggestions = await resolver.get_library_suggestions("ax") print(f"💡 제안 수: {len(suggestions)}") for suggestion in suggestions: print(f" - {suggestion.name}: {suggestion.library_id}") print("\n🎯 라이브러리 해결자 테스트 완료!") if __name__ == "__main__": asyncio.run(test_library_resolver())

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