스테이트리스 MCP 변경 사항이 게이트웨이에 미치는 영향
요약
MCP 사양이 스테이트리스(stateless)로 변경됨에 따라 게이트웨이의 작동 방식에 큰 변화가 예상됩니다. 기존에는 세션 ID를 통해 상태 정보를 관리했지만, 새로운 사양은 초기화 과정과 세션을 완전히 제거합니다. 이에 맞춰 Agent Router는 디스커버리 및 리스트 요청에 스테이트리스 팬아웃을 추가하며 적응하고 있습니다.
핵심 포인트
- MCP 사양이 스테이트리스로 변경되어 세션 ID와 초기화 핸드셰이크가 사라짐.
- 게이트웨이는 이제 클라이언트의 메모리 역할을 대신해야 하며, 모든 정보는 헤더나 메타데이터에 담겨 전송됨.
- 새로운 요청은 `Mcp-Method` 및 `Mcp-Name` 헤더를 통해 프로토콜 버전과 기능을 명시함.
- Agent Router는 스테이트리스 팬아웃을 추가하여 변화하는 MCP 게이트웨이 환경에 대응하고 있음.
MCP 사양이 7월 28일에 스테이트리스(stateless)로 바뀌었습니다. 만약 하나의 MCP 서버를 운영한다면, 이는 주로 세션 처리를 삭제하고 server/discover 메서드를 추가하는 것을 의미합니다. 하지만 게이트웨이가 수많은 MCP 서버 앞에 위치하여 이를 클라이언트에게 하나처럼 보이게 제공하는 경우라면, 전체 작동 방식이 바뀝니다.
저는 Agent Router (과거 Envoy AI Gateway로 알려졌던 AAIF 프로젝트)가 이 변화를 어떻게 처리하고 있는지 지켜보고 있었습니다. 9월에 팀은 PR #2545를 병합했는데, 이는 디스커버리(discovery) 및 리스트 요청에 스테이트리스 팬아웃(stateless fan-out)을 추가합니다. 아직 트래픽을 처리하고 있지는 않으며, 이것은 의도된 것입니다. 하지만 세션이 사라진 후 MCP 게이트웨이가 어떤 모습일지 가장 명확하게 보여주는 부분입니다.
게이트웨이가 이전에는 의존했던 것들
구 사양(old spec) 하에서는 클라이언트가 initialize를 통해 세션을 열고, 그 이후의 모든 요청에 Mcp-Session-Id를 포함했습니다. Agent Router는 이 세션을 핵심 기반으로 사용했습니다. 클라이언트가 초기화할 때, 게이트웨이는 라우트의 각 백엔드(backend)에 initialize를 보내고, 각 백엔드의 세션 ID와 기능을 수집한 다음, 이 모든 것을 하나의 암호화된 세션 ID로 패킹하여 클라이언트에게 돌려주었습니다.
그 이후부터는 각 요청이 게이트웨이가 필요로 하는 것들, 즉 어떤 라우트인지, 호출자가 누구인지, 각 백엔드를 위한 업스트림(upstream) 세션 ID가 무엇인지, 그리고 각 백엔드가 무엇을 할 수 있는지 등을 포함하게 되었습니다. 게이트웨이 자체는 아무것도 기억할 필요가 없었습니다. 클라이언트가 게이트웨이의 메모리 역할을 대신 수행하고 있었던 것입니다.
2026-07-28 사양은 이 모든 것을 제거합니다. 더 이상 initialize 핸드셰이크도, Mcp-Session-Id도, 세션 자체도 없습니다. 이제 각 요청은 프로토콜 버전, 클라이언트 정보, 그리고 클라이언트 기능을 _meta에 담아 전송하며, 인프라가 본문을 파싱하지 않고도 요청 내용을 알 수 있도록 Mcp-Method와 Mcp-Name 헤더를 포함합니다. 만약 클라이언트가 서버의 기능을 미리 알고 싶다면, server/discover를 호출합니다. 엘리시테이션(elicitation)이나 샘플링(sampling)과 같은 서버 시작 요청은 여러 번 왕복하는 요청이 되며, 이때 서버는 input_required 결과를 반환하고 클라이언트는 그 답변을 가지고 재시도하게 됩니다.
따라서 게이트웨이는 이전에 세션 ID에 존재했던 모든 정보 조각들을 위한 새로운 보금자리를 찾아야 했습니다.
동일한 tools/list 호출이라 할지라도 각 사양마다 다르게 보입니다. 2025-11-25 사양에서는 클라이언트가 먼저 세션을 열고 그 ID를 계속 가지고 있어야 합니다:
POST /mcp
Content-Type: application/json
...
반면, 2026-07-28 사양에서는 첫 단계 자체가 없습니다. 요청은 헤더에 자신의 메서드 이름을 명시하고, 버전, 식별자, 기능을 _meta에 담아 전송합니다:
POST /mcp
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/list
...
각 정보 조각의 새로운 위치
이 작업에 대한 설계 제안은 이 과정을 하나하나 설명하며, 대부분의 답변은 Agent Router가 이미 가지고 있던 것들이었습니다.
라우트 이름(route name)은 Envoy가 설정하는 헤더에 각 요청마다 도착합니다. 이전 코드는 initialize 중에만 이를 읽었지만, 이제는 모든 요청에서 읽습니다.
단일 대상 호출인 tools/call의 백엔드는 이미 도구 이름에 인코딩되어 있습니다. Agent Router는 도구를 backend__toolname 형식으로 노출하므로, 이름 자체가 게이트웨이가 어디로 호출을 보낼지 알려줍니다. 새로운 사양(spec) 하에서도 이 이름은 Mcp-Name 헤더에 나타납니다.
현대적인 백엔드는 세션이 없기 때문에, 백엔드별 세션 ID는 더 이상 필요하지 않습니다.
신원 확인(Identity)은 각 요청의 Bearer 토큰에서 가져오며, 이는 게이트웨이가 이미 확인하던 방식입니다. 세션이 없으므로 가로챌 세션도 없습니다.
역량(Capabilities)만이 새로운 작업이 필요했던 부분입니다. 이제 게이트웨이는 server/discover를 통해 각 백엔드에 요청하고 응답을 병합합니다.
#2545가 추가하는 것
해당 PR은 여러 개의 백엔드로 가야 하는 다섯 가지 요청(server/discover, tools/list, resources/list, resources/templates/list, prompts/list)에 대한 스테이트리스(stateless) 경로를 구축합니다. 게이트웨이는 각 선택된 백엔드에 요청을 보내고, 응답을 병합하여 하나의 JSON-RPC 결과를 반환합니다.
몇 가지 세부 사항이 눈에 띕니다.
백엔드 선택은 이제 매 요청마다 이루어집니다. 이전 세션 방식에서는 클라이언트가 볼 수 있는 백엔드 목록이 initialize 시점에 고정되었습니다. 리뷰 스레드에서 작성자는 매 요청마다 재평가하는 것이 올바른 스테이트리스 동작이라고 지적했습니다. 왜냐하면 새로운 토큰이 호출자가 볼 수 있는 백엔드를 변경할 수 있기 때문입니다. 누군가의 특정 백엔드 접근을 취소(Revoke)하면, 그들의 다음 tools/list 요청에 반영됩니다. 이전 모델에서는 세션이 끝날 때까지 계속해서 해당 목록을 볼 수 있었습니다.
역량 병합은 이전 세션 경로와 새로운 스테이트리스 경로 모두에서 하나의 공통 헬퍼를 사용하므로, 응답한 어떤 백엔드가 광고하는 역량은 두 경우 모두 병합된 결과에 동일하게 나타납니다.
새 사양(ttlMs 및 cacheScope)의 캐시 힌트는 가장 제한적인 값들을 취하여 병합됩니다. 만약 한 백엔드가 자신의 도구 목록이 즉시 만료된다고 한다면, 병합된 목록도 즉시 만료됩니다. 만약 어떤 백엔드라도 그 결과를 비공개(private)로 표시하면, 병합된 결과 역시 비공개가 됩니다.
종합적으로 볼 때, docs 백엔드와 tickets 백엔드를 가진 라우트에서 병합된 응답은 대략 다음과 같습니다. 도구 이름에는 해당 백엔드가 접두사로 포함되는데, 이것이 나중에 tools/call이 출처를 찾을 수 있게 하는 방식입니다. 만약 docs가 목록 유효 기간을 60초라고 하고 tickets가 30초라고 한다면, 병합된 목록은 30초의 유효 기간을 갖게 됩니다:
{
"jsonrpc": "2.0",
"id": 7,
...
부분적인 실패가 라우트 전체를 다운시키지는 않습니다. 만약 하나의 백엔드가 연결할 수 없거나 잘못된 데이터를 보내면, 게이트웨이는 해당 백엔드를 건너뛰고(skip), 경고 로그를 남기며 백엔드 이름을 기록하고, 오류 메트릭을 기록한 후 다른 백엔드들이 보낸 결과를 반환합니다. 만약 어떤 백엔드도 응답하지 않으면, 정상적인 서버가 도구가 없는 것처럼 보이는 빈 목록 대신 500 에러 코드를 받게 됩니다. 한 검토자(reviewer)인 mohitgurnani는 이 모든 실패 케이스를 발견하는 확인 절차(all-failed check)가 디스커버리뿐만 아니라 네 개의 목록 핸들러 모두를 포괄하도록 요청했고, 이는 병합되기 전에 반영되었습니다.
아직 라이브가 아닌 이유
새로운 코드가 호출되는 곳을 찾아보더라도 테스트 코드 외에는 호출하는 부분이 없습니다. 이것이 계획이며 간과한 것이 아닙니다.
해당 제안은 작업을 여러 단계로 나눕니다. Phase 0에서는 요청 수명 주기(request lifecycle)의 각 단계를 거치며 전체 스테이트리스 경로를 참조되지 않은 코드로 구축합니다: 들어오는 요청 분류, 백엔드 선택 및 디스커버리, 포워딩 및 병합, 그리고 subscriptions/listen입니다. 이 제안은 Phase 0의 어떤 부분도 도달할 수 없으며 기존 동작이 변경되지 않는다고 명시합니다. Phase 1에서는 디스패처(dispatcher)가 전환되어 최신 클라이언트의 요청이 새로운 경로를 따르기 시작합니다.
따라서 #2545는 활성화를 기다리는 스테이지드 코드입니다. tools/call과 같은 단일 대상 호출 및 통합 작업을 다루는 PR #2692은 제가 확인할 때도 여전히 열려 있었습니다. subscriptions/listen은 별도의 후속 작업입니다.
[
활성화되면 가능해지는 것들
가장 큰 변화는 게이트웨이와 그 뒤에 있는 서버를 운영하는 사람들에게 영향을 미칩니다. Agent Router의 암호화된 세션 ID는 이미 어떤 게이트웨이 인스턴스라도 모든 요청을 처리할 수 있게 했지만, 기존 설정은 여전히 라우트의 각 백엔드에 initialize가 전송되었고, 각 백엔드는 나중에 요청들이 도착해야 하는 자체 세션을 유지했습니다. 새로운 모델에서는 체인 내 어느 것도 세션을 보유하지 않습니다. 요청이 필요한 것을 가지고 오고, 게이트웨이가 이를 전달하며, 상태 비저장(stateless) 백엔드는 로드 밸런서가 선택하는 어떤 인스턴스에서든 응답할 수 있습니다. 사용자는 스티키 라우팅 없이 일반적인 순환 방식(round-robin balancing)으로 게이트웨이 인스턴스와 백엔드 인스턴스를 확장할 수 있으며, 이 제안은 현대적인 경로를 위해 Redis와 같은 공유 세션 저장소 추가를 명시적으로 배제합니다.
라우팅 비용도 절감됩니다. 이 제안의 목표 중 하나는 Envoy가 JSON-RPC 본문을 파싱하지 않고 Mcp-Method 및 Mcp-Name 헤더를 기반으로 라우팅할 수 있도록 하는 것입니다. 이는 헤더만으로 도구별 속도 제한(per-tool rate limits), 메서드별 정책, 엣지에서 이루어지는 라우팅 결정을 가능하게 합니다.
승인 흐름(Approval flows)이 게이트웨이를 통해 더 쉽게 실행됩니다. 이전 사양에서는 통화 중간에 사용자에게 무언가를 요청하려는 서버가 클라이언트로 돌아가는 열린 스트림을 필요로 했고, 게이트웨이는 답변을 올바른 백엔드로 라우팅하기 위해 요청 ID를 재작성해야 했습니다. 다중 왕복(multi round-trip) 요청의 경우, 백엔드는 input_required를 반환하고, 클라이언트는 답변과 함께 재시도하며, 게이트웨이는 다른 어떤 호출처럼 도구 이름으로 해당 재시도를 라우팅합니다. 이 제안은 이를 손대지 않고 전달할 계획입니다.
접근 변경 사항은 다음 요청에서 적용됩니다. 백엔드 선택이 요청별로 이루어지기 때문에, 호출자가 특정 백엔드에 대한 접근을 박탈당하는 것은 재연결하는 다음 순간이 아니라 그들의 다음 호출에서 효력이 발생합니다.
여전히 주목할 부분
팬아웃(fan-out)은 백엔드에 순차적으로 요청을 보내기 때문에, 느린 백엔드가 여러 개 포함된 경로는 누적 효과를 가져옵니다. 게다가 부분적인 결과는 클라이언트 측에서 완전한 결과와 구별하기 어렵습니다. 만약 10개의 백엔드를 가진 경로에서 8개의 도구(tool)가 반환된다면, 클라이언트는 이것이 전부인지 아니면 두 개의 백엔드가 실패했는지 알 수 없습니다. 저자는 어떤 백엔드가 실패했는지 표시하는 것이 도움이 될 것이라는 점에 동의했지만, 기존 세션 경로를 일관되게 유지하기 위해서는 동일한 변경이 필요하므로 이를 연기했습니다. 이 기능이 구현될 때까지는 개별 백엔드 로그와 메트릭을 확인하는 것이 유일한 방법이니 모니터링 설정을 반드시 확인하세요.
또한 디스커버리(discovery) 응답에 작은 불일치가 있습니다. 여기에는 게이트웨이가 몇 개의 백엔드를 집계하고 있는지 알려주는 라인이 포함되어 있는데, 이 숫자는 실제로 응답한 백엔드가 아니라 선택된 백엔드의 개수를 세는 것입니다. 두 개의 백엔드 중 하나가 다운되었더라도 여전히 두 개라고 표시합니다.
더 큰 미해결 과제는 혼합 배포(mixed deployments)입니다. Phase 1은 최신 클라이언트가 최신 백엔드와 통신하는 경우를 다룹니다. 만약 최신 클라이언트가 세션을 사용하는 백엔드에 접근하거나, 구형 클라이언트가 스테이트리스(stateless) 백엔드에 접근해야 한다면, 게이트웨이가 두 모델 사이에서 변환을 수행해야 합니다. 이 부분은 제안서에서 Phase 2로 미루었습니다. 그 이유는 대부분의 새로운 백엔드가 첫날부터 새로운 사양(spec)으로 출시될 것이라는 점입니다. 이것이 유지되는지는 현재 의존하고 있는 MCP 서버들이 얼마나 빨리 업그레이드하는지에 달려 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기