MCP 새 스펙(2026-07-28) 총정리 - 세션이 사라졌어요! 스테이트리스 전환·MCP Apps·Tasks 🔌
목차20개 항목
안녕하세요! Mojangcam입니다 😎
어제(2026-07-28) MCP(Model Context Protocol) 새 스펙 2026-07-28이 정식 발행됐어요. 그런데 이번 건 "기능 몇 개 추가" 수준이 아니에요. 프로토콜의 뼈대 자체가 바뀐, MCP 출시 이후 가장 큰 변경입니다.
한 줄로 요약하면 이래요. MCP가 스테이트리스(Stateless)가 됐어요. 핸드셰이크도 없고, 세션 ID도 없어졌어요.
어제 Claude Opus 5 출시 글에 이어 오늘도 AI 개발자 뉴스인데요, 이번 글은 ① MCP가 뭔지 30초 복습 → ② 뭐가 바뀌었는지 → ③ 내 Claude Code 설정은 어떻게 되는지 → ④ 내가 만든 MCP 서버는 어떻게 고치는지 순서로 입문자 눈높이에 맞춰 정리해볼게요!

🧠 잠깐, MCP가 뭐였죠? (30초 복습)
MCP는 AI 모델과 외부 도구를 연결하는 표준 규격이에요. USB-C 단자를 떠올리면 쉬워요. 예전엔 도구마다 연결 방식이 제각각이었는데, MCP가 나오면서 "이 규격만 맞추면 어느 AI든 붙는다"가 된 거죠.
- MCP 서버 : 도구를 제공하는 쪽 (예: 노션 연동, 깃허브 연동, 내 회사 DB 조회)
- MCP 클라이언트 : 도구를 쓰는 쪽 (예: Claude Code, Claude 데스크톱 앱, Cursor)
Claude Code 입문 글에서 MCP 서버를 붙여보셨다면, 그게 바로 이 프로토콜이에요. 공식 발표에 따르면 Tier 1 SDK 기준 월 다운로드가 4억 건을 넘었다고 하니, 이제 사실상 업계 표준이 된 셈이에요.
📌 이번 스펙 핵심 3줄 요약
- 스테이트리스 전환 —
initialize핸드셰이크와Mcp-Session-Id헤더가 사라졌어요. 요청 하나하나가 독립적이라 아무 서버 인스턴스에나 꽂혀도 돼요. - 확장(Extensions) 프레임워크 정식화 — Tasks가 코어에서 빠져 확장으로 이사했고, MCP Apps·EMA와 함께 정식 확장이 됐어요.
- 인증 하드닝 + 첫 공식 지원 중단 정책 — RFC 9207
iss검증이 들어오고, DCR이 CIMD에 자리를 내줬어요. 그리고 최소 12개월 지원 중단 유예 정책이 처음 생겼어요.
🧩 변화 1 - 핸드셰이크와 세션이 사라졌어요
가장 큰 변화예요. 기존 MCP는 연결할 때 initialize → notifications/initialized 인사를 주고받고, 그 뒤로 Mcp-Session-Id 헤더로 세션을 유지했어요. 이 둘이 통째로 제거됐습니다.
대신 이제 모든 요청이 자기소개를 들고 다녀요. 프로토콜 버전과 클라이언트 정보를 _meta에 담아 보내는 식이에요.
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"search","arguments":{"q":"otters"},
"_meta":{"io.modelcontextprotocol/clientInfo":{"name":"my-app","version":"1.0"}}}}하나씩 뜯어볼게요 💡
- MCP-Protocol-Version : 이 요청이 어떤 스펙 버전으로 말하는지 알려줘요.
- _meta : 클라이언트 신원(
clientInfo)·능력(clientCapabilities)·프로토콜 버전을 담는 자리예요. 예전에 핸드셰이크 때 한 번만 주고받던 정보가 매 요청에 붙는다고 보시면 돼요. - server/discover : 서버 능력을 미리 알고 싶으면 쓰는 새 RPC예요. 서버는 반드시 구현해야 하지만, 클라이언트는 안 불러도 돼요.
왜 이렇게 바꿨을까요? 확장성 때문이에요. 세션이 없으니 요청이 서버 A로 갔다가 다음 요청은 서버 B로 가도 아무 문제가 없어요. 즉 평범한 라운드로빈 로드밸런서 뒤에 그냥 세워도 되고, 서버리스·엣지에도 배포할 수 있어요. 공유 스토리지도 필요 없고요.
그럼 "내 서버는 상태를 들고 있어야 하는데?" 싶으시죠. 공식 권장은 이래요. 툴이 핸들(handle)을 발급하고, 모델이 그 핸들을 인자로 다시 넘기게 하세요. 전송 계층에 숨어 있던 상태를 모델이 볼 수 있는 자리로 끌어올린 거예요.
🧩 변화 2 - MRTR, 중간에 사용자한테 물어보는 새 방식
툴이 실행 도중에 사용자한테 뭘 물어봐야 할 때가 있어요. "정말 삭제할까요?" 같은 확인이나, 빠진 파라미터를 받아야 할 때요.
예전엔 서버가 클라이언트한테 elicitation/create·sampling/createMessage·roots/list 요청을 거꾸로 보냈어요. 이러려면 양방향 스트림을 계속 열어둬야 했죠. 스테이트리스랑 정면으로 부딪히는 구조예요.
그래서 나온 게 MRTR(Multi Round-Trip Requests)예요. 흐름은 이래요.
- 클라이언트가
tools/call호출 - 서버가
resultType: "input_required"와inputRequests(필요한 정보 목록)를 응답 - 클라이언트가 답을
inputResponses에 담아 원래 요청을 다시 호출
스트림을 붙잡고 있을 필요가 없어졌어요. 그리고 이와 함께 모든 응답에 resultType 필드가 필수가 됐어요. 값은 "complete"(보통 결과) 또는 "input_required"(중간 결과) 둘 중 하나고요. 구버전 서버가 이 필드를 안 보내면 클라이언트는 "complete"로 취급해야 해요.

