내부 도구 20개를 Claude Desktop에 연결하며, 본체 코드는 한 줄도 안 바꿨다
사내 도구를 Claude Desktop에서도 쓰게 해달라는 요구가 들어왔습니다. 그런데 이런 요구는 하나로 끝나지 않습니다. Claude Desktop이 되면 Cursor에서도 쓰고 싶고, 그다음엔 Cline 이야기가 나옵니다. AI 클라이언트마다 연동을 하나씩 만들다 보면 본체 코드에 클라이언트별 커넥터가 덕지덕지 붙고, 유지 비용은 클라이언트 수에 비례해 늘어납니다. 내부 프로젝트 관리 도구를 운영하면서 실제로 마주친 상황이었고, 이 글은 그 요구를 본체 코드 한 줄 안 바꾸고 푼 기록입니다.
클라이언트마다 통합을 박으면 벌어지는 일
내부 프로젝트 관리 도구의 백엔드는 이미 HTTP API를 제공하고 있었습니다. 문제는 부르는 쪽이었습니다. Claude Desktop, Cursor, Cline 같은 AI 클라이언트에서 이 도구를 자연어로 쓰고 싶다는 요구 앞에서 선택지는 두 갈래였습니다.
클라이언트마다 통합 박기 — Claude Desktop용, Cursor용, Cline용… 클라이언트가 N개면 통합 코드도 N벌. 본체에 결합이 N개 생긴다
MCP 어댑터 한 벌 — 본체는 그대로 HTTP를 제공하고, 별도 어댑터가 MCP 프로토콜로 번역한다. 본체 변경 0, 클라이언트 무관
첫 번째 길의 비용은 만들 때보다 유지할 때 큽니다. 본체 API가 바뀔 때마다 통합 N벌을 같이 고치게 되고, 새 클라이언트가 나올 때마다 통합이 하나씩 늘어납니다. 처음엔 Claude Desktop 하나만 붙이면 될 것 같았지만, 운영해보니 이런 요구는 반드시 늘어난다는 쪽에 걸어야 했습니다. 그래서 두 번째 길을 택했습니다.

본체를 안 건드리는 MCP 어댑터
MCP(Model Context Protocol)는 AI 클라이언트가 외부 도구를 부르는 표준 프로토콜입니다. Claude Desktop, Cursor, Cline이 모두 이 프로토콜을 지원하기 때문에, MCP 서버 하나만 세우면 클라이언트별 통합 자체가 필요 없어집니다.
설계에서 고민한 지점은 하나였습니다. 어댑터를 본체 안에 둘 것인가, 별도 패키지로 둘 것인가. 별도 패키지를 택했습니다. 본체와의 결합이 0이 되고, 나중에 다른 머신에 어댑터만 따로 배포하기도 쉬워지기 때문입니다. Python 3.11에 FastMCP와 httpx를 얹어 어댑터 패키지를 신설했고, 진입점·설정·HTTP 클라이언트에 도구 모듈 네 그룹(조회·할 일·기록·관계)을 올렸습니다.
동작 방식은 단순합니다. AI 클라이언트가 어댑터 프로세스를 직접 띄우고 stdio로 대화합니다. 어댑터는 받은 호출을 HTTP로 번역해 본체 API에 전달합니다. stdio 방식이라 별도의 네트워크 포트나 인증 게이트웨이 없이 로컬 프로세스 하나로 끝납니다. 이 작업 전체에서 본체 쪽 변경은 정확히 0건이었습니다.

환경변수 두 개가 입구
어댑터의 설정은 환경변수 두 개가 전부입니다. 본체 서버 주소(API_URL)와 API 키(API_KEY). 이 단순함이 배포 유연성으로 그대로 이어졌습니다.
같은 어댑터 코드를 로컬에 두면 로컬 백엔드에 붙고, 주소만 외부 호스트로 바꾸면 다른 머신의 백엔드에 붙습니다. 배포 분기를 코드로 만들지 않고 환경변수로 밀어냈기 때문에, 머신을 바꾸든 환경을 바꾸든 설정 한 줄 차이입니다. 어댑터를 어디에 몇 개 두든 코드는 하나입니다.

