MCP 서버에 CLI를 붙였더니 생긴 일 — AI Toolkit CLI 완성기

📝 한줄 요약

MCP 서버의 모든 기능을 터미널 명령어로 쓸 수 있는 aitk CLI를 만들어서, MCP 연결 없이도 팀 스킬을 검색/배포/관리할 수 있게 됐습니다.

바쁘시면 이것만 읽어도 돼요:

  • MCP 서버의 15개 도구를 터미널 명령어 15개와 1:1 대응시킨 CLI를 만들었습니다

  • npm install -g @gpters/aitk 한 줄이면 설치 끝

  • CLI 로그인으로 토큰을 한 번 발급받으면 이후 명령어는 바로 실행됩니다

  • 만드는 것보다 기존 버그 찾는 게 더 오래 걸렸습니다 — deploy 하나에 버그 3개가 겹쳐 있었음

  • Claude Code, OpenCode, Codex, 그리고 이제 터미널에서도 동일한 팀 스킬 경험 제공

🎯 이런 분들께 도움돼요

  • MCP를 지원하지 않는 AI 도구에서도 팀 스킬을 쓰고 싶은 분

  • 스킬을 만들었는데 배포 과정이 번거로웠던 분

  • MCP 서버를 운영하면서 "이거 CLI도 있으면 좋겠다"고 생각했던 분

배경: MCP 서버에 CLI 인터페이스를 처음 만들다

이전 글에서 MCP 서버 하나로 Claude Code와 OpenCode를 동시에 지원하는 구조를 소개했습니다. 플러그인을 설치하면 MCP 서버에 자동 연결되고, 에이전트가 스킬을 검색하고 로드하는 방식이었습니다.

이 구조는 대화형 작업에는 매우 편했습니다. 에이전트가 대화 흐름 속에서 필요한 스킬을 찾고 실행할 수 있기 때문입니다.

다만 사용하다 보니 한 가지 아이디어가 떠올랐습니다.

"이 서버 기능을 CLI로도 제공하면 어떨까?"

이유는 단순했습니다.

에이전트가 도구를 실행할 때는 CLI 형태가 훨씬 다루기 쉽기 때문입니다.

CLI는 구조가 단순합니다.

  • 명령어 실행 → 결과(JSON) 파싱

  • 대화 컨텍스트를 거의 사용하지 않아 토큰 사용량이 가벼움

  • 다른 도구나 에이전트 워크플로우에 도구처럼 연결하기 쉬움

그래서 MCP를 대체하려는 것이 아니라 같은 서버 위에 CLI 인터페이스를 하나 더 추가하기로 했습니다.

웹 애플리케이션의 구조를 보여주는 다이어그램

이번 글은 MCP 서버에 첫 CLI 클라이언트(aitk)를 처음부터 만든 과정에 대한 기록입니다.

🛠️ 사용한 도구

  • Claude Code: CLI 전체 개발 및 버그 수정

  • 모델: Claude Opus 4.6

  • 빌드: Bun (번들링) + npm (배포)

  • 백엔드: 기존 MCP 서버 (Next.js + Neon PostgreSQL)

🔧 작업 과정

MCP 서버에 이미 API가 있었다 — CLI 자리는 명확했다

MCP 서버에는 원래 두 가지 API가 있었습니다. 플러그인이 쓰는 JSON-RPC 2.0과, 간단한 호출용 Simple REST API.

꼭 두 개가 "필수"였던 건 아닙니다. 기술적으로는 CLI도 JSON-RPC만으로 전부 구현할 수 있습니다. 다만 당시에는 (1) 플러그인/MCP 경로와 무관하게 가벼운 엔드포인트로 빠르게 호출할 수 있게 하려는 목적, (2) 일부 액션은 tools/call 래핑 없이 HTTP 요청 한 번으로 끝내기 좋다는 이유로 REST를 함께 두었습니다.

이미 서버 로직이 API로 분리되어 있었기 때문에, CLI는 사실상 얇은 클라이언트만 만들면 되는 구조였습니다.

그래서 CLI에서는 기존에 있던 인터페이스를 그대로 활용했습니다.

  • 단순 호출/응답이 깔끔한 작업은 REST

  • MCP 도구와 1:1로 붙는 작업은 JSON-RPC의 tools/call

즉 "REST가 있어서 CLI가 가능했다"기보다는, 이미 있던 두 인터페이스 중 작업 성격에 맞는 쪽을 골라 쓴 것에 가깝습니다.

어떤 도구를 쓰든, 심지어 아무 도구 없이 터미널만 있어도, 동일한 팀 스킬에 접근할 수 있습니다.

cls의 다이어그램

CLI를 만들다 보니 MCP 도구 6개가 빠져 있었다

처음에는 기본 명령어만 구현했는데, MCP 서버의 tools 목록과 하나씩 비교해 보니 CLI에 아직 구현되지 않은 도구들이 있다는 걸 발견했습니다.

MCP 도구

CLI

상태

semantic_search

aitk search

있음

get_plugin_content

aitk get

있음

deploy_skill

aitk deploy

있음

check_updates

aitk updates

있음

undeploy_skill

없음

suggest_improvement

없음

list_suggestions

없음

resolve_suggestion

없음

add_files

없음

remove_files

없음

스킬 삭제, 개선 제안, 파일 관리 — 전부 MCP에서는 되는데 CLI에서는 안 되는 기능들이었습니다.

6개 명령어를 한 번에 추가했다

패턴은 단순합니다.

  • 토큰 확인

  • JSON-RPC tools/call 호출

  • 응답 JSON 파싱

