MCP 서버, 튜토리얼 다음이 진짜 설계다

MCP 서버, 튜토리얼 다음이 진짜 설계다

20분 만에 띄운 MCP 서버가 프로덕션에서 52%가 죽는 이유—테크 리드가 체크해야 할 6가지 운영 설계 원칙

MCP 서버 프로덕션 배포 SSRF 방어 에러 설계 AI 에이전트 보안 Claude Code npm 보안 권한 설계
광고

52%가 죽었다는 숫자가 말해주는 것

2026년 4월, 2,181개의 원격 MCP 엔드포인트를 스캔한 결과가 공개됐다. 52%는 완전히 죽어 있었고, 완전히 정상인 서버는 9%에 불과했다. 이 서버들은 누군가 '배포했고 동작할 것이라 믿었던' 것들이다. 프로토콜 버그 때문이 아니었다. 프로토콜 자체는 멀쩡하다. 죽인 건 프로토콜 바깥의 것들—에러 처리, 인증, 상태 관리, 보안—이었다.

튜토리얼은 npm install, 툴 등록, stdio 연결까지만 가르쳐준다. 20분이면 충분하다. 문제는 그다음이다. dev.to의 실무 가이드("The parts of building an MCP server that the tutorials skip")는 이 간극을 구체적인 코드와 함께 짚는다. 테크 리드 입장에서 이 글이 가리키는 지점들을 팀 운영 체크리스트로 재구성해봤다.

운영을 망치는 6가지 설계 누락

1. stdout은 프로토콜 채널이다

stdio 트랜스포트를 쓸 때 console.log를 단 한 줄만 잘못 써도 JSON-RPC 스트림이 오염된다. 클라이언트는 어디서 터진지 모를 파싱 에러를 뱉고 죽는다. 정답은 stderr로만 로그를 쓰는 것. 튜토리얼이 절대 알려주지 않는 이유는 튜토리얼 자체가 로그를 출력하지 않기 때문이다.

2. 에러 가독성은 모델을 위한 설계다

툴이 실패할 때 에이전트가 internal error만 받으면 재시도할지, 입력을 수정할지, 포기할지 판단할 수 없다. 결과: 에이전트가 실패한 호출을 루프로 재시도하며 API 예산을 밤새 태운다. 머신 리더블한 에러 코드([forbidden_host], [invalid_input])를 타입으로 정의하고, 모든 핸들러가 동일한 래퍼를 통과하게 만드는 것이 설계 의무다. 에러 가독성은 개발자가 아니라 모델을 위한 것이다.

3. URL을 받는 툴은 기본이 SSRF 프록시다

URL을 파라미터로 받아 fetch하는 툴을 만드는 순간, 호스트 allowlist 없이는 에이전트(혹은 프롬프트 인젝션에 당한 에이전트)가 내부 메타데이터 서버나 어드민 패널을 가리킬 수 있다. HTTPS 강제, 호스트 화이트리스트, 타임아웃—이 세 가지는 '보안 강화'가 아니라 최소 요건이다.

4. 인증은 fail-closed여야 한다

2026년 조사에 따르면 원격 MCP 서버의 절반 이상이 OAuth를 구현하지 못하고 정적 키로 후퇴했다. OAuth 실패는 조용하게 일어나 디버깅이 가장 어렵다. Bearer 토큰이라도 제대로 구현한다면, 핵심 원칙은 하나다: 토큰이 설정되지 않으면 모든 요청을 거부한다. '일단 열고 나중에 막자'는 없다. 열린 채로 배포된 MCP 서버는 인터넷에 툴을 공개하는 것과 같다.

5. 상태는 프로세스 메모리에 두지 않는다

52% 사망 엔드포인트의 상당수가 이 문제에서 비롯됐을 가능성이 높다. streamable HTTP 트랜스포트가 세션 상태를 인메모리로 들고 있으면, Lambda·Cloud Run·Fly처럼 스케일 투 제로가 되는 환경에서 다음 요청은 세션을 모르는 콜드 인스턴스에 떨어진다. 클라이언트는 에러도 없이 그냥 죽는다. 기본 설계는 요청마다 서버와 트랜스포트를 새로 빌드하는 무상태다. 교차 요청 상태가 필요해지면 그때 Redis를 붙여라. 절대 모듈 변수에 넣지 마라.

6. 실패 경로를 테스트하지 않으면 가드가 없는 것이다

대부분의 MCP 툴 테스트는 해피패스만 검증한다. 그러나 위 다섯 항목은 전부 '거부 동작'이다. SSRF 가드가 forbidden_host를 반환하는지, 인증 없이 실행 시 모든 요청을 거부하는지를 테스트로 고정해두지 않으면—리팩토링 한 번에 forbidden_hostinternal로 바뀌어도 타입 체커도 해피패스 테스트도 아무것도 잡아주지 않는다.