🧩 변화 3 - 메서드 이름이 HTTP 헤더로 나왔어요
Streamable HTTP POST 요청에 Mcp-Method와 Mcp-Name 헤더가 필수가 됐어요.
별거 아닌 것 같지만 운영 입장에선 꽤 커요. 게이트웨이·레이트리미터·WAF가 JSON 본문을 파싱하지 않고 헤더만 보고 라우팅하고 요금 측정을 할 수 있거든요. 툴 파라미터에서 커스텀 헤더를 넘기는 x-mcp-header도 함께 추가됐어요.
🧩 변화 4 - 목록 응답을 캐싱할 수 있어요
tools/list·prompts/list·resources/list·resources/read·resources/templates/list 응답에 ttlMs와 cacheScope 필드가 필수로 붙었어요.
- ttlMs : "이 응답은 몇 밀리초 동안 신선해요"라는 힌트예요. 클라이언트가 매번 다시 물어보지 않아도 되죠.
- cacheScope :
"public"이면 중간 캐시 서버도 캐싱 가능,"private"이면 안 돼요.
덤으로 서버는 tools/list 결과를 매번 같은 순서로 돌려주는 게 권장돼요. 순서가 흔들리면 LLM 프롬프트 캐시가 깨지거든요. 이거 은근 성능·비용에 영향이 커요 💡
🧩 변화 5 - Tasks는 확장으로, MCP Apps는 정식으로
실험 단계였던 Tasks(오래 걸리는 작업)가 코어에서 빠져 io.modelcontextprotocol/tasks 공식 확장이 됐어요. 바뀐 점은 이래요.
- 블로킹 방식
tasks/result→ 폴링 방식tasks/get으로 교체 - 클라이언트가 중간 입력을 넣는
tasks/update신설 tasks/list제거- 서버가 요청별 opt-in 없이도 태스크 핸들을 먼저 돌려줄 수 있게 됨
그리고 대화창 안에 인터랙티브 UI를 띄우는 MCP Apps, 기업 IdP로 커넥터를 일괄 승인하는 EMA(Enterprise Managed Authorization)도 정식 확장 목록에 들어왔어요. 코어를 안 건드리고 기능을 얹는 길이 공식화된 거예요.
🧩 변화 6 - 인증(Auth)이 단단해졌어요
- RFC 9207
iss검증 : 인가 서버는 응답에iss를 넣어야 하고, 클라이언트는 코드를 교환하기 전에 이걸 검증해야 해요. 인가 서버 혼동(mix-up) 공격을 막는 장치예요. application_type지정 : DCR 때 이 값을 넣도록 해서, CLI·데스크톱 앱의localhost리다이렉트가 거부되는 문제를 없앴어요. OAuth 로그인에서redirect_uri에러 보신 적 있다면 바로 이거예요.- 자격증명은 발급한 인가 서버에 묶임 : 다른 인가 서버에 재사용 금지, 서버가 바뀌면 재등록해야 해요.
- DCR 지원 중단 : 동적 클라이언트 등록(RFC 7591)이 CIMD(Client ID Metadata Documents)에 자리를 내줬어요. 당장은 계속 동작하지만 새로 만들 땐 CIMD를 쓰세요.
⚠️ 깨지는 변경 정리 (Breaking Changes)
제 서버가 영향받는지 여기서 빠르게 확인해보세요.
| 항목 | 변화 |
|---|---|
initialize / notifications/initialized |
제거 |
Mcp-Session-Id 헤더 |
제거 |
ping, logging/setLevel, notifications/roots/list_changed |
제거 |
resources/subscribe / unsubscribe, HTTP GET 엔드포인트 |
subscriptions/listen 스트림으로 대체 |
tasks/list |
제거 (Tasks 확장으로 이동) |
SSE 재개(Last-Event-ID)·메시지 재전송 |
제거 — 스트림 끊기면 새 요청 ID로 재요청해야 함 |
| 리소스 없음 오류 코드 | -32002 → -32602 (Invalid Params) |
Mcp-Method / Mcp-Name 헤더 |
필수화 |
resultType 필드 |
모든 응답에 필수화 |
| Roots · Sampling · Logging | 지원 중단(deprecated) — 최소 12개월은 동작 |
| HTTP+SSE 레거시 전송 | 지원 중단 — Streamable HTTP로 이전 |
오류 코드도 정리됐어요. -32000-32019는 구현체 자유 구역, -32020-32099는 MCP 스펙 예약 구역이 됐고요. 이에 맞춰 HeaderMismatch는 -32020, MissingRequiredClientCapability는 -32021, UnsupportedProtocolVersion은 -32022로 번호가 바뀌었어요.
🛠️ 내 Claude Code·Cursor 설정은 뭐가 바뀌나요?
결론부터요. 일반 사용자는 지금 당장 할 일이 거의 없어요. 😌
- 클라이언트 쪽 대응은 SDK와 앱이 알아서 해줘요. Claude 제품군은 새 스펙 지원이 순차 적용 중이라고 공지됐어요.
.mcp.json/claude_mcp설정 형식 자체가 바뀐 건 아니에요. 서버 주소·명령어를 적는 방식은 그대로예요.- 다만 내가 붙여둔 원격 MCP 서버가 구버전 스펙에 머물러 있으면, 클라이언트가 새 스펙으로 말하기 시작할 때 호환 문제가 생길 수 있어요. 그래서 Claude Code와 MCP 서버 양쪽 다 최신으로 올려두는 게 안전해요.
# Claude Code 최신화
claude update
# 등록된 MCP 서버 상태 확인
claude mcp list연결이 이상하면 /mcp 명령으로 서버별 연결 상태와 인증 상태를 먼저 확인해보세요.

