그어봄
단톡방에서 공정한 중간지점을 찾아주는 MCP.
그어봄은?
여러 명의 출발지를 받아 모두에게 공정한 중간 만남지점 top 3를 카카오 길찾기로 계산해주는 MCP 서버다. 대화 에이전트가 대화 흐름 안에서 도구처럼 호출한다. 실제로 새김AI가 이 MCP를 연동해 쓴다.
포지셔닝이 분명하다: 맛집·핫플 추천이 아니라 "공정한 집결지 계산기". 지도 앱은 "A→B 한 사람 경로"만 보지만, 그어봄은 여러 명을 동시에 놓고 모두에게 공정한 한 점을 찾는다.

단톡방에서 출발지들 → 공정한 중간지점 top 3
왜 만들었나
단톡방에서 약속을 잡을 때 늘 나오는 말이 있다. "그래서 어디서 봐?" 누군가는 가깝고 누군가는 멀고, 결국 목소리 큰 사람 동네나 "그냥 강남"으로 끝난다. 이 집결지 문제 자체를 정면으로 다뤘다.
새김AI가 "모든 걸 넣어서 아무것도 아니게" 된 것과 정반대로, 그어봄은 한 가지 문제에만 집중했다. 그리고 그 하나를 실제 알고리즘 + 회귀 벤치 + 못 하는 건 거절하는 경계 처리로 단단하게 만들었다.
전체 구조
사람에게 보여줄 문장은 호출한 LLM이 생성하고, 그어봄은 구조화된 데이터만 반환한다(MCP 철학). 출발지 추출도 대화 AI의 몫이고, 그어봄은 "공정한 지점 계산"에만 집중한다.
공정하게 만난다는 것
이 프로젝트의 핵심은 "공정"을 어떻게 정의하고 계산하느냐다.
공정성 스코어링
"다들 똑같은 시간"이 공정이라는 통념을 기각했다. 50/50/50분보다 37/16/43분이 낫다. 가장 오래 걸리는 사람의 시간을 줄이는 게(미니맥스) 먼저고, 균형은 보조다.
점수는 낮을수록 좋게, 0.7 × 최장시간 + 0.2 × 평균 + 0.1 × 표준편차로 계산한다. 균형을 max−min 격차가 아니라 표준편차로 본 게 포인트다. "한 명만 유독 튀는" 불공정을 잡아낸다. 화면에는 이걸 0~100점으로 정규화해 보여주고, 각자의 소요시간을 전부 공개한다(숨기지 않는 게 공정의 일부).

