Git · 캘린더 · HRWeb 통합 MCP 에이전트 개발
아키텍처
주간보고 작성, HRWeb 공수 입력, 한컴 공수 등 반복 업무를 자동화하기 위해 개인적으로 개발 후 공유한 MCP 기반 에이전트입니다. FastMCP로 8개 도구를 구현하고 Cursor·Claude Desktop에서 바로 호출할 수 있도록 배포했습니다.
핵심 기능
엑셀 서식 100% 보존 - zipfile + XML 직접 조작openpyxl 서식 손실 → ElementTree로 셀 직접 주입
주간보고 템플릿에는 병합 셀, 조건부 서식, 드롭다운 유효성 검사가 들어 있었습니다.openpyxl로 저장하면 이 서식이 전부 사라졌습니다. openpyxl이 xlsx를 읽어 재직렬화할 때 자신이 지원하지 않는 XML 요소를 그냥 버리기 때문입니다.
xlsx는 ZIP 아카이브입니다.zipfile로 압축을 풀고 xml.etree.ElementTree로 내부 xl/worksheets/sheet1.xml을 직접 파싱해 타깃 셀의 값만 교체한 뒤 다시 압축했습니다. 원본 ZIP의 나머지 파일은 전혀 건드리지 않아 서식·수식·드롭다운이 100% 보존됐습니다.
import zipfile, shutil, io
from lxml import etree
def inject_cells(template_path, out_path, cell_map: dict):
"""cell_map: {"D5": "내용", "H5": "내용"} 형태로 전달"""
shutil.copy(template_path, out_path)
with zipfile.ZipFile(out_path, "r") as zin:
names = zin.namelist()
files = {n: zin.read(n) for n in names}
sheet_xml = files["xl/worksheets/sheet1.xml"]
tree = etree.fromstring(sheet_xml)
ns = {"x": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}
for ref, value in cell_map.items():
cell = tree.find(f'.//x:c[@r="{ref}"]', ns)
if cell is not None:
v = cell.find("x:v", ns)
if v is None:
v = etree.SubElement(cell, "{...}v")
# 문자열 셀: 인라인 문자열(t="inlineStr") 방식으로 주입
cell.set("t", "inlineStr")
is_elem = etree.SubElement(cell, "{...}is")
t_elem = etree.SubElement(is_elem, "{...}t")
t_elem.text = value
files["xl/worksheets/sheet1.xml"] = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
with zipfile.ZipFile(out_path, "w", zipfile.ZIP_DEFLATED) as zout:
for name, data in files.items():
zout.writestr(name, data)Playwright로 Blazor Server 폼 자동화Blazor 비동기 렌더링 타이밍 · 부분 일치 셀렉터
사내 HRWeb은 Blazor Server 앱입니다. 일반 웹과 달리 폼 값이 SignalR을 통해 서버에서 바인딩되므로 Playwright가 클릭 직후 바로 다음 요소를 조작하면 아직 렌더링이 끝나지 않아 입력이 무시됐습니다.
각 인터랙션 후 time.sleep으로 짧은 대기를 끼워 Blazor 재렌더링이 완료되길 기다렸습니다. 또한 날짜·프로젝트 셀렉터 옵션 텍스트가 매달 바뀌어부분 일치로 선택하도록 했습니다.
async def fill_hrweb(page, date: str, project_keyword: str, hours: float):
# 월 선택 드롭다운 - 부분 일치
await page.select_option("select#month", label=re.compile(date[:7]))
await asyncio.sleep(0.8) # Blazor 재렌더링 대기
# 날짜 행 클릭
row = page.locator(f'tr:has-text("{date}")')
await row.click()
await asyncio.sleep(0.5)
# 프로젝트 셀렉터 - 키워드 부분 일치
await page.select_option(
"select#project",
label=re.compile(project_keyword, re.IGNORECASE)
)
await asyncio.sleep(0.5)
# 공수 입력
hours_input = page.locator("input#workHours")
await hours_input.fill(str(hours))
await page.click("button#save")
await asyncio.sleep(1.0) # 저장 완료 대기한컴 공수 시스템은 Google 계정으로 로그인하는 별도 서비스였습니다. 로그인 후 sessionStorage 토큰을 추출해 이후 API 요청에 재사용하는 방식으로 Playwright 세션 유지 비용을 줄였습니다.
Git · Calendar 병합으로 주간보고 자동 생성커밋 없는 날 패턴 추론 · 폴더 단위 그룹핑
주간보고에는 날짜별로 어떤 프로젝트에서 무슨 작업을 했는지 기록해야 합니다. Git 커밋 로그와 Google Calendar 출장 일정 두 소스를 병합해 초안을 만들었습니다.
| 소스 | 수집 방법 | 활용 |
|---|---|---|
| Git 커밋 | subprocess git log --all --since --until | 날짜별 작업 내용 |
| Google Calendar | OAuth2 Events API (token.json 캐시) | 출장·회의 일정 |
| 패턴 추론 | 주변 커밋 폴더명 + 평일 기본 템플릿 | 커밋 없는 날 보완 |
리포 경로의 폴더명을 프로젝트 이름으로 사용해 커밋을 그룹핑했습니다. 커밋이 없는 평일은 해당 주 다른 날의 커밋에서 가장 많이 등장한 폴더명을 가져와 “유지보수” 템플릿으로 채웠습니다. 생성된 보고서는 Gmail SMTP로 즉시 발송하거나 엑셀 파일로 저장 후 AI 클라이언트에 반환했습니다.
@mcp.tool()
def generate_report(start_date: str, end_date: str) -> str:
"""Git + Calendar 병합 주간보고 생성"""
commits = _collect_commits(start_date, end_date) # subprocess git log
trips = _get_calendar_events(start_date, end_date) # Google Calendar API
days = _merge_by_date(commits, trips)
for day in days:
if not day["commits"] and day["weekday"] < 5: # 커밋 없는 평일
day["inferred"] = _infer_from_context(days, day["date"])
return _render_report(days) # 엑셀 XML 주입 후 base64 반환FastMCP 8개 도구 구성 · 팀 배포Cursor · Claude Desktop 바로 호출
FastMCP를 사용하면 Python 함수에 @mcp.tool() 데코레이터만 붙이면 MCP 프로토콜 직렬화·스키마 생성이 자동으로 처리됩니다. 총 8개 도구를 구현했습니다.
| 도구 | 역할 |
|---|---|
| list_commits | 기간별 Git 커밋 로그 수집 · 폴더 그룹핑 |
| get_trips | Google Calendar 출장·일정 조회 |
| create_calendar_event | Calendar 일정 생성 |
| generate_report | Git + Calendar → 주간보고 엑셀 생성 |
| generate_report_with_content | AI가 내용 작성 → 엑셀 주입 |
| send_report | 생성된 보고서 Gmail SMTP 발송 |
| preview_hrweb | HRWeb 입력 전 미리보기 텍스트 반환 |
| upload_hrweb | Playwright로 HRWeb · 한컴 공수 자동 입력 |
팀원에게 배포할 때는 mcp.json에 서버 경로만 추가하면 됩니다. Git·캘린더 인증 정보는 각자 token.json과 .env를 두고 공용 코드를 공유하는 방식으로 배포했습니다.
Jira·Bitbucket 연동 자동 브랜치·PR 문화 정착티켓 상태 변경 시 브랜치 자동 생성 → PR까지 자동화
같은 사내 자동화의 연장선으로, Jira 티켓을 발급받은 뒤 브랜치를 만들고 PR을 여는 흐름도 매번 수작업으로 반복되고 있었습니다. 티켓 상태가 바뀌면 규칙에 따라 브랜치를 자동으로 생성하고, 작업이 끝나면 PR 생성까지 이어지는 자동화를 구축해 팀 내 브랜치·PR 명명 규칙과 작업 흐름을 정착시켰습니다.