5_M-SPACE.jpg

<aside>

한 줄 요약

IT융합대학 스터디룸을 사람 없이 예약하고 예약만으로 입실하는 시스템을 4인 팀(AI-go-do)으로 만들고 있고, 저는 "내일 수업 끝나고 빈방 잡아 줘"를 실제 예약으로 바꾸는 AI 비서와 MCP 서버를 맡았습니다. (진행 중)

처음에는 혼자 명세 · 학생 설문 · 예약 프로토타입까지 만들었고, 이를 바탕으로 팀을 꾸려 2026 IT Festival 캡스톤디자인 경진대회에 출품합니다. 배포 주소는 아직 없어 저장소로 연결합니다.

</aside>

<aside>

모델 0회

쓰기 실행 경로에 AI 개입

예약 · 취소는 확인 토큰 + 학생 버튼으로만 실행 (설계 확정, 구현 중)

</aside>

<aside>

7 → 0

CI에서만 실패한 테스트

.gitignore가 소스 패키지를 무시한 원인을 찾아 해결 (TS-02)

</aside>

<aside>

10건 중 1건

1인 프로토타입 동시 예약

팀 결성 전, EXCLUDE 제약으로 겹치는 예약 차단 검증

</aside>

<aside>

개발 기간

2026.10 ~

11/13 기능 동결 · 11/20 발표

</aside>

<aside>

팀 구성

4명 팀

예약 코어 · IoT 키패드 · 인프라 · AI 비서로 저장소를 나눠 개발

</aside>

<aside>

내 역할

AI 비서 · MCP 서버

기획 · 설문 · 팀 공통 문서도 함께

</aside>

<aside>

담당 업무

기술 스택 · Java 21, Spring Boot 4.1, Spring AI 2.0 (MCP 서버), Maven 멀티 모듈, Gemini API, GitHub Actions, SonarQube

</aside>

<aside>

Review AI가 틀려도 잘못된 예약이 생기지 않는 구조를 먼저 설계


프로젝트 진행 과정

<aside>

</aside>

01. 화면

1인 프로토타입 — 설문용 데모 화면

1인 프로토타입 — 설문용 데모 화면

02. 비서 흐름

<aside>

학생 "내일 2시 1번 방 4명 잡아 줘" → 에이전트 → 모델(Gemini)이 create_reservation 도구 호출 → MCP 서버는 예약하지 않고 확인안만 저장(토큰 · 2분 만료) → 학생 화면에 확인 카드(요약 + 참여자 학번 입력 + 확인 버튼) → 학생이 확인 → MCP 서버가 토큰을 한 번만 꺼내 예약 코어로 요청(Idempotency-Key = 토큰). 실행 요청은 도구가 아닌 일반 REST라 모델이 부를 방법이 없습니다.

</aside>

03. 기술 선택 이유

기술 고른 이유 포기한 것 (트레이드오프)
Spring AI MCP 서버 메서드에 @McpTool 한 줄로 도구와 입력 스키마가 등록돼, 공식 SDK로 직접 짜는 것보다 1주 안에 끝낼 수 있습니다. 자동 설정이 HTTP 헤더를 도구까지 넘기지 않아 전송 빈을 직접 만들었습니다.
확인 토큰 + 버튼 모델이 바로 실행하면 잘못된 예약이 생기고, 모델에게 확인 여부를 묻게 하면 프롬프트 주입에 뚫립니다. 토큰 저장소가 하나 더 생기고, 메모리에 두어 서버 재시작 시 대기 중인 확인이 사라집니다.
멱등 키 코어 응답이 끊겨도 같은 키로 한 번 더 보내면 예약이 두 번 생기지 않습니다. (코어 담당 팀원과 합의, DB 유일 제약) 코어에 컬럼 · 제약이 하나 늘어납니다.

04. 트러블슈팅

<aside>

1. 예약 시각이 9시간 당겨져 모델에 전달됐다

문제 코어가 14:00+09:00을 줬는데 도구 결과에는 05:00Z가 나와, 모델이 "오전 5시 예약"이라고 답할 수 있었습니다.

원인 Jackson이 OffsetDateTime을 읽을 때 기본으로 UTC로 바꿉니다. 같은 순간이지만 모델은 시차를 계산하지 않고 숫자를 그대로 읽습니다.

해결 코어 응답을 도구 결과로 바꿀 때 서울 시간으로 되돌립니다.

<aside>

결과 도구 결과의 모든 시각이 +09:00으로 전달

</aside>

증거: MyReservationsToolTest

</aside>

<aside>

2. 로컬은 통과, CI에서만 테스트 7개가 모두 실패했다

문제 GitHub Actions에서만 ReservationQueryPort 빈이 없다는 오류가 났습니다.

원인 .gitignore의 out/이 모든 깊이의 out 폴더를 무시해, 팀 컨벤션 패키지 adapter/out의 소스가 커밋에서 빠졌습니다.

해결 /out/으로 루트만 무시하게 바꾸고 빠진 파일을 커밋했습니다. 이후 PR 전에 무시된 소스가 없는지 확인합니다.

<aside>

결과 CI 실패 7 → 0

</aside>

증거: docs/트러블슈팅.md TS-02

</aside>

<aside>

3. (1인 프로토타입) 동시 예약에서 일부 요청이 500이었다

문제 팀 결성 전 혼자 만든 예약 프로토타입에서, 같은 시간 예약이 동시에 들어오면 일부가 500을 받았습니다.

원인 EXCLUDE 검사는 겹치는 커밋 전 행을 기다리는데, 두 트랜잭션이 서로를 기다리면 PostgreSQL이 한쪽을 교착(40P01)으로 끊습니다.

해결 교착을 "이미 예약된 시간"(409)으로 바꿨습니다. 이 경험을 팀 코어 설계와 비서의 409 안내 흐름에 반영했습니다.

<aside>

결과 겹치는 예약 10건 동시 요청 → 1건만 성공, 나머지 409

</aside>

증거: JpaConstraintMappingTest (happyzerojun/M-SPACE)

</aside>

05. GitHub