Skip to main content
Glama

Slack MCP Server

by yeoamlog
slack_mcp_server.py40.2 kB
""" Slack MCP Server - 6.5.2 ========================================== FastMCP v2를 사용한 Slack MCP 서버 구현입니다. 과제 가이드라인에 따라 8개 필수/선택 기능과 뽀모도로 타이머를 포함합니다. Features (기능): 🔴 Required Features (필수 기능 4개): - ✅ send_slack_message: 메시지 전송 - ✅ get_slack_channels: 채널 목록 조회 - ✅ get_slack_channel_history: 메시지 히스토리 조회 - ✅ send_slack_direct_message: DM 전송 🟡 Optional Features (선택 기능 4개): - ✅ get_slack_users: 사용자 목록 조회 - ✅ search_slack_messages: 메시지 검색 - ✅ upload_file_to_slack: 파일 업로드 - ✅ add_slack_reaction: 메시지 반응 추가 🟢 Bonus Features (보너스 기능): - ✅ start_pomodoro_timer: 뽀모도로 타이머 시작 - ✅ cancel_pomodoro_timer: 타이머 취소 - ✅ list_active_timers: 활성 타이머 목록 - ✅ get_timer_status: 타이머 상태 조회 Author: JunHyuck Kwon Version: 6.5.2 (Lazy Initialization) Updated: 2025-06-02 """ import asyncio import logging import os from typing import Dict, Any, Optional from dotenv import load_dotenv from pathlib import Path from datetime import datetime, timezone # FastMCP imports from fastmcp import FastMCP # 모듈 imports from slack_api_client import SlackAPIClient from pomodoro_timer import PomodoroTimerManager # ==================== 1. 환경 설정 및 초기화 ==================== # 환경 변수 로드 load_dotenv() # 로깅 설정 logging.basicConfig( level=getattr(logging, os.getenv('LOG_LEVEL', 'INFO')), format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) # FastMCP 서버 인스턴스 생성 mcp = FastMCP( name=os.getenv('MCP_SERVER_NAME', 'Slack MCP Server - Complete'), dependencies=["aiohttp>=3.9.0", "python-dotenv>=1.0.0"] ) # 전역 클라이언트 변수들 slack_client: Optional[SlackAPIClient] = None pomodoro_manager: Optional[PomodoroTimerManager] = None # 동시성 보호를 위한 락 _initialize_lock = asyncio.Lock() # ==================== 2. 서버 초기화 ==================== async def initialize_clients() -> tuple[SlackAPIClient, PomodoroTimerManager]: """ Initialize all client modules with thread safety 스레드 안전성을 보장하여 모든 클라이언트 모듈을 초기화합니다. Returns: -------- tuple[SlackAPIClient, PomodoroTimerManager] Initialized Slack client, and pomodoro manager (초기화된 Slack 클라이언트, 뽀모도로 매니저) """ global slack_client, pomodoro_manager # 동시성 보호 async with _initialize_lock: if slack_client is None: try: # Slack API 클라이언트 초기화 slack_client = SlackAPIClient() # 연결 테스트 (봇 토큰) bot_connection = await slack_client.test_connection(test_user_token=False) if bot_connection['success']: bot_info = bot_connection['bot_info'] logger.info(f"✅ Slack Bot Token 연결 성공: {bot_info['user']} ({bot_info['team']})") else: logger.error(f"❌ Slack Bot Token 연결 실패: {bot_connection.get('error')}") raise Exception(f"Slack Bot Token 연결 실패: {bot_connection.get('error')}") # 사용자 토큰 연결 테스트 (선택적) if slack_client.user_token: user_connection = await slack_client.test_connection(test_user_token=True) if user_connection['success']: logger.info("✅ Slack User Token 연결 성공 (검색, 파일업로드 기능 사용 가능)") else: logger.warning("⚠️ Slack User Token 연결 실패 (검색, 파일업로드 기능 제한)") else: logger.info("ℹ️ User Token 없음 (검색, 파일업로드 기능 제한)") # 뽀모도로 매니저 초기화 pomodoro_manager = PomodoroTimerManager(slack_client) logger.info("✅ 뽀모도로 매니저 초기화 완료") logger.info("🎉 모든 모듈 초기화 완료") except Exception as e: logger.error(f"❌ 클라이언트 초기화 실패: {e}") raise return slack_client, pomodoro_manager # ==================== 3. 필수 기능 (Required Features - 4개) ==================== @mcp.tool() async def send_slack_message(channel: str, text: str, thread_ts: str = None) -> Dict[str, Any]: """ Send message to Slack channel UTF-8을 완전히 지원하는 Slack 채널 메시지 전송 기능입니다. Parameters: ----------- channel : str Target channel ID or name (대상 채널 ID 또는 이름) Examples: 'C08UZKK9Q4R', '#bot-testserver', 'general' text : str Message content to send (전송할 메시지 내용) Supports Korean, emoji, mentions (한글, 이모지, 멘션 지원) thread_ts : str, optional Parent message timestamp for thread reply (스레드 답글용 부모 메시지 타임스탬프) Returns: -------- Dict[str, Any] Message sending result (메시지 전송 결과) - success: bool (전송 성공 여부) - message: str (결과 메시지) - channel: str (실제 채널 ID) - timestamp: str (메시지 타임스탬프) Example: -------- LLM can call this tool like: >>> send_slack_message("C08UZKK9Q4R", "Hello! MCP에서 보내는 메시지입니다! 🚀") """ if slack_client is None: await initialize_clients() try: result = await slack_client.send_message(channel, text, thread_ts) if result['success']: logger.info(f"✅ 메시지 전송 성공: {channel} -> {text[:50]}{'...' if len(text) > 50 else ''}") return result except Exception as e: logger.error(f"❌ 메시지 전송 실패: {e}") return { "success": False, "error": f"메시지 전송 중 오류: {str(e)}", "suggestion": "네트워크 연결과 채널 권한을 확인해주세요." } @mcp.tool() async def get_slack_channels(exclude_archived: bool = True, types: str = 'public_channel,private_channel') -> Dict[str, Any]: """ Get list of accessible Slack channels 접근 가능한 Slack 채널 목록을 조회합니다. Parameters: ----------- exclude_archived : bool, default True Whether to exclude archived channels (보관된 채널 제외 여부) types : str, default 'public_channel,private_channel' Channel types to include (포함할 채널 타입들) Returns: -------- Dict[str, Any] Channel list with metadata (메타데이터가 포함된 채널 목록) - success: bool (조회 성공 여부) - channels: List[Dict] (채널 목록) - total_count: int (전체 채널 수) - member_count: int (봇이 멤버인 채널 수) Example: -------- LLM can call this to see available channels: >>> get_slack_channels() """ if slack_client is None: await initialize_clients() try: channels = await slack_client.get_channels(exclude_archived, types) # 통계 계산 member_channels = [ch for ch in channels if ch.get('is_member', False)] logger.info(f"✅ 채널 목록 조회 성공: {len(channels)}개 채널 (멤버: {len(member_channels)}개)") return { "success": True, "channels": channels, "total_count": len(channels), "member_count": len(member_channels), "message": f"{len(channels)}개 채널 조회 완료 (봇이 멤버인 채널: {len(member_channels)}개)" } except Exception as e: logger.error(f"❌ 채널 목록 조회 실패: {e}") return { "success": False, "error": f"채널 목록 조회 중 오류: {str(e)}", "suggestion": "봇 권한과 스코프를 확인해주세요." } @mcp.tool() async def get_slack_channel_history(channel_id: str, limit: int = 10, latest: str = None, oldest: str = None) -> Dict[str, Any]: """ Get message history from Slack channel Slack 채널의 메시지 히스토리를 조회합니다. Parameters: ----------- channel_id : str Channel ID to fetch history from (히스토리를 가져올 채널 ID) Example: 'C08UZKK9Q4R' limit : int, default 10 Number of recent messages to fetch (가져올 최근 메시지 수) latest : str, optional End of time range (시간 범위 끝) oldest : str, optional Start of time range (시간 범위 시작) Returns: -------- Dict[str, Any] Message history with metadata (메타데이터가 포함된 메시지 히스토리) - success: bool (조회 성공 여부) - messages: List[Dict] (메시지 목록) - message_count: int (메시지 수) - channel_id: str (채널 ID) Example: -------- LLM can call this to read recent messages: >>> get_slack_channel_history("C08UZKK9Q4R", limit=5) """ if slack_client is None: await initialize_clients() try: messages = await slack_client.get_channel_history(channel_id, limit, latest, oldest) logger.info(f"✅ 메시지 히스토리 조회 성공: {len(messages)}개 메시지") return { "success": True, "messages": messages, "message_count": len(messages), "channel_id": channel_id or os.getenv('SLACK_TEST_USER_ID'), "message": f"채널 {channel_id}에서 {len(messages)}개 메시지 조회 완료" } except Exception as e: logger.error(f"❌ 메시지 히스토리 조회 실패: {e}") return { "success": False, "error": f"메시지 히스토리 조회 중 오류: {str(e)}", "suggestion": "채널 ID와 봇 권한을 확인해주세요." } @mcp.tool() async def send_slack_direct_message(user_id: str, text: str) -> Dict[str, Any]: """ Send direct message to specific user 특정 사용자에게 다이렉트 메시지를 전송합니다. Parameters: ----------- user_id : str Target user ID (대상 사용자 ID) Example: 'U08VBHQCFME' text : str Message content to send (전송할 메시지 내용) Returns: -------- Dict[str, Any] DM sending result (DM 전송 결과) - success: bool (전송 성공 여부) - message: str (결과 메시지) - target_user_id: str (대상 사용자 ID) - dm_channel_id: str (DM 채널 ID) - timestamp: str (메시지 타임스탬프) Example: -------- LLM can call this to send DMs: >>> send_slack_direct_message("U08VBHQCFME", "안녕하세요! MCP에서 보내는 DM입니다.") """ if slack_client is None: await initialize_clients() try: result = await slack_client.send_direct_message(user_id, text) if result['success']: logger.info(f"✅ DM 전송 성공: {user_id}") return result except Exception as e: logger.error(f"❌ DM 전송 실패: {e}") return { "success": False, "error": f"DM 전송 중 오류: {str(e)}", "suggestion": "사용자 ID와 DM 권한을 확인해주세요." } # ==================== 4. 선택 기능 (Optional Features - 4개) ==================== @mcp.tool() async def get_slack_users( include_bots: bool = False, limit: int = 50, user_types: str = None ) -> Dict[str, Any]: """ Get workspace users with comprehensive filtering and categorization 포괄적인 필터링과 분류를 지원하는 워크스페이스 사용자 조회 기능입니다. Parameters: ----------- include_bots : bool, default False Whether to include bot users (봇 사용자 포함 여부) limit : int, default 50 Maximum number of users to return (반환할 최대 사용자 수) user_types : str, optional Filter by specific user types (특정 사용자 타입으로 필터링) Comma-separated: 'member,admin,owner,bot' Returns: -------- Dict[str, Any] User list with comprehensive metadata - success: bool (조회 성공 여부) - users: List[Dict] (사용자 목록) - total_count: int (총 사용자 수) - user_stats: Dict (사용자 타입별 통계) Example: -------- LLM can call this to see workspace users: >>> get_slack_users() >>> get_slack_users(include_bots=True, limit=100) """ if slack_client is None: await initialize_clients() try: # 사용자 타입 필터링 처리 user_type_list = None if user_types: user_type_list = [t.strip() for t in user_types.split(',')] users = await slack_client.get_users(include_bots, limit, user_type_list) # 통계 계산 user_stats = {} dm_candidates = 0 for user in users: user_type = user.get('user_type', 'member') user_stats[user_type] = user_stats.get(user_type, 0) + 1 if user.get('can_receive_dm', False): dm_candidates += 1 logger.info(f"✅ 사용자 목록 조회 성공: {len(users)}명 (DM 가능: {dm_candidates}명)") return { "success": True, "users": users, "total_count": len(users), "user_stats": user_stats, "dm_candidates_count": dm_candidates, "message": f"{len(users)}명 사용자 조회 완료 (DM 가능: {dm_candidates}명)" } except Exception as e: logger.error(f"❌ 사용자 목록 조회 실패: {e}") return { "success": False, "error": f"사용자 목록 조회 중 오류: {str(e)}", "suggestion": "봇 권한과 users:read 스코프를 확인해주세요." } @mcp.tool() async def search_slack_messages( query: str, sort: str = 'timestamp', sort_dir: str = 'desc', count: int = 20 ) -> Dict[str, Any]: """ Search messages in workspace using User Token User Token을 사용한 워크스페이스 메시지 검색 기능입니다. Parameters: ----------- query : str Search query string (검색 쿼리 문자열) Examples: 'MCP', 'from:@user', 'in:#channel', 'has:link' sort : str, default 'timestamp' Sort method: 'timestamp' or 'score' (정렬 방식) sort_dir : str, default 'desc' Sort direction: 'asc' or 'desc' (정렬 방향) count : int, default 20 Number of results to return (반환할 결과 수) Returns: -------- Dict[str, Any] Search results with comprehensive metadata - success: bool (검색 성공 여부) - messages: List[Dict] (검색된 메시지 목록) - total: int (총 검색 결과 수) - query: str (검색 쿼리) Example: -------- LLM can call this to search messages: >>> search_slack_messages("MCP 서버") >>> search_slack_messages("from:@jhyuck", count=10) """ if slack_client is None: await initialize_clients() try: result = await slack_client.search_messages(query, sort, sort_dir, count) if result['success']: logger.info(f"✅ 메시지 검색 성공: '{query}' - {len(result.get('messages', []))}개 결과") return result except Exception as e: logger.error(f"❌ 메시지 검색 실패: {e}") return { "success": False, "error": f"메시지 검색 중 오류: {str(e)}", "suggestion": "User Token과 search:read 스코프를 확인해주세요.", "query": query } @mcp.tool() async def upload_file_to_slack( file_path: str, channels: str = os.getenv('SLACK_TEST_CHANNEL_ID'), title: str = None, comment: str = None ) -> Dict[str, Any]: """ Upload file to Slack channel with smart processing 스마트 처리가 포함된 Slack 채널 파일 업로드 기능입니다. Parameters: ----------- file_path : str Path to file to upload (업로드할 파일 경로) Supports: PDF, DOC, DOCX, TXT, MD, JPG, PNG, MP3, WAV, MP4, ZIP, etc. channel_id : str, default 'C08UZKK9Q4R' Target channel ID (대상 채널 ID) title : str, optional File title (파일 제목) comment : str, optional File description (파일 설명) Returns: -------- Dict[str, Any] Upload result with processing method - success: bool (업로드 성공 여부) - method: str (사용된 처리 방식) - message: str (결과 메시지) - file_info: dict (파일 정보) Example: -------- LLM can call this to upload files: >>> upload_file_to_slack("/path/to/report.pdf", comment="분석 보고서") >>> upload_file_to_slack("./data.csv", title="데이터 파일") """ if slack_client is None: await initialize_clients() try: result = await slack_client.smart_upload(file_path=file_path, channel_id=channels, title=title, comment=comment) if result['success']: logger.info(f"✅ 파일 업로드 성공: {result.get('file_info', {}).get('filename', file_path)} ({result.get('method')})") return result except Exception as e: logger.error(f"❌ 파일 업로드 실패: {e}") return { "success": False, "error": f"파일 업로드 중 오류: {str(e)}", "suggestion": "파일 경로와 권한을 확인해주세요." } @mcp.tool() async def get_file_preview( file_path: str, max_lines: int = 20 ) -> Dict[str, Any]: """ Preview file content without uploading 업로드하지 않고 파일 내용을 미리보기합니다. Parameters: ----------- file_path : str Path to file (파일 경로) Example: '/path/to/file.txt', './data.csv' max_lines : int, default 20 Maximum lines to preview (미리보기할 최대 라인 수) Returns: -------- Dict[str, Any] File preview result with content info - success: bool (미리보기 성공 여부) - file_info: dict (파일 정보) - preview_content: str (미리보기 내용, 텍스트 파일인 경우) - lines_shown: int (표시된 라인 수) Example: -------- LLM can call this to preview files: >>> get_file_preview("/path/to/report.txt") >>> get_file_preview("./data.csv", max_lines=50) """ if slack_client is None: await initialize_clients() try: # SlackAPIClient의 _get_file_preview 메서드 사용 result = slack_client._get_file_preview(file_path, max_lines) if result['success']: logger.info(f"✅ 파일 미리보기 성공: {result.get('file_info', {}).get('name', file_path)}") return result except Exception as e: logger.error(f"❌ 파일 미리보기 실패: {e}") return { "success": False, "error": f"파일 미리보기 중 오류: {str(e)}", "suggestion": "파일 경로를 확인해주세요." } @mcp.tool() async def add_slack_reaction( channel: str, timestamp: str, emoji: str ) -> Dict[str, Any]: """ Add emoji reaction to specific message 특정 메시지에 이모지 반응을 추가합니다. Parameters: ----------- channel : str Channel ID where the message is located (메시지가 있는 채널 ID) Example: 'C08UZKK9Q4R' timestamp : str Message timestamp (메시지 타임스탬프) Example: '1234567890.123456' emoji : str Emoji name to add (추가할 이모지 이름) Examples: 'thumbsup', 'heart', 'rocket', '👍' Returns: -------- Dict[str, Any] Reaction add result (반응 추가 결과) - success: bool (추가 성공 여부) - message: str (결과 메시지) - channel: str (채널 ID) - timestamp: str (메시지 타임스탬프) - emoji: str (추가된 이모지) Example: -------- LLM can call this to add reactions: >>> add_slack_reaction("C08UZKK9Q4R", "1234567890.123456", "thumbsup") >>> add_slack_reaction("C08UZKK9Q4R", "1234567890.123456", "🚀") """ if slack_client is None: await initialize_clients() try: result = await slack_client.add_reaction(channel, timestamp, emoji) if result['success']: logger.info(f"✅ 반응 추가 성공: {emoji} -> {channel}") return result except Exception as e: logger.error(f"❌ 반응 추가 실패: {e}") return { "success": False, "error": f"반응 추가 중 오류: {str(e)}", "suggestion": "채널 ID, 타임스탬프, 이모지 이름을 확인해주세요." } @mcp.tool() async def verify_or_create_file( file_path: str, content: str = None ) -> Dict[str, Any]: """ Verify or create file based on file path and content 파일이 존재하는지 확인하거나 내용으로 파일을 생성합니다. Parameters: ----------- file_path : str Path to file (파일 경로) Example: '/path/to/file.txt', './data.csv' content : str, optional Content to write if file doesn't exist (파일이 없을 때 작성할 내용) Returns: -------- Dict[str, Any] File verification/creation result - success: bool (성공 여부) - path_exists: bool (경로 존재 여부) - is_file: bool (파일 여부) - file_created: bool (파일 생성 여부) - file_path: str (파일 경로) - file_info: dict (파일 정보) Example: -------- LLM can call this to verify or create files: >>> verify_or_create_file("/path/to/report.txt") >>> verify_or_create_file("./new_file.txt", content="Hello World!") """ file_path_obj = Path(file_path) try: if file_path_obj.exists(): # 파일이 존재함 if not file_path_obj.is_file(): logger.warning(f"지정된 경로가 파일이 아닙니다: {file_path}") return { "success": False, "error": f"지정된 경로가 파일이 아닙니다: {file_path}", "suggestion": "파일 경로를 확인해주세요.", "path_exists": True, "is_file": False } file_stat = file_path_obj.stat() file_size = file_stat.st_size logger.info(f"✅ 파일 존재 확인: {file_path_obj.name} ({file_size} bytes)") return { "success": True, "path_exists": True, "is_file": True, "file_created": False, "file_path": str(file_path_obj), "file_info": { "name": file_path_obj.name, "size": file_size, "size_mb": round(file_size / (1024*1024), 2), "extension": file_path_obj.suffix, "modified": datetime.fromtimestamp(file_stat.st_mtime, tz=timezone.utc).isoformat(), "created": datetime.fromtimestamp(file_stat.st_ctime, tz=timezone.utc).isoformat() }, "message": f"파일이 존재합니다: {file_path_obj.name}" } else: # 파일이 존재하지 않음 if not content: logger.warning(f"파일이 존재하지 않으며 내용이 제공되지 않았습니다: {file_path_obj}") return { "success": False, "error": f"파일이 존재하지 않으며 내용이 제공되지 않았습니다: {file_path_obj}", "suggestion": "파일을 생성하려면 내용을 제공해주세요.", "path_exists": False } try: # 필요시 디렉토리 생성 file_path_obj.parent.mkdir(parents=True, exist_ok=True) # 파일 생성 with open(file_path_obj, 'w', encoding='utf-8') as f: f.write(content) file_stat = file_path_obj.stat() file_size = file_stat.st_size logger.info(f"🎉 파일 생성 완료: {file_path_obj.name} ({file_size} bytes)") return { "success": True, "path_exists": True, "is_file": True, "file_created": True, "file_path": str(file_path_obj), "file_info": { "name": file_path_obj.name, "size": file_size, "size_mb": round(file_size / (1024*1024), 2), "extension": file_path_obj.suffix, "modified": datetime.fromtimestamp(file_stat.st_mtime, tz=timezone.utc).isoformat(), "created": datetime.fromtimestamp(file_stat.st_ctime, tz=timezone.utc).isoformat() }, "message": f"파일이 생성되었습니다: {file_path_obj.name}" } except OSError as e: logger.error(f"파일 생성 중 오류 ({file_path_obj}): {e}") return { "success": False, "error": f"파일 생성 중 오류: {str(e)}", "suggestion": "디렉토리/파일 쓰기 권한을 확인하거나 관리자 권한으로 실행해주세요.", "path_exists": False, "is_file": False } except Exception as e: logger.error(f"파일 처리 중 예기치 않은 오류 ({file_path_obj}): {type(e).__name__}: {e}") return { "success": False, "error": f"파일 처리 중 예기치 않은 오류: {type(e).__name__}: {str(e)}", "suggestion": "파일 경로와 권한을 확인해주세요. 문제가 지속되면 시스템 관리자에게 문의하세요.", "path_exists": False, "is_file": False } # ==================== 5. 뽀모도로 타이머 기능 (Bonus Features - 4개) ==================== @mcp.tool() async def start_pomodoro_timer( timer_type: str, channel_id: str = 'C08UZKK9Q4R', duration_minutes: int = None, custom_name: str = None ) -> Dict[str, Any]: """ Start a pomodoro timer with automatic Slack notifications 자동 Slack 알림 기능이 포함된 뽀모도로 타이머를 시작합니다. Parameters: ----------- timer_type : str Type of timer (타이머 타입) Options: 'study', 'work', 'break', 'meeting', 'custom' channel_id : str, default 'C08UZKK9Q4R' Channel for notifications (알림을 받을 채널 ID) duration_minutes : int, optional Timer duration in minutes (타이머 지속 시간, 분 단위) If not provided, uses default for timer type custom_name : str, optional Custom name for the timer (타이머의 사용자 정의 이름) Returns: -------- Dict[str, Any] Timer start result (타이머 시작 결과) - success: bool (시작 성공 여부) - timer_id: str (고유 타이머 ID) - timer_type: str (타이머 타입) - duration_minutes: int (지속 시간) - start_time: str (시작 시간) - end_time: str (종료 예정 시간) Example: -------- LLM can call this to start timers: >>> start_pomodoro_timer("study", duration_minutes=50, custom_name="파이썬 학습") >>> start_pomodoro_timer("work", custom_name="MCP 서버 개발") >>> start_pomodoro_timer("break", duration_minutes=15) """ if pomodoro_manager is None: await initialize_clients() try: result = await pomodoro_manager.start_timer( timer_type=timer_type, channel_id=channel_id, duration_minutes=duration_minutes, custom_name=custom_name ) if result['success']: logger.info(f"✅ 뽀모도로 타이머 시작: {result.get('timer_id')} ({timer_type}, {result.get('duration_minutes')}분)") return result except Exception as e: logger.error(f"❌ 뽀모도로 타이머 시작 실패: {e}") return { "success": False, "error": f"뽀모도로 타이머 시작 중 오류: {str(e)}", "suggestion": "타이머 타입과 설정을 확인해주세요." } @mcp.tool() async def cancel_pomodoro_timer(timer_id: str) -> Dict[str, Any]: """ Cancel an active pomodoro timer 활성 뽀모도로 타이머를 취소합니다. Parameters: ----------- timer_id : str Timer ID to cancel (취소할 타이머 ID) Example: 'study_20250602_143022_123456' Returns: -------- Dict[str, Any] Cancellation result (취소 결과) - success: bool (취소 성공 여부) - timer_id: str (타이머 ID) - message: str (결과 메시지) - timer_info: dict (타이머 정보) Example: -------- LLM can call this to cancel timers: >>> cancel_pomodoro_timer("study_20250602_143022_123456") """ if pomodoro_manager is None: await initialize_clients() try: result = await pomodoro_manager.cancel_timer(timer_id) if result['success']: logger.info(f"✅ 뽀모도로 타이머 취소: {timer_id}") return result except Exception as e: logger.error(f"❌ 뽀모도로 타이머 취소 실패: {e}") return { "success": False, "error": f"뽀모도로 타이머 취소 중 오류: {str(e)}", "suggestion": "타이머 ID를 확인해주세요." } @mcp.tool() async def list_active_timers() -> Dict[str, Any]: """ List all active pomodoro timers 현재 활성화된 모든 뽀모도로 타이머 목록을 조회합니다. Returns: -------- Dict[str, Any] Active timers list (활성 타이머 목록) - success: bool (조회 성공 여부) - active_timers: List[dict] (활성 타이머 목록) - total_active: int (총 활성 타이머 수) - message: str (결과 메시지) Example: -------- LLM can call this to see active timers: >>> list_active_timers() """ if pomodoro_manager is None: await initialize_clients() try: result = await pomodoro_manager.list_active_timers() if result['success']: logger.info(f"✅ 활성 타이머 목록 조회: {result.get('total_active', 0)}개") return result except Exception as e: logger.error(f"❌ 활성 타이머 목록 조회 실패: {e}") return { "success": False, "error": f"활성 타이머 목록 조회 중 오류: {str(e)}", "suggestion": "타이머 상태를 확인해주세요." } @mcp.tool() async def get_timer_status(timer_id: str) -> Dict[str, Any]: """ Get status of specific pomodoro timer 특정 뽀모도로 타이머의 상태를 조회합니다. Parameters: ----------- timer_id : str Timer ID to check (확인할 타이머 ID) Example: 'study_20250602_143022_123456' Returns: -------- Dict[str, Any] Timer status information (타이머 상태 정보) - success: bool (조회 성공 여부) - timer_id: str (타이머 ID) - status: str (타이머 상태) - timer_info: dict (상세 타이머 정보) Example: -------- LLM can call this to check timer status: >>> get_timer_status("study_20250602_143022_123456") """ if pomodoro_manager is None: await initialize_clients() try: result = await pomodoro_manager.get_timer_status(timer_id) if result['success']: logger.info(f"✅ 타이머 상태 조회: {timer_id} ({result.get('status')})") return result except Exception as e: logger.error(f"❌ 타이머 상태 조회 실패: {e}") return { "success": False, "error": f"타이머 상태 조회 중 오류: {str(e)}", "suggestion": "타이머 ID를 확인해주세요." } # ==================== 6. 기타 유틸리티 도구들 ==================== @mcp.tool() async def test_slack_connection() -> Dict[str, Any]: """ Test Slack API connection and get bot information Slack API 연결을 테스트하고 봇 정보를 확인합니다. Returns: -------- Dict[str, Any] Connection test result (연결 테스트 결과) - success: bool (연결 성공 여부) - bot_info: Dict (봇 정보, 성공 시) - user_token_available: bool (사용자 토큰 사용 가능 여부) Example: -------- LLM can call this to check Slack connectivity: >>> test_slack_connection() """ if slack_client is None: await initialize_clients() try: # 봇 토큰 테스트 bot_result = await slack_client.test_connection(test_user_token=False) # 사용자 토큰 테스트 (선택적) user_token_available = False if slack_client.user_token: user_result = await slack_client.test_connection(test_user_token=True) user_token_available = user_result.get('success', False) if bot_result['success']: logger.info("✅ Slack 연결 테스트 성공") return { "success": True, "bot_info": bot_result.get('bot_info', {}), "user_token_available": user_token_available, "message": "Slack 연결이 정상적으로 작동합니다.", "capabilities": { "send_messages": True, "read_channels": True, "read_history": True, "send_dm": True, "search_messages": user_token_available, "upload_files": user_token_available } } else: logger.error(f"❌ Slack 연결 테스트 실패: {bot_result.get('error')}") return bot_result except Exception as e: logger.error(f"❌ 연결 테스트 중 오류: {e}") return { "success": False, "error": f"연결 테스트 중 오류: {str(e)}", "suggestion": "봇 토큰과 네트워크 연결을 확인해주세요." } @mcp.tool() async def get_workspace_info() -> Dict[str, Any]: """ Get comprehensive workspace information 워크스페이스의 종합적인 정보를 조회합니다. Returns: -------- Dict[str, Any] Comprehensive workspace information (종합 워크스페이스 정보) - success: bool (조회 성공 여부) - workspace: dict (워크스페이스 정보) - stats: dict (통계 정보) - capabilities: dict (사용 가능한 기능) Example: -------- LLM can call this to get workspace overview: >>> get_workspace_info() """ if slack_client is None: await initialize_clients() try: result = await slack_client.get_workspace_info() if result['success']: logger.info("✅ 워크스페이스 정보 조회 성공") return result except Exception as e: logger.error(f"❌ 워크스페이스 정보 조회 실패: {e}") return { "success": False, "error": f"워크스페이스 정보 조회 중 오류: {str(e)}", "suggestion": "봇 권한을 확인해주세요." } # ==================== 7. 메인 서버 실행 ==================== async def main(): """ MCP 서버 메인 실행 함수 Main function to run the complete MCP server with all features """ try: # 모든 모듈 초기화 logger.info("🚀 Complete Slack MCP 서버 시작...") await initialize_clients() # MCP 서버 실행 정보 logger.info("📡 Complete MCP 서버가 준비되었습니다.") logger.info("🔴 필수 기능 (Required Features):") logger.info(" 📨 send_slack_message: 메시지 전송") logger.info(" 📋 get_slack_channels: 채널 목록 조회") logger.info(" 📜 get_slack_channel_history: 메시지 히스토리 조회") logger.info(" 💬 send_slack_direct_message: DM 전송") logger.info("🟡 선택 기능 (Optional Features):") logger.info(" 👥 get_slack_users: 사용자 목록 조회") logger.info(" 🔍 search_slack_messages: 메시지 검색") logger.info(" 📤 upload_file_to_slack: 파일 업로드") logger.info(" 📁 get_file_preview: 파일 미리보기") logger.info(" 😀 add_slack_reaction: 메시지 반응 추가") logger.info(" 📋 verify_or_create_file: 파일 확인/생성") logger.info("🟢 보너스 기능 (Bonus Features):") logger.info(" ⏰ start_pomodoro_timer: 뽀모도로 타이머 시작") logger.info(" ⏹️ cancel_pomodoro_timer: 타이머 취소") logger.info(" 📋 list_active_timers: 활성 타이머 목록") logger.info(" 📊 get_timer_status: 타이머 상태 조회") logger.info("🛠️ 유틸리티 기능:") logger.info(" 🔌 test_slack_connection: 연결 테스트") logger.info(" 🏢 get_workspace_info: 워크스페이스 정보") logger.info("📊 총 등록된 도구: 16개") # stdio로 MCP 서버 실행 await mcp.run_stdio_async() except KeyboardInterrupt: logger.info("🛑 서버가 사용자에 의해 중단되었습니다.") except Exception as e: logger.error(f"❌ 서버 실행 중 오류: {e}") raise finally: # 리소스 정리 if slack_client: await slack_client.close() logger.info("🧹 서버 리소스 정리 완료") if __name__ == "__main__": asyncio.run(main())

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/yeoamlog/slack-mcp-server'

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