도구 20개, 그리고 본체 없는 검증
어댑터가 노출하는 도구는 총 20개입니다. 평평하게 20개를 나열하지 않고 기능군으로 묶었습니다. 도구가 한 덩어리로 쏟아지면 LLM이 어떤 도구를 골라야 할지 길을 잃기 때문입니다.
그룹 | 개수 | 내용 |
|---|---|---|
조회 | 6 | 대시보드·프로젝트 현황·검색·목록류 |
할 일 쓰기 | 6 | 생성·완료·보류·취소·수정 등 상태 전이 |
기록 쓰기 | 6 | Q&A·회의·노하우·에픽 등록 등 |
관계·전환 | 2 | 항목 간 연결, 항목 전환 |
이번에 노출한 도구는 20개였지만, 운영해보면 도구 카탈로그는 반드시 늘어납니다. 도구가 수십 개를 넘어가는 순간부터는 노출 자체보다 관리가 문제가 되는데, 그 다음 단계의 고민은 따로 정리했습니다.
검증은 본체 없이 돌도록 만들었습니다. respx로 HTTP 계층을 모킹해 설정 5종과 클라이언트 10종, 총 15개의 단위 테스트를 붙였고, 서버를 raw JSON-RPC로 부팅해 tools/list 응답에 도구 20개가 정상 노출되는지 확인했습니다. 마지막 리뷰에서 세 가지를 더 다듬었습니다.
httpx.RequestError를 도메인 에러로 래핑 — 외부 라이브러리 오류를 그대로 흘리면 호출하는 AI 클라이언트가 어댑터 내부 구현을 알게 된다. 래핑하면 나중에 라이브러리를 갈아끼우기도 자유롭다
graceful shutdown 추가 — stdio 프로토콜은 클라이언트가 어댑터 프로세스를 직접 죽인다. 종료 시 정리가 없으면 잔여 핸들이 남는다
도구 트리거 문구 다듬기 — LLM이 도구를 고르는 기준은 결국 설명 문구다
이 구조의 한계
어댑터가 만능은 아닙니다. 본체와 별도로 배포되는 만큼 본체 API가 바뀌면 어댑터도 따라가는 버전 동기화 부담이 생깁니다. stdio 방식은 클라이언트 하나가 프로세스 하나를 띄우는 구조라, 동시 클라이언트가 N개면 프로세스도 N개입니다. 그리고 어댑터 자체가 네트워크 접근을 열어주지는 않습니다. 다른 머신에서 부르려면 본체가 외부 접근 가능해야 한다는 전제는 그대로 남습니다.
그럼에도 이 구조를 택한 이유는 명확합니다. 본체 변경 0이라는 숫자는 회귀 위험 0과 롤백 비용 0을 뜻합니다. 어댑터만 끄면 외부 노출이 깨끗하게 사라진다는 사실이, 내부 시스템을 바깥에 여는 입장에서 가장 큰 안전판이었습니다.
내부 도구의 AI 클라이언트 연동 — 본체를 고치는 방식인가, 어댑터를 세우는 방식인가
어댑터를 껐을 때 외부 노출이 깨끗하게 사라지는가 (롤백 비용 0인지)
배포 분기가 환경변수 수준으로 단순한가 — 머신·환경 변경이 설정 한 줄인가
어댑터 테스트가 본체 없이 도는가 (HTTP 모킹으로 어댑터 로직만 검증)
본체 API 변경 시 어댑터 버전을 따라 올릴 절차가 있는가
AI 클라이언트는 앞으로도 계속 늘어날 것이고, 사내 도구를 거기에 연결해달라는 요구도 함께 늘어날 겁니다. 그때마다 본체에 커넥터를 박는 대신, 표준 프로토콜로 번역하는 어댑터 한 벌을 옆에 세우는 것 — 20개 도구를 열면서 본체를 한 줄도 안 고친 사람으로서, 다음에도 같은 선택을 할 생각입니다.