맥락: MCP가 팀 도구로 확장될수록 공격 면이 넓어진다

이 설계 누락들이 개인 해킹 프로젝트 수준에서는 무해하다. 문제는 MCP가 팀의 실제 워크플로우로 들어오는 순간이다.

NPMScan이 Claude Code와 Cursor를 위한 공개 MCP 서버를 출시했다("I Added an MCP Server to NPMScan for AI Coding Agents"). claude mcp add --transport http npmscan https://npmscan.com/api/mcp 한 줄이면 에이전트가 npm 패키지 취약점, 설치 스크립트, 보안 어드바이저리를 에디터 밖으로 나가지 않고 직접 조회할 수 있다. AI 에이전트가 package.json을 직접 수정하는 시대에, 모델이 추천하는 패키지의 보안 정보를 실시간으로 검증하는 MCP 툴은 선택이 아니라 워크플로우 기본 구성요소가 되어가고 있다.

그런데 이런 외부 MCP 서버를 팀 에이전트에 연결할 때, 그 서버가 위 6가지 설계를 갖추고 있는지는 사용자가 알 수 없다. 연결하는 순간 당신의 에이전트가 신뢰하는 컨텍스트가 된다.

권한 설계는 플랫폼마다 다르게 동작한다

Claude Code의 파일 업로드 권한 사례("My agent filled in every field on the form. It could not attach the three images.")는 다른 각도에서 같은 교훈을 던진다. 폼의 모든 텍스트 필드를 채우고 저장까지 했지만, 이미지 3장 첨부에서 에이전트가 멈췄다. file_upload 툴은 존재하지만, 데스크톱 앱과 CLI가 동일한 툴 이름으로 다른 권한 게이트를 구현하고 있었다.

CLI(claude --chrome)에서는 /add-dir로 폴더를 허용하면 즉시 해결됐다. 데스크톱 앱에서는 /add-dir가 "이 환경에서는 사용할 수 없습니다"를 반환했다. 같은 기계, 같은 확장, 다른 답. 각 문서는 거짓을 말하지 않았다—그냥 서로 다른 서피스를 설명하고 있었고, 둘 다 읽어야 겹치는 부분이 없다는 것을 알 수 있었다.

이것이 MCP를 팀 도구로 도입할 때 반드시 짚어야 할 지점이다: 툴의 이름이 같아도 실행 환경마다 권한 동작이 다르다. 팀원이 각자 다른 클라이언트(CLI, VS Code, 데스크톱 앱)를 쓰고 있다면, 같은 MCP 서버가 사람마다 다르게 동작할 수 있다.

테크 리드를 위한 프로덕션 체크리스트

세 사례를 합치면 팀이 MCP 서버를 프로덕션에 올리기 전 확인해야 할 최소 항목이 나온다:

  • 로깅: stdio 환경에서 stdout 출력 여부 감사
  • 에러 설계: 머신 리더블한 에러 코드 타입 정의 및 래퍼 구현
  • SSRF 방어: URL 파라미터를 받는 모든 툴에 호스트 allowlist + HTTPS 강제 + 타임아웃
  • 인증: fail-closed 기본값. 키 미설정 시 전체 거부
  • 상태 관리: 요청별 무상태 설계. 인메모리 세션 상태 금지
  • 테스트: 거부 동작(SSRF 차단, 인증 실패)을 머신 리더블 코드로 고정
  • 외부 MCP 서버 연결 전: 해당 서버의 인증·SSRF·에러 설계 검증
  • 클라이언트별 권한 매트릭스: 팀이 쓰는 모든 클라이언트 환경에서 동일 툴의 권한 동작 검증

전망: MCP 생태계가 커질수록 설계 부채가 먼저 터진다

MCP 서버를 만드는 건 쉬워졌다. 연결하는 것도 쉬워졌다. 그게 문제다. 생태계가 빠르게 팽창할수록, 위 6가지를 건너뛴 서버들이 팀의 에이전트 워크플로우 안으로 들어오는 속도도 빨라진다.

"튜토리얼 다음"은 기술적으로 어렵지 않다. 이미 이름이 붙은 문제들이고, 코드도 공개돼 있다. 어려운 건 인식이다—이 체크리스트가 필요하다는 사실을 처음 배포할 때 모른다는 것. 52%가 죽은 서버들이 증명한다. 다음번엔 당신 팀의 서버가 그 통계 안에 들어가지 않으면 된다.

출처

더 많은 AI 트렌드를 Seedora 앱에서 확인하세요