블로그를 대신 발행해 주는 커스텀 MCP 서버 구축기: 디버깅 로그
요약
Claude가 블로그 게시물을 직접 발행할 수 있도록 커스텀 MCP(Model Context Protocol) 서버를 구축하는 과정과 디버깅 경험을 다룹니다. 패키지 이름 충돌 문제와 Claude Desktop 설정 시 주의해야 할 JSON 구조 오류를 해결하는 방법을 공유합니다.
핵심 포인트
- MCP를 활용해 AI 모델이 외부 도구(API, 파일 등)를 호출하도록 구현 가능
- PyPI의 패키지 이름 중복 문제로 인한 잘못된 SDK 설치 주의
- uv를 사용한 명시적인 버전 고정(pinning)의 중요성
- Claude Desktop 설정 파일(claude_desktop_config.json)의 올바른 계층 구조 확인 필요
블로그를 대신 발행해 주는 커스텀 MCP 서버 구축하기
시작하게 된 계기
최근 AI 엔지니어링 준비에 깊이 몰두하고 있습니다. 풀스택 AI 프로젝트 몇 개를 배포했고, 오픈 가중치 LLM을 파인튜닝했으며, 취업을 위해 DSA(자료구조 및 알고리즘) 공부에 매진했습니다. 이 모든 과정의 중간쯤에서 저는 MCP(Model Context Protocol)에 대해 궁금증을 느꼈고, 단순히 읽는 것에 그치지 않고 실제로 무언가를 구축하기로 결정했습니다.
아이디어는 간단했습니다. 클로드(Claude)가 제 컴퓨터에 놓인 초안 블로그 게시물을 읽어 정리한 다음, 제가 Dev.to UI를 전혀 건드리지 않고 바로 발행하게 할 수 있다면 어떨까?
알고 보니, 그것이 바로 MCP의 용도였습니다.
MCP란 무엇인가요? 간략히 설명하자면
MCP는 Claude 같은 AI 모델이 단순히 텍스트를 생성하는 대신, 사용자가 정의한 도구(tool) — 파일을 읽거나, API를 호출하거나, 스크립트를 실행하는 것 — 를 호출할 수 있게 해주는 프로토콜입니다. 사용자들은
uv python pin 3.12
uv venv --python 3.12
그 후 pyproject.toml에서 requires-python을 >=3.10으로 수정하자, 설치가 깔끔하게 완료되었습니다.
패키지 이름의 함정 (The Package Name Trap)
이것은 실제로 한동안 저를 괴롭혔던 문제입니다. 설치가 성공한 것처럼 보였음에도 불구하고, fastmcp를 임포트(import)할 때마다 ModuleNotFoundError: No module named 'mcp.server.fastmcp' 오류가 계속 발생했습니다. import mcp는 정상적으로 작동하고 폴더 안에 분명히 server 디렉토리가 있었음에도 말이죠.
확인 결과, uv pip show mcp를 실행했을 때 설치된 패키지 버전이 2.0.0이었으며, httpx2, mcp-types, pyjwt, pywin32와 같은 종속성(dependencies)을 가지고 있었습니다. 이 중 그 어떤 것도 실제 MCP SDK에 속하는 것이 아니었습니다. PyPI에 mcp라는 이름을 점유하고 있는 관련 없는 패키지가 있었고, 실제 modelcontextprotocol SDK 대신 이 패키지가 설치된 것이었습니다.
해결 방법은 버전 범위를 명시적으로 고정(pin)하는 것이었습니다:
uv remove mcp
uv add "mcp[cli]>=1.2.0,<2.0.0"
이렇게 하니 정식 SDK(당시 기준 1.29.0)가 설치되었고, from mcp.server.fastmcp import FastMCP가 마침내 정상적으로 작동했습니다.
교훈: 만약 uv add somepackage가 "성공"했지만 그 이후에 아무것도 제대로 작동하지 않는다면, uv pip show를 확인하세요. PyPI의 이름이 당신이 생각하는 그 프로젝트라고 함부로 가정해서는 안 됩니다.
Claude Desktop에 연결하기 (Wiring It Into Claude Desktop)
Claude Desktop은 최상위 mcpServers 키 아래의 claude_desktop_config.json 파일에서 MCP 서버 목록을 읽어옵니다. 현재 이 파일에는 다른 관련 없는 앱 설정들이 들어있어서, 실수로 서버를 mcpServers 내부에 중첩(nested)시키지 않고 mcpServers와 같은 레벨의 형제(sibling)로 추가하기 쉽습니다. 이렇게 하면 아무런 반응 없이 작동하지 않습니다. 저는 Settings → Developer → Local MCP servers를 쳐다보며 왜 filesystem만 나타나는지 의아해하며 이 사실을 어렵게 깨달았습니다.
올바른 구조:
{
"mcpServers": {
"filesystem": {
...
Windows 환경에서의 또 다른 주의사항: Claude Desktop이 항상 사용자의 셸(shell) PATH를 상속받는 것은 아니기 때문에, `
Claude Desktop을 완전히 종료 후 다시 실행한 후(단순히 창을 닫는 것이 아니라 — 트레이에 남아 있을 수 있음), 두 서버 모두 실행 중 (running) 상태로 나타났습니다.
MCP Inspector를 이용한 테스트
Claude Desktop에 무언가를 연결하기 전에, 다음 명령어를 사용하여 서버를 단독으로 테스트했습니다:
uv run mcp dev src/mcp_server_demo/__init__.py
이 명령은 MCP Inspector를 실행합니다. 이는 Claude를 개입시키지 않고도 도구(tools)를 직접 호출하고 가공되지 않은 요청/응답 페이로드(request/response payloads)를 확인할 수 있는 로컬 웹 UI입니다. 채팅 인터페이스를 통해 디버깅하는 대신 버그를 조기에 잡아내는 데 진정으로 유용합니다.
블로그 발행
두 서버가 모두 연결된 상태에서 실제 발행 단계는 거의 허무할 정도로 간단합니다. 그냥 평소처럼 Claude에게 말하면 됩니다:
"[폴더]에 있는 blog.txt 파일이 있어. 내용을 다듬은 다음, 관련 태그와 함께 Dev.to에 초안(draft)으로 발행해 줘."
Claude는 스스로 도구들을 체이닝(chaining)합니다:
read_text_file(파일 시스템 서버)을 호출하여 원본 초안을 가져옵니다.- 내용을 인라인(inline)으로 다시 쓰고 정리합니다.
- 제목, 마크다운 본문, 태그와 함께
publish_blog_to_devto(나의 커스텀 서버)를 호출합니다.
Dev.to 에디터에 복사해서 붙여넣을 필요도, 수동으로 서식을 맞출 필요도 없습니다. 저는 먼저 published: false로 설정하여 게시하기 전에 초안으로 검토하도록 했습니다. 이는 미완성된 내용을 게시하는 것에 대한 저렴한 보험과 같습니다.
이 과정에서 실제로 배운 점
여기서 얻은 진짜 배움의 대부분은 프로토콜로서의 MCP에 관한 것이 아니었습니다. 그것은 PATH 문제, Python 버전 고정(pinning), 선점된 패키지 이름(squatted package name), 그리고 JSON 중첩(nesting) 실수와 같은 표준적인 환경 디버깅에 관한 것이었습니다. 환경이 정상화된 후의 MCP 자체는 docstring을 포함한 약 20줄 정도의 Python 코드에 불과했습니다.
이것이 아마도 과소평가된 교훈일 것입니다: 에이전트 도구(agent tooling)의 신뢰성은 그 밑바탕에 깔린 지루한 배관(plumbing) 작업만큼만 유지됩니다. 가상 환경(venv), 패키지 버전, 그리고 설정(config)의 형태를 제대로 갖춰 놓으면, "AI가 흥미로운 부분을 처리한다"는 것은 자연스럽게 따라옵니다.
다음 단계로, 동일한 서버에 몇 가지 도구(tools)를 더 추가할 계획입니다. 예를 들어, 제가 이번 주에 무엇을 만들었는지에 대한 포스트를 자동으로 초안 작성할 수 있도록 제 GitHub 커밋 히스토리(commit history)를 가져오는 도구 같은 것 말이죠.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기