fix(mcp): JSON-RPC 규약 적합 — 버전 협상 부재와 비객체 프레임 무응답(응답 증발) - #3720
Closed
kevin9327 wants to merge 1 commit into
Closed
Conversation
두 결함 모두 실기 재현 확인:
① initialize 가 요청 protocolVersion 을 무조건 되비췄다. "9999-99-99" 도
"banana" 도 합의된 것처럼 응답하고, 몸통은 2025-06-18 전용 표면
(structuredContent)을 내보낸다 — 클라이언트는 끊어야 할 신호를 못 받은 채
못 읽는 응답을 받는다. MCP lifecycle 은 지원하지 않는 개정판이면 서버가
지원하는 개정판으로 답하라고 MUST 로 정한다.
② 파싱은 됐지만 Request 객체가 아닌 프레임(배치 배열·문자열·숫자·불리언·null)이
한 바이트도 응답 없이 버려졌다. msg.get("id") 가 None 이라 알림과 구분되지
않았기 때문이다. 실측: 배치 프레임 뒤 ping 만 응답되어 응답 하나가 통째로
증발 — 클라이언트는 그 id 를 영원히 기다린다.
method 가 없거나 문자열이 아닌 경우도 -32601 이 아니라 -32600 이다
(문구도 "지원하지 않는 메서드: " 처럼 이름이 빈 채로 나갔다).
- SUPPORTED_PROTOCOL_VERSIONS 목록 + negotiate_protocol_version
- INVALID_REQUEST(-32600) 신설, 비객체 프레임·비문자열 method 라우팅
- 배열은 "2025-06-18 에서 배치가 제거됨"을 사유로 밝힌다 — 사유가 곧 수정 지시다
- 계약 테스트 6종: 무응답이 hang 이 아니라 실패로 보고되도록 타임아웃 하네스 사용
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
요약
mcp-serve의 JSON-RPC 2.0 / MCP 규약 위반 2건입니다. 둘 다 실제 바이너리를 stdio 로 구동해 재현했습니다.①
initialize가 클라이언트가 보낸protocolVersion을 무조건 되비춘다존재하지 않는 개정판도, 아예 버전이 아닌 문자열도 "지원한다"고 답합니다. 그래 놓고 몸통은 2025-06-18 에 신설된
structuredContent를 내보냅니다 — 같은 세션에서 실측:MCP 2025-06-18 lifecycle 은 이렇게 정합니다:
되비추기는 마지막 문장의 메커니즘을 파괴합니다 — 클라이언트의 검사가 항상 통과하므로 끊을 기회 자체가 사라지고, 결국 못 읽는 응답을 받습니다.
② 파싱은 됐지만 Request 객체가 아닌 프레임이 무응답으로 버려진다
가장 날카로운 증거 — 스트림은 살아 있는데 응답 하나만 증발합니다:
원인은
msg.get("id")가 비객체 프레임에None을 돌려주고, 그None이 알림(notification)과 구분되지 않아continue로 빠지는 것입니다. 클라이언트는 id 1 을 영원히 기다립니다.같은 뿌리의 인접 결함 —
method가 없거나 문자열이 아닌 경우:unwrap_or("")가 빈 이름을 만들어-32601로 흘려보내고, 문구까지 이름이 빈 채로 나가 호출자가 원인을 못 짚습니다. JSON-RPC 2.0 §4 상method는 String 이어야 하므로 이것은 "그런 메서드가 없다"가 아니라 "요청 구조가 틀렸다"(-32600)입니다.수정
SUPPORTED_PROTOCOL_VERSIONS목록 신설 +negotiate_protocol_version(). 지원 개정판이면 같은 값으로 확인, 아니면 서버 기준판을 제시합니다. 새 개정판을 실제로 구현하면 배열에 한 줄 더하는 것이 유일한 변경점입니다. 현재 목록은["2025-06-18"]뿐입니다 — 서버의 실제 와이어 표면이 그것이고, 구현하지 않은 개정판을 주장하면 지금 고치는 결함을 그대로 재생산하게 됩니다.INVALID_REQUEST(-32600)신설. 비객체 프레임은 id 를 알아낼 수 없으므로 JSON-RPC 2.0 §5 대로id: null로 응답합니다.method가 문자열이 아니면-32600+ 프레임에서 읽어낸 id 를 그대로 되돌립니다.동작 변화표
initializew/"9999-99-99"·"banana"·"2024-11-05""2025-06-18"initializew/"2025-06-18""2025-06-18"initializew/oprotocolVersion"2025-06-18"[{...}]배치-32600, idnull, 배치 제거 사유"ping"/42/true/null-32600, idnull{"id":7}(method 없음)-32601"지원하지 않는 메서드: "-32600, id7{"id":8,"method":123}-32601동문-32600, id8{not json-32700, idnull{"jsonrpc":"2.0","method":"notifications/initialized"}회귀 가드 6종
무응답 결함은 기존 하네스(
Server::request)로 검증할 수 없습니다 —read_line에서 막혀 테스트가 실패가 아니라 hang 합니다. 그래서 읽기 전용 스레드 +recv_timeout(5s)하네스(raw_frames)를 따로 만들어 회귀가 행이 아니라 실패로 보고되게 했습니다.initialize_negotiates_instead_of_echoing_client_version— 미지원 5종initialize_keeps_supported_version_and_defaults_when_absent— 짝 테스트(없으면 "항상 서버 버전 박기"로 잘못 고쳐도 통과)non_object_frames_get_invalid_request_instead_of_silence— 배치·문자열·숫자·불리언·nulljsonrpc_batch_is_rejected_with_batching_removed_note— 사유에 개정판이 명시되는지request_without_string_method_is_invalid_request_not_method_not_foundinvalid_frame_does_not_swallow_the_next_response— 프레임 2건 → 응답 2건, 순서 보존검증
cargo test --test mcp_server_contract— 12/12 (신규 6종 포함)mcp_session_edit·mcp_session_query·agent_profile_router— 15/15 무회귀cargo clippy --profile release-test --bin rhwp— 경고 0 /cargo fmt --check— 통과"2025-06-18"을 보내므로 협상 변경은 기존 스위트에 무영향입니다.범위 밖 (후속)
JSON-RPC 2.0 §4 는
id도 String·Number·Null 이어야 한다고 정하는데, 현재{"id":{"a":1}}는 객체를 그대로 응답 id 로 되돌립니다. 같은 계열이지만 이 PR 의 증적 범위 밖이라 별도 이슈로 남깁니다.🤖 Generated with Claude Code