Skip to main content
Glama
Skynotdie

MCP Localization Project

by Skynotdie
browser_tools_main.py30.4 kB
#!/usr/bin/env python3 """ Browser Tools MCP - 포괄적 웹 감사 및 모니터링 시스템 Chrome Extension 기반의 고급 브라우저 도구 시뮬레이션 주요 기능: - 콘솔 로그 및 에러 모니터링 - 네트워크 활동 추적 및 분석 - 웹 접근성, 성능, SEO 감사 - 실시간 스크린샷 및 요소 선택 - NextJS 전용 감사 및 디버깅 모드 - Lighthouse 통합 분석 """ import asyncio import json import time import uuid import logging import os import base64 import tempfile import sqlite3 from typing import Dict, List, Optional, Any, Union from dataclasses import dataclass, asdict from pathlib import Path from enum import Enum from datetime import datetime, timedelta from urllib.parse import urlparse import hashlib # 로깅 설정 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class LogLevel(Enum): """로그 레벨""" ERROR = "error" WARNING = "warning" LOG = "log" INFO = "info" DEBUG = "debug" class AuditType(Enum): """감사 유형""" ACCESSIBILITY = "accessibility" PERFORMANCE = "performance" SEO = "seo" BEST_PRACTICES = "best_practices" NEXTJS = "nextjs" @dataclass class ConsoleLogEntry: """콘솔 로그 항목""" timestamp: str level: str message: str source: str line_number: Optional[int] = None stack_trace: Optional[str] = None @dataclass class NetworkLogEntry: """네트워크 로그 항목""" timestamp: str method: str url: str status_code: int response_time: float request_size: int response_size: int headers: Dict[str, str] error: Optional[str] = None @dataclass class AuditResult: """감사 결과""" audit_type: str score: float passed_audits: int failed_audits: int warnings: int recommendations: List[str] details: Dict[str, Any] timestamp: str @dataclass class SelectedElement: """선택된 요소 정보""" tag_name: str id: Optional[str] class_list: List[str] text_content: str attributes: Dict[str, str] position: Dict[str, int] styles: Dict[str, str] class BrowserToolsDatabase: """Browser Tools 데이터베이스 관리자""" def __init__(self, db_path: str = "browser_tools.db"): self.db_path = db_path self.init_database() def init_database(self): """데이터베이스 초기화""" with sqlite3.connect(self.db_path) as conn: conn.execute(""" CREATE TABLE IF NOT EXISTS console_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, level TEXT NOT NULL, message TEXT NOT NULL, source TEXT NOT NULL, line_number INTEGER, stack_trace TEXT, session_id TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) """) conn.execute(""" CREATE TABLE IF NOT EXISTS network_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, method TEXT NOT NULL, url TEXT NOT NULL, status_code INTEGER NOT NULL, response_time REAL NOT NULL, request_size INTEGER NOT NULL, response_size INTEGER NOT NULL, headers TEXT, error TEXT, session_id TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) """) conn.execute(""" CREATE TABLE IF NOT EXISTS audit_results ( id INTEGER PRIMARY KEY AUTOINCREMENT, audit_type TEXT NOT NULL, score REAL NOT NULL, passed_audits INTEGER NOT NULL, failed_audits INTEGER NOT NULL, warnings INTEGER NOT NULL, recommendations TEXT, details TEXT, timestamp TEXT NOT NULL, url TEXT, session_id TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) """) conn.execute(""" CREATE TABLE IF NOT EXISTS screenshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, filename TEXT NOT NULL, url TEXT, timestamp TEXT NOT NULL, size_bytes INTEGER, width INTEGER, height INTEGER, session_id TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) """) def store_console_log(self, log_entry: ConsoleLogEntry, session_id: str): """콘솔 로그 저장""" with sqlite3.connect(self.db_path) as conn: conn.execute(""" INSERT INTO console_logs (timestamp, level, message, source, line_number, stack_trace, session_id) VALUES (?, ?, ?, ?, ?, ?, ?) """, ( log_entry.timestamp, log_entry.level, log_entry.message, log_entry.source, log_entry.line_number, log_entry.stack_trace, session_id )) def store_network_log(self, log_entry: NetworkLogEntry, session_id: str): """네트워크 로그 저장""" with sqlite3.connect(self.db_path) as conn: conn.execute(""" INSERT INTO network_logs (timestamp, method, url, status_code, response_time, request_size, response_size, headers, error, session_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( log_entry.timestamp, log_entry.method, log_entry.url, log_entry.status_code, log_entry.response_time, log_entry.request_size, log_entry.response_size, json.dumps(log_entry.headers), log_entry.error, session_id )) def store_audit_result(self, audit_result: AuditResult, url: str, session_id: str): """감사 결과 저장""" with sqlite3.connect(self.db_path) as conn: conn.execute(""" INSERT INTO audit_results (audit_type, score, passed_audits, failed_audits, warnings, recommendations, details, timestamp, url, session_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( audit_result.audit_type, audit_result.score, audit_result.passed_audits, audit_result.failed_audits, audit_result.warnings, json.dumps(audit_result.recommendations), json.dumps(audit_result.details), audit_result.timestamp, url, session_id )) class BrowserToolsSimulator: """Browser Tools 시뮬레이터 - Chrome Extension 동작 시뮬레이션""" def __init__(self): self.session_id = str(uuid.uuid4()) self.console_logs: List[ConsoleLogEntry] = [] self.network_logs: List[NetworkLogEntry] = [] self.is_connected = False self.current_url = "https://example.com" self.selected_element: Optional[SelectedElement] = None # 시뮬레이션 데이터 생성 self._generate_sample_data() def _generate_sample_data(self): """샘플 데이터 생성""" # 콘솔 로그 시뮬레이션 self.console_logs = [ ConsoleLogEntry( timestamp=datetime.now().isoformat(), level="error", message="TypeError: Cannot read property 'length' of undefined", source="app.js", line_number=42, stack_trace="at validateInput (app.js:42:15)\n at handleSubmit (app.js:78:9)" ), ConsoleLogEntry( timestamp=datetime.now().isoformat(), level="warning", message="Deprecated API usage: document.createEvent", source="legacy.js", line_number=156 ), ConsoleLogEntry( timestamp=datetime.now().isoformat(), level="info", message="Application initialized successfully", source="main.js", line_number=12 ) ] # 네트워크 로그 시뮬레이션 self.network_logs = [ NetworkLogEntry( timestamp=datetime.now().isoformat(), method="GET", url="https://api.example.com/users", status_code=200, response_time=245.6, request_size=0, response_size=2048, headers={"content-type": "application/json", "cache-control": "no-cache"} ), NetworkLogEntry( timestamp=datetime.now().isoformat(), method="POST", url="https://analytics.example.com/track", status_code=500, response_time=1250.3, request_size=512, response_size=128, headers={"content-type": "application/json"}, error="Internal Server Error" ) ] # 선택된 요소 시뮬레이션 self.selected_element = SelectedElement( tag_name="button", id="submit-btn", class_list=["btn", "btn-primary"], text_content="Submit Form", attributes={"type": "submit", "disabled": "false"}, position={"x": 150, "y": 300, "width": 120, "height": 40}, styles={"background-color": "blue", "color": "white", "border": "none"} ) def generate_audit_result(self, audit_type: AuditType) -> AuditResult: """감사 결과 생성""" # 감사 유형별 시뮬레이션 결과 audit_configs = { AuditType.ACCESSIBILITY: { "score": 0.85, "passed": 23, "failed": 4, "warnings": 2, "recommendations": [ "Add alt text to images", "Improve color contrast ratio", "Add ARIA labels to form inputs" ] }, AuditType.PERFORMANCE: { "score": 0.72, "passed": 18, "failed": 8, "warnings": 5, "recommendations": [ "Optimize images for web", "Minimize JavaScript bundles", "Enable browser caching" ] }, AuditType.SEO: { "score": 0.90, "passed": 27, "failed": 3, "warnings": 1, "recommendations": [ "Add meta description", "Improve page loading speed", "Fix broken internal links" ] }, AuditType.BEST_PRACTICES: { "score": 0.78, "passed": 20, "failed": 6, "warnings": 3, "recommendations": [ "Use HTTPS everywhere", "Remove console.log statements", "Update deprecated dependencies" ] }, AuditType.NEXTJS: { "score": 0.88, "passed": 15, "failed": 2, "warnings": 1, "recommendations": [ "Optimize Next.js Image component usage", "Implement proper error boundaries" ] } } config = audit_configs.get(audit_type, audit_configs[AuditType.ACCESSIBILITY]) return AuditResult( audit_type=audit_type.value, score=config["score"], passed_audits=config["passed"], failed_audits=config["failed"], warnings=config["warnings"], recommendations=config["recommendations"], details={ "url": self.current_url, "user_agent": "Mozilla/5.0 (Simulator)", "lighthouse_version": "10.4.0", "timing": { "audit_start": datetime.now().isoformat(), "audit_duration": 2.5 } }, timestamp=datetime.now().isoformat() ) class BrowserToolsMCP: """Browser Tools MCP 메인 클래스""" def __init__(self): self.database = BrowserToolsDatabase() self.simulator = BrowserToolsSimulator() logger.info("🔧 Browser Tools MCP 초기화 완료") # 1. 콘솔 로그 관련 기능 async def get_console_logs(self, log_type: Optional[str] = None, limit: int = 100) -> Dict[str, Any]: """브라우저 콘솔 로그 가져오기""" try: logs = self.simulator.console_logs # 로그 타입 필터링 if log_type: logs = [log for log in logs if log.level == log_type] # 제한 적용 logs = logs[:limit] # 데이터베이스에 저장 for log in logs: self.database.store_console_log(log, self.simulator.session_id) logger.info(f"📋 콘솔 로그 조회: {len(logs)}개 항목 (타입: {log_type or 'all'})") return { "success": True, "logs": [asdict(log) for log in logs], "total_count": len(logs), "session_id": self.simulator.session_id } except Exception as e: logger.error(f"❌ 콘솔 로그 조회 실패: {e}") return {"success": False, "error": str(e)} async def get_console_errors(self) -> Dict[str, Any]: """브라우저 콘솔 에러만 가져오기""" return await self.get_console_logs(log_type="error") # 2. 네트워크 로그 관련 기능 async def get_network_logs(self, limit: int = 100) -> Dict[str, Any]: """모든 네트워크 로그 가져오기""" try: logs = self.simulator.network_logs[:limit] # 데이터베이스에 저장 for log in logs: self.database.store_network_log(log, self.simulator.session_id) logger.info(f"🌐 네트워크 로그 조회: {len(logs)}개 요청") return { "success": True, "logs": [asdict(log) for log in logs], "total_count": len(logs), "session_id": self.simulator.session_id } except Exception as e: logger.error(f"❌ 네트워크 로그 조회 실패: {e}") return {"success": False, "error": str(e)} async def get_network_errors(self) -> Dict[str, Any]: """네트워크 에러 로그만 가져오기""" try: error_logs = [log for log in self.simulator.network_logs if log.error] logger.info(f"🚨 네트워크 에러 로그 조회: {len(error_logs)}개 에러") return { "success": True, "error_logs": [asdict(log) for log in error_logs], "error_count": len(error_logs), "session_id": self.simulator.session_id } except Exception as e: logger.error(f"❌ 네트워크 에러 로그 조회 실패: {e}") return {"success": False, "error": str(e)} # 3. 스크린샷 기능 async def take_screenshot(self, filename: Optional[str] = None) -> Dict[str, Any]: """현재 페이지 스크린샷 촬영""" try: if not filename: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"screenshot_{timestamp}.png" # 시뮬레이션된 스크린샷 데이터 생성 screenshot_data = base64.b64encode(b"fake_screenshot_data").decode() # 임시 파일에 저장 (시뮬레이션) temp_dir = tempfile.gettempdir() filepath = os.path.join(temp_dir, filename) with open(filepath, "w") as f: f.write(f"Screenshot captured at {datetime.now().isoformat()}") # 데이터베이스에 메타데이터 저장 with sqlite3.connect(self.database.db_path) as conn: conn.execute(""" INSERT INTO screenshots (filename, url, timestamp, size_bytes, width, height, session_id) VALUES (?, ?, ?, ?, ?, ?, ?) """, (filename, self.simulator.current_url, datetime.now().isoformat(), len(screenshot_data), 1920, 1080, self.simulator.session_id)) logger.info(f"📸 스크린샷 촬영 완료: {filename}") return { "success": True, "filename": filename, "filepath": filepath, "url": self.simulator.current_url, "timestamp": datetime.now().isoformat(), "size": {"width": 1920, "height": 1080} } except Exception as e: logger.error(f"❌ 스크린샷 촬영 실패: {e}") return {"success": False, "error": str(e)} # 4. 요소 선택 기능 async def get_selected_element(self) -> Dict[str, Any]: """현재 선택된 요소 정보 가져오기""" try: if not self.simulator.selected_element: return { "success": False, "error": "No element selected" } logger.info(f"🎯 선택된 요소 조회: {self.simulator.selected_element.tag_name}") return { "success": True, "element": asdict(self.simulator.selected_element), "session_id": self.simulator.session_id } except Exception as e: logger.error(f"❌ 선택된 요소 조회 실패: {e}") return {"success": False, "error": str(e)} # 5. 로그 관리 기능 async def wipe_logs(self) -> Dict[str, Any]: """모든 브라우저 로그 메모리에서 지우기""" try: console_count = len(self.simulator.console_logs) network_count = len(self.simulator.network_logs) # 메모리에서 로그 삭제 self.simulator.console_logs.clear() self.simulator.network_logs.clear() logger.info(f"🧹 로그 삭제 완료: 콘솔 {console_count}개, 네트워크 {network_count}개") return { "success": True, "cleared": { "console_logs": console_count, "network_logs": network_count }, "timestamp": datetime.now().isoformat() } except Exception as e: logger.error(f"❌ 로그 삭제 실패: {e}") return {"success": False, "error": str(e)} # 6-10. 감사 기능들 async def run_accessibility_audit(self) -> Dict[str, Any]: """접근성 감사 실행""" return await self._run_audit(AuditType.ACCESSIBILITY) async def run_performance_audit(self) -> Dict[str, Any]: """성능 감사 실행""" return await self._run_audit(AuditType.PERFORMANCE) async def run_seo_audit(self) -> Dict[str, Any]: """SEO 감사 실행""" return await self._run_audit(AuditType.SEO) async def run_best_practices_audit(self) -> Dict[str, Any]: """모범 사례 감사 실행""" return await self._run_audit(AuditType.BEST_PRACTICES) async def run_nextjs_audit(self) -> Dict[str, Any]: """NextJS 전용 감사 실행""" return await self._run_audit(AuditType.NEXTJS) async def _run_audit(self, audit_type: AuditType) -> Dict[str, Any]: """공통 감사 실행 로직""" try: # 감사 시뮬레이션 (실제로는 시간이 걸림) await asyncio.sleep(0.5) audit_result = self.simulator.generate_audit_result(audit_type) # 데이터베이스에 저장 self.database.store_audit_result( audit_result, self.simulator.current_url, self.simulator.session_id ) logger.info(f"🔍 {audit_type.value} 감사 완료: 점수 {audit_result.score:.2f}") return { "success": True, "audit_result": asdict(audit_result), "session_id": self.simulator.session_id } except Exception as e: logger.error(f"❌ {audit_type.value} 감사 실패: {e}") return {"success": False, "error": str(e)} # 11-12. 특수 모드 async def run_debugger_mode(self) -> Dict[str, Any]: """디버거 모드 실행""" try: # 디버거 모드 시뮬레이션 debug_info = { "breakpoints_set": 3, "variables_inspected": 15, "call_stack_depth": 8, "performance_metrics": { "memory_usage": "45.2 MB", "cpu_usage": "12.5%", "network_requests": len(self.simulator.network_logs) }, "debug_session_id": str(uuid.uuid4()) } logger.info("🐛 디버거 모드 활성화") return { "success": True, "debugger_info": debug_info, "timestamp": datetime.now().isoformat() } except Exception as e: logger.error(f"❌ 디버거 모드 실행 실패: {e}") return {"success": False, "error": str(e)} async def run_audit_mode(self) -> Dict[str, Any]: """종합 감사 모드 실행""" try: # 모든 감사를 동시에 실행 audit_tasks = [ self.run_accessibility_audit(), self.run_performance_audit(), self.run_seo_audit(), self.run_best_practices_audit() ] audit_results = await asyncio.gather(*audit_tasks) # 종합 점수 계산 total_score = sum(result["audit_result"]["score"] for result in audit_results if result["success"]) / len(audit_results) logger.info(f"📊 종합 감사 모드 완료: 평균 점수 {total_score:.2f}") return { "success": True, "comprehensive_audit": { "overall_score": total_score, "individual_audits": audit_results, "recommendations_count": sum( len(result["audit_result"]["recommendations"]) for result in audit_results if result["success"] ) }, "timestamp": datetime.now().isoformat() } except Exception as e: logger.error(f"❌ 종합 감사 모드 실행 실패: {e}") return {"success": False, "error": str(e)} class BrowserToolsMCPServer: """Browser Tools MCP 서버 클래스""" def __init__(self): self.browser_tools = BrowserToolsMCP() logger.info("🔧 Browser Tools MCP 서버 초기화 완료") async def handle_request(self, method: str, params: Dict[str, Any] = None) -> Dict[str, Any]: """MCP 요청 처리""" if params is None: params = {} # 메서드 매핑 method_map = { "getConsoleLogs": self.browser_tools.get_console_logs, "getConsoleErrors": self.browser_tools.get_console_errors, "getNetworkLogs": self.browser_tools.get_network_logs, "getNetworkErrors": self.browser_tools.get_network_errors, "takeScreenshot": self.browser_tools.take_screenshot, "getSelectedElement": self.browser_tools.get_selected_element, "wipeLogs": self.browser_tools.wipe_logs, "runAccessibilityAudit": self.browser_tools.run_accessibility_audit, "runPerformanceAudit": self.browser_tools.run_performance_audit, "runSEOAudit": self.browser_tools.run_seo_audit, "runBestPracticesAudit": self.browser_tools.run_best_practices_audit, "runNextJSAudit": self.browser_tools.run_nextjs_audit, "runDebuggerMode": self.browser_tools.run_debugger_mode, "runAuditMode": self.browser_tools.run_audit_mode } if method not in method_map: return { "success": False, "error": f"Unknown method: {method}" } try: return await method_map[method](**params) except Exception as e: logger.error(f"❌ 메서드 실행 실패 ({method}): {e}") return { "success": False, "error": str(e) } # 테스트 함수 async def test_browser_tools_mcp(): """Browser Tools MCP 종합 테스트""" try: print("🧪 Browser Tools MCP 테스트 시작...\n") # MCP 서버 초기화 print("🔧 Browser Tools MCP 서버 초기화...") server = BrowserToolsMCPServer() print(f"초기화 결과: True\n") # 1. 콘솔 로그 조회 print("📋 콘솔 로그 조회...") console_result = await server.handle_request("getConsoleLogs") print(f"콘솔 로그: {console_result.get('success', False)}") if console_result.get('success'): print(f" 로그 수: {console_result.get('total_count', 0)}") # 2. 콘솔 에러만 조회 print("\n🚨 콘솔 에러 조회...") error_result = await server.handle_request("getConsoleErrors") print(f"콘솔 에러: {error_result.get('success', False)}") # 3. 네트워크 로그 조회 print("\n🌐 네트워크 로그 조회...") network_result = await server.handle_request("getNetworkLogs") print(f"네트워크 로그: {network_result.get('success', False)}") if network_result.get('success'): print(f" 요청 수: {network_result.get('total_count', 0)}") # 4. 스크린샷 촬영 print("\n📸 스크린샷 촬영...") screenshot_result = await server.handle_request("takeScreenshot") print(f"스크린샷: {screenshot_result.get('success', False)}") # 5. 선택된 요소 조회 print("\n🎯 선택된 요소 조회...") element_result = await server.handle_request("getSelectedElement") print(f"선택된 요소: {element_result.get('success', False)}") # 6. 접근성 감사 print("\n♿ 접근성 감사...") a11y_result = await server.handle_request("runAccessibilityAudit") print(f"접근성 감사: {a11y_result.get('success', False)}") if a11y_result.get('success'): score = a11y_result.get('audit_result', {}).get('score', 0) print(f" 점수: {score:.2f}") # 7. 성능 감사 print("\n⚡ 성능 감사...") perf_result = await server.handle_request("runPerformanceAudit") print(f"성능 감사: {perf_result.get('success', False)}") # 8. SEO 감사 print("\n🔍 SEO 감사...") seo_result = await server.handle_request("runSEOAudit") print(f"SEO 감사: {seo_result.get('success', False)}") # 9. 종합 감사 모드 print("\n📊 종합 감사 모드...") audit_mode_result = await server.handle_request("runAuditMode") print(f"종합 감사: {audit_mode_result.get('success', False)}") if audit_mode_result.get('success'): overall_score = audit_mode_result.get('comprehensive_audit', {}).get('overall_score', 0) print(f" 종합 점수: {overall_score:.2f}") # 10. 로그 삭제 print("\n🧹 로그 삭제...") wipe_result = await server.handle_request("wipeLogs") print(f"로그 삭제: {wipe_result.get('success', False)}") print("\n🎯 Browser Tools MCP 테스트 완료!") # 테스트 결과 통계 test_results = [ console_result.get('success', False), error_result.get('success', False), network_result.get('success', False), screenshot_result.get('success', False), element_result.get('success', False), a11y_result.get('success', False), perf_result.get('success', False), seo_result.get('success', False), audit_mode_result.get('success', False), wipe_result.get('success', False) ] success_count = sum(test_results) total_count = len(test_results) success_rate = (success_count / total_count) * 100 print(f"\n📊 테스트 결과: {success_count}/{total_count} 성공 ({success_rate:.1f}%)") except Exception as e: print(f"❌ 테스트 중 오류: {e}") import traceback traceback.print_exc() if __name__ == "__main__": asyncio.run(test_browser_tools_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