Skip to main content
Glama
slack_mcp_server.py14.8 kB
#!/usr/bin/env python3 """ FastMCP를 이용한 Slack MCP 서버 구현 """ import os import sys import asyncio import logging from typing import Dict, List, Optional, Any from pathlib import Path # 환경 변수 로드 from dotenv import load_dotenv load_dotenv() # FastMCP 및 사용자 정의 모듈 임포트 try: from fastmcp import FastMCP except ImportError: print("❌ FastMCP가 설치되지 않았습니다. pip install fastmcp를 실행하세요.") sys.exit(1) from slack_api import get_slack_client, cleanup_slack_client, SlackAPIError # # 로깅 설정 # logging.basicConfig( # level=logging.INFO, # format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', # handlers=[ # logging.StreamHandler(), # logging.FileHandler('slack_mcp.log') # ] # ) # logger = logging.getLogger(__name__) # FastMCP 인스턴스 생성 mcp = FastMCP("Slack MCP Server") # Slack Bot Token 확인 SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN") if not SLACK_BOT_TOKEN: print("SLACK_BOT_TOKEN 환경 변수가 설정되지 않았습니다.") sys.exit(1) if not SLACK_BOT_TOKEN.startswith("xoxb-"): print("올바른 Slack Bot Token이 아닙니다. xoxb-로 시작해야 합니다.") sys.exit(1) # Slack 클라이언트 초기화 slack_client = get_slack_client(SLACK_BOT_TOKEN) @mcp.tool() async def send_slack_message(channel: str, text: str) -> Dict[str, Any]: """ Slack 채널에 메시지 전송 Args: channel (str): 채널 ID 또는 채널명 (예: #general, C1234567890) text (str): 전송할 메시지 내용 Returns: Dict[str, Any]: 메시지 전송 결과 """ try: # logger.info(f"Sending message to channel: {channel}") result = await slack_client.send_message(channel, text) # 성공 응답 포맷팅 return { "success": True, "message": "메시지가 성공적으로 전송되었습니다.", "data": { "channel": result.get("channel"), "timestamp": result.get("ts"), "message_text": text } } except SlackAPIError as e: # logger.error(f"Slack API error in send_message: {e}") return { "success": False, "error": f"메시지 전송 실패: {e.error}", "details": e.response } except Exception as e: # logger.error(f"Unexpected error in send_message: {e}") return { "success": False, "error": f"예상치 못한 오류: {str(e)}" } @mcp.tool() async def get_slack_channels() -> Dict[str, Any]: """ 접근 가능한 모든 채널 목록 조회 Returns: Dict[str, Any]: 채널 목록 및 정보 """ try: # logger.info("Fetching Slack channels") channels = await slack_client.get_channels() # 채널 정보 포맷팅 formatted_channels = [] for channel in channels: formatted_channels.append({ "id": channel.get("id"), "name": channel.get("name"), "is_private": channel.get("is_private", False), "is_member": channel.get("is_member", False), "is_archived": channel.get("is_archived", False), "num_members": channel.get("num_members", 0), "topic": channel.get("topic", {}).get("value", ""), "purpose": channel.get("purpose", {}).get("value", "") }) return { "success": True, "message": f"총 {len(formatted_channels)}개의 채널을 찾았습니다.", "data": { "channel_count": len(formatted_channels), "channels": formatted_channels } } except SlackAPIError as e: # logger.error(f"Slack API error in get_channels: {e}") return { "success": False, "error": f"채널 목록 조회 실패: {e.error}", "details": e.response } except Exception as e: # logger.error(f"Unexpected error in get_channels: {e}") return { "success": False, "error": f"예상치 못한 오류: {str(e)}" } @mcp.tool() async def get_slack_channel_history(channel_id: str, limit: int = 10) -> Dict[str, Any]: """ 지정된 채널의 최근 메시지 히스토리 조회 Args: channel_id (str): 조회할 채널의 ID limit (int): 조회할 메시지 수 (기본값: 10, 최대: 100) Returns: Dict[str, Any]: 메시지 히스토리 """ try: # limit 값 검증 및 조정 limit = max(1, min(limit, 100)) # logger.info(f"Fetching history for channel: {channel_id}, limit: {limit}") messages = await slack_client.get_channel_history(channel_id, limit) # 메시지 정보 포맷팅 formatted_messages = [] for message in messages: formatted_messages.append({ "timestamp": message.get("ts"), "user": message.get("user"), "text": message.get("text", ""), "type": message.get("type"), "subtype": message.get("subtype"), "edited": message.get("edited"), "reactions": message.get("reactions", []) }) return { "success": True, "message": f"채널 {channel_id}에서 {len(formatted_messages)}개의 메시지를 조회했습니다.", "data": { "channel_id": channel_id, "message_count": len(formatted_messages), "messages": formatted_messages } } except SlackAPIError as e: # logger.error(f"Slack API error in get_channel_history: {e}") return { "success": False, "error": f"채널 히스토리 조회 실패: {e.error}", "details": e.response } except Exception as e: # logger.error(f"Unexpected error in get_channel_history: {e}") return { "success": False, "error": f"예상치 못한 오류: {str(e)}" } @mcp.tool() async def send_slack_direct_message(user_id: str, text: str) -> Dict[str, Any]: """ 특정 사용자에게 1:1 다이렉트 메시지 전송 Args: user_id (str): 메시지를 받을 사용자의 ID text (str): 전송할 메시지 내용 Returns: Dict[str, Any]: 다이렉트 메시지 전송 결과 """ try: # logger.info(f"Sending direct message to user: {user_id}") result = await slack_client.send_direct_message(user_id, text) return { "success": True, "message": "다이렉트 메시지가 성공적으로 전송되었습니다.", "data": { "user_id": user_id, "channel": result.get("channel"), "timestamp": result.get("ts"), "message_text": text } } except SlackAPIError as e: # logger.error(f"Slack API error in send_direct_message: {e}") return { "success": False, "error": f"다이렉트 메시지 전송 실패: {e.error}", "details": e.response } except Exception as e: # logger.error(f"Unexpected error in send_direct_message: {e}") return { "success": False, "error": f"예상치 못한 오류: {str(e)}" } # 선택 기능들 @mcp.tool() async def get_slack_users() -> Dict[str, Any]: """ 워크스페이스의 모든 사용자 정보 조회 Returns: Dict[str, Any]: 사용자 목록 """ try: # logger.info("Fetching Slack users") users = await slack_client.get_users() # 사용자 정보 포맷팅 (활성 사용자만) formatted_users = [] for user in users: if not user.get("deleted", False) and not user.get("is_bot", False): formatted_users.append({ "id": user.get("id"), "name": user.get("name"), "real_name": user.get("real_name"), "display_name": user.get("profile", {}).get("display_name", ""), "email": user.get("profile", {}).get("email", ""), "is_admin": user.get("is_admin", False), "is_owner": user.get("is_owner", False), "timezone": user.get("tz", ""), "status": user.get("profile", {}).get("status_text", "") }) return { "success": True, "message": f"총 {len(formatted_users)}명의 사용자를 찾았습니다.", "data": { "user_count": len(formatted_users), "users": formatted_users } } except SlackAPIError as e: # logger.error(f"Slack API error in get_users: {e}") return { "success": False, "error": f"사용자 목록 조회 실패: {e.error}", "details": e.response } except Exception as e: # logger.error(f"Unexpected error in get_users: {e}") return { "success": False, "error": f"예상치 못한 오류: {str(e)}" } @mcp.tool() async def add_slack_reaction(channel: str, timestamp: str, name: str) -> Dict[str, Any]: """ 특정 메시지에 이모지 반응 추가 Args: channel (str): 채널 ID timestamp (str): 메시지 타임스탬프 name (str): 이모지 이름 (콜론 없이, 예: 'thumbsup') Returns: Dict[str, Any]: 반응 추가 결과 """ try: # logger.info(f"Adding reaction {name} to message {timestamp} in channel {channel}") result = await slack_client.add_reaction(channel, timestamp, name) return { "success": True, "message": f"반응 '{name}'이 성공적으로 추가되었습니다.", "data": { "channel": channel, "timestamp": timestamp, "reaction": name } } except SlackAPIError as e: # logger.error(f"Slack API error in add_reaction: {e}") return { "success": False, "error": f"반응 추가 실패: {e.error}", "details": e.response } except Exception as e: logger.error(f"Unexpected error in add_reaction: {e}") return { "success": False, "error": f"예상치 못한 오류: {str(e)}" } @mcp.tool() async def search_slack_messages(query: str, count: int = 20) -> Dict[str, Any]: """ 키워드를 통한 메시지 검색 Args: query (str): 검색 키워드 count (int): 반환할 결과 수 (기본값: 20, 최대: 100) Returns: Dict[str, Any]: 검색 결과 """ try: # count 값 검증 및 조정 count = max(1, min(count, 100)) # logger.info(f"Searching messages with query: '{query}', count: {count}") results = await slack_client.search_messages(query, count) # 검색 결과 포맷팅 formatted_results = [] for result in results: formatted_results.append({ "text": result.get("text", ""), "user": result.get("user"), "username": result.get("username"), "timestamp": result.get("ts"), "channel_id": result.get("channel", {}).get("id"), "channel_name": result.get("channel", {}).get("name"), "permalink": result.get("permalink") }) return { "success": True, "message": f"검색어 '{query}'에 대해 {len(formatted_results)}개의 결과를 찾았습니다.", "data": { "query": query, "result_count": len(formatted_results), "results": formatted_results } } except SlackAPIError as e: # logger.error(f"Slack API error in search_messages: {e}") return { "success": False, "error": f"메시지 검색 실패: {e.error}", "details": e.response } except Exception as e: # logger.error(f"Unexpected error in search_messages: {e}") return { "success": False, "error": f"예상치 못한 오류: {str(e)}" } @mcp.tool() async def test_slack_connection() -> Dict[str, Any]: """ Slack API 연결 테스트 Returns: Dict[str, Any]: 연결 테스트 결과 """ try: # logger.info("Testing Slack API connection") result = await slack_client.test_connection() return { "success": True, "message": "Slack API 연결이 성공적으로 확인되었습니다.", "data": { "user": result.get("user"), "user_id": result.get("user_id"), "team": result.get("team"), "team_id": result.get("team_id"), "url": result.get("url") } } except SlackAPIError as e: logger.error(f"Slack API error in test_connection: {e}") return { "success": False, "error": f"연결 테스트 실패: {e.error}", "details": e.response } except Exception as e: logger.error(f"Unexpected error in test_connection: {e}") return { "success": False, "error": f"예상치 못한 오류: {str(e)}" } async def cleanup(): """ 서버 종료 시 정리 작업 """ # logger.info("Cleaning up Slack MCP server...") await cleanup_slack_client() def main(): """ 메인 실행 함수 """ try: # logger.info("Starting Slack MCP Server...") # logger.info(f"Slack Bot Token: {SLACK_BOT_TOKEN[:12]}...") # stdio transport를 사용하여 MCP 서버 실행 mcp.run(transport="stdio") except KeyboardInterrupt: logger.info("Server interrupted by user") except Exception as e: logger.error(f"Server error: {e}") sys.exit(1) finally: # 정리 작업 실행 asyncio.run(cleanup()) if __name__ == "__main__": main()

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/MCP-Mirror/teresjsiu_MCP'

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