후보 다양성, 점수보다 후보가 먼저
공정 공식이 아무리 좋아도, 좋은 후보가 애초에 비교군에 없으면 소용없다. 초기엔 무게중심 한 점 주변만 뒤져서 후보 8개가 한 동네(신림)에 몰렸고, 환승 좋은 이수역이 후보에서 통째로 빠졌다.
그래서 무게중심에 더해 각 출발지 방향으로 씨앗을 뿌리고, 후보를 2km 간격으로 공간 분산시켜 여러 동네가 경쟁하게 했다. 역·터미널·청사·브랜드를 후보로 삼고 편의점은 뺀다.
2단계 채점, 자차 → 대중교통
대중교통 API(ODsay)는 무료 티어에 호출 제한이 빡세다. 그래서 먼저 전 후보를 자차(카카오내비, 무료·무제한)로 채점해 서로 다른 동네 3곳을 추린 뒤, 대중교통 모드면 그 3곳만 재채점한다. 13초 예산을 넘거나 실패하면 조용히 자차 결과로 폴백한다. 느려도 답은 나온다.
ODsay는 카카오 대중교통 API가 열려 있지 않아 임시로 붙인 것이다. provider를 파일 하나로 격리해둬서, 다른 제공자로 갈아끼울 때 그 파일만 건드리면 된다.
동명 지명 · 경계 처리
"광주"가 광역시인지 경기 광주인지, "서면"이 부산인지. 이렇게 모호한 지명은 다른 출발지 맥락으로 보정한다. 영문 지명도 한글로 매핑한다.
못 하는 걸 지어내지 않는 게 이 프로젝트의 품격이다: 도시 간 장거리(120km 초과)는 대중교통 길찾기가 부정확해서 자차로 강제하고, 제주↔육지는 "배·비행기 영역"이라 아예 거절한다. 못 찾는 시골 지명은 지어내지 않고 "시/구를 함께 알려주세요"라고 되묻는다.
MCP 도구화
중간지점 탐색을 MCP 단일 툴로 노출해, 다른 에이전트가 대화 중 그대로 물려 쓸 수 있게 했다. 이게 핵심 재사용 가치다. 실제로 새김AI가 이 툴을 편입해 쓴다.
재밌는 디테일: 결과 카드가 이름·시간 문자열 안에 핵심값을 박아 넣는다("OO역 · 공정성 92점 · 최장 43분"). 호출한 AI가 세부 필드를 버려도 헤드라인과 노선은 렌더된다는 관찰에서 나온 방어책이다.
기술 스택
- MCP: FastMCP (Streamable HTTP · stateless) · 단일 툴
find_meeting_point - 런타임: Python 3.12 · httpx (async · 병렬 지오코딩)
- 경로/지도: 카카오 로컬(주소·키워드·카테고리·좌표변환) · 카카오내비 길찾기 · 카카오맵 링크
- 대중교통: ODsay (임시. provider를 파일 하나로 격리)
- 배포: Docker (python:3.12-slim) → 카카오 PlayMCP / KakaoCloud
왜 이 스택
- MCP · FastMCP: 중간지점 찾기가 별도 앱이 아니라 대화 흐름 안에서 도구처럼 불려야 했다. MCP 서버로 만들어 새김AI가 실제로 호출해 쓴다. FastMCP는 Streamable HTTP·stateless라 배포가 가볍다.
- Python · httpx: 여러 출발지의 지오코딩·길찾기를 async로 병렬 호출해야 응답이 빨라진다.
- 카카오 API: 공정성 계산의 근거가 직선거리가 아니라 실제 이동시간이라, 카카오내비 길찾기를 썼다.
- ODsay: 대중교통 계산용. 카카오 대중교통 API를 쓸 수 없어 임시로 붙였고, 교체 비용을 줄이려고 provider를 파일 하나로 격리했다.
오라클 회귀 벤치 (BMT)
정확도를 LLM 심사가 아니라 규칙 기반 오라클로 자동 채점했다(재현 가능, 주관 제거). 케이스를 8 → 24개로 늘려가며 회귀로 돌렸고, 발견한 결함을 하나씩 고쳤다.
| 버전 | 케이스 | 최저점 | 고친 것 |
|---|---|---|---|
| revert | 8 | 80 | 장거리 "공정성 0점" 표기 버그 |
| longdist | 8 | 70 | ODsay 타임아웃 → 자차 오폴백 발견 |
| fastconnect | 8 | 100 | connect 타임아웃 10s → 3s (예산 내 회복) |
| landmark | 8 | 100 | 랜드마크 우선 네이밍 (프랜차이즈 1위 제거) |
| landmark | 24 | 100 | 케이스 8→24로 늘려도 전 항목 통과 |
이 점수는 엔진의 정확도 회귀 채점이지, "공정성의 옳고 그름" 판정이나 공모전 심사 결과가 아니다. "벤치 100점 = 완성"이 아님.
Decision Log
"다들 똑같은 시간"을 기각하고, 가장 오래 걸리는 사람을 줄이는 걸 먼저로 뒀다. 균형은 표준편차로 봐서 "한 명만 튀는" 불공정을 포착한다.
좋은 후보가 비교군에 없으면 공식이 무의미하다(한 동네 쏠림을 실측으로 확인). 방향 씨앗 + 공간 분산으로 여러 동네를 경쟁시킨다.
무료 대중교통 API의 호출 제한을 피하려고, 무료·무제한인 자차로 전 후보를 먼저 걸러 3곳만 대중교통으로 정밀 채점한다. 실패해도 자차로 폴백.
대중교통 provider를 한 파일로 격리해, ODsay에서 다른 API로 "파일 하나 교체"로 갈아끼울 수 있게 했다.
주관을 빼고 재현 가능하게, 정확도 채점을 규칙 오라클로. 회귀 테스트도 취약한 문자열 비교가 아니라 불변식으로 잠갔다.
표현은 대화 에이전트가, 도구는 데이터만. 그래서 어떤 에이전트에도 그대로 물려 쓸 수 있다.
회고: 작게, 집중, 검증된 0→1
그어봄은 새김AI의 정확한 반대편에 있다. 새김AI가 "모든 걸 넣어 무너진" 종료작이라면, 그어봄은 한 문제(집결지 공정성)에만 집중해 작고 단단하게 만든 0→1이다. 실제 공정성 알고리즘, 8→24케이스로 늘린 오라클 회귀 벤치, 제주↔육지를 거절하는 경계 처리까지. "확장 → 집중"이라는 교훈이 실물로 나타난 셈이다.
그리고 MCP로 만들어 둔 덕에 새김AI가 그대로 편입해 쓴다. 재사용이 말이 아니라 코드로 증명된다.
카카오 AGENTIC PLAYER 10 출품작이고, PlayMCP에 배포했다. 공모전에서는 탈락했다. 남은 것은 코드 쪽이다. 공정성 스코어링, 8→24케이스 오라클 회귀 벤치, 장거리와 제주를 거절하는 경계 처리, 그리고 새김AI가 그대로 물려 쓰는 MCP 단일 툴.