setup_project.shβ’40.8 kB
#!/bin/bash
# -*- coding: utf-8 -*-
#
# Meeting Room MCP νλ‘μ νΈ κ΅¬μ‘° μλ μμ± μ€ν¬λ¦½νΈ
#
# μ¬μ©λ²:
# chmod +x setup_project.sh
# ./setup_project.sh
#
set -e # μλ¬ λ°μ μ μ€λ¨
echo "=========================================="
echo "Meeting Room MCP νλ‘μ νΈ μμ± μμ"
echo "=========================================="
# νλ‘μ νΈ λ£¨νΈ νμΈ
PROJECT_ROOT="$(pwd)"
echo "νλ‘μ νΈ λ£¨νΈ: $PROJECT_ROOT"
# λλ ν°λ¦¬ μμ±
echo ""
echo "π λλ ν°λ¦¬ μμ± μ€..."
mkdir -p auth
mkdir -p api
mkdir -p utils
echo " β auth/"
echo " β api/"
echo " β utils/"
# __init__.py νμΌ μμ±
echo ""
echo "π __init__.py νμΌ μμ± μ€..."
touch auth/__init__.py
touch api/__init__.py
touch utils/__init__.py
echo " β auth/__init__.py"
echo " β api/__init__.py"
echo " β utils/__init__.py"
# ============================================================================
# config.py μμ±
# ============================================================================
echo ""
echo "π config.py μμ± μ€..."
cat > config.py << 'EOF'
# -*- coding: utf-8 -*-
"""
μ€μ λ° μμ κ΄λ¦¬
νκ²½λ³μλ₯Ό λ‘λνκ³ μ μ μ€μ κ°μ κ΄λ¦¬ν©λλ€.
"""
import os
from dotenv import load_dotenv
load_dotenv()
# ============================================================================
# μ¬μ©μ μ 보
# ============================================================================
USER_ID = os.getenv("USER_ID")
USER_PASSWORD = os.getenv("USER_PASSWORD")
USER_NAME = os.getenv("USER_NAME")
GROUP_ID = os.getenv("GROUP_ID")
GROUP_NAME = os.getenv("GROUP_NAME")
# ============================================================================
# URL μ€μ
# ============================================================================
SSO_LOGIN_URL = os.getenv("SSO_LOGIN_URL")
BASE_URL = os.getenv("MEETING_ROOM_BASE_URL")
PORTAL_URL = "https://works.ktds.co.kr/group/main"
# ============================================================================
# κ²½λ‘ μ€μ
# ============================================================================
CHROMEDRIVER_PATH = r"D:\workspace\meeting-room-mcp\chromedriver\chromedriver.exe"
LOG_FILE = r'D:\workspace\meeting-room-mcp\mcp_debug.log'
# ============================================================================
# μ¬λ¬΄μ€ ID λ§€ν
# ============================================================================
OFFICE_MAP = {
"λ°©λ°°": 1,
"λΆλΉ": 2,
"κ°μ": 3,
"ν¨μ²": 4
}
# ============================================================================
# κΈ°λ³Έ μ€μ
# ============================================================================
DEFAULT_OFFICE = "λΆλΉ"
DEFAULT_OFFICE_ID = 2
DEFAULT_PAGE_SIZE = 9999
EOF
echo " β config.py"
# ============================================================================
# auth/login.py μμ±
# ============================================================================
echo ""
echo "π auth/login.py μμ± μ€..."
cat > auth/login.py << 'EOF'
# -*- coding: utf-8 -*-
"""
SSO λ‘κ·ΈμΈ κ΄λ¦¬ λͺ¨λ
ν¬νΈ λ‘κ·ΈμΈ ν SSO ν ν°μ ν΅ν΄ νμμ€ μμ€ν
μ μ κ·Όνκ³
μΈμ
μΏ ν€λ₯Ό κ΄λ¦¬ν©λλ€.
"""
import time
import re
import logging
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
logger = logging.getLogger(__name__)
class LoginManager:
"""SSO λ‘κ·ΈμΈ κ΄λ¦¬ ν΄λμ€"""
def __init__(self, config):
"""
Args:
config: μ€μ λͺ¨λ (config.py)
"""
self.config = config
self.session_cookies = {}
def login(self):
"""
μ 체 λ‘κ·ΈμΈ νλ‘μΈμ€ μ€ν
Returns:
dict: μΈμ
μΏ ν€ λμ
λ리
Raises:
Exception: λ‘κ·ΈμΈ μ€ν¨ μ
"""
logger.info("μλ λ‘κ·ΈμΈ μμ...")
driver = self._init_driver()
try:
# 1. ν¬νΈ λ‘κ·ΈμΈ
self._login_to_portal(driver)
# 2. SSO ν ν°μΌλ‘ νμμ€ μμ€ν
μ κ·Ό
self._access_meeting_room_system(driver)
# 3. μΏ ν€ μμ§
self._collect_cookies(driver)
logger.info("λ‘κ·ΈμΈ μλ£!")
return self.session_cookies
except Exception as e:
logger.error(f"λ‘κ·ΈμΈ μ€ν¨: {str(e)}", exc_info=True)
raise
finally:
driver.quit()
def _init_driver(self):
"""Chrome λλΌμ΄λ² μ΄κΈ°ν"""
options = webdriver.ChromeOptions()
options.add_argument('--start-maximized')
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
service = Service(self.config.CHROMEDRIVER_PATH)
return webdriver.Chrome(service=service, options=options)
def _login_to_portal(self, driver):
"""ν¬νΈ λ‘κ·ΈμΈ"""
logger.info("νμ¬ ν¬νΈ μ μ μ€...")
driver.get(self.config.PORTAL_URL)
time.sleep(3)
logger.info("λ‘κ·ΈμΈ νΌ μ
λ ₯ μ€...")
wait = WebDriverWait(driver, 10)
user_id_field = wait.until(EC.presence_of_element_located((By.ID, "userId")))
user_pwd_field = driver.find_element(By.ID, "userPwd")
login_button = driver.find_element(By.ID, "login_area")
user_id_field.send_keys(self.config.USER_ID)
user_pwd_field.send_keys(self.config.USER_PASSWORD)
login_button.click()
logger.info("λ‘κ·ΈμΈ μ²λ¦¬ μ€...")
time.sleep(5)
def _access_meeting_room_system(self, driver):
"""SSOλ₯Ό ν΅ν νμμ€ μμ€ν
μ κ·Ό"""
# λ°©λ² 1: SSO ν ν° μΆμΆ
if self._try_sso_token_method(driver):
return
# λ°©λ² 2: λ²νΌ ν΄λ¦
if self._try_button_click_method(driver):
return
raise Exception("νμμ€ μμ€ν
μ κ·Ό μ€ν¨")
def _try_sso_token_method(self, driver):
"""SSO ν ν° μΆμΆ λ°©μ"""
logger.info("SSO ν ν° μΆμΆ μλ...")
page_source = driver.page_source
pattern = r"https://srs\.ktds\.co\.kr:8443/front/ssoLoginRoom\.do\?.*?sso_token=([^'\"]+)"
matches = re.findall(pattern, page_source)
if matches:
sso_token = matches[0]
logger.info(f"SSO ν ν° λ°κ²¬: {sso_token[:50]}...")
sso_url = f"https://srs.ktds.co.kr:8443/front/ssoLoginRoom.do?sso_token={sso_token}"
driver.get(sso_url)
time.sleep(5)
logger.info(f"νμμ€ μμ€ν
μ μ μλ£: {driver.current_url}")
return True
logger.warning("SSO ν ν°μ μ°Ύμ§ λͺ»ν¨")
return False
def _try_button_click_method(self, driver):
"""νμμ€ λ²νΌ ν΄λ¦ λ°©μ"""
logger.info("νμμ€ λ²νΌ ν΄λ¦ λ°©μ μλ...")
try:
meeting_button = self._find_meeting_button(driver)
if meeting_button:
logger.info("νμμ€ λ²νΌ ν΄λ¦...")
meeting_button.click()
time.sleep(3)
# μ μ°½ μ²λ¦¬
if len(driver.window_handles) > 1:
driver.switch_to.window(driver.window_handles[-1])
time.sleep(2)
logger.info(f"νμμ€ μμ€ν
μ μ μλ£: {driver.current_url}")
return True
except Exception as e:
logger.error(f"λ²νΌ ν΄λ¦ μ€ν¨: {str(e)}")
return False
def _find_meeting_button(self, driver):
"""νμμ€ λ²νΌ μ°ΎκΈ° (μ¬λ¬ λ°©λ² μλ)"""
# λ°©λ² 1: IDλ‘ μ°ΎκΈ°
try:
return driver.find_element(By.ID, "_qmytodo_INSTANCE__for_personal_a_100474891")
except:
pass
# λ°©λ² 2: ν
μ€νΈλ‘ μ°ΎκΈ°
try:
links = driver.find_elements(By.TAG_NAME, "a")
for link in links:
if "νμμ€ μμ½" in link.text or "νμμ€" in link.text:
return link
except:
pass
# λ°©λ² 3: onclick μμ±μΌλ‘ μ°ΎκΈ°
try:
elements = driver.find_elements(By.XPATH, "//*[@onclick]")
for elem in elements:
onclick = elem.get_attribute("onclick")
if "ssoLoginRoom" in onclick:
return elem
except:
pass
return None
def _collect_cookies(self, driver):
"""μΏ ν€ μμ§"""
cookies = driver.get_cookies()
logger.info(f"μ΄ {len(cookies)}κ°μ μΏ ν€ λ°κ²¬")
for cookie in cookies:
self.session_cookies[cookie['name']] = cookie['value']
logger.debug(f"μΏ ν€: {cookie['name']}")
if cookie['name'] == 'SESSION':
logger.info(f"SESSION μΏ ν€ νλ: {cookie['value'][:20]}...")
if 'SESSION' not in self.session_cookies:
raise Exception("SESSION μΏ ν€λ₯Ό μ°Ύμ μ μμ΅λλ€.")
def get_headers(self):
"""
API μμ²μ© ν€λ μμ±
Returns:
dict: HTTP ν€λ
Raises:
Exception: λ‘κ·ΈμΈλμ§ μμ κ²½μ°
"""
if not self.session_cookies:
raise Exception("λ‘κ·ΈμΈμ΄ νμν©λλ€. login()μ λ¨Όμ νΈμΆνμΈμ.")
cookie_str = '; '.join([f"{k}={v}" for k, v in self.session_cookies.items()])
return {
'Cookie': cookie_str,
'Content-Type': 'application/json;charset=UTF-8',
'Accept': 'application/json, text/plain, */*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'Referer': f'{self.config.BASE_URL}/front/allFloorsMeeting.do',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
EOF
echo " β auth/login.py"
# ============================================================================
# utils/date_parser.py μμ±
# ============================================================================
echo ""
echo "π utils/date_parser.py μμ± μ€..."
cat > utils/date_parser.py << 'EOF'
# -*- coding: utf-8 -*-
"""
λ μ§/μκ° νμ± μ νΈλ¦¬ν°
λ€μν νμμ λ μ§/μκ° λ¬Έμμ΄μ νμ€ νμμΌλ‘ λ³νν©λλ€.
"""
from datetime import datetime
class DateParser:
"""λ μ§/μκ° λ¬Έμμ΄ νμ± ν΄λμ€"""
@staticmethod
def parse_date(date_str: str) -> str:
"""
λ€μν λ μ§ νμμ YYYY-MM-DDλ‘ λ³ν
Args:
date_str: λ μ§ λ¬Έμμ΄ ("10.24", "2025-10-24" λ±)
Returns:
str: YYYY-MM-DD νμμ λ μ§
Examples:
>>> DateParser.parse_date("10.24")
"2025-10-24"
>>> DateParser.parse_date("2025-10-24")
"2025-10-24"
"""
try:
# "10.24" νμ
if '.' in date_str:
parts = date_str.split('.')
month = int(parts[0])
day = int(parts[1])
year = datetime.now().year
return f"{year}-{month:02d}-{day:02d}"
# "2025-10-24" νμ
if '-' in date_str and len(date_str) == 10:
return date_str
# κΈ°λ³Έκ°: μ€λ
return datetime.now().strftime("%Y-%m-%d")
except:
return datetime.now().strftime("%Y-%m-%d")
@staticmethod
def parse_time(time_str: str) -> str:
"""
μκ° λ¬Έμμ΄μ HH:00 νμμΌλ‘ λ³ν
Args:
time_str: μκ° λ¬Έμμ΄ ("4μ", "16:00" λ±)
Returns:
str: HH:00 νμμ μκ°
Examples:
>>> DateParser.parse_time("4μ")
"16:00"
>>> DateParser.parse_time("16:00")
"16:00"
"""
try:
time_str = time_str.replace('μ', '').strip()
hour = int(time_str.split(':')[0])
# 1~8μλ μ€νλ‘ μ²λ¦¬
if 1 <= hour <= 8:
hour += 12
return f"{hour:02d}:00"
except:
return "09:00"
EOF
echo " β utils/date_parser.py"
# ============================================================================
# utils/time_checker.py μμ±
# ============================================================================
echo ""
echo "π utils/time_checker.py μμ± μ€..."
cat > utils/time_checker.py << 'EOF'
# -*- coding: utf-8 -*-
"""
μκ°λ κ°μ©μ± μ²΄ν¬ μ νΈλ¦¬ν°
νμμ€μ μμ½ κ°λ₯ν μκ°λλ₯Ό μ°Ύμ΅λλ€.
"""
import logging
logger = logging.getLogger(__name__)
class TimeChecker:
"""νμμ€ μκ°λ κ°μ©μ± μ²΄ν¬ ν΄λμ€"""
@staticmethod
def time_to_minutes(time_str: str) -> int:
"""
HH:MMμ λΆμΌλ‘ λ³ν
Args:
time_str: "HH:MM" νμμ μκ°
Returns:
int: μμ λΆν°μ λΆ λ¨μ μκ°
Examples:
>>> TimeChecker.time_to_minutes("09:30")
570
"""
h, m = map(int, time_str.split(':'))
return h * 60 + m
@classmethod
def check_available_slots(cls, room_id: int, reservations: list,
start_hour: int = 12, duration: int = 2) -> tuple:
"""
νΉμ νμμ€μ κ°λ₯ν μκ°λ μ°ΎκΈ°
Args:
room_id: νμμ€ ID
reservations: μ 체 μμ½ λͺ©λ‘
start_hour: κ²μ μμ μκ° (κΈ°λ³Έ: 12μ)
duration: νμ μκ° (μκ° λ¨μ, κΈ°λ³Έ: 2μκ°)
Returns:
tuple: (κ°λ₯ν μκ°λ 리μ€νΈ, κΈ°μ‘΄ μμ½ μ 보 리μ€νΈ)
Examples:
>>> slots, reservations = TimeChecker.check_available_slots(123, all_res, 12, 2)
>>> print(slots)
['12:00-14:00', '15:00-17:00']
"""
# ν΄λΉ νμμ€μ μμ½ μ°ΎκΈ°
room_reservations = []
for res in reservations:
try:
targets = res.get('reservation', {}).get('reservationTargets', [])
if targets and targets[0].get('room', {}).get('id') == room_id:
start_time = res['startDateTime'][11:16]
end_time = res['endDateTime'][11:16]
room_reservations.append({
'start': cls.time_to_minutes(start_time),
'end': cls.time_to_minutes(end_time),
'title': res.get('reservation', {}).get('title', 'μ λͺ©μμ')
})
except Exception as e:
logger.debug(f"μμ½ νμ± μ€ν¨: {str(e)}")
continue
# κ°λ₯ν μκ°λ μ°ΎκΈ°
available_slots = []
end_search_hour = 19 - duration # 19μ μ’
λ£ κ°λ₯νλλ‘
for hour in range(start_hour, end_search_hour + 1):
slot_start = hour * 60
slot_end = slot_start + (duration * 60)
# μ΄ μκ°λκ° λΉμ΄μλμ§ νμΈ
is_available = True
for res in room_reservations:
# κ²ΉμΉλμ§ νμΈ (μμ < κΈ°μ‘΄μ’
λ£ AND μ’
λ£ > κΈ°μ‘΄μμ)
if not (slot_end <= res['start'] or slot_start >= res['end']):
is_available = False
break
if is_available:
available_slots.append(f"{hour:02d}:00-{(hour+duration):02d}:00")
return available_slots, room_reservations
EOF
echo " β utils/time_checker.py"
# ============================================================================
# api/rooms.py μμ±
# ============================================================================
echo ""
echo "π api/rooms.py μμ± μ€..."
cat > api/rooms.py << 'EOF'
# -*- coding: utf-8 -*-
"""
νμμ€ API λͺ¨λ
νμμ€ λͺ©λ‘ μ‘°ν, κ²μ, νν°λ§ κΈ°λ₯μ μ 곡ν©λλ€.
"""
import logging
import requests
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
logger = logging.getLogger(__name__)
class RoomsAPI:
"""νμμ€ κ΄λ ¨ API ν΄λμ€"""
def __init__(self, base_url: str, login_manager):
"""
Args:
base_url: νμμ€ μμ€ν
λ² μ΄μ€ URL
login_manager: LoginManager μΈμ€ν΄μ€
"""
self.base_url = base_url
self.login_manager = login_manager
def get_all_rooms(self, office_id: int = 2, size: int = 9999):
"""
μ 체 νμμ€ λͺ©λ‘ μ‘°ν
Args:
office_id: μ¬λ¬΄μ€ ID (1:λ°©λ°°, 2:λΆλΉ, 3:κ°μ, 4:ν¨μ²)
size: μ‘°νν μ΅λ κ°μ
Returns:
list: νμμ€ λͺ©λ‘
Raises:
Exception: API νΈμΆ μ€ν¨ μ
"""
url = f"{self.base_url}/room/api/v1/rooms"
params = {
"officeId": office_id,
"size": size,
"valid": "true",
"sort": "name,asc"
}
logger.info(f"νμμ€ λͺ©λ‘ μ‘°ν: officeId={office_id}")
response = requests.get(
url,
headers=self.login_manager.get_headers(),
params=params,
verify=False
)
if response.status_code == 200:
rooms = response.json()
logger.info(f"νμμ€ {len(rooms)}κ° μ‘°ν μλ£")
return rooms
else:
raise Exception(f"νμμ€ λͺ©λ‘ μ‘°ν μ€ν¨: {response.status_code}")
def filter_by_floor(self, rooms: list, floor_filter: str) -> list:
"""
μΈ΅μΌλ‘ νμμ€ νν°λ§
Args:
rooms: μ 체 νμμ€ λͺ©λ‘
floor_filter: μΈ΅ μ 보 ("7μΈ΅", "7F", "12F_A" λ±)
Returns:
list: νν°λ§λ νμμ€ λͺ©λ‘
"""
if not floor_filter:
return rooms
floor_filter = floor_filter.replace('μΈ΅', '').strip()
filtered = [r for r in rooms if floor_filter in r['floorName']]
logger.info(f"μΈ΅ νν°λ§: {floor_filter} β {len(filtered)}κ°")
return filtered
def get_room_by_name(self, room_name: str, office_id: int = 2) -> dict:
"""
μ΄λ¦μΌλ‘ νμμ€ μ°ΎκΈ°
Args:
room_name: νμμ€ μ΄λ¦ (λΆλΆ μΌμΉ)
office_id: μ¬λ¬΄μ€ ID
Returns:
dict: νμμ€ μ 보 (μμΌλ©΄ None)
"""
rooms = self.get_all_rooms(office_id)
for room in rooms:
if room_name in room['name']:
logger.info(f"νμμ€ λ°κ²¬: {room['name']}")
return room
logger.warning(f"νμμ€μ μ°Ύμ μ μμ: {room_name}")
return None
EOF
echo " β api/rooms.py"
# ============================================================================
# api/reservations.py μμ±
# ============================================================================
echo ""
echo "π api/reservations.py μμ± μ€..."
cat > api/reservations.py << 'EOF'
# -*- coding: utf-8 -*-
"""
μμ½ API λͺ¨λ
μμ½ νν© μ‘°ν λ° μμ½ μμ± κΈ°λ₯μ μ 곡ν©λλ€.
"""
import logging
import requests
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
logger = logging.getLogger(__name__)
class ReservationsAPI:
"""μμ½ κ΄λ ¨ API ν΄λμ€"""
def __init__(self, base_url: str, login_manager, config):
"""
Args:
base_url: νμμ€ μμ€ν
λ² μ΄μ€ URL
login_manager: LoginManager μΈμ€ν΄μ€
config: μ€μ λͺ¨λ
"""
self.base_url = base_url
self.login_manager = login_manager
self.config = config
def get_reservations(self, date: str, office_id: int = 2):
"""
μμ½ νν© μ‘°ν
Args:
date: λ μ§ (YYYY-MM-DD)
office_id: μ¬λ¬΄μ€ ID
Returns:
list: μμ½ λͺ©λ‘ (μ€ν¨ μ λΉ λ¦¬μ€νΈ)
"""
url = f"{self.base_url}/room/api/v1/reservations/-/reserved-dates"
params = {
"officeId": office_id,
"useDate": date,
"reservedDateStatus": "RESERVED,ENTRANCE,LEAVING",
"page": 0,
"size": 3600
}
logger.info(f"μμ½ μ‘°ν: date={date}, officeId={office_id}")
response = requests.get(
url,
headers=self.login_manager.get_headers(),
params=params,
verify=False
)
if response.status_code == 200:
reservations = response.json()
logger.info(f"μμ½ {len(reservations)}κ° μ‘°ν μλ£")
return reservations
else:
logger.warning(f"μμ½ μ‘°ν μ€ν¨: {response.status_code}")
return []
def create_reservation(self, room: dict, date: str, start_time: str,
end_time: str, title: str = "νμ"):
"""
μμ½ μμ±
Args:
room: νμμ€ μ 보 λμ
λ리
date: λ μ§ (YYYY-MM-DD)
start_time: μμ μκ° (HH:MM)
end_time: μ’
λ£ μκ° (HH:MM)
title: νμ μ λͺ©
Returns:
requests.Response: API μλ΅ κ°μ²΄
"""
url = f"{self.base_url}/room/api/v1/reservations"
reservation_data = {
"id": "",
"title": title,
"startDateTime": f"{date} {start_time}",
"endDateTime": f"{date} {end_time}",
"organizerId": self.config.USER_ID,
"organizerName": self.config.USER_NAME,
"organizerGroupId": self.config.GROUP_ID,
"organizerGroupName": self.config.GROUP_NAME,
"status": "RESERVED",
"useAllDay": False,
"useRecurrence": False,
"useResponse": False,
"eventId": "",
"visibility": None,
"recurrence": {},
"reservationTargets": [{
"roomId": room['id'],
"attendees": [{
"attendeeId": self.config.USER_ID,
"attendeeType": "INNER_PERSON",
"displayName": f"{self.config.USER_NAME} μ μ",
"companyName": self.config.GROUP_NAME,
"email": "",
"mobileNo": ""
}]
}]
}
logger.info(f"μμ½ μμ±: {room['name']}, {date} {start_time}-{end_time}")
response = requests.post(
url,
headers=self.login_manager.get_headers(),
json=reservation_data,
verify=False
)
if response.status_code == 201:
logger.info("μμ½ μμ± μ±κ³΅")
else:
logger.error(f"μμ½ μμ± μ€ν¨: {response.status_code}")
return response
EOF
echo " β api/reservations.py"
# ============================================================================
# meeting_room_mcp.py λ©μΈ νμΌ μμ± (κ°λ¨ λ²μ )
# ============================================================================
echo ""
echo "π meeting_room_mcp.py μμ± μ€..."
cat > meeting_room_mcp.py << 'EOF'
# -*- coding: utf-8 -*-
"""
νμμ€ μμ½ MCP μλ² (λ©μΈ)
μ£Όμ κΈ°λ₯:
1. SSO λ‘κ·ΈμΈμ ν΅ν νμμ€ μμ€ν
μ κ·Ό
2. νμμ€ κ²μ (μκ°λλ³, μΈ΅λ³)
3. μμ½ νν© μ‘°ν
4. μμ½ μμ±
"""
import os
import sys
import asyncio
import logging
from typing import Any
# Windows νκΈ μΈμ½λ© μ€μ
if sys.platform == 'win32':
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
# λ‘κΉ
μ€μ
logging.basicConfig(
filename='mcp_debug.log',
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
encoding='utf-8'
)
logger = logging.getLogger(__name__)
# MCP κ΄λ ¨ import
from mcp.server import Server
from mcp.types import Tool, TextContent
# 컀μ€ν
λͺ¨λ import
import config
from auth.login import LoginManager
from api.rooms import RoomsAPI
from api.reservations import ReservationsAPI
from utils.date_parser import DateParser
from utils.time_checker import TimeChecker
# ============================================================================
# μ μ λ³μ
# ============================================================================
login_manager = None
rooms_api = None
reservations_api = None
app = Server("meeting-room-mcp")
# ============================================================================
# μ΄κΈ°ν
# ============================================================================
def initialize_services():
"""μλΉμ€ μ΄κΈ°ν"""
global login_manager, rooms_api, reservations_api
login_manager = LoginManager(config)
rooms_api = RoomsAPI(config.BASE_URL, login_manager)
reservations_api = ReservationsAPI(config.BASE_URL, login_manager, config)
logger.info("μλΉμ€ μ΄κΈ°ν μλ£")
def ensure_login():
"""λ‘κ·ΈμΈ μν νμΈ λ° νμμ λ‘κ·ΈμΈ"""
global login_manager
if not login_manager or not login_manager.session_cookies:
logger.info("μΈμ
μμ. λ‘κ·ΈμΈ μλ...")
login_manager.login()
# ============================================================================
# MCP Tools μ μ
# ============================================================================
@app.list_tools()
async def list_tools() -> list[Tool]:
"""μ¬μ© κ°λ₯ν λꡬ λͺ©λ‘"""
return [
Tool(
name="search_available_rooms",
description="νΉμ λ μ§μ μκ°λμ μμ½ κ°λ₯ν νμμ€ κ²μ",
inputSchema={
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "λ μ§ (μ: '10.24', '2025-10-24')"
},
"office": {
"type": "string",
"description": "μ¬λ¬΄μ€ μμΉ (λΆλΉ, λ°©λ°°, κ°μ, ν¨μ²)",
"default": "λΆλΉ"
},
"floor": {
"type": "string",
"description": "μΈ΅ μ 보 (μ: '7μΈ΅', '12F_A')",
"default": ""
},
"start_hour": {
"type": "integer",
"description": "μμ μκ° (μ: 12)",
"default": 9
},
"duration": {
"type": "integer",
"description": "νμ μκ° (μκ° λ¨μ, μ: 2)",
"default": 1
}
},
"required": ["date"]
}
),
Tool(
name="get_room_details",
description="νΉμ νμμ€μ μμΈ μ 보 λ° μμ½ νν© μ‘°ν",
inputSchema={
"type": "object",
"properties": {
"room_name": {
"type": "string",
"description": "νμμ€ μ΄λ¦ (μ: '705 νμμ€', '1202')"
},
"date": {
"type": "string",
"description": "λ μ§"
}
},
"required": ["room_name", "date"]
}
),
Tool(
name="create_reservation",
description="νμμ€ μμ½ μμ±",
inputSchema={
"type": "object",
"properties": {
"room_name": {
"type": "string",
"description": "νμμ€ μ΄λ¦ (μ: '705 νμμ€')"
},
"date": {
"type": "string",
"description": "λ μ§"
},
"start_time": {
"type": "string",
"description": "μμ μκ° (μ: '16:00', '4μ')"
},
"end_time": {
"type": "string",
"description": "μ’
λ£ μκ° (μ: '18:00', '6μ')"
},
"title": {
"type": "string",
"description": "νμ μ λͺ©",
"default": "νμ"
}
},
"required": ["room_name", "date", "start_time", "end_time"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: Any) -> list[TextContent]:
"""λꡬ μ€ν"""
logger.info(f"Tool called: {name} with args: {arguments}")
try:
ensure_login()
if name == "search_available_rooms":
return await search_available_rooms(arguments)
elif name == "get_room_details":
return await get_room_details(arguments)
elif name == "create_reservation":
return await create_reservation(arguments)
else:
return [TextContent(type="text", text=f"Unknown tool: {name}")]
except Exception as e:
logger.error(f"μ€λ₯ λ°μ: {str(e)}", exc_info=True)
return [TextContent(type="text", text=f"μ€λ₯ λ°μ: {str(e)}")]
# ============================================================================
# Tool ꡬν
# ============================================================================
async def search_available_rooms(args: dict) -> list[TextContent]:
"""μμ½ κ°λ₯ν νμμ€ κ²μ"""
logger.info(f"search_available_rooms νΈμΆ: {args}")
date = DateParser.parse_date(args.get("date", ""))
office = args.get("office", "λΆλΉ")
floor_filter = args.get("floor", "")
start_hour = args.get("start_hour", 9)
duration = args.get("duration", 1)
office_id = config.OFFICE_MAP.get(office, 2)
try:
all_rooms = rooms_api.get_all_rooms(office_id)
if floor_filter:
all_rooms = rooms_api.filter_by_floor(all_rooms, floor_filter)
reservations = reservations_api.get_reservations(date, office_id)
result = f"[{date}] {office} νμμ€ νν©\n"
result += f"κ²μ 쑰건: {start_hour}μ μ΄ν {duration}μκ° μ¬μ© κ°λ₯\n\n"
available_count = 0
for room in all_rooms:
slots, room_reservations = TimeChecker.check_available_slots(
room['id'], reservations, start_hour, duration
)
if slots:
available_count += 1
result += f"β
{room['name']} ({room['floorName']}, {room['personnel']}λͺ
)\n"
result += f" κ°λ₯ μκ°:\n"
for slot in slots:
result += f" β’ {slot}\n"
if room_reservations:
result += f" κΈ°μ‘΄ μμ½:\n"
for res in room_reservations:
h_start = res['start'] // 60
m_start = res['start'] % 60
h_end = res['end'] // 60
m_end = res['end'] % 60
result += f" β’ {h_start:02d}:{m_start:02d}~{h_end:02d}:{m_end:02d} ({res['title']})\n"
result += "\n"
if available_count == 0:
result += "β 쑰건μ λ§λ νμμ€μ΄ μμ΅λλ€.\n"
else:
result += f"\nμ΄ {available_count}κ° νμμ€ μμ½ κ°λ₯!"
return [TextContent(type="text", text=result)]
except Exception as e:
return [TextContent(type="text", text=f"κ²μ μ€ν¨: {str(e)}")]
async def get_room_details(args: dict) -> list[TextContent]:
"""νμμ€ μμΈ μ 보 μ‘°ν"""
logger.info(f"get_room_details νΈμΆ: {args}")
room_name = args.get("room_name", "")
date = DateParser.parse_date(args.get("date", ""))
try:
room = rooms_api.get_room_by_name(room_name)
if not room:
return [TextContent(type="text", text=f"'{room_name}' νμμ€μ μ°Ύμ μ μμ΅λλ€.")]
office_id = room.get('officeId', 2)
reservations = reservations_api.get_reservations(date, office_id)
room_reservations = []
for res in reservations:
try:
targets = res.get('reservation', {}).get('reservationTargets', [])
if targets and targets[0].get('room', {}).get('id') == room['id']:
room_reservations.append(res)
except:
continue
result = f"[νμμ€ μ 보] {room['name']}\n\n"
result += f"μμΉ: {room.get('officeName', 'N/A')} {room.get('floorName', 'N/A')}\n"
result += f"μμ© μΈμ: {room.get('personnel', 'N/A')}λͺ
\n"
result += f"체ν¬μΈ νμ: {'μ' if room.get('useCheckInOut', False) else 'μλμ€'}\n\n"
if room_reservations:
result += f"[{date} μμ½ νν©]\n"
for res in room_reservations:
title = res.get('reservation', {}).get('title', 'μ λͺ©μμ')
start = res.get('startDateTime', 'N/A')[11:16]
end = res.get('endDateTime', 'N/A')[11:16]
organizer = res.get('reservation', {}).get('organizerName', 'N/A')
result += f" β’ {start}~{end}: {title} (μμ½μ: {organizer})\n"
else:
result += f"[{date}] μμ½ μμ - μ’
μΌ μ¬μ© κ°λ₯\n"
return [TextContent(type="text", text=result)]
except Exception as e:
return [TextContent(type="text", text=f"μ‘°ν μ€ν¨: {str(e)}")]
async def create_reservation(args: dict) -> list[TextContent]:
"""νμμ€ μμ½ μμ±"""
logger.info(f"create_reservation νΈμΆ: {args}")
room_name = args.get("room_name", "")
date = DateParser.parse_date(args.get("date", ""))
start_time = DateParser.parse_time(args.get("start_time", ""))
end_time = DateParser.parse_time(args.get("end_time", ""))
title = args.get("title", "νμ")
try:
room = rooms_api.get_room_by_name(room_name)
if not room:
return [TextContent(type="text", text=f"'{room_name}' νμμ€μ μ°Ύμ μ μμ΅λλ€.")]
response = reservations_api.create_reservation(
room, date, start_time, end_time, title
)
if response.status_code == 201:
result_data = response.json()
result = f"[μμ½ μλ£]\n\n"
result += f"νμμ€: {room['name']}\n"
result += f"λ μ§: {date}\n"
result += f"μκ°: {start_time} ~ {end_time}\n"
result += f"μ λͺ©: {title}\n"
result += f"μμ½ ID: {result_data.get('id')}"
return [TextContent(type="text", text=result)]
else:
return [TextContent(type="text", text=f"μμ½ μ€ν¨: {response.status_code}\n{response.text}")]
except Exception as e:
return [TextContent(type="text", text=f"μμ½ μ€ν¨: {str(e)}")]
# ============================================================================
# λ©μΈ μ€ν
# ============================================================================
async def main():
"""MCP μλ² μ€ν"""
from mcp.server.stdio import stdio_server
logger.info("MCP μλ² μμ")
initialize_services()
async with stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
app.create_initialization_options()
)
if __name__ == "__main__":
import warnings
warnings.filterwarnings('ignore', message='Unverified HTTPS request')
asyncio.run(main())
EOF
echo " β meeting_room_mcp.py"
# ============================================================================
# README.md μμ±
# ============================================================================
echo ""
echo "π README.md μμ± μ€..."
cat > README.md << 'EOF'
# Meeting Room MCP
νμμ€ μμ½ μμ€ν
μ μν MCP (Model Context Protocol) μλ²
## π νλ‘μ νΈ κ΅¬μ‘°
```
meeting-room-mcp/
βββ meeting_room_mcp.py # λ©μΈ MCP μλ²
βββ config.py # μ€μ λ° μμ
βββ auth/
β βββ __init__.py
β βββ login.py # SSO λ‘κ·ΈμΈ κ΄λ¦¬
βββ api/
β βββ __init__.py
β βββ rooms.py # νμμ€ API
β βββ reservations.py # μμ½ API
βββ utils/
β βββ __init__.py
β βββ date_parser.py # λ μ§/μκ° νμ±
β βββ time_checker.py # μκ°λ 체ν¬
βββ requirements.txt
βββ .env
βββ README.md
```
## π μ€μΉ λ° μ€ν
1. κ°μνκ²½ μμ± λ° νμ±ν
```bash
python -m venv venv
source venv/bin/activate # Linux/Mac
# λλ
venv\Scripts\activate # Windows
```
2. μμ‘΄μ± μ€μΉ
```bash
pip install -r requirements.txt
```
3. .env νμΌ μ€μ
```env
USER_ID=your_id
USER_PASSWORD=your_password
USER_NAME=your_name
GROUP_ID=your_group_id
GROUP_NAME=your_group_name
MEETING_ROOM_BASE_URL=https://srs.ktds.co.kr:8443
```
4. MCP μλ² μ€ν
```bash
python meeting_room_mcp.py
```
## π§ μ£Όμ κΈ°λ₯
### 1. search_available_rooms
νΉμ λ μ§μ μκ°λμ μμ½ κ°λ₯ν νμμ€ κ²μ
**νλΌλ―Έν°:**
- date: λ μ§ (μ: "10.24", "2025-10-24")
- office: μ¬λ¬΄μ€ (λΆλΉ, λ°©λ°°, κ°μ, ν¨μ²)
- floor: μΈ΅ μ 보 (μ: "7μΈ΅", "12F_A")
- start_hour: μμ μκ° (κΈ°λ³Έ: 9)
- duration: νμ μκ° (κΈ°λ³Έ: 1)
### 2. get_room_details
νΉμ νμμ€μ μμΈ μ 보 λ° μμ½ νν© μ‘°ν
**νλΌλ―Έν°:**
- room_name: νμμ€ μ΄λ¦
- date: λ μ§
### 3. create_reservation
νμμ€ μμ½ μμ±
**νλΌλ―Έν°:**
- room_name: νμμ€ μ΄λ¦
- date: λ μ§
- start_time: μμ μκ°
- end_time: μ’
λ£ μκ°
- title: νμ μ λͺ© (μ ν)
## π λ‘κ·Έ
λͺ¨λ λ‘κ·Έλ `mcp_debug.log` νμΌμ κΈ°λ‘λ©λλ€.
## π μΈμ¦
SSO ν ν° κΈ°λ° μλ λ‘κ·ΈμΈμ μ¬μ©ν©λλ€.
- ν¬νΈ λ‘κ·ΈμΈ
- SSO ν ν° μΆμΆ
- νμμ€ μμ€ν
μ κ·Ό
- μΈμ
μΏ ν€ κ΄λ¦¬
EOF
echo " β README.md"
# ============================================================================
# μλ£ λ©μμ§
# ============================================================================
echo ""
echo "=========================================="
echo "β
νλ‘μ νΈ μμ± μλ£!"
echo "=========================================="
echo ""
echo "μμ±λ νμΌ:"
echo " π auth/"
echo " βββ login.py"
echo " π api/"
echo " βββ rooms.py"
echo " βββ reservations.py"
echo " π utils/"
echo " βββ date_parser.py"
echo " βββ time_checker.py"
echo " π config.py"
echo " π meeting_room_mcp.py"
echo " π README.md"
echo ""
echo "λ€μ λ¨κ³:"
echo " 1. .env νμΌ νμΈ λ° μμ "
echo " 2. requirements.txt νμΈ"
echo " 3. python meeting_room_mcp.py μ€ν"
echo ""