6개 명령어 파일을 병렬로 만들었습니다.

aitk undeploy my-old-skill
aitk suggest --plugin-id code-reviewer --title "보안 체크 추가" --description "OWASP top 10 포함"
aitk suggestions --plugin-id my-skill --status pending
aitk resolve --suggestion-id abc123 --action accept --comment "좋은 제안!"
aitk add-files --id my-skill script.mjs reference.md
aitk remove-files --id my-skill --files "old-script.mjs"

만드는 건 금방이었습니다. 진짜 문제는 그다음이었죠.

삽질 기록: deploy 하나에 버그가 3개 겹쳐있었다

aitk deploy를 실행했더니 동작하지 않았습니다. 원인을 찾아보니 세 가지 문제가 동시에 있었습니다.

첫 번째: API 라우트 validation에 deploy가 없었습니다.

route.ts의 switch에 case 'deploy'가 빠져 있어서 400 에러가 발생했습니다.

두 번째: create_plugin은 관리자 전용이었습니다.

처음에는 deploy를 action=create로 매핑했는데 이 도구는 admin 전용이라 권한 에러가 났습니다.

세 번째: 응답 포맷이 JSON이 아니었습니다.

deploy_skill 핸들러가 JSON 뒤에 사람이 읽는 텍스트를 붙여서 JSON.parse()가 실패했습니다.

웹이나 MCP에서는 경로가 달라 문제가 드러나지 않았는데, CLI에서는 한 번에 호출되면서 세 개가 동시에 터졌습니다.

문제를 하나씩 고치고 나니 흐름은 이렇게 정리됐습니다.

aitk deploy → REST ?action=deploy → deploy_skill 핸들러 → DB 저장 + 임베딩 생성

updates 명령어도 고장나 있었다

aitk updates를 실행했더니 다음 오류가 발생했습니다.

Missing required field: installations

check_updates MCP 도구는 installations 배열이 필수인데 CLI에서는 {}를 보내고 있었습니다.

그래서 로컬에 설치된 스킬을 ~/.claude/skills/에서 자동으로 탐지해서 배열로 보내도록 수정했습니다.

설치된 스킬이 없으면 API 호출 없이 바로 안내 메시지를 출력합니다.

전체 15개 명령어를 E2E 테스트했다

마지막으로 전체 흐름을 실제로 테스트했습니다.

aitk deploy → aitk search → aitk get → aitk add-files → aitk suggest → aitk suggestions → aitk resolve → aitk remove-files → aitk undeploy

15개 명령어가 모두 정상 동작했습니다.

이후 npm에 @gpters/[email protected]로 퍼블리시했습니다.

이번 작업은 대부분 Claude Code와 함께 진행했습니다.

💬 이 과정에서 배운 AI 활용 팁

효과적이었던 것

  1. MCP 서버를 먼저 만들면 CLI는 거의 공짜입니다. 비즈니스 로직이 이미 서버에 있으니까 CLI는 "HTTP 호출 + JSON 파싱"만 하면 됩니다. 6개 명령어 추가에 실제 코딩 시간보다 기존 버그 찾는 시간이 더 길었습니다.

  2. stdout은 JSON, stderr는 사람용으로 나눠야 합니다. stdout에는 JSON만 내보내고(AI가 파싱), stderr에는 상태 메시지를 내보냅니다. 이렇게 하면 aitk search "키워드" | jq '.[]' 같은 파이프라인이 자연스럽게 됩니다.

  3. 병렬 작업을 적극 활용했습니다. 6개 명령어 파일을 한 번에 생성하고, 5개 명령어를 동시에 테스트하는 식으로 진행했습니다. Claude Code가 병렬 도구 호출을 잘 처리합니다.

이렇게 하면 안 돼요

  1. API를 CLI로 감싸면 숨은 버그가 드러납니다. 웹이나 MCP에서 잘 되던 것도 CLI에서 호출하면 안 되는 경우가 많습니다. route validation 누락, 응답 포맷 불일치, 필수 파라미터 미전달 — 전부 CLI를 만들면서 발견한 것들입니다.

  2. VERSION을 하드코딩하면 안 됩니다. bin/aitk.ts에 VERSION 상수를 직접 넣어뒀더니, package.json만 올리고 소스는 안 바꿔서 빌드된 JS에 옛날 버전이 박히는 문제가 생겼습니다.

🌍 다른 업무에 적용한다면?

MCP 서버 위에 여러 인터페이스를 얹는 패턴은 다른 서비스에도 그대로 적용됩니다.

이 패턴은 "MCP 서버가 있는데 CLI도 필요한" 모든 상황에 적용됩니다:

  • 사내 API 문서 검색: MCP 서버에 API 스펙을 넣어두고, CLI로도 검색 가능하게

  • 팀 템플릿 관리: aitk deploy처럼 CLI 한 줄로 팀 공유 템플릿 배포

  • 온보딩 자동화: 새 팀원이 npm install -g @gpters/aitk && aitk login 두 줄이면 팀 스킬 전체 접근 가능

핵심은 "MCP 서버 하나 + 도구별 클라이언트 껍데기"라는 구조입니다. CLI는 가장 가벼운 껍데기고, 새 도구가 나와도 클라이언트만 추가하면 됩니다.

🚀 앞으로의 계획

현재 @gpters.org 팀 외에도 누구나 사용할 수 있습니다. Welcome 페이지에서 플러그인 및 CLI 연결 방법을 확인하세요!

코딩 에이전트를 위한 AI 툴킷
3
7개의 답글

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요