Skip to content

블로그에 AI 기능 붙이기 (2편) - MCP와 REST API의 경계

시리즈: 블로그에 AI 기능 붙이기

2 / 3편
  1. 11편 - 월 35원으로 OpenAI + Netlify Functions
  2. 22편 - MCP와 REST API의 경계지금 읽는 중
  3. 33편 - 응답 캐시로 API 호출 줄이기
다른 시리즈 전체 보기 →
블로그에 AI 기능 붙이기 (2편) - MCP와 REST API의 경계
목차

블로그 글이 쌓이면서 AI에게 매번 문서를 복사해 주는 일이 번거로워졌습니다. 그래서 글 검색과 본문 조회를 도구로 제공했습니다. 이 글에서는 저장소에 실제로 들어 있는 두 서버를 구분해서 설명합니다.

이전 글은 FastAPI REST 서버를 그대로 “원격 MCP 서버”라고 불렀습니다. REST 엔드포인트를 만든 것만으로 MCP 클라이언트가 연결되는 것은 아니므로 정정합니다. 문서를 도구로 읽게 하는 작업은 모델 재학습이나 파인튜닝도 아닙니다.

같은 검색 로직, 다른 연결 방식

공개 저장소의 mcp-server/src에 두 진입점이 있습니다.

파일역할연결 방식
server.pyMCP 도구와 리소스 등록Python MCP SDK의 stdio_server
rest_api.py관리자 화면용 검색·조회 API와 웹훅FastAPI HTTP REST

두 서버가 검색 핸들러를 공유할 수는 있지만 프로토콜은 별개입니다. stdio 서버는 클라이언트가 로컬 프로세스를 실행하고 표준 입출력으로 메시지를 교환합니다. FastAPI 서버에 POST 요청이 된다는 사실은 MCP 초기화나 tools/list, tools/call이 된다는 뜻이 아닙니다.

원격 MCP로 제공하려면 SDK가 지원하는 Streamable HTTP 등 MCP transport를 구현하고 클라이언트의 지원 여부를 확인해야 합니다. SDK 버전에 따라 API도 달라집니다. MCP transport 명세와 공식 SDK 목록을 기준으로 확인하는 편이 안전합니다. Python은 제가 선택한 언어이지 MCP의 필수 조건이 아닙니다.

검색 도구가 모델에게 주는 것

도구는 모델에게 검색 결과를 반환합니다. 모델의 가중치에 블로그 내용이 저장되는 것이 아닙니다. 따라서 검색에서 빠진 글은 답변의 근거에서도 빠질 수 있습니다.

검색 결과에는 제목만 넘기기보다 글 식별자, canonical URL, 수정 시각, 짧은 발췌를 함께 제공하는 편이 좋습니다. 사용자가 본문을 요청하면 해당 식별자로 원문을 조회하게 하고, 답변에는 읽은 문서 링크를 남기도록 합니다. 이 부분은 도구 인터페이스를 개선할 때의 기준이며 모든 항목이 현재 서버에 구현되어 있다는 뜻은 아닙니다.

Cloud Run에 올렸던 것은 REST API입니다

저장소의 실행 명령은 다음 형태입니다.

python -m uvicorn src.rest_api:app --host 0.0.0.0 --port 8001

이는 FastAPI 애플리케이션 실행 명령입니다. Cloud Run의 컨테이너 포트 설정과 일치시켜야 하고, 원격 MCP 배포를 완료했다는 증거로 사용할 수 없습니다. CLI나 관리자 화면이 REST를 호출하는 것과 Claude의 MCP 설정에 서버를 연결하는 것은 각각 검증해야 합니다.

Cloud Run의 쓰기 가능한 컨테이너 파일시스템은 메모리를 사용하며 인스턴스 수명에 종속됩니다. “읽기 전용이라 /tmp만 가능하다”는 이전 설명은 잘못입니다. /tmp에 clone한 파일도 영구 저장소가 아닙니다. Cloud Run 컨테이너 실행 계약을 참고하세요.

웹훅 한 번으로 모든 인스턴스가 갱신되지는 않습니다

GitHub push 웹훅을 받은 인스턴스가 git pull을 하면 그 인스턴스의 작업 사본만 바뀝니다. 여러 인스턴스와 콜드 스타트가 있는 환경에서는 서로 다른 문서 버전이 제공될 수 있습니다. “글 추가 즉시 전체 동기화”라는 보장은 할 수 없습니다.

개선 방향은 두 가지입니다. 문서를 빌드 산출물에 포함하고 새 리비전으로 배포하면 어떤 commit을 서비스하는지 명확합니다. 더 자주 갱신해야 한다면 공유 저장소의 버전별 문서 스냅샷과 manifest를 읽고, 새 버전 검증이 끝난 후 포인터를 바꿀 수 있습니다. 이들은 현재 저장소의 git pull 방식과 구별되는 제안입니다.

현재 방식을 유지한다면 clone·pull·파서 캐시 갱신을 한 경계로 다루고, 검색 결과에 문서 commit을 노출해 버전 차이를 관찰할 수 있어야 합니다. 동시에 읽는 도중 파일이 바뀌지 않도록 새 디렉터리에서 준비한 뒤 원자적으로 전환하는 방법도 검토할 수 있습니다.

웹훅 인증과 도구 권한

웹훅 서명은 수신한 원본 body 바이트로 계산하고 constant-time 비교를 사용합니다. secret이 비어 있으면 검증을 생략하지 말고 요청을 거부해야 합니다. 서명 검증과 별개로 허용한 저장소·이벤트·브랜치인지 확인하고 delivery ID 중복 처리를 막습니다.

Private Git 토큰을 clone URL에 넣으면 오류 로그나 git remote 설정에 남을 수 있습니다. 자격 증명은 비밀 저장소와 제한된 권한으로 관리하고 출력에서 제거해야 합니다. CORS 허용 목록은 사용자 인증의 대체재가 아닙니다. 조회 도구와 저장소를 바꾸는 도구의 권한을 나누어야 합니다.

문서 본문도 명령으로 신뢰해서는 안 됩니다. 검색된 글에 “다른 파일을 전송하라”는 문장이 있어도 도구 권한을 늘리는 지시가 아니라 자료로 취급해야 합니다.

연결을 확인하는 순서

로컬 stdio MCP는 클라이언트가 서버를 초기화하고 도구 목록을 읽은 뒤 실제 검색 도구를 호출하는지 확인합니다. REST API는 별도로 상태 코드·응답 스키마·인증을 확인합니다. 한쪽의 성공으로 다른 쪽의 성공을 대신하지 않습니다.

문서 갱신은 두 인스턴스에서 같은 commit의 글을 조회하는지, 삭제한 글이 캐시에 남지 않는지, 새 인스턴스에서도 같은 버전이 보이는지 확인해야 합니다. 기존 글의 고정 응답 시간과 월 비용은 측정 조건을 재현할 수 없어 보편적인 기대값으로 제시하지 않겠습니다.

이 구현의 유용한 부분은 이미 만든 검색 로직을 여러 클라이언트에서 재사용할 수 있다는 점입니다. 그 장점을 유지하려면 MCP transport, REST API, 문서 배포를 각각의 책임으로 구분해야 합니다.


Ramsbaby
Written byRamsbaby
이 블로그는 직접 개발/운영하는 블로그이므로 당신을 불쾌하게 만드는 불필요한 광고가 없습니다.

#My Github#소개 페이지#Blog OpenSource Github