🛠️ 내가 만든 MCP 서버는 어떻게 고치나요?
준비물
- Python 3.10 이상 (새 Python SDK
mcp2.x의 최소 요구 버전이에요) - MCP Python SDK 2.x
pip install --upgrade "mcp>=2.0.0"TypeScript·Go·C# Tier 1 SDK 4종은 어제 자로 모두 2026-07-28을 지원하고, Rust SDK는 베타로 지원해요.
1단계 - 세션에 기대던 코드부터 찾기
가장 먼저 해야 할 일이에요. 세션 ID를 키로 뭔가를 저장하고 있었다면 전부 걷어내야 해요.
# ❌ 예전 방식 - 세션에 상태를 숨겨둠
SESSIONS = {}
def start_job(session_id, path):
SESSIONS[session_id] = {"path": path, "step": 0}
return "작업을 시작했어요"세션이 없어졌으니 이 코드는 동작할 수가 없어요.
2단계 - 핸들(handle)을 발급해 인자로 넘기기
공식 권장 패턴이에요. 서버가 핸들을 만들어 결과로 돌려주고, 모델이 다음 툴 호출에 그 핸들을 인자로 넣게 하는 거예요.
# mcp 2.x / Python 3.10+
import uuid
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("job-runner")
# 상태는 외부 저장소(여기선 예시로 dict)에 핸들 키로 저장
JOBS: dict[str, dict] = {}
@mcp.tool()
def start_job(path: str) -> str:
"""작업을 시작하고 작업 핸들을 돌려줘요."""
job_id = uuid.uuid4().hex[:12]
JOBS[job_id] = {"path": path, "step": 0}
return f"작업을 시작했어요. job_id={job_id}"
@mcp.tool()
def job_status(job_id: str) -> str:
"""작업 핸들로 진행 상황을 확인해요."""
job = JOBS.get(job_id)
if job is None:
return "그런 job_id는 없어요. 다시 확인해주세요."
return f"{job['path']} 처리 중 (step {job['step']})"
if __name__ == "__main__":
mcp.run()하나씩 뜯어볼게요 💡
- job_id : 서버가 발급한 핸들이에요. 세션 대신 이 값이 요청들을 이어줘요.
- JOBS : 예시라 딕셔너리를 썼지만, 실제 배포에선 Redis·DB 같은 공용 저장소에 넣어야 해요. 인스턴스가 여러 대여도 아무 데서나 조회되게요.
- 모델이 핸들을 본다 : 전송 계층에 숨는 대신 결과 텍스트에 드러나니, 모델이 다음 호출에 자연스럽게 넣어줘요. 공식 발표에서도 세션 상태보다 이 방식이 더 잘 동작한다고 설명해요.
⚠️ 위 코드는 핸들 패턴을 보여주기 위한 예시예요. SDK 2.x의 세부 API는 버전마다 조금씩 다를 수 있으니, 실제 마이그레이션 전엔 각 SDK 저장소의 마이그레이션 노트를 꼭 확인해주세요.
3단계 - 제거된 기능 대체하기
| 쓰던 기능 | 대체 방법 |
|---|---|
| Roots | 디렉터리·파일 경로를 툴 파라미터나 리소스 URI, 서버 설정으로 전달 |
| Sampling | LLM 제공자 API를 직접 호출 |
| Logging | stdio면 stderr로 출력, 아니면 OpenTelemetry 사용 |
resources/subscribe |
subscriptions/listen 스트림에 opt-in |
| HTTP+SSE 전송 | Streamable HTTP로 이전 |
로그 레벨은 이제 _meta의 io.modelcontextprotocol/logLevel로 요청마다 지정해요. 그리고 이 필드가 없는 요청에는 서버가 notifications/message를 보내면 안 돼요.
❗ 자주 나는 에러
1. UnsupportedProtocolVersionError (-32022)
- 원인 : 클라이언트가 보낸
MCP-Protocol-Version을 서버가 지원하지 않아요. 한쪽만 업데이트했을 때 잘 나요. - 해결 : 클라이언트·서버 SDK를 둘 다 최신으로 올리세요. 미리 확인하려면
server/discover를 호출해 지원 버전을 받아보면 돼요.
2. HeaderMismatchError (-32020)
- 원인 : HTTP 헤더의
Mcp-Method·Mcp-Name값이 JSON 본문의 메서드·툴 이름과 안 맞아요. 프록시를 직접 짜서 붙였을 때 자주 발생해요. - 해결 : 헤더와 본문을 같은 값으로 맞추세요. 이 두 헤더는 이제 선택이 아니라 필수예요.
3. 리소스를 못 찾는데 에러 코드가 예전과 달라요
- 원인 : 리소스 없음 코드가
-32002→-32602로 바뀌었어요. - 해결 :
-32002를 하드코딩해둔 에러 처리 분기를 전부 찾아 고치세요. 조용히 안 잡히는 유형이라 특히 조심해야 해요.
4. 스트림이 끊긴 뒤 이어받기가 안 돼요
- 원인 : SSE 재개(
Last-Event-ID)와 메시지 재전송이 제거됐어요. - 해결 : 재개를 기대하지 말고 새 요청 ID로 요청을 다시 보내세요. 클라이언트에 재시도 로직을 넣어두는 게 좋아요.
5. localhost 리다이렉트에서 OAuth가 거부돼요
- 원인 : DCR 때
application_type을 안 넣어서 인가 서버가localhost를 거부해요. - 해결 : 등록 시
application_type을 알맞게 지정하세요. 신규 구축이라면 아예 CIMD로 가는 걸 권해요.
💬 그래서, 지금 뭘 해야 하나요?
입장별로 정리하면 이래요.
- MCP를 쓰기만 하는 분 : 할 일 거의 없어요. Claude Code·클라이언트 앱만 최신으로 유지하시면 돼요.
- MCP 서버를 만든 분 : 세션 의존 코드부터 찾아 걷어내세요. 다행히 지원 중단 기능들은 최소 12개월 유예가 있어서 시간이 있어요.
- 이제 만들려는 분 : 처음부터 스테이트리스 + 핸들 패턴으로 설계하세요. 서버리스에 그냥 올릴 수 있다는 게 생각보다 큰 이점이에요.
📌 마무리
오늘은 어제 나온 MCP 2026-07-28 스펙을 정리해봤어요. 요약하면 세션이 사라지고(스테이트리스), 확장 프레임워크가 정식화되고, 인증이 단단해졌다 이 세 가지예요.
깨지는 변경이 적지 않아요. 하지만 "임시방편으로 덮지 말고 제대로 고치자"는 방향이라 장기적으론 훨씬 나은 선택이라고 봐요. 무엇보다 12개월 지원 중단 정책이 생긴 게 반가워요. 이제 갑자기 뭔가 깨지는 일 없이 계획을 세울 수 있으니까요 🙌
다음 편에서는 디스코드 봇 데이터 저장 - JSON vs SQLite를 다뤄볼게요. 봇에 출석·포인트 같은 기능을 붙이려면 꼭 필요한 내용이니 기대해주세요!
📚 함께 보면 좋은 글
- Claude Code 입문 - 설치부터 첫 실행까지
- Claude Opus 5 출시! Claude Code 기본 모델 교체 - /model·/effort 활용법
- Cursor 사용법 입문 - AI 코드 에디터 완전 정복
