2026년 7월 28일, MCP가 출시 이후 가장 큰 변화를 발표했다. initialize 핸드셰이크와 세션 ID가 사라졌다. 그런데 진짜 이야기는 '세션을 없앴다'가 아니라 '세션은 애초에 작동한 적이 없었다'는 고백이다. ChatGPT는 도구 호출마다 세션을 새로 만들고 있었다. 명세 문서와 SEP 원문, 그리고 1,000개 저장소 조사 결과를 따라가며 무엇이 왜 바뀌었는지 정리했다.
왼쪽 끝의 노트북 플러그에서 시작해, 갈라지는 화살표(엔드포인트 2개)와 합쳐진 화살표(엔드포인트 1개)를 지나, 자물쇠(인가 정비)를 거쳐, 오른쪽 끝에서 서버 무리로 부챗살처럼 퍼집니다.
타임라인에서 보이듯 전송 계층은 계속 손을 봤습니다. 엔드포인트 두 개짜리 HTTP+SSE를 하나로 합치고(Streamable HTTP), 인가를 OAuth 2.1로 정비하고, 구조화된 출력을 넣고. 하지만 세션이라는 뼈대는 그대로 두고 살을 고친 것이었습니다.
2025년 12월, Anthropic이 MCP를 리눅스 재단 산하 에이전틱 AI 재단(AAIF) 에 기증합니다. Anthropic·Block·OpenAI가 공동 창립사로 참여했습니다. 표준이 한 회사의 것이 아니게 된 시점이고, 동시에 "이제 진짜 인프라로 써야 한다"는 압력이 커진 시점이기도 합니다.
2부. 세션이 지키지 못한 약속
여기서부터가 이번 개정의 진짜 이유입니다. 그리고 흔히 알려진 것과 다릅니다.
보통 이렇게 설명됩니다 — "확장성 때문에 세션을 없앴다." 맞는 말이지만 절반입니다. SEP-2567 원문을 읽으면 더 근본적인 진단이 나옵니다.
1년 넘게 명세에 있었지만, 세션은 클라이언트마다 일관된 의미로 수렴하지 못했다. 어떤 클라이언트는 도구 호출 단위로, 어떤 클라이언트는 앱 실행 단위로, 어떤 클라이언트는 페이지 로드 단위로 세션을 잡는다. 그리고 거의 아무도 재개(resume)하지 않는다.
문제 ① 수명이 정의되지 않았다
명세는 세션이 언제 시작하고 끝나는지 규정하지 않았습니다. 호스트 애플리케이션에 달린 문제라고 봤기 때문입니다. 결과는 이랬습니다.
클라이언트 유형
세션 경계
서버 입장에서의 결과
ChatGPT · 초기 Claude.ai
도구 호출 하나마다 새 세션
다음 호출 전에 상태가 파괴됨
대부분의 데스크톱·IDE 클라이언트
앱 실행 시 하나, 프로세스 수명 내내 유지
모든 대화가 상태를 공유하고, 재시작하면 소실
웹 클라이언트
페이지 로드마다
새로고침하면 사라짐
서브에이전트
부모 것을 쓸 수도, 자기 것을 만들 수도 — 관례 없음
예측 불가
서버 작성자는 무엇을 세션에 담을지 결정하는 사람입니다. 그런데 세션이 무엇에 대응하는지 알 수 없다면 그 결정을 올바르게 내릴 방법이 없습니다.
SEP는 이 지점을 정확히 짚습니다.
Playwright 서버가 브라우저 인스턴스를 세션에 묶으려면, 그 세션이 사용자 턴 하나인지, 에이전트 프로세스 하나인지, 긴 대화 하나인지 알아야 한다. 명세는 이를 규정하지 않고 호스트마다 다른 답을 준다. 서버는 자기가 통제하지 못하는 의미를 향해 설계하고 있는 것이다.
그리고 결정적인 한 문장.
세션 상태를 잘 쓰고 있는 것처럼 보이는 서버들은 대개 stdio 서버가 프로세스 수명에 기대고 있는 경우다. 그것은 프로토콜의 성질이 아니라 전송 방식의 성질이다.
즉 로컬에서 잘 되던 게 원격에서 안 되던 이유가 여기 있었습니다. 로컬에서 작동한 건 세션이 아니라 프로세스였습니다.
문제 ② 목록을 캐시할 수 없었다
이건 성능 문제인데, 규모가 커질수록 아파집니다.
tools/list는 세션마다 다른 결과를 낼 수 있도록 허용돼 있었습니다. 실제로 그런 패턴이 있었습니다 — connect_database를 부르면 그 후의 tools/list에 query와 list_tables가 나타나는 식입니다.
그래서 클라이언트는 한 세션에서 받은 목록을 다음 세션에서 재사용할 수 없었습니다. 도구 목록이 빌드 시점에 고정되어 절대 변하지 않는 서버라도 마찬가지입니다. 클라이언트는 어느 서버가 세션 의존적인지 미리 알 수 없으니까요.
서브에이전트를 많이 띄우는 오케스트레이터에게 이건 곱셈으로 돌아옵니다.
세션이 있을 때
세션이 없을 때
tools/list 호출 횟수
O(서브에이전트 수 × 서버 수)
O(서버 수)
이유
모든 서브에이전트가, 모든 서버에 대해, 매번 다시 물어봐야 함
오케스트레이터가 한 번 받아두면 모든 서브에이전트가 재사용
SEP의 표현이 인상적입니다.
짧게 사는 서브에이전트를 많이 띄우는 오케스트레이터에서는, 이 오버헤드가 실제 도구 호출의 프로토콜 트래픽을 넘어설 수 있다.
그리고 이 비용은 세션이 쓰여서가 아니라 세션이 가능해서 발생합니다. 그래서 "세션을 선택 사항으로 두자"는 절충안이 기각됐습니다. 선택 사항이어도 클라이언트는 여전히 캐시할 수 없으니까요.
문제 ③ 상태가 딱 하나만 존재할 수 있었다
이건 표현력의 문제입니다. 세션에 묶인 상태는 세션당 정확히 하나입니다. 장바구니 하나, 브라우저 하나. 두 개를 가질 수도, 0개를 가질 수도 없습니다.
SEP가 든 예시가 명쾌합니다. 오케스트레이터가 서브에이전트 여러 개를 띄워 살 물건을 각자 조사하게 한다고 해봅시다.
장바구니는 공유해야 합니다 (하나의 주문을 함께 만드는 중이니까)
브라우저는 격리돼야 합니다 (각자 다른 사이트를 보는 중이니까)
세션 모델
장바구니 (원하는 것: 공유)
브라우저 (원하는 것: 격리)
서브에이전트가 부모 세션 공유
✓ 공유됨
✗ 같이 공유되어 서로 덮어씀
서브에이전트가 각자 세션
✗ 격리되어 따로 놀음
✓ 격리됨
어느 쪽도 답이 아닙니다. 세션은 경계를 하나만 제공하는데, 필요한 경계는 두 개였습니다.
문제 ④ 그리고 인프라 비용
여기까지가 잘 안 알려진 이유들이고, 익히 알려진 이유가 이것입니다. 세션 ID가 있으면 요청은 그 세션을 들고 있는 인스턴스로 가야 합니다.
// 클라이언트가 만들어내는 요청
POST /mcp
Mcp-Method: tools/call
Mcp-Name: execute_sql
Mcp-Param-Region: us-west1 ← 본문을 안 열고도 지역별 라우팅 가능
보안 장치도 함께 들어갔습니다. 헤더 값과 본문 값이 다르면 서버는 반드시 400과 함께 HeaderMismatch(-32020)를 돌려줘야 합니다. 로드밸런서는 헤더를 보고 라우팅하는데 서버는 본문을 보고 실행한다면, 그 둘이 어긋나는 순간이 곧 취약점이기 때문입니다.
3-3. 서버가 되묻는 법 — MRTR
여기가 이번 개정에서 가장 큰 구조 변경입니다.
예전에는 서버가 열린 스트림 위로 자기 JSON-RPC 요청을 밀어 넣을 수 있었습니다. 이제는 못 합니다. 명세가 못 박습니다 — 서버는 JSON-RPC 요청을 시작해서는 안 되며, 클라이언트는 JSON-RPC 응답을 보내지 않는다.
대신 Multi Round-Trip Requests(MRTR) 라는 패턴이 들어왔습니다. 한 단계씩 따라가 보세요.
발상의 전환이 깔끔합니다. 서버가 기다리는 대신, 지금까지의 진행 상황을 봉인해서(requestState) 클라이언트에게 맡기고 요청을 끝냅니다. 클라이언트는 사용자에게 물어본 답과 그 봉인을 함께 들고 다시 옵니다.
서버는 아무것도 기억할 필요가 없습니다. 그래서 아무 인스턴스나 이어받을 수 있습니다.
다만 이 설계에는 대가가 따르고, 명세가 그걸 정직하게 다룹니다.
requestState 보안 요구사항
이유
서버는 이 값을 공격자가 조작할 수 있는 입력으로 다뤄야 한다
클라이언트를 거쳐 오는 값이므로
인가·자원 접근·비즈니스 로직에 영향을 준다면 무결성을 보호해야 한다 (HMAC 또는 AEAD)
변조된 상태로 권한을 우회당하지 않기 위해
봉인 안에 인증 주체를 넣고, 다른 주체가 제시하면 거부
사용자 간 재사용(cross-user replay) 차단
봉인 안에 짧은 만료 시각(TTL)을 넣기
재전송 공격 창을 좁히기 위해
봉인 안에 원 요청 식별자(메서드명 + 파라미터 다이제스트)를 넣기
다른 요청에 갖다 붙이는 것 차단
명세는 여기서 한 번 더 정직합니다 — 이 조치들은 재전송 창을 좁히고 교차 사용·교차 요청 재사용을 막지만, 그 자체로 1회용을 보장하지는 않는다. 일회성이 꼭 필요하면 서버가 따로 강제해야 합니다.
3-4. 그럼 알림은 어떻게 받나
서버가 먼저 말을 걸 수 없게 되면 "도구 목록이 바뀌었다" 같은 알림은 어떻게 받을까요.
클라이언트가 subscriptions/listen을 요청으로 보내고, 그 응답이 오래 열려 있는 SSE 스트림이 됩니다. 요청-응답 구조는 유지한 채 스트림을 얻는 방식입니다.
요청 범위 알림 진행률·로그 그 요청의 응답 스트림에서만
장기 알림 목록 변경·리소스 갱신 subscriptions/listen 스트림에서
참고로 Last-Event-ID를 이용한 스트림 재개는 지원하지 않습니다. 스트림 상태는 요청 범위이고, 끊기면 클라이언트가 다시 요청합니다.
3-5. 이제 목록을 캐시할 수 있다
세션이 사라지면서 목록 결과가 안정적인 대상이 됐고, 그래서 캐싱이 가능해졌습니다. tools/list 같은 응답에 두 필드가 붙습니다.
필드
의미
ttlMs
이 결과를 얼마나 오래 캐시해도 되는지
cacheScope
어느 범위에서 공유 가능한지 (HTTP Cache-Control을 본떴습니다)
여기에 notifications/*/list_changed가 무효화 신호로 결합합니다. 웹이 30년간 써온 캐싱 모델을 그대로 가져온 것입니다.
3-6. 그럼 상태가 필요한 서버는?
가장 실무적인 질문입니다. 장바구니는요? 브라우저 세션은요? DB 트랜잭션은요?
답은 명시적 핸들(explicit handle) 패턴입니다. 그리고 중요한 점 — 이건 프로토콜 기능이 아닙니다.
// ← 결과
{ "structuredContent": { "basket_id": "bsk_a1b2c3" } }
// → 이후 호출들: 모델이 그 값을 인자로 들고 다닌다
{ "name": "add_item", "arguments": { "basket_id": "bsk_a1b2c3", "sku": "shoes" } }
{ "name": "checkout", "arguments": { "basket_id": "bsk_a1b2c3" } }
SEP의 표현대로, 프로토콜 관점에서 basket_id는 도구 결과에 담긴 문자열이고 도구 인자로 들어가는 문자열일 뿐입니다. 다른 데이터와 구별되지 않습니다. 스키마도, 와이어 포맷도, handles/* 메서드도 없습니다.
그리고 이건 이미 널리 쓰이던 패턴입니다. SEP가 든 예시들:
서버
생성 도구 → 반환되는 ID
그 ID를 받는 도구들
Linear
create_issue → 이슈 id
get_issue, update_issue
Notion
notion-create-pages → 페이지 id
notion-update-page
GitHub
create_pull_request → PR 번호
pull_request_read, merge_pull_request
Stripe
create_customer → 고객 id
create_invoice, list_subscriptions
그리고 이 방식이 세션보다 표현력이 높습니다. 앞서 본 서브에이전트 문제로 돌아가 보면 — 오케스트레이터가 create_basket()을 한 번 부르고 그 basket_id를 모든 서브에이전트에게 넘기고, 각 서브에이전트는 따로 create_browser()를 부릅니다. 공유할 것은 공유하고 격리할 것은 격리합니다. 모델이 결정합니다.
재개도 공짜로 따라옵니다. 핸들은 도구 결과에 담겨 있으므로 대화 기록의 일부입니다. 대화를 저장하는 클라이언트라면 핸들도 자동으로 저장됩니다. 앱을 껐다 켜도, 다른 기기에서 열어도, 그 값이 모델 앞에 다시 놓입니다.
실무 지침도 SEP가 정리해뒀습니다.
핸들 설계 지침
이유
불투명하게 만들 것 bsk_a1b2c3 ○ / cart_user42_2026-03-11 ×
구조가 보이면 클라이언트가 파싱하거나 모델이 값을 추측하려 든다
소지 ≠ 권한 매 호출마다 (핸들, 인증주체) 쌍을 검증
핸들은 채팅 로그·복사 버퍼·서브에이전트 프롬프트에 남는다
수명을 도구 설명에 쓸 것 "basket_id를 반환하며 24시간 미사용 시 만료"
문서에만 있으면 모델이 못 읽는다
만료 오류를 친절하게 "invalid argument" × / "bsk_a1b2c3은 만료되었습니다" ○
명확한 오류라야 모델이 create_*를 다시 불러 복구한다
생성 시 인자를 받을 것 create_context(cluster="staging")
왕복이 한 번으로 줄고, 절반만 설정된 상태가 존재할 수 없다
인증이 없는 서버라면 핸들이 곧 베어러 토큰이 됩니다. 그럴 때는 최소 128비트의 암호학적 난수로 만들고 수명을 제한하라고 명시합니다 — "링크 아는 사람은 누구나" 공유 URL이나 비밀번호 재설정 토큰과 같은 자세입니다.
4부. 버려진 것들, 그리고 새로 온 것들
샘플링·루트·로깅의 퇴장
세 기능이 폐기 예고됐습니다. SEP-2577의 이유를 보면 공통점이 있습니다 — 복잡도 대비 채택률이 낮았습니다.
기능
무엇이었나
왜 폐기하나
대체재
샘플링 sampling/createMessage
서버가 클라이언트의 모델에게 추론을 요청
구현이 복잡함(사람 승인 절차, 모델 선택 로직, 보안, 도구 루프까지) → 지원하는 클라이언트가 거의 없음
LLM 공급자 API를 서버가 직접 호출
루트 roots/list
클라이언트가 서버에게 "이 디렉터리를 보라"고 알려주기
의미가 모호했음 — 서버가 존중할 의무가 없는 "정보성 안내"였음
도구 파라미터 · 리소스 URI · 서버 설정
로깅 logging/setLevel
서버가 프로토콜로 로그 메시지 전송
이미 성숙한 인프라와 중복 · 모든 구현체의 부담만 늘림
stderr(stdio) · OpenTelemetry
보안 관점에서는 순이득이라고 SEP는 평가합니다. 샘플링이 셋 중 가장 민감했습니다 — 서버가 클라이언트를 통해 모델 추론을 요청할 수 있다는 건 프롬프트 인젝션과 데이터 유출의 통로였기 때문입니다.
다만 당장 사라지는 게 아닙니다. 최소 12개월은 그대로 작동하고, 와이어 수준 동작도 변하지 않습니다. 신규 개발에 쓰지 말라는 신호입니다.
인증: 등록에서 조회로
인증 쪽에도 흥미로운 전환이 있습니다. 동적 클라이언트 등록(DCR) 이 뒤로 물러나고 CIMD(Client ID Metadata Documents) 가 들어옵니다.
DCR (RFC 7591)
CIMD
방식
클라이언트가 서버에 등록 요청 → 서버가 client_id 발급
클라이언트가 HTTPS URL에 JSON 문서를 올려둠 → 그 URL이 곧 client_id
서버 부담
등록마다 DB 쓰기 발생
필요할 때 그 URL을 조회하면 끝
MCP에서의 문제
에이전트 하나가 수천 개 서버에 접속 → 서버는 다시 안 볼지도 모를 클라이언트 기록을 무한히 쌓아야 함
쌓을 것이 없음
같은 철학입니다. 미리 등록해서 상태를 만드는 대신, 필요할 때 조회한다. 세션을 없앤 것과 정확히 같은 방향입니다.
여기에 인가 강화 SEP가 여섯 개 더 붙었습니다 — RFC 9207에 따른 iss 검증 의무화, 자격증명을 발급 서버의 issuer에 바인딩, 스코프 누적 규칙 명확화 등입니다.
MCP Apps — 서버가 UI를 배달한다
폐기만 있는 건 아닙니다. 2026년 1월 첫 공식 확장으로 자리 잡은 MCP Apps(SEP-1865)가 이번에 정식 편입됐습니다.
서버가 대화형 HTML 인터페이스를 배달하고, 호스트가 그것을 샌드박스 iframe에서 렌더링합니다. 도구가 표를 텍스트로 뱉는 대신 정렬 가능한 표를 보여주고, 차트를 그리고, 폼을 띄울 수 있습니다.
보안 모델이 촘촘합니다.
MCP Apps 보안 장치
내용
샌드박스 필수
모든 UI는 권한이 제한된 샌드박스 iframe에서만 렌더링
사전 선언
UI 템플릿을 미리 선언 → 프리페치와 보안 검토가 가능
통신 경로 단일화
UI ↔ 호스트 통신은 postMessage를 거쳐 감사 가능한 MCP JSON-RPC로만
ui:// 스킴
UI 리소스를 별도 URI 스킴으로 구분
Claude Desktop, VS Code Copilot, Goose, Postman 등이 지원합니다.
확장 체계와 태스크
이번 개정의 조용하지만 중요한 성과가 확장(Extensions) 체계 공식화(SEP-2133)입니다.
역DNS 형식 ID로 식별하고(io.modelcontextprotocol/tasks), extensions 능력 맵으로 협상하고, 명세와 독립적으로 버전을 매깁니다. 핵심 명세를 얇게 유지하면서 실험을 계속할 수 있는 통로입니다.
실험적이던 태스크(Tasks) 가 이 체계를 타고 공식 확장이 됐습니다. 오래 걸리는 작업에 쓰는 기능인데, 스테이트리스에 맞춰 재설계됐습니다 — tools/call이 태스크 핸들을 돌려주고, 클라이언트가 tasks/get으로 진행 상황을 확인합니다.
여기서 재미있는 삭제가 하나 있습니다. tasks/list가 없어졌습니다. 이유가 명쾌합니다 — 세션 없이는 안전하게 범위를 한정할 수 없기 때문에. 세션이 없으면 "누구의 태스크 목록인지"를 정의할 방법이 마땅치 않습니다. 상태를 없애면 이런 부수 효과도 따라온다는 걸 보여주는 사례입니다.
5부. 현장 성적표
명세가 좋아 보이는 것과 실제로 이득이 나오는 것은 다른 문제입니다. 발표 전후로 실제 배포한 곳들의 보고를 모았습니다.
가장 구체적인 사례입니다. GitHub은 7월 23일 공지에서 세 가지를 바꿨다고 밝혔습니다.
변경
효과
Redis 세션 저장소 제거
"initialize에서의 DB 쓰기가 사라졌고, 모든 호출에서의 DB 읽기가 사라졌다"
심층 패킷 검사 제거
로깅과 시크릿 스캐닝에 필요한 값을 헤더에서 읽음 → "이제 모든 요청의 페이로드를 뜯어보지 않아도 된다"
일리시테이션 전환
URL 기반에서 단계별 HTTP 요청으로. Go SDK가 두 방식을 다 지원하는 래퍼 제공
단, GitHub은 "더 빨라졌다(snappier)"고만 했고 구체적인 지연 시간 수치는 공개하지 않았습니다.
나머지 생태계
조직
보고 내용
Cloudflare
"MCP 서버가 이제 Worker 하나만으로 돌아간다 — 상태 유지 인프라가 필요 없다." 자사 Code Mode 서버는 2026년 2월 출시 이후 수십억 건의 도구 호출을 처리
Manufact 수천 개 MCP 서버 호스팅
SDK 패키지 크기 83% 감소, 속도 25% 개선
Sentry
명세 확정 전에 프로덕션 배포 — 장애 없음
Supabase
세션 의존 연결에서는 불가능했던 호출 중간 사용자 확인이 가능해짐 (MRTR 덕분)
Microsoft
Foundry가 "수십 개 통합에서 수천 개로" 확장 가능해짐
AWS · Google Cloud
Bedrock AgentCore에 통합 / "엔터프라이즈 AI 확장성의 거대한 도약"
Supabase 사례가 특히 시사적입니다. MRTR은 제약을 푼 게 아니라 기능을 열었습니다. 예전에는 열린 스트림이 있어야만 중간에 되물을 수 있었는데, 세션 없는 서버는 그 스트림을 유지할 수 없어서 아예 포기해야 했습니다. 이제는 봉인해서 넘기면 되니 가능해진 것입니다.
규모
2025년 (The Register 보도)
월 9,700만 다운로드
2026년 7월 (공식 발표)
월 약 5억 다운로드
Tier 1 SDK 기준이고, TypeScript와 Python은 각각 누적 10억 다운로드를 넘었습니다. 그리고 네 개 Tier 1 SDK(TypeScript, Python, Go, C#)가 발표 당일부터 새 명세를 지원했습니다. Rust는 베타입니다.
InfoQ 기사 제목이 그 정서를 그대로 담았습니다 — "MCP가 스테이트리스로 갔다, 그리고 개발자들은 그게 그냥 다시 API가 된 것 아니냐고 묻는다."
일리가 있는 지적입니다. 세션을 없애고, 요청을 자기완결적으로 만들고, HTTP 헤더로 라우팅하고, 캐시 제어를 넣고 — 이건 REST가 20년 전에 정착시킨 원칙 그대로입니다.
비판 쪽 논거
아래는 해커뉴스 토론에서 나온 지적들입니다(공식 통계가 아니라 개발자들의 관찰과 우려입니다).
비판
내용
토큰 낭비
상태가 없으니 모델이 ID를 계속 들고 다녀야 한다. 예전엔 서버가 알아서 주입할 수 있던 것을 이제 컨텍스트에 실어야 한다
양방향 호환성 파괴
SDK만 올려서 끝나지 않고 리팩터링이 필요한 경우가 있다. 한 토론자는 색인된 62,700개 서버 중 활발히 관리되는 것은 약 30%뿐이라며, 나머지가 그대로 방치될 것을 우려했다
차별성 상실
MCP를 특별하게 만들던 양방향성이 사라졌다면, OpenAPI 명세와 무엇이 다른가
SDK 대응 격차
Java SDK는 Tier 2로 분류되어 대응 기한이 더 길다 — 그 사이 생태계가 갈라질 수 있다
첫 번째 비판(토큰 낭비)은 정직하게 인정해야 하는 대가입니다. basket_id를 매번 인자로 넘기는 것은 공짜가 아닙니다. SEP도 이 반론을 예상하고 답을 적어뒀는데, 요지는 "모델은 이미 파일 경로·URL·커밋 해시·PR 번호 같은 불투명한 식별자를 일상적으로 들고 다니며 잘 해낸다" 입니다. 다만 컨텍스트 압축(compaction)이 어려운 경우라는 것도 함께 인정합니다.
옹호 쪽 논거
반론 ①
"그냥 REST"라면, 없앤 것이 실제로 작동하고 있었어야 한다
2부에서 본 대로 세션은 작동하지 않고 있었습니다. ChatGPT는 호출마다 새로 만들었고, 거의 아무도 재개하지 않았습니다. 사라진 것은 기능이 아니라 기능의 환상이었습니다.
반론 ②
MCP의 가치는 상태 유지가 아니라 '합의'였다
도구를 어떻게 기술하고, 어떻게 발견하고, 어떻게 인가하고, 어떤 오류 코드를 쓰는지에 대한 공통 규약. REST는 이 층위를 표준화한 적이 없습니다 — 그래서 모든 API가 제각각인 것입니다.
반론 ③
웹의 문법으로 돌아온 것은 후퇴가 아니라 성숙이다
"에이전트 인프라를 웹의 나머지 부분처럼 만드는 것 — 스테이트리스하고, 캐시 가능하고, 라우팅 가능하고, 전역으로 확장되는." 30년간 검증된 인프라를 그대로 재사용할 수 있게 됐습니다.
Simon Willison의 평가가 균형점을 잘 짚습니다. 그는 MCP가 한동안 스킬(Skills)에 가려져 관심이 식었다가, 이번 변화로 다시 흥미를 되찾았다고 썼습니다.
"출시 이후 MCP 명세에 가해진 가장 중대한 변화이며, 이 프로토콜에 대한 내 개인적 관심에도 다시 불을 붙였다."
"클라이언트와 서버 양쪽 모두 구현 복잡도가 크게 줄어든다. 양쪽 관점에서 훨씬 깔끔하다."
그가 짚은 또 다른 장점이 흥미롭습니다 — 셸 접근을 주는 방식에 비해 MCP 도구는 감사하고 통제하기 쉽고, 노트북에서 도는 작은 모델도 충분히 다룰 만큼 단순하다는 것입니다. 그는 이 변화 이후 mcp-explorer, datasette-mcp, llm-mcp-client 세 가지를 직접 만들었고, "민감한 애플리케이션을 만들 때 MCP에 훨씬 더 기대겠다"고 썼습니다.
정리하자면
REST/OpenAPI
MCP 2026-07-28
전송 방식
HTTP 요청/응답
HTTP 요청/응답 (거의 같아짐)
도구 기술 방식
API마다 제각각
표준 스키마 (JSON Schema 2020-12)
발견
문서를 읽어야 함
tools/list 표준
모델이 되묻기
없음
MRTR 표준
인가
제각각
OAuth 2.1 + CIMD 프로파일
UI 배달
없음
MCP Apps
전송 계층은 REST에 수렴했고, 그 위의 규약은 여전히 MCP의 것입니다. 논쟁의 양측이 사실 서로 다른 층위를 이야기하고 있었던 셈입니다.
7부. 실무 — 내 서버는 어떻게 하나
가장 중요한 소식부터. 대부분은 할 일이 거의 없습니다.
SEP-2567은 오픈소스 MCP 서버 1,000개를 무작위 표본으로 뽑아 조사했습니다. 결과입니다.
세션 ID를 아예 참조하지 않음 → 할 일 없음
90.0%
SDK 보일러플레이트뿐 → SDK가 알아서 처리
6.3%
세션에 애플리케이션 상태를 저장 → 핸들로 이전
2.5%
게이트웨이 스티키 라우팅 → 재설계 필요
0.7%
인증 아티팩트를 세션에 바인딩 → 주의해서 교체
0.5%
90%는 SDK 버전만 올리면 됩니다. 진짜 손이 가는 건 하위 3.7%입니다.
직접 진단해 보세요.
서두르지 않아도 되는 이유
프로토콜 버전 협상이 혼합 배포를 처리합니다. 두 버전을 지원하는 클라이언트는 아직 이전하지 않은 서버에는 예전 프로토콜로, 나머지에는 새 프로토콜로 말합니다.
SEP는 이 롤아웃 방식을 "깔끔한 단절(clean break)" 이라고 부르는데, 이유가 흥미롭습니다. 클라이언트가 두 모드를 동시에 지원하는 버전을 내놓으면, 연결된 서버 중 하나라도 세션 방식일 가능성이 있으므로 목록 캐싱 이득이 사라집니다. 그래서 버전 경계를 명확히 그은 것입니다.
그래도 지금 해두면 좋은 것
작업
난이도
효과
Tier 1 SDK 최신 버전으로 올리기
낮음
90%는 여기서 끝. TypeScript · Python · Go · C#은 이미 지원
도구 스키마를 JSON Schema 2020-12 검증기에 통과시키기
낮음
느슨하게 쓴 스키마는 이제 거부될 수 있음. 대신 oneOf/$ref 같은 조합 키워드를 쓸 수 있게 됨
목록 응답에 ttlMs 붙이기
낮음
클라이언트가 캐시할 수 있게 되어 왕복이 줄어듦
클라이언트에서 -32002 비교 코드 찾기
낮음
-32602로 변경 (SEP-2164)
세션에 담긴 상태를 핸들로 옮기기
중간~높음
도구 인터페이스가 바뀌므로 모델이 보는 화면도 달라짐 — 도구 설명을 다시 쓸 것
서버 주도 요청을 MRTR로 전환
중간~높음
requestState 무결성 보호(HMAC/AEAD)가 필수
마지막 두 줄은 보안 검토를 함께 받으시길 권합니다. 특히 requestState는 클라이언트를 거쳐 오는 값이므로, 서명 없이 신뢰하면 그대로 권한 우회 통로가 됩니다.
놓치기 쉬운 것들
함정
내용
stdio 서버도 안심할 수 없다
프로세스 수명에 기대는 상태는 당장은 깨지지 않습니다. 하지만 명세는 그러지 말라고 권합니다 — 프로세스 수명도 세션과 똑같이 의미가 정의되지 않은 경계이고, HTTP로 옮기는 순간 동등한 동작을 제공할 수 없습니다
목록을 도중에 바꾸던 패턴은 금지
connect_database() 후에 도구가 나타나는 방식은 이제 허용되지 않습니다. 처음부터 다 노출하고 connection_id를 인자로 받으세요