MCP C# SDK 2.0 안정화: 기본적으로 HTTP는 무상태(stateless)이며, 업그레이드 시 10가지 변경 사항이 문제를 일으킬 수
요약
Microsoft가 MCP C# SDK 2.0 안정 버전을 발표하며, HTTP 전송 계층을 기본적으로 무상태(stateless)로 변경했습니다. 이는 로드 밸런싱 및 수평 확장을 용이하게 하는 핵심 변화입니다. 또한, 기존의 `initialize` 핸드셰이크는 사라지고 Discovery 메커니즘과 헤더 기반 라우팅 표준화가 적용되었습니다.
핵심 포인트
- HTTP 전송 계층은 기본적으로 무상태(stateless)로 설정되어 수평 확장이 용이합니다.
- 세션 상태 유지가 필요할 경우, 명시적으로 옵트인(`Stateless = false`)해야 합니다.
- 레거시 `initialize` 핸드셰이크는 사라지고 Discovery가 클라이언트의 탐색(probe)을 통해 대체됩니다.
- 도구 호출은 헤더 기반 라우팅 표준화(SEP-2243)를 위해 개선되었습니다.
Microsoft는 이번 주에 공식 MCP C# SDK의 안정 버전인 2.0을 발표했으며, InfoQ가 이 발표를 10월 26일에 다루었습니다. 요약하자면, SDK는 이제 2026-07-28 사양 개정판을 구현하며, HTTP 전송 계층은 기본적으로 무상태(stateless)이고, 업그레이드 과정에서 10가지의 번호가 매겨진 변경 사항이 발생할 수 있습니다. 저는 계획을 세우기 전에 GitHub에서 v2.0.0 릴리스 노트를 읽었습니다. 그 이유는 이 중 절반가량이 런타임 오류(runtime errors)보다는 빌드 경고(build warnings)로 나타나기 때문에, 배포 기간 전에 어느 것이 어떤 것인지 알아야 하기 때문입니다.
무엇이 출시되었으며, 언제
프리뷰 버전을 테스트하고 넘어간 경우 패키지 이력을 확인하는 것이 중요합니다. v2.0.0 태그는 사양 개정판이 확정된 날인 7월 28일에 GitHub에 올라왔습니다. 이후 세 번의 마이너 릴리스가 이어졌습니다: 8월 5일의 2.1, 8월 13일의 2.2, 그리고 10월 20일의 2.3입니다. 이번 주에 새로워진 것은 Microsoft가 안정 버전을 발표했다는 점이며, 따라서 여름에 보류했던 코드는 이미 세 번이나 변경되었습니다. 이 개정판 자체는 MCP 출시 이후 가장 큰 프로토콜 변화를 가져왔습니다: initialize 핸드셰이크와 Mcp-Session-Id 헤더가 사라졌고, 프로토콜 버전과 기능(capabilities)이 모든 요청에 포함되며, 교차 호출 상태(cross-call state)는 이제 프로토콜의 문제가 아니라 사용자의 문제가 되었습니다.
세션은 이제 옵트인(opt-in)입니다
2.0에서는 HttpServerTransportOptions.Stateless가 기본값으로 true입니다. 무상태 서버는 전송 세션을 생성하지 않으며, 독립적인 SSE GET/DELETE 엔드포인트를 노출하지 않고, 원치 않는(unsolicited) 서버-클라이언트 요청을 지원하지 않습니다. 모든 인스턴스가 모든 요청을 처리할 수 있다는 것이 핵심입니다: 이는 라운드 로빈 부하 분산기(round-robin load balancers)와 수평 확장(horizontal scaling)이 공유 세션 저장소 없이 작동한다는 의미입니다. 만약 서버가 예전 동작이 정말로 필요하다면, 옵트인하여 경고를 수락해야 합니다:
// HttpServerTransportOptions: 2.0부터 stateful은 옵트인입니다
transportOptions.Stateless = false;
상태 기반 전용 옵션은 이제 MCP9006을 발생시키며, 다운그레이드된 initialize-handshake 연결에만 적용됩니다. 이 플래그를 변경하기 전에 세션이 실제로 무엇을 담고 있었는지 확인하세요. 제가 본 대부분의 서버는 유도(elicitation) 상태만을 가지고 있었으며, 이제 더 나은 대체재가 있습니다.
Discovery가 핸드셰이크를 대체합니다
클라이언트는 먼저 server/discover를 탐색(probe)하고, 서버가 구형인 경우 레거시 initialize 핸드셰이크로 폴백합니다. 전송 계층에서, tools/call 요청은 이제 Mcp-Method 및 Mcp-Name 헤더를 포함하며, [McpHeader] 속성은 도구 매개변수를 Mcp-Param-* 헤더로 승격시킵니다. 이것이 바로 SEP-2243 표준화입니다: 로드 밸런서와 게이트웨이가 본문을 파싱하지 않고도 헤더를 기반으로 라우팅할 수 있게 됩니다. 본문은 여전히 권위적이며, 헤더 불일치는 서버가 HeaderMismatch 오류를 반환하게 하므로, 잘못 구성된 프록시는 잘못 라우팅하는 대신 명확한 실패(fail loud)를 일으킵니다. 레거시 SSE 엔드포인트는 종료 시 MCP9004 경고를 발생시킵니다.
유도(Elicitation)가 MRTR로 이동했습니다
무상태성(Statelessness)은 채널 서버가 호출 도중에 클라이언트에게 입력을 요청하던 방식을 제거합니다. 대체재는 다중 왕복 요청(Multi Round-Trip Requests, MRTR)입니다. 도구는 하나 이상의 입력 요청을 담아 InputRequiredException을 발생시키며, 이는 InputRequest.ForElicitation, ForSampling, 또는 ForRootsList로 구성되고 불투명한(opaque) requestState를 포함합니다. 클라이언트는 답변들을 수집하고 inputResponses와 함께 호출을 재개하며, 핸들러가 등록된 경우 McpClient가 이를 자동으로 처리합니다. 알아야 할 두 가지 측면이 있습니다: 2025-11-25 이전의 클라이언트가 있는 상태 기반 세션에서는 SDK가 구형 서버 시작 요청으로 브리징(bridges)하며, 무상태 다운그레이드 연결에서는 해당 요청이 McpException으로 표출됩니다. 도중에 발생하는 질문에 의존하기 전에 이 오류 경로를 계획하세요.
사용 중단된 API 및 Tasks 패키지 이동
Roots, sampling 및 logging은 이제 MCP9005 경고를 발생시킵니다. 이는 2026년 7월 28일 사양(SEP-2577)에서 해당 서피스들을 폐기했기 때문입니다. 하위 버전 연결은 계속해서 이를 사용할 수 있으며, 마이그레이션을 계획하는 동안 MCP9005를 일시적으로 억제할 수 있습니다. Tasks는 더 큰 문제입니다. v1.4.x 구현은 코어(core)에서 전용 패키지로 이동했으며, 1.3 및 1.4의 실험적 버전과는 API 또는 와이어 호환성이 없습니다. 이는 재컴파일이 아닌 코드 변경입니다:
dotnet add package ModelContextProtocol.Extensions.Tasks
그런 다음 WithTasks(..)로 등록하고, RequestMethods.Tasks* 상수를 TasksProtocol 멤버로 대체해야 합니다. 만약 서버가 이전 실험적 Tasks API를 통해 장기 실행 작업을 노출했다면, 이 부분에 실제 마이그레이션 시간을 충분히 할당하십시오. 이는 SDK 팀에서 단순히 구식(obsoleted)이라기보다는 비호환적(incompatible)이라고 지적한 항목입니다.
이제 실패하는 OAuth 검사 사항들 (OAuth checks that now fail closed)
네 가지 인증 변경 사항이 모두 허용(tolerating) 대신 거부(rejecting) 방향으로 진행됩니다. Issuer 유효성 검사는 RFC 9207 및 RFC 8414를 따르므로, 일관되지 않은 인증 서버 메타데이터는 이제 통과하지 못하고 실패합니다. 따라서 체크 우회보다는 메타데이터 자체를 수정해야 합니다. 서버 메타데이터가 code_challenge_methods_supported에 S256을 광고하지 않으면 인증이 실패하므로, PKCE 지원은 필수 요구사항입니다. 동적 클라이언트 등록(Dynamic client registration)은 추론된 application_type을 전송하며, 요청 형태를 검증하는 서버는 이를 수용해야 합니다. 그렇지 않다면 DynamicClientRegistrationOptions.ApplicationType을 명시적으로 설정해야 합니다. 또한, AuthorizationRedirectDelegate는 이제 MCP9007을 발생시키므로, 콜백이 코드(code), 상태(state), 발행자(issuer)를 반환할 수 있도록 ClientOAuthOptions.AuthorizationCallbackHandler로 마이그레이션해야 합니다. 추가적인 동작 변경 사항으로는, 새로운 스코프를 추가하지 않는 반복적인 insufficient_scope 도전 시도가 무한히 재시도하는 대신 McpException을 던지게 되어, 한 종류의 조용한 루프가 가시적인 실패로 바뀌었습니다.
테스트에서 먼저 영향을 받는 사소한 변경 사항들 (The quiet breaks that hit tests first)
릴리스 노트 헤드라인에 거의 등장하지 않는 세 가지 변경 사항이 있습니다. UseStructuredContent = true를 사용하고 비(非)객체 반환 타입을 가진 도구는 이제 이를 { "result": 72 }와 같이 감싸지 않고, 원시 값인 structuredContent: 72를 방출합니다. 따라서 result 속성을 읽는 클라이언트에서 오류가 발생할 수 있습니다. 또한, inputSchema 없이 Tool 페이로드를 역직렬화(Deserializing)하면 스키마를 조용히 기본값으로 설정하는 대신 JsonException을 발생시킵니다. 빈 {}만 있어도 충분하며, 이는 도구 JSON을 직접 구성하는 프록시 및 테스트 픽스처에 중요합니다. 마지막으로, 명시적 SSE(Server-Sent Events) 연결은 모든 것을 IOException으로 감싸는 대신 근본적인 HttpRequestException 또는 TimeoutException을 노출하므로, 이전의 래퍼(wrapper)를 기준으로 오류 처리를 수행하던 코드는 자체적으로 어떤 오류도 발생하지 않으면서 작동을 멈춥니다.
5분 업그레이드 점검
dotnet build 2>&1 | grep -E 'MCP90(04|05|06|07)'
위의 모든 히트가 상단의 섹션에 매핑됩니다. 그런 다음 코드베이스에서 RequestMethods.Tasks, AuthorizationRedirectDelegate, 그리고 IOException("Failed to connect transport.")과 일치하는 핸들러를 검색하십시오. 경고는 스펙이 이동한 위치를 알려주며, 예외(exceptions)가 프로덕션 환경에 도달하는 것들이 문제입니다.
만약 Claude Code로 자체 MCP 서버를 실행한다면, 이 개정판의 클라이언트 측은 더 일찍 도착했으며 저는 그때 이를 테스트했습니다: Claude Code 2.1.292가 귀하의 MCP 서버에 2026-07-28을 요청합니다. .NET에서의 서버 측은 이 SDK입니다. 동일한 개정판이며, 두 가지 업그레이드 경로가 존재하며, 클라이언트와 서버가 세션의 존재 여부에 대해 의견이 불일치하기 전에 둘 다 닫을 가치가 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기