지난 두 글에서 Claude Desktop과 로컬 Gemma로 LDBD 봇을 만들었다. 둘 다 결국 내 노트북이 켜져 있어야 돌아가는 구조였다.
이번에는 반대로 가봤다. ChatGPT가 내 노트북을 거치지 않고, OpenAI 클라우드에서 바로 LDBD에 예측을 제출하게 만드는 방식이다.
Claude Desktop에서는 npx mcp-ldbd를 등록하면 됐다. 로컬에서 실행되는 앱이라 stdio MCP를 그대로 붙일 수 있었기 때문이다. ChatGPT는 다르다. 내 노트북의 localhost를 볼 수 없다.
그래서 LDBD에 공개 HTTP MCP 엔드포인트를 하나 만들었다 — https://ldbd.app/mcp. 라우트 하나면 끝날 줄 알았다. 실제 코드 변경도 크지 않았다. 그런데 ChatGPT에 제대로 붙이는 데 하루가 걸렸다.
i18n 미들웨어가 라우트를 가로채고, ChatGPT는 stats 같은 read-only 도구까지 전부 “파괴적”(destructive)으로 분류했고, 그 다음에는 Vercel 함수 타임아웃과 캐시까지 의심해야 했다. 이 글은 그 하루의 디버깅 일지다.
한 가지 미리 밝혀둘 것 — 이번에 검증한 범위는 “ChatGPT가 LDBD 도구를 읽고 쓸 수 있는가”까지다. 스케줄 태스크로 매일 자동 실행하는 부분은 아직 따로 확인하지 않았다.
준비물은 두 가지다.
- 커스텀 MCP 커넥터를 등록할 수 있는 ChatGPT 플랜
- LDBD 계정
이 기능은 플랜·지역·시점에 따라 보이지 않을 수 있으니, 먼저 설정 화면에서 “개발자 모드”가 있는지 확인하는 게 빠르다. 여기에 하루치 디버깅 인내심을 더하면 된다.
접근 비교 (이 시리즈 정리)
| 방식 | LLM 위치 | MCP 형태 | 노트북 필요? |
|---|---|---|---|
| Gemma + Ollama/MLX | 로컬 | 안 씀 (직접 API) | 예 (24시간) |
| Claude Desktop + mcp-ldbd | 로컬 | stdio | 예 (스케줄 시점만) |
| ChatGPT + 원격 MCP | 클라우드 | HTTP | 아니오 |
세 번째 방식의 매력은 분명하다. 내가 자고 있어도 ChatGPT는 정해진 주기로 LDBD에 붙을 수 있다. 다만 그게 가능하려면 우선 LDBD 쪽에 라우트가 준비되어 있어야 했다.
Step 1. (운영자 입장) HTTP MCP 라우트 만들기
이 절은 자기 서비스에 ChatGPT 커넥터를 붙이려는 사람을 위한 내용이다. LDBD 사용자라면 라우트가 이미 깔려 있으니 Step 2부터 봐도 무방하다.
구현하기 전에 결정해야 했던 건 두 가지였다.
- 새 도메인 vs 기존 라우트 — 처음엔
mcp.ldbd.app이라는 서브도메인을 따로 만들거나 별도 npm 패키지로 분리할까 싶었다. 결국 그냥ldbd.app/mcp로 Next.js 라우트를 직접 하나 더 만들기로 했다. 이쪽이 새 배포 인프라를 추가로 만들 필요가 없어서 가장 빨랐다. - MCP SDK 의존성 vs JSON-RPC 직접 구현 — 도구 5개에 JSON-RPC 메서드 몇 개뿐이라, SDK를 추가하는 것보다 직접 구현하는 편이 코드도 이해도 더 짧았다. 결과적으로
app/mcp/route.ts가 약 110줄,lib/mcp/tools.ts가 약 150줄이고, 추가된 의존성은 0개다.
라우트가 처리하는 JSON-RPC 메서드는 initialize, tools/list, tools/call, ping, 그리고 notifications/*다. 도구 정의는 stdio 버전(mcp-ldbd npm 패키지)에 있는 것과 같은 5개를 그대로 노출한다 — search, get_asset, get_my_stats, list_open, submit_prediction. 사용자가 보낸 LDBD API key는 Authorization: Bearer ... 헤더로 받아 그대로 LDBD 내부 API로 전달하는 구조다.
Step 2. (사용자 입장) ChatGPT Connector에 LDBD 등록
여기는 따라 하기용 가이드라 단계가 좀 자세하다. 디버깅 함정이 더 궁금하면 Step 3로 바로 점프해도 된다.
먼저 /settings에서 type=AI Bot으로 identity를 만들고 API key를 발급해 둔다. Claude Desktop이나 Gemma 봇 글에서 했던 절차와 같다. 발급된 키 문자열은 딱 한 번만 화면에 나타나기 때문에 안전한 곳에 바로 복사해 두는 게 좋다. 여기까지 준비됐다면 이제 ChatGPT 쪽으로 넘어간다.
2-1. 개발자 모드 켜고 앱 만들기
ChatGPT의 설정 → 앱(Apps)으로 들어가서 개발자 모드를 켠다. 토글을 켜면 같은 화면에 “앱 만들기” 버튼이 새로 나타나는데, 그 버튼이 진입점이다.
2-2. MCP 서버 정보 입력
앱 만들기 폼에 다음 네 가지를 채운다.
- 이름:
LDBD(편한 이름이면 뭐든 상관없다) - MCP 서버 URL:
https://ldbd.app/mcp - 인증: 액세스 토큰 / API 키 방식을 고른다 (OAuth는 아니다)
- 헤더 스킴:
Bearer를 선택한다. 기본값이 Bearer라면 그대로 둔다.
2-3. 비공개 앱 목록에서 LDBD 열고 토큰 등록
“만들기” 버튼을 누르면 설정 화면의 “개발자 모드에서 생성한 비공개 앱” 섹션에 방금 만든 LDBD 카드가 추가된다. 그 카드를 누르고 들어가면 상세 페이지가 열리는데, 여기서 확인할 게 두 가지 있다.
하나는 액션 목록이다. LDBD가 노출하는 도구 5개가 모두 보여야 한다 — ldbd_search_assets, ldbd_get_asset, ldbd_get_my_stats, ldbd_list_my_open_predictions, ldbd_submit_prediction. 만약 비어 있거나 일부만 보이면 URL/인증 설정 중 뭔가가 어긋난 거다.
다른 하나는 상세 페이지 상단에 있는 “연결하기” 버튼이다. 누르면 액세스 토큰 또는 API 키 입력란이 뜨고, 거기에 앞서 발급해 둔 LDBD API key (ldbd_로 시작하는 문자열)를 그대로 붙여 넣으면 연결이 끝난다.
2-4. 채팅창에서 커넥터 붙이기
설정에서 빠져나와 새 채팅창을 열면 상단에 “개발자 모드”라는 표시가 뜨는 게 보인다. 입력란 옆의 + 버튼을 누르면 커넥터 목록이 나오고, 거기서 방금 등록한 LDBD 커넥터를 선택하면 해당 대화에서 LDBD 도구를 사용할 수 있다.
2-5. 첫 호출 — read 도구로 stats 보기
커넥터를 붙인 상태에서 채팅에 다음 한 줄을 던진다.
LDBD 커넥터로 내 stats 보여줘연결이 제대로 됐다면 ChatGPT가 “도구 요청 완료” 같은 라벨을 띄우면서 프로필 정보, 누적 점수, 최근 예측 같은 항목을 정리해서 보여준다. 여기까지 오면 read 경로는 일단 완성이다. 호출 자체에 문제가 없다는 신호다.
2-6. write 도구 — 예측 제출과 permission 모달
read가 되면 다음은 write 차례다. 같은 채팅에 이런 식으로 입력한다.
LDBD에 VOO 1주일 상승 예측을 reasoning은 'ChatGPT 자동 제출 첫 테스트'로 제출해줘이 경우엔 ChatGPT가 곧바로 실행하지 않고 “Submit price prediction for VOO?” 같은 확인 모달을 먼저 띄운다. 선택지는 거절하기 또는 확인. 확인을 누르면 LDBD에 예측이 정상적으로 등록된다.
permission 없이 자동 제출되는 모드는 따로 시도해보지 않았다. 일단 매번 확인을 거치는 경로로 동작이 되는지 정도만 검증했다.
Step 3. 첫 시도와 함정 — 실제 디버깅 일지
여기서부터가 이 글의 진짜 알맹이다. 라우트 코드를 처음 올린 날, 결국 같은 날에 다섯 번을 다시 푸시해야 했다.
함정 1. 내 미들웨어가 내 라우트를 잡아먹었다
https://ldbd.app/mcp로 첫 호출을 보내자 ChatGPT가 받은 건 Next.js의 not-found HTML 페이지였다. 404. 분명 라우트 파일은 만들어둔 상태였기 때문에 한참 헤맸다.
원인은 i18n 미들웨어였다. LDBD는 한국어/영어 라우팅을 위해 미들웨어를 쓰고 있는데, 그 matcher가 api는 예외로 빼뒀지만 mcp는 빼두지 않았다. 그래서 /mcp 호출이 자동으로 /en/mcp 같은 로케일 경로로 리다이렉트되며 404로 떨어지고 있었다.
// proxy.ts
- matcher: ['/((?!api|_next/static|...
+ matcher: ['/((?!api|mcp|_next/static|...한 단어 추가로 해결됐다. 비개발자 입장에서 처음 만난 “내 미들웨어가 내 라우트를 가로채는” 함정이었다.
함정 2. ChatGPT가 모든 도구를 “파괴적”으로 본다
라우트가 살아난 다음, ChatGPT 채팅에 “LDBD 커넥터로 내 stats 보여줘”를 입력했다. 결과는 이상했다. ChatGPT는 “사용 가능한 도구 찾아봄”을 네 번쯤 반복하고, “Thought for 41s” 같은 표시를 띄우다가, 결국 “조회 가능한 결과가 나오지 않았습니다” 라고 답하고 끝났다.
그래서 이번엔 아예 도구 이름을 찍어서 다시 시켰다 — “ldbd_get_my_stats 도구를 직접 호출해줘”. 이번엔 다른 종류의 거절이 돌아왔다. “Resource not found. api_tool에 노출된 실행 가능 리소스 목록도 비어 있습니다.” 개발자 모드 토글도 분명히 켜져 있는데 도구 호출만 안 되는 상태였다.
답은 커넥터 상세 페이지에 있었다. ChatGPT가 우리 도구 5개 모두에 “공개적으로 쓰기 / 오픈 월드 / 파괴적” 같은 태그를 붙여둔 상태였다. stats 조회처럼 read-only인 것까지 전부. MCP 도구 정의에 annotations 필드를 안 채워뒀더니, ChatGPT가 안전 기본값으로 모든 도구를 destructive로 간주해버린 것이다.
해결은 MCP 스펙에 정의된 annotations를 도구 정의에 붙여주는 것이었다.
const READ_ONLY = {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
}
// 4개 read 도구에 READ_ONLY 부착
// submit_prediction은 실제로 write라 그냥 둠커넥터를 새로 고치니 “파괴적” 태그가 사라지고, stats 호출도 한 번에 정상 결과를 돌려줬다.
이게 이 글에서 가장 쓸 만한 인사이트일 거다. annotations를 안 채우면 ChatGPT는 MCP 도구를 전부 위험한 것으로 다룬다. 다른 MCP 서버를 만드는 사람이라면 처음 도구 정의를 짤 때부터 같이 넣어두는 게 좋다.
함정 3. Vercel 함수 타임아웃 + 캐시
annotations까지 해결되고 나서 write 도구를 시도했다. 확인 모달이 뜨고, 승인을 누르고, 그 다음부터 ChatGPT가 한참 동안 “잘 생각하기” 상태에 들어갔다가 결국 prediction은 등록되지 않은 채로 끝났다.
첫 번째 의심은 타임아웃이었다. Vercel 함수 기본 타임아웃이 10초인데, /mcp가 내부적으로 /api/v1/predictions를 호출하고, 그 뒤에서 가격 조회와 DB 왕복까지 이어지면 전체 시간이 5–10초쯤 걸렸다. 둘을 더하면 10초를 쉽게 넘긴다.
// app/mcp/route.ts
export const maxDuration = 60 // Vercel hobby 플랜 최대치이 한 줄을 푸시했는데도 증상이 똑같았다. ChatGPT는 다시 무한히 “생각하기”에 들어갔다.
Vercel 로그를 까보니 또 다른 증상이 보였다. POST /api/v1/predictions가 511ms만에 401 Invalid API key로 떨어지고 있었다. 같은 시점에 stats 조회는 정상이었다. 같은 forward 코드인데 read는 되고 write만 401이 나는 게 이상했다.
키 prefix 다섯 글자 정도만 찍는 디버그 로그를 한 줄 더 넣어서 다시 푸시했다. 그리고 배포가 끝나길 기다리는 사이에 ChatGPT 쪽에서 한 번 더 시도해봤더니 갑자기 예측이 등록됐다.
가장 그럴듯한 설명은 이렇다. 첫 번째 maxDuration 푸시 때 Vercel이 함수 설정 캐시를 충분히 갈아엎지 않았고, 두 번째 푸시(디버그 로그)에서 함수가 새로 빌드되면서 그제서야 60초 실행 시간이 실제로 적용된 게 아닐까 싶다. 그때 보였던 401들도 아직 배포가 완전히 정리되기 전의 이상한 중간 상태였을지도 모른다.
솔직히 말하면 마지막에 정확히 뭐가 고쳐졌는지 100% 단정하기 어렵다. 그냥 한 번 더 해보니 됐고, 그 뒤로는 같은 호출이 계속 잘 동작했다. AI 시대의 디버깅에서도 결국 가끔은 “다시 해보니 됨” 같은 순간을 만난다.
최종 결과
- 엔드포인트:
https://ldbd.app/mcp(POST JSON-RPC) - 인증: 사용자가 본인 LDBD API key를 ChatGPT 커넥터에 등록 → 헤더로 그대로 forward
- 도구 5개: search / get_asset / get_my_stats / list_open / submit_prediction
- 첫 ChatGPT 제출 테스트: VOO 1주 상승, reasoning은 “ChatGPT 자동 제출 첫 테스트”로 정상 등록 확인 (제출 시 confirm 모달을 거친 수동 호출)
매일 자동화는 아직 해보지 않았다
내 환경에서는 커넥터 연결과 수동 호출까지만 검증했다. ChatGPT의 예약 실행 기능에서 write 도구의 confirm 모달이 어떻게 동작하는지는 아직 따로 확인하지 못했다. 커스텀 커넥터를 쓰는 플랜을 계속 유지할지 결정을 미뤄둔 상태라, 그 검증은 일단 보류로 남겨두기로 했다.
write 도구를 부를 때마다 confirm 모달이 떴던 점도 변수다. 스케줄 태스크가 예측 제출까지 자동으로 끝내려면 매 호출마다 permission이 다시 필요할 수도 있다는 뜻이고, 실제 동작은 한 번 돌려봐야 알 것 같다. 시도해보게 되면 그 결과만 따로 짧게 적을 생각이다.
한 줄 정리
결론만 보면 작았다. Next.js 라우트 하나, 추가 라이브러리 0개, JSON-RPC 메서드 몇 개, annotations 객체 하나.
하지만 실제로 붙여보니 함정은 코드 양이 아니라 경계면에 있었다. 로컬 앱과 클라우드 앱의 차이, i18n 미들웨어의 예외 처리, MCP 도구의 안전성 annotation, Vercel 함수 실행 시간, ChatGPT의 permission 모달. 이게 ChatGPT용 MCP 서버를 만들 때 실제로 부딪히는 항목들이고, 그중 하나라도 빠지면 사용자 입장에서 도구가 “안 보이거나” “안 부르거나” “끝나지 않거나” 같은 증상으로 나타났다.
라우트 하나를 추가했을 뿐인데 ChatGPT가 LDBD에 직접 붙었다. 그리고 그 라우트 하나가 제대로 붙기까지 하루가 걸렸다.
다음 글
다음은 LDBD에 AI 봇 붙이기 시리즈의 마지막이다. 커넥터를 끼지 않고 Python에서 직접 OpenAI API를 호출해 LDBD에 예측을 제출하는 봇을 다룰 예정이다. 여기서 다룬 커넥터 방식과 같은 ChatGPT/OpenAI 계열이지만 운영 책임이 어디로 옮겨가는지, 그리고 자동화의 자유도가 어떻게 달라지는지가 주된 비교 포인트가 될 것 같다.
본인 ChatGPT에도 LDBD를 붙여보고 싶다면 먼저 /settings에서 ai_bot identity와 API key부터 만들어두면 된다. 가입은 메인 페이지에서 가능하고, 사용은 무료다.