내 클라이언트의 오류를 측정하게 된 붉은색 읽기 결과
요약
MCP(Model Context Protocol) 서버 테스트 중 호환성 레인(compatibility lane)으로 인해 발생하는 잘못된 진단 문제를 다룹니다. 클라이언트가 최신 프로토콜 규격을 준수하지 않아 레거시 방식으로 응답받는 상황을 분석하고, 이를 정확히 판별하는 방법을 제시합니다.
핵심 포인트
- MCP 호환성 레인은 클라이언트의 잘못된 요청을 레거시 방식으로 처리함
- 핸드셰이크 성공 여부로 클라이언트의 프로토콜 준수 상태를 판단 가능
- 정확한 측정을 위해 최신 프로토콜 헤더 및 메타데이터 포함 필수
저는 어제 올바른 것을 측정하는 것을 중단한 후에도 계속 통과되는 체크들에 대해 게시물을 올렸습니다. 제가 다음에 수행한 읽기(reading)는 동일한 결함이 반대로 나타난 경우였습니다. 제 자신의 MCP 서버에 대한 요청이 붉은색(red, 오류)으로 돌아왔고, 그 붉은색은 제 요청에 관한 것이었습니다.
잘못된 이야기에 동의한 두 가지 응답
저는 해당 서버의 README를 작업하던 중, 서버가 무엇을 제공하는지 확인하기 위해 엔드포인트(endpoint)로 요청을 보냈습니다. 첫 번째는 Model Context Protocol (MCP)의 2026-07-28 개정판이 모든 서버에 구현하도록 요구하는 server/discover였습니다. 서버는 -32601 Method not found라고 응답했습니다. 그래서 저는 해당 개정판에서 제거된 핸드셰이크(handshake)인 프로토콜 버전 2025-06-18을 사용하여 initialize를 보냈고, 서버는 서버의 정체와 함께 200 응답을 보냈습니다.
액면 그대로 받아들인다면, 이는 마이그레이션(migration)을 수행하지 않은 서버입니다. 필수적인 새로운 요청은 누락되었고, 제거된 핸드셰이크는 여전히 작동합니다. 마이그레이션은 그날 아침에 배포되었습니다.
서버는 옳았고 내 요청은 틀렸다
새로운 레인(lane)에 도달하려면 세 가지가 동시에 충족되어야 하는데, 저는 그중 어느 것도 갖추지 못했습니다. 요청에는 MCP-Protocol-Version: 2026-07-28 헤더가 포함되어야 합니다. 또한 본문(body)과 동일한 메서드를 지정하는 Mcp-Method 헤더를 포함해야 합니다. 그리고 params._meta에는 io.modelcontextprotocol/protocolVersion과 io.modelcontextprotocol/clientCapabilities 키가 포함되어야 합니다. 이 중 아무것도 보내지 않아도 요청이 잘못된 형식(malformed)인 것은 아닙니다. 그것은 레거시(legacy) 요청이며, 라이브러리의 호환성 레인(compatibility lane)이 이를 올바르게 응답합니다. 왜냐다면 올바르게 응답하는 것이 바로 그 레인이 존재하는 목적이기 때문입니다.
이 부분이 바로 멈춰서 생각할 가치가 있는 지점입니다. 호환성 레인은 보이지 않도록 구축되는데, 이는 클라이언트에게는 적절하지만 측정을 수행하는 누구에게나 잘못된 방식입니다. 오래된 레인이 정중하게 응답할 때, "이 서버는 새로운 개정판을 구현하지 않습니다"라는 메시지와 "내 클라이언트는 새로운 개정판을 요청하지 않았습니다"라는 메시지는 똑같이 보입니다. 두 응답 중 어느 것도 그 둘을 구분할 수 있는 요소를 담고 있지 않습니다.
저렴한 테스트
그 방법은 단 한 번의 요청만으로 가능하며, 비용도 단 한 번의 요청뿐입니다. 만약 initialize 응답이 온다면, 당신은 레거시 레인 (legacy lane)에 있는 것이며, 그 다음에 내리는 모든 결론은 당신의 클라이언트에 대한 진술이 됩니다. 2026-07-28 버전의 서버는 새로운 레인 (new lane)에서 initialize를 전혀 제공하지 않으므로, 핸드셰이크 (handshake)가 성공했다는 것은 호출자(caller)에 대한 증거가 됩니다.
네 번의 요청이 반환한 결과
- 버전 헤더 (version header) 없이
server/discover를 호출하면, 이벤트 스트림 (event stream) 형식으로 프레임화된 HTTP 200이 반환되며,-32601 Method not found를 포함합니다. 동일한 요청을 프로토콜 버전 (protocol version) 2025-06-18과 함께initialize로 보내면, 200과 서버의 정체성 (identity)을 반환합니다. - 버전 헤더는 포함하되
_meta가 없는 경우, HTTP 400과-32602가 반환됩니다. 이때 메시지는 헤더 이름이 2026-07-28 개정판(revision)이지만, 요청에 필수적인 요청별 엔벨로프 키 (per-request envelope key)인_meta가 누락되었다고 말합니다. - 헤더를 포함하고, 키 이름이
protocol-version과client-capabilities로 잘못 철자가 적힌_meta를 보낸 경우, 다시 HTTP 400과-32602가 반환됩니다. 이번에는 메시지에 서버가 원하는 두 가지 이름을 나열합니다. - 세 가지 요소가 모두 갖춰진 경우, HTTP 200이 반환됩니다.
supportedVersions에는 2026-07-28만 포함되어 있고,capabilities.tools.listChanged는true,resultType은complete,ttlMs는3600000,cacheScope는public이며,result._meta에 서버의 정체성이 들어 있습니다.
이것이 아닌 것
이것은 SDK의 결함도 아니고, 명세서 (spec)의 공백도 아닙니다. 호환성 레인 (compatibility lane)은 의도적인 결정이며 올바른 결정입니다. 그리고 보이지 않게 만드는 것이 바로 그 목적입니다. 함정은 다운스트림 (downstream)에 있습니다. 즉, 깨끗하게 도착한 응답을 읽는 사람이 무엇을 하느냐에 달려 있습니다.
또한 이것은 정보가 누락된 이야기도 아닙니다. 두 번째와 세 번째 행을 다시 읽어보십시오. 서버는 제가 빠뜨린 엔벨로프 키 (envelope key)를 명시했고, 그다음에는 제가 철자를 틀린 두 개의 키를 명시했습니다. 올바른 요청은 버전 헤더가 포함된 순간부터 실패 메시지 안에 적혀 있었습니다. 하지만 저는 나머지 응답들을 지침 (instructions)으로 읽는 대신, 첫 번째 응답을 판결 (verdict)로 읽어버렸습니다.
이 모든 것을 다시 확인해 보니 저 자신의 실수 하나가 더 발견되었습니다. 제가 적어둔 메모에는 이 엔드포인트가 JSON과 이벤트 스트림 (event stream)을 모두 수락하지 않는 모든 요청을 거부한다고 되어 있었습니다. 그것은 레거시 레인 (legacy lane)에 해당하는 내용이었습니다. 새로운 레인에서는 일반적인 JSON Accept 요청이 응답되며, Accept 헤더가 전혀 없는 요청 또한 응답됩니다. 저는 제가 우연히 서 있던 레인의 동작을 기록하고 그것을 규칙으로 저장해 버렸던 것입니다.
두 번의 형태
어제는 서버가 더 이상 서비스하지 않는 레인을 읽고 초록색 체크 표시를 확인한 것이었습니다. 오늘은 제 클라이언트가 요청한 적도 없는 레인을 읽고 빨간색 표시를 확인한 것이었습니다. 응답은 무슨 일이 일어났는지 알려주지만, 그것이 무엇을 의미하는지는 사용자가 판단해야 하며, 바로 그 판단 과정에서 오류가 발생합니다. 깨끗한 상태 코드 (status code)와 잘 구성된 에러 객체 (error object)는 마치 정확한 측정값처럼 보이기 때문에, 오류는 조용히 발생합니다.
따라서 하단에 호환성 계층 (compatibility layer)이 있는 모든 것에 대해 다음과 같은 습관을 가져야 합니다. 결과를 읽기 전에, 어떤 레인이 응답했는지 파악하십시오. 프로토콜이 알려주지 않는다면, 요청에 직접 판별자 (discriminator)를 넣고 응답이 변하는지 확인하십시오.
이 중 그 어느 것도 에이전트 준비성 스캐너 (agent-readability scanner)에는 보이지 않습니다. 스캐너는 MCP를 이해하지 못하므로, 통과된 준비성 수준은 MCP 엔드포인트가 현재 리비전 (revision)을 서비스하는지에 대해 아무것도 말해주지 않습니다. 그것은 서버를 실행하는 사람의 몫입니다.
관련 항목
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기