
Anthropic의 AI (Opus)가 자사 규격인 MCP 서버를 실제 버그를 수정하면서 망가뜨린 이야기
요약
Claude Opus가 MCP(Model Context Protocol) 서버의 버그를 수정하는 과정에서 오히려 서버를 기동 불능 상태로 만든 사례를 분석합니다. AI 모델의 코드 수정 능력이 실제 런타임 환경을 검증하지 못해 발생할 수 있는 위험성을 다룹니다.
핵심 포인트
- Claude Opus가 버그를 수정했으나 MCP SDK 기동을 방해하는 치명적 오류 발생
- 모델의 검증 단계가 import 단계에 머물러 실제 서버 기동 여부를 확인하지 못함
- AI가 생성한 코드의 런타임 안정성을 보장하기 위한 추가적인 검증 필요성
- Obsidian과 Claude Code Hook을 활용한 AI 대화 기록의 체계적 관리 방법
아키타의 사무직입니다. 평소에는 Excel VBA 매크로를 Claude Code와 대화하며 만들고 있습니다. 직접 만든 VBA 관리 도구(shu-vba-manager)를 MCP 서버로 만들어, 열려 있는 Excel과 AI를 연결하여 사용하고 있습니다.
본론에 들어가기에 앞서, 최근 도입한 도구에 대해 잠시 이야기하겠습니다. 이번 기사는 그 도구가 없었다면 쓸 수 없었을 것입니다.
Obsidian을 도입했습니다.
Obsidian은 로컬에서 작동하는 메모 앱입니다. 내용은 단순한 Markdown 파일의 집합이며, 이를 보관소(Vault)라고 부르는 폴더에 저장하여 전체 검색과 메모 간의 링크로 오갈 수 있게 해줍니다. 클라우드에 맡기지 않는다. 독자적인 형식에 갇히지 않는다. 앱이 사라져도 10년 뒤에도 단순한 텍스트로서 읽을 수 있다. 기록을 오래 남기고 싶은 사람에게는 이 수수함이 가장 효과적입니다.
그리고 Claude Code에는 후크(Hook)라는 메커니즘이 있어서, 세션이 끝날 때마다 원하는 스크립트를 실행할 수 있습니다. 저는 그곳에 "대화 전문을 Markdown으로 만들어 보관소에 쓰기" 처리를 심어두었습니다. 이후로 AI와의 대화는 단 한 마디도 사라지지 않습니다. 무엇을 부탁했는지, AI가 무엇이라 답했는지, 어떤 성공 보고를 했고, 나중에 무엇을 자백했는지——전부 날짜가 붙은 파일로 수중에 남습니다.
게다가 보관소는 단순한 파일의 집합이기에, 제가 10년 이상 키워온 파일 목록 도구(FileList-Excel)에서 그대로 목록으로 불러와 업데이트 날짜순으로 정렬하여 관리할 수 있습니다. 독자적인 형식에 갇히지 않는 도구는 제가 가진 도구들과 이렇게 멋대로 연결됩니다.
즉 이런 것입니다. AI와의 대화는 모두 증거로서 남는다.
도입했을 때는 작업 기록용이라고 생각했습니다. 설마 첫 번째 큰 업무가 "사고의 증거품 창고"가 될 줄은 몰랐습니다.
지난 기사의 후반부에서, "Opus에게 점검을 부탁했더니 많이 고쳐졌고, 많이 망가졌다"라는 이야기를 불렛 포인트로 적었습니다. 그중 단 한 건, 격이 다른 것이 섞여 있었습니다.
MCP 서버가 기동 불능 상태가 되는 치명상입니다.
MCP는 Anthropic이 만든 규격입니다. 그것을 Anthropic의 상위 모델인 Claude Opus 4.8(이 기사에서는 이후 Opus)이, 그것도 실제 버그를 고치는 그 손으로 망가뜨렸습니다. 이번에는 이 한 건만을 깊이 파고들겠습니다. 증거는 전부 남아 있습니다. 커밋(Commit) 3개와, 보관소에 원문 그대로 잠들어 있던 본인의 자백 로그입니다.
버전까지 적어두는 이유는 이 분야의 세대교체가 빠르기 때문입니다. 언제의, 어느 세대 모델 이야기인지는 이런 종류의 기사의 유통기한과 관련이 있습니다.
- Opus는 "워커의 출력이 JSON-RPC 통신선으로 새어 나간다"라는 실제 버그를 찾아내고 고쳤다.
- 그 수정으로 설정한 프록시 객체에
buffer속성이 없어, MCP SDK가 기동되는 순간 추락하는 상태가 되었다. - Opus의 검증은 import 단계에서 멈춰서, 서버가 기동되는지를 한 번도 확인하지 않고 "모두 통과"라고 보고했다.
- 30분 후, 다른 AI (Fable)가 "폭발 반경이 큰 순서"의 긴급 점검으로 단번에 찾아내어 11줄로 고쳤다.
- 실해는 제로. 다만 Fable이 보지 않았다면, 다음 날 아침 도구는 통째로 죽어 있었을 것이다.
2026년 7월 15일 심야, 리포지토리에는 이 순서로 커밋이 쌓였습니다.
| 시각 | 커밋 | 내용 |
|---|---|---|
| 02:47 | 052b282 | Opus가 철저 점검으로 실해 52건을 수정 (13개 파일 · 약 1,400행) |
| 03:13 | 04d9ddc | 요청하지 않은 가드(Guard) 6건을 철거 (Opus 스스로 지적을 받고) |
| 03:16 | 2c3638b | Fable이 기동 불능의 치명상을 수정 (11행) |
어떤 모델의 작업인지는 커밋 서명에 그대로 남아 있습니다. 가드 철거 커밋에는 Co-Authored-By: Claude Opus 4.8이, 복구 커밋에는 Co-Authored-By: Claude Fable 5가 적혀 있습니다. 망가뜨린 쪽도 고친 쪽도 실명이 들어 있습니다.
02:47 시점에 MCP 서버는 죽어 있었습니다. 03:16에 살아났습니다. 이 29분 동안 저는 그것을 알지 못했습니다. 망가졌다는 것도, 고쳐졌다는 것도, 다음 날 아침 도구를 실행하면 알 수 없는 채로 끝났을 이야기입니다.
이 부분이 이번에 가장 쓰고 싶었던 대목입니다. Opus는 대충 일해서 망가뜨린 것이 아닙니다. 올바른 문제를 찾아내고, 그것을 고치는 수정으로 인해 망가뜨린 것입니다.
저의 MCP 서버는 Excel과의 통신이 멈췄을 때 워커(Worker) 스레드를 버리고 세대교체를 하는 구조로 되어 있습니다. 그런데 버려진 워커가 나중에 출력을 내뱉으면, 그것이 표준 출력(Standard Output) 즉, JSON-RPC 통신선으로 그대로 새어 나가는 허점이 있었습니다. MCP의 stdio 방식은 표준 출력이 곧 통신선이기 때문에, 그곳에 관계없는 텍스트가 섞이면 프로토콜이 어긋납니다. 이것은 실제로 존재하는 버그였고, 수정해야 할 대상이었습니다.
Opus는 sys.stdout을 교체하는 기존 방식을 버리고, 스레드별로 출력 대상을 분배하는 대리 객체(_ThreadRouted)를 기동 시점에 단 한 번 배치하는 설계로 변경했습니다. JSON-RPC 선과 작업(Job)의 출력을 입구에서 분리하는 것. 생각의 논리는 타당합니다.
_ThreadRouted는 io.TextIOBase로부터 만들어졌습니다. 이 기저 클래스(Base Class)에는 buffer 속성이 없습니다.
그리고 MCP SDK의 stdio 서버는, 기동 시에 sys.stdin.buffer와 sys.stdout.buffer를 잡으러 옵니다.
즉, 다음과 같은 상황이 됩니다.
서버 기동
→ SDK가 `sys.stdin.buffer`를 참조
→ 그곳에 있는 것은 `buffer`를 가지지 않는 대리 객체
...
통신이 어긋나거나 가끔 떨어지는 식의 고장이 아닙니다. 모든 커맨드의 입구가 열리지 않습니다. 제 도구에는 75개의 커맨드가 있는데, 그 전부가 동시에 죽는 지점을 정확히 찔러버린 것입니다.
발각된 후 Opus의 말이 보관함의 세션 로그에 원문 그대로 남아 있습니다. 다음 인용은 모두 거기서 복사한 것입니다.
저의 오프라인 검증은
import단계에서 멈춰 있었습니다. 스레드별 출력 분배가 올바르게 작동하는지를 4개 항목으로 테스트하여 "모두 통과"라고 보고했지만, 서버가 기동되는 경로를 단 한 번도 거치지 않았습니다.
이 정도라면 "검증이 안일했다"로 끝날 이야기입니다. 질이 나쁜 것은 여기서부터입니다. 이날의 Opus는 다른 수정 사항에 대해서는 실기(Real Machine) 검증을 수행하고 있었습니다. 다이얼로그 모니터링은 실기로 수행했고, 보호 해제는 실기로 측정했으며, 성능은 벤치마크까지 돌렸습니다. 가지와 잎은 실탄으로 확인하면서, 줄기(Trunk)만 확인하지 않았던 것입니다.
"실탄으로 확인했습니다"라고 몇 번이나 말하면서, 가장 중요한 "기동되는가"를 쏘지 않았던 셈입니다.
또 하나. Opus는 이날 "실기로 돌리지 않은 부분"의 미확인 리스트를 스스로 작성하여 보고했습니다. 자각은 하고 있었던 것입니다. 하지만——
미확인 리스트를 만들 정도로 자각은 있었지만, 그 리스트에 "MCP 서버의 기동"이 들어있지 않았습니다. 검증했다고 믿고 있었던 만큼, 이쪽이 더 위험합니다.
미검증 상태임을 자각하고 있는 곳은 아직 경계할 수 있습니다. 정말로 위험한 것은 "검증했다"라고 본인이 믿고 있는 곳이었습니다.
찾아낸 것은 인간이 아니라, 다른 AI였습니다. 그날 작업이 끝날 무렵, 저는 Fable(Claude의 다른 모델)에게 "사용량이 얼마 남지 않았으니, 굉장히 중요한 부분만 체크해 줘"라고 부탁했습니다.
Fable의 답변은 방침 선언부터 시작되었습니다.
폭발 반경이 가장 큰 곳으로만 좁혀서 보겠습니다. 즉 "망가졌을 때 모든 커맨드가 죽는" 지점입니다.
살펴본 곳은 단 3곳뿐이었습니다. 그중 첫 번째 지점에서 정답을 맞혔습니다. 수정은 buffer와 fileno를 실제 스트림으로 전달하는 속성을 추가하는 것뿐인, 11줄짜리 작업이었습니다. SDK가 기동 시에 수행하는 것과 동일한 절차를 거치는 것을 확인하고, push 하여 끝냈습니다.
52건을 수정한 쪽은 52건을 나란히 늘어놓고 취급하고 있었습니다. Opus 스스로도 나중에 인정한 사실입니다.
저는 52건을 나란히 취급하며, 어떤 것이 치명적인지에 대한 가중치를 두지 않았습니다.
단 3곳만 본 쪽이 승리한 것은 지능의 차이가 아닙니다. 의심하는 순서의 차이입니다. Fable은 "Opus가 검증했다고 말한 지점이 정말로 검증되었는가"부터 시작했습니다. 성공 보고를 믿지 않았습니다. 단지 그뿐입니다.
"검증했습니다"라는 말은 무엇을 검증했는지에 대한 리스트와 세트로 존재해야 의미가 있습니다. "모두 통과"의 "모두"에 기동 테스트가 포함되어 있지 않은 상황이 실제로 발생합니다. AI의 성공 보고를 받으면, 무엇을 쏘았고 무엇을 쏘지 않았는지를 확인해야 합니다. 쏘지 않은 리스트에 줄기가 빠져 있지 않은지 확인해야 합니다.
점검은 폭발 반경이 큰 순서대로. 52건을 나란히 놓고 보는 것보다, "망가지면 전부 죽는 곳" 3군데를 보는 것이 더 강력합니다. 이는 AI에게 점검을 부탁할 때의 지시 사항으로 그대로 사용할 수 있습니다. 실제로 이 한마디로 부탁한 3군데 점검이, 52건 수정 과정에서 발생한 누락을 막아냈습니다.
MCP 서버의 계층은 "건드리지 않는 것을 전제로 한 얇은 계층"으로 두어야 합니다. 저의 서버는 창구 역할을 하는 도구 정의(tool definition)만을 가진 얇은 계층이며, 구현은 전부 도구 본체 측에 있습니다. 이번에도 그럼에도 불구하고 망가졌지만, 망가진 범위가 얇은 계층의 파일 1개에 국한되었기에 11줄로 고칠 수 있었다고도 할 수 있습니다. 배관(piping)에 비즈니스 로직(business logic)을 섞어 놓았다면 이렇게 되지 않았을 것입니다.
그리고 규격을 만든 회사의 AI이기 때문에 규격 구현을 안전하게 다룰 수 있다는 것은 아닙니다. Opus는 MCP의 사양(specification)을 알고 있습니다. 지식의 문제가 아닙니다. 망가뜨린 것은 "작동하고 있는 것은 건드리지 않는다", "건드렸다면 구동부터 확인한다"라는, 프로그래머라면 누구나 알고 있는 사소한 작법(etiquette) 측면이었습니다. 영리함과 자제력은 별개의 능력입니다.
사고 다음 날, Opus는 이렇게 남겼습니다.
오늘 가장 효과적이었던 대책은 페이블(Fable) 씨에게 검분을 맡긴 것이었습니다. 그것은 저를 의심했기에 실행할 수 있었던 수였습니다.
고친 것도 AI, 망가뜨린 것도 AI, 자백한 것도 AI입니다. 그 자백을 한 글자도 빠짐없이 기록해 둔 것은 Obsidian이었고, 인간의 일은 단 하나뿐이었습니다. 믿지 않는 것입니다.
AI와 함께 코드를 작성하는 생활은 벌써 2년 차에 접어들었습니다. 편리함에 대한 이야기는 연재를 통해 충분히 써왔으므로, 이번에는 반대편 이야기를 해두겠습니다. AI는 진짜 버그를 고치면서, 그 손으로 서버를 망가뜨릴 때가 있습니다. 그리고 "고쳤습니다, 모두 통과입니다"라고 보고해 옵니다. 거기에는 악의가 없습니다. 그렇기에 의심하는 절차를 시스템으로 만들어 두는 가치가 있습니다.
사건은 해결되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기