문제 생겼다! 사용자 입장에서 배우는 디버깅과 트러블슈팅
MCP를 쓰다 보면 꼭 한 번은 이런 순간이 옵니다.
"분명 어제까지 됐는데 오늘은 왜 안 되지?" "도구는 보이는데 왜 실행이 안 되지?" "이 에러 코드, 사람한테 하는 말 맞나?"
처음에는 에러 메시지가 공포 영화 자막처럼 보입니다. Method not found, 401, 403, spawn ENOENT, connection closed 같은 문구가 툭 튀어나오면, 괜히 내가 뭘 엄청 잘못한 것 같죠. 하지만 대부분의 문제는 의외로 범인이 뻔합니다. 경로가 틀렸거나, 권한이 없거나, 설정이 잘못됐거나, 서버가 생각보다 수다스러워서 표준 출력에 쓸데없는 말을 했거나 말입니다.
이 장의 목표는 단순합니다. 에러를 무서워하지 않고, 단계별로 쪼개서 보는 눈을 만드는 것입니다. 이 눈이 생기면 MCP 문제는 "알 수 없는 재앙"이 아니라 "체크리스트로 잡는 생활형 사고"가 됩니다.
디버깅은 추리가 아니라 순서다
문제가 생기면 많은 사람이 바로 설정 파일을 열고, 패키지를 다시 설치하고, 앱을 껐다 켰다가, 인터넷에서 비슷한 에러를 검색합니다. 물론 그중 하나가 맞을 수도 있습니다. 하지만 더 빠른 방법은 어디에서 망가졌는지 먼저 구분하는 것입니다.
MCP 문제는 보통 다섯 단계 중 하나에서 생깁니다.
서버가 아예 시작되지 않는다.
서버는 시작됐지만 클라이언트가 연결하지 못한다.
연결은 됐지만 도구 목록이 보이지 않는다.
도구는 보이는데 실행이 실패한다.
실행은 되지만 응답이 이상하거나 너무 늦다.
이 순서대로 보면 훨씬 편합니다. 마치 병원에서 접수도 안 했는데 수술실부터 찾지 않는 것과 비슷합니다.
먼저 해야 할 일: 증상을 한 줄로 적기
디버깅의 출발점은 감정이 아니라 기록입니다. "안 돼요"는 너무 큰 말입니다. 대신 이렇게 바꿔 적어 보세요.
Claude에서는 서버가 안 보인다.
Cursor에서는 서버는 연결됐는데 도구가 없다.
VS Code에서는 도구는 보이지만 호출하면 403이 난다.
특정 도구만 60초 뒤 타임아웃이 난다.
파일 읽기는 되는데 GitHub 검색만 실패한다.
이렇게 적으면 문제가 절반은 풀립니다. 왜냐하면 범위를 줄였기 때문입니다. MCP 전체가 고장 난 게 아니라, 어느 단계의 어느 기능이 고장 났는지가 보이기 시작하거든요.
1단계 문제: 서버가 아예 시작되지 않을 때
가장 흔한 첫 번째 사고는 "설정은 넣었는데 아무 일도 안 일어나는" 경우입니다. 이때는 보통 MCP 서버 프로세스 자체가 시작되지 않았습니다.
사용자 입장에서 확인할 첫 포인트는 아주 단순합니다. 설정 파일에 적은 실행 명령을 터미널에서 직접 돌려보는 것입니다. 예를 들어 설정에 node /절대경로/server.js가 들어 있다면, 그 명령을 그대로 복사해 터미널에서 실행해 봅니다. 여기서 바로 에러가 보이면, 문제는 MCP가 아니라 실행 환경입니다.
자주 만나는 원인은 대체로 이렇습니다.
node,python,npx경로가 실제와 다르다.서버 파일 경로가 상대 경로라 앱이 못 찾는다.
필요한 패키지가 설치되지 않았다.
JSON 설정 파일 문법이 틀렸다.
앱을 "다시 시작한 것 같지만 사실 완전히 종료되지 않았다."
특히 이 마지막이 의외로 자주 나옵니다. Windows에서는 창을 닫아도 백그라운드 프로세스가 남아 있는 경우가 있습니다. 설정을 바꿨다면 정말로 완전히 종료한 뒤 다시 실행하는 습관이 필요합니다.
여기서 중요한 원칙 하나만 기억하면 됩니다. MCP 설정에서는 상대 경로보다 절대 경로가 안전하다는 점입니다. 터미널에서는 되던 명령이 Claude나 Cursor 안에서는 안 되는 이유 중 하나가, 앱의 작업 디렉터리 기준이 다르기 때문입니다.
2단계 문제: 연결 실패와 "서버 끊김" 메시지
서버가 시작은 됐는데 disconnected, connection closed, spawn ENOENT, could not attach, timeout 같은 메시지가 뜨면 이제는 연결 단계 문제입니다.
이 단계에서는 "서버가 있느냐"보다 클라이언트가 서버와 말이 통하느냐가 핵심입니다. 여기서 제일 흔한 원인은 세 가지입니다.
첫째, 실행 경로 문제입니다. 특히 node나 python을 버전 관리 도구로 설치했다면, 터미널에서 보이는 경로와 앱 내부에서 찾는 경로가 다를 수 있습니다. 이럴 때는 node라는 이름만 쓰지 말고, 실제 실행 파일의 절대 경로를 넣는 쪽이 훨씬 안전합니다.
둘째, 전송 방식이 안 맞는 경우입니다. 로컬 서버는 보통 stdio를 쓰고, 원격 서버는 HTTP 계열 전송을 씁니다. 그런데 클라이언트가 HTTP처럼 붙으려는데 서버는 stdio 프로그램이거나, 반대로 오래된 HTTP+SSE 서버에 최신 방식으로 붙으려 하면 연결은 실패합니다. 이때는 "URL이 맞는가?"만 볼 게 아니라 어떤 transport를 쓰는지도 같이 봐야 합니다.
셋째, 표준 출력 오염입니다. 이름은 어려운데 뜻은 쉽습니다. stdio 방식에서는 서버가 표준 출력(stdout)에 오직 JSON-RPC 메시지만 써야 합니다. 그런데 서버가 시작하면서 Server starting... 같은 일반 로그를 stdout으로 뿜어버리면, 클라이언트 입장에서는 대화 도중 뜬금없는 잡음이 들어온 셈이 됩니다. 그러면 연결이 바로 끊기거나 이상해집니다.
이 경우 사용자가 할 수 있는 일은 두 가지입니다. 먼저 로그를 확인해 정말 이 증상인지 파악합니다. 그다음 직접 만든 서버가 아니라면 공식 서버 버전이나 검증된 서버로 바꿔 테스트해 봅니다. 공식 서버는 이런 기본 실수가 적기 때문입니다.
연결 문제를 가장 빨리 잡는 체크 순서
연결 문제가 의심되면 아래 순서대로 보면 좋습니다.
설정 파일의 JSON 문법이 맞는지 확인한다.
command와args에 절대 경로를 사용한다.같은 명령을 터미널에서 직접 실행해 본다.
앱을 완전히 종료하고 다시 시작한다.
로그 파일에서
spawn,ENOENT,closed,timeout같은 키워드를 찾는다.
이 다섯 가지를 했는데도 안 되면, 그다음부터는 "내가 뭘 놓쳤지?"보다 "서버 구현 또는 클라이언트 호환성 문제인가?"를 의심해 볼 차례입니다.
3단계 문제: 서버는 연결됐는데 도구가 안 보일 때
이 단계는 은근히 사람을 헷갈리게 합니다. 연결은 성공했다고 뜨는데, 정작 쓸 도구가 안 보이거나 일부만 보이는 경우죠.
여기서 중요한 사실은, 연결 성공과 도구 사용 가능은 같은 말이 아니라는 점입니다. 서버와 악수는 했지만, 자기소개를 제대로 못 했을 수 있습니다.
이런 경우는 보통 다음과 같은 이유로 생깁니다.
서버가 도구 스키마를 잘못 내보낸다.
클라이언트가 특정 스키마 형태를 제대로 지원하지 못한다.
도구 등록은 되었지만 초기화 이전이나 직후에 문제가 생겼다.
클라이언트와 서버의 프로토콜 버전 호환성이 어긋난다.
사용자 입장에서 가장 현실적인 대응은 같은 서버를 다른 클라이언트나 Inspector에서 확인해 보는 것입니다. 예를 들어 Claude에서는 안 보이는데 Inspector에서는 보인다면, 서버가 완전히 고장 난 것이 아니라 클라이언트 호환성 문제일 가능성이 커집니다.
또 한 가지 실전 팁이 있습니다. 도구가 하나도 안 보일 때는 거창한 프롬프트를 던지지 말고, 아주 기초적인 질문으로 확인해 보세요.
"현재 연결된 MCP 도구 목록을 보여줘."
이 질문은 문제를 해결하진 않지만, 적어도 모델이 도구 자체를 인식하고 있는지 빠르게 확인하게 해 줍니다.
스키마 오류는 왜 무섭게 느껴질까
스키마는 "이 도구가 어떤 입력을 받는지 설명하는 설계도"입니다. 사용자는 설계도를 직접 쓰지 않더라도, 설계도가 틀리면 피해를 바로 받습니다.
예를 들어 이런 일이 벌어집니다.
도구가 목록에서 사라진다.
도구는 보이는데 실행 버튼이나 입력 폼이 이상하다.
문자열을 넣어야 하는데 숫자를 기대한다.
배열이나 enum 같은 복잡한 타입에서 클라이언트가 헷갈린다.
이럴 때 사용자가 기억해야 할 핵심은 하나입니다. 내 질문이 나빠서 실패한 게 아니라, 도구 설명서가 클라이언트에 제대로 읽히지 않았을 수 있다는 점입니다.
특히 어떤 클라이언트는 복잡한 JSON Schema를 다루는 데 약합니다. 그래서 도구가 안 보이거나 잘못 보인다면, 서버 개발자에게 "이 도구의 입력 스키마가 단순한지", "특정 클라이언트에서 검증했는지"를 물어보는 게 도움이 됩니다.
사용자 입장에서는 일단 입력을 단순하게 줄이는 것이 좋습니다. 긴 중첩 객체를 한 번에 넘기기보다, 가능한 한 짧고 평평한 인자를 사용하는 도구부터 시험해 보는 편이 안전합니다.
4단계 문제: 권한이 막힐 때
MCP에서 권한 문제는 대개 두 얼굴로 나타납니다. 하나는 로컬 권한 문제, 다른 하나는 원격 인증 문제입니다.
로컬 권한 문제는 파일 시스템, 로컬 데이터베이스, 로컬 Git 저장소에서 자주 생깁니다. 예를 들어 filesystem 서버를 연결했는데 어떤 폴더는 읽히고 어떤 폴더는 안 읽히는 경우가 그렇습니다. 이때는 대개 서버가 허용한 경로 밖을 읽으려 했거나, 운영체제 자체 권한이 막고 있습니다.
원격 인증 문제는 GitHub, 사내 API, 클라우드 데이터베이스, 원격 검색 서버 같은 곳에서 자주 나옵니다. 이때는 401 Unauthorized, 403 Forbidden 같은 메시지가 흔합니다.
둘의 차이는 이렇습니다.
401은 "신분증이 없거나 틀렸다"에 가깝고, 403은 "신분증은 봤는데 이 방에는 못 들어간다"에 가깝습니다.
즉, 401은 토큰이나 API 키 자체를 먼저 확인해야 하고, 403은 권한 범위나 허용 정책을 봐야 합니다.
권한 문제를 만났을 때의 현실적인 질문
권한 문제는 감으로 풀리지 않습니다. 아래 질문을 차례대로 해보면 좋습니다.
이 서버가 접근하도록 허용된 경로나 리소스가 정확히 무엇인가?
내 API 키나 토큰이 실제로 설정되어 있는가?
GUI 앱이 셸 환경 변수를 자동으로 물려받는다고 착각한 건 아닌가?
읽기 권한만 있는데 쓰기 작업을 요청한 건 아닌가?
회사 네트워크, VPN, 방화벽 정책이 바뀐 건 아닌가?
특히 환경 변수는 함정입니다. 터미널에서는 API_KEY가 잘 잡히는데 Claude나 Cursor에서만 실패하는 경우가 있습니다. 이유는 간단합니다. GUI 앱이 셸에서 설정한 환경 변수를 그대로 상속하지 않는 경우가 있기 때문입니다. 이때는 설정 파일의 env 항목처럼 앱이 실제로 전달하는 방식으로 넣어야 합니다.
5단계 문제: 응답이 이상하거나 너무 느릴 때
여기까지 왔다면 연결은 대충 된 겁니다. 이제부터는 "되는 것 같긴 한데 이상한" 구간입니다. 가장 흔한 증상은 다음과 같습니다.
요청이 오래 걸리다가 타임아웃 난다.
결과가 비어 있다.
엉뚱한 형식으로 답이 온다.
중간에 연결이 끊긴다.
큰 파일이나 큰 쿼리에서만 실패한다.
이 문제는 서버 성능이나 구현 문제일 수도 있지만, 사용자 쪽에서 요청을 더 잘게 쪼개면 해결되는 경우도 많습니다.
예를 들어 "프로젝트 전체를 분석해서 문제를 다 찾아줘"는 사람에게도 버거운 부탁입니다. MCP 서버에도 마찬가지입니다. 대신 이렇게 나누면 훨씬 안정적입니다.
먼저 "루트 폴더 파일 목록만 보여줘", 그다음 "src 폴더만 읽어줘", 그다음 "에러가 나는 파일 한 개만 열어줘", 마지막으로 "이 함수만 설명해줘".
이런 식으로 범위를 줄이면 타임아웃과 대용량 응답 문제를 피할 수 있습니다. 디버깅에서 범위 축소는 거의 만능 기술입니다.
타임아웃은 서버가 느리다는 뜻일까
꼭 그렇지는 않습니다. 타임아웃은 "정해진 시간 안에 응답을 못 받았다"는 뜻이지, 무조건 서버가 나쁘다는 뜻은 아닙니다.
원인은 여러 가지입니다.
외부 API가 느리다.
DNS 또는 네트워크 상태가 나쁘다.
한 번에 너무 큰 데이터를 처리한다.
서버 내부에서 무한 대기나 데드락이 생긴다.
클라이언트의 대기 시간이 너무 짧다.
사용자 입장에서는 먼저 요청을 작게 만들고, 같은 도구를 아주 단순한 입력으로 시험해 보는 것이 좋습니다. 짧은 입력은 성공하고 긴 입력만 실패한다면, 문제는 "연결"이 아니라 "처리량"일 가능성이 큽니다.
로그는 무서운 문학이 아니라 힌트 모음집이다
많은 사람이 로그를 보면 바로 겁먹습니다. 영어고, 길고, 파일 경로도 잔뜩 나오니까요. 하지만 로그는 생각보다 읽는 법이 간단합니다.
첫째, 맨 마지막 줄만 보지 말고 첫 에러가 나온 지점을 봅니다. 뒤쪽은 앞 에러의 여파인 경우가 많습니다.
둘째, 코드와 키워드를 먼저 봅니다. 예를 들면 ENOENT, 401, 403, Method not found, Invalid params 같은 것들입니다. 어떤 구현체는 여기에 자체 에러 코드까지 덧붙이기도 합니다.
셋째, 이게 어느 층의 문제인지 구분합니다. 실행 문제인지, 연결 문제인지, 권한 문제인지, 호출 문제인지 구분하면 로그가 훨씬 덜 무섭습니다.
아래 표는 자주 보는 메시지를 사람이 읽는 말로 바꿔 놓은 것입니다.
| 메시지 | 쉬운 해석 | 먼저 할 일 |
|---|---|---|
Spawn ENOENT | 실행 파일을 못 찾음 | node, python, npx 경로를 절대 경로로 바꾸기 |
Connection closed | 연결이 중간에 끊김 | 서버 직접 실행, stdout 오염 여부, 즉시 종료 여부 확인 |
Request timed out | 시간 안에 응답 못 받음 | 요청 범위 줄이기, 네트워크 확인, 큰 작업 쪼개기 |
401 Unauthorized | 인증 정보가 없거나 틀림 | 토큰, API 키, 헤더, env 설정 확인 |
403 Forbidden | 인증은 됐지만 권한 부족 | 허용 범위, 접근 정책, 서버 권한 확인 |
Method not found / -32601 | 지원하지 않는 메서드 호출 | 클라이언트-서버 호환성, 서버 기능 지원 범위 확인 |
| 도구가 안 보임 | 스키마 또는 호환성 문제 가능 | Inspector나 다른 클라이언트에서 확인 |
이 표만 머리에 넣어도 로그가 훨씬 덜 차갑게 느껴집니다.
Claude, VS Code, Cursor에서 확인할 것
사용자 입장에서 디버깅은 결국 어디에서 로그와 상태를 보느냐의 문제이기도 합니다.
Claude 계열 앱에서는 연결된 서버 상태, 도구 표시 여부, 로그 파일 확인이 핵심입니다. 설정을 바꿨다면 앱을 완전히 종료한 뒤 다시 켜는 것도 중요합니다. 특히 로컬 서버는 설정 반영이 생각보다 즉시 되지 않는 경우가 있습니다.
VS Code나 Cursor 같은 IDE 계열에서는 출력 패널이나 MCP 관련 채널을 보는 습관이 필요합니다. 채팅창에서 "안 됨"이라고만 보이는 문제가 출력 패널에서는 훨씬 구체적인 메시지로 남아 있는 경우가 많습니다.
즉, 대화창은 증상, 로그는 원인 후보라고 생각하면 편합니다.
가장 강력한 기술: 최소 재현 만들기
문제가 복잡할수록 해결은 단순화에서 시작합니다. 이것을 개발자들은 "최소 재현"이라고 부르는데, 사용자에게도 아주 강력한 기술입니다.
예를 들어 GitHub 서버, filesystem 서버, DB 서버를 한 번에 붙여 놓고 "왜 안 되지?"라고 보면 너무 복잡합니다. 대신 이렇게 줄입니다.
서버를 하나만 켠다.
도구를 하나만 시험한다.
입력을 한 줄로 줄인다.
읽기 작업부터 시도한다.
큰 파일 대신 아주 작은 파일로 시험한다.
이렇게 하면 어느 순간 "아, 전체가 안 되는 게 아니라 DB 서버만 안 되는구나" 또는 "검색은 되는데 쓰기만 막히는구나" 같은 사실이 드러납니다. 문제의 정체가 드러나는 순간, 공포감도 같이 줄어듭니다.
Inspector는 사용자에게도 유용한 현미경이다
MCP Inspector는 개발자만 쓰는 물건처럼 보이지만, 실제로는 사용자 입장에서도 문제를 분리해 보는 도구로 매우 유용합니다.
왜 좋으냐 하면, Claude나 Cursor 같은 호스트 앱을 거치지 않고 서버 자체가 무엇을 내놓는지 직접 확인할 수 있기 때문입니다. 서버 연결, 도구 목록, 스키마, 실제 응답을 눈으로 볼 수 있습니다.
만약 앱에서는 안 되는데 Inspector에서는 된다면, 서버가 완전히 망가진 게 아닐 가능성이 큽니다. 반대로 Inspector에서도 안 된다면, 서버 쪽 문제일 확률이 높아집니다.
쉽게 말해 Inspector는 "문제가 내 앱에 있는지, 서버에 있는지"를 가르는 분기점 역할을 합니다.
원시 테스트도 가끔은 답이다
조금만 익숙해지면 터미널에서 아주 작은 JSON-RPC 요청을 보내 보는 것도 도움이 됩니다. 물론 모든 사용자가 이렇게까지 할 필요는 없습니다. 하지만 문제를 빠르게 분리하고 싶을 때는 정말 강력합니다.
예를 들어 도구 목록만 확인하는 작은 테스트는 "도구 등록은 정상인가?"를 바로 보여줍니다. 앱 안에서는 모델의 추론, UI 표시, 권한 정책이 함께 얽히지만, 원시 테스트에서는 서버의 기본 반응만 볼 수 있기 때문입니다.
즉, 복잡할수록 더 낮은 층으로 내려가 확인하는 것이 좋습니다. 멋있어 보이진 않지만, 정말 잘 먹힙니다.
프롬프트를 바꾸면 고쳐지는 문제도 있다
이건 의외지만 사실입니다. MCP 문제처럼 보였는데, 사실은 질문이 너무 넓어서 모델이 적절한 도구를 고르지 못한 경우가 있습니다.
예를 들어 "내 프로젝트 문제 좀 봐줘"보다 "filesystem 도구를 사용해서 src/config.ts 파일만 읽고 오류 가능성을 알려줘"가 훨씬 안정적일 수 있습니다.
물론 항상 도구 이름을 강제로 지정할 수 있는 것은 아니지만, 요청 범위를 줄이고 대상 파일이나 리소스를 명확히 적으면 성공률이 올라갑니다. 이전 장에서 배운 질문법이 여기서도 살아납니다. 좋은 질문은 디버깅 도구이기도 합니다.
이건 내 잘못이 아닐 수도 있다
사용자는 종종 모든 문제를 자기 탓으로 돌립니다. 하지만 MCP에서는 실제로 서버 구현 문제나 클라이언트 버그도 적지 않습니다.
다음과 같은 경우에는 내 설정만 의심하지 말고, 서버나 클라이언트 이슈를 적극적으로 생각해 봐도 됩니다.
같은 설정으로 어제까지 되다가 클라이언트 업데이트 후 갑자기 안 된다.
한 클라이언트에서는 되는데 다른 클라이언트에서는 안 된다.
특정 도구만 사라지거나 특정 타입의 입력만 실패한다.
공식 예제와 똑같이 했는데도 특정 버전에서만 깨진다.
로그를 봐도 내 경로나 토큰 문제가 아니라 프로토콜/스키마 관련 오류가 나온다.
이때는 버전 업데이트나 다운그레이드, 다른 클라이언트에서 재현, 이슈 트래커 확인이 오히려 정답일 수 있습니다.
문제를 설명하는 법도 실력이다
혼자 해결이 안 될 때는 질문을 잘 던지는 것이 중요합니다. "MCP 안 돼요"보다 아래처럼 말하면 도움을 훨씬 빨리 받을 수 있습니다.
사용한 클라이언트: Claude Desktop / Cursor / VS Code
운영체제: macOS / Windows / Linux
사용한 서버: filesystem / GitHub / 직접 만든 서버
증상: 연결 실패 / 도구 미표시 / 403 / 타임아웃
마지막으로 성공했던 시점: 어제 / 특정 업데이트 전
이미 해본 것: 앱 재시작, 절대 경로 변경, 직접 실행, 로그 확인
핵심 로그 한두 줄
좋은 문제 설명은 상대의 시간을 아껴 줍니다. 그리고 신기하게도, 이렇게 정리하다 보면 스스로 답을 찾는 경우도 많습니다.
기억해 둘 사용자용 트러블슈팅 루틴
마지막으로, 실제로 문제를 만났을 때 바로 꺼내 쓸 수 있는 루틴을 정리해 보겠습니다.
증상을 한 줄로 구체화한다.
서버가 시작되는지 터미널에서 직접 확인한다.
설정 파일의 경로와 JSON 문법을 점검한다.
앱을 완전히 종료하고 다시 실행한다.
로그에서 에러 코드와 첫 실패 지점을 찾는다.
서버 하나, 도구 하나, 입력 하나로 최소 재현을 만든다.
요청 범위를 줄여서 타임아웃과 대용량 문제를 분리한다.
권한 문제라면 401과 403을 구분한다.
가능하면 Inspector로 서버 자체를 따로 확인한다.
그래도 안 되면 버전 호환성이나 클라이언트 버그를 의심한다.
이 루틴을 몸에 익히면, 에러 메시지가 더 이상 초자연 현상이 아닙니다. 그냥 "어느 층에서 멈췄는지 알려주는 표지판"이 됩니다.
에러 메시지가 덜 무서워졌다면, 이미 실력이 늘었다
MCP를 잘 쓰는 사람은 에러를 안 만나는 사람이 아닙니다. 에러를 봤을 때 당황하지 않고 층을 나눠 보는 사람입니다.
연결 실패는 연결 문제로, 권한 거부는 권한 문제로, 도구 미표시는 스키마나 호환성 문제로, 타임아웃은 범위와 처리량 문제로 나눠 볼 수 있으면 이미 절반은 이긴 것입니다.
이제 낯선 에러가 보여도 심장이 철렁 내려앉기보다는 이런 생각이 들 겁니다.
"좋아, 넌 연결 단계 쪽이구나."
그 순간부터 MCP 디버깅은 공포 영화가 아니라, 꽤 풀 만한 추리 게임이 됩니다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.