1. 왜 핸드셰이크가 필요한가
REST 엔드포인트가 URL로 두드릴 수 있는 개별 문들의 모음이라면, MCP는 하나의 채널에서 지속되는 대화에 가깝습니다. 클라이언트는 단지 흩어진 요청만 보내는 것이 아니라, 먼저 세션을 확립합니다. 핸드셰이크는 이 세션의 시작에서 이뤄지는 인사 절차입니다.
MCP에서는 이 순간이 클라이언트가 전송(STDIO, HTTP/stream, WebSocket — 상관없음)을 설정하자마자 보내는 특별한 요청 initialize로 구현됩니다. 이 요청에서 클라이언트는 “나는 이런 버전의 MCP로 대화하고, 이런 것들을 지원하며, 나의 정체는 이렇다”라고 알립니다. 서버는 “나는 이런 버전과 이런 역량들을 지원한다, 반갑다”라고 응답합니다.
성공적으로 교환이 끝나면 클라이언트는 notifications/initialized 알림을 보내고, 그 이후에야 실제 작업이 시작됩니다: tools/list, resources/list, tools/call 등 유용한 메서드들이 이어집니다.
비유하자면, MCP 핸드셰이크는 데이터센터에 서버를 들여놓기 전에 체결하는 임대 계약과 같습니다. 규칙(프로토콜 형식, 데이터센터가 제공하는 서비스, 과금 주체 등)에 합의하기 전에는 서버를 옮겨봐야 소용이 없습니다.
실무적인 관점에서 핸드셰이크는 세 가지를 해결합니다:
- 프로토콜 버전 호환성을 점검합니다.
- MCP의 “프리미티브” 중 서버가 지원하는 항목을 선언합니다: tools, resources, prompts, 로깅, 노티피케이션 등.
- 클라이언트와 서버의 메타정보 — 구현 이름과 버전 — 를 제공합니다.
2. MCP 연결의 생명 주기: 핸드셰이크는 어디에 위치하나
추상적으로 느껴지지 않도록, 전형적인 연결 시나리오(flow)를 크게 단순화해 보겠습니다:
sequenceDiagram
participant C as 클라이언트 (ChatGPT/Inspector)
participant S as MCP 서버
C->>S: (1) 트랜스포트 설정 (STDIO/HTTP-stream)
C->>S: (2) Request: "initialize"
S-->>C: (3) Result: "initialize" (capabilities, serverInfo)
C->>S: (4) Notification: "notifications/initialized"
C->>S: (5) Request: "tools/list" / "resources/list"
S-->>C: (6) Result: 도구/리소스 목록
C->>S: (7) Request: "tools/call" 등
기술적으로는 다음과 같은 단계입니다:
- 트랜스포트가 설정됩니다: 예를 들어 ChatGPT가 여러분의 서버를 서브프로세스로 실행하고 STDIO에 연결하거나, Inspector가 /mcp에 HTTP/stream 요청을 보냅니다.
- 클라이언트가 JSON-RPC 요청 initialize를 보냅니다.
- 서버는 protocolVersion, capabilities, serverInfo 필드가 포함된 JSON-RPC 결과로 응답합니다.
- 클라이언트가 notifications/initialized 노티피케이션을 보냅니다 — “모두 읽었으니, 이제 작업을 시작해도 된다”는 신호입니다.
- 클라이언트는 서버의 capabilities에서 확인한 항목에 맞춰 discovery 메서드(tools/list, resources/list, prompts/list)를 호출합니다.
- 서버는 도구/리소스/프롬프트의 메타데이터를 반환합니다.
- 이후에는 “업무용” 요청들이 이어집니다: tools/call, resources/read 등.
중요한 점은, 핸드셰이크는 initialize라는 평범한 JSON-RPC 호출일 뿐이라는 사실입니다. 마법은 없습니다. MCP 메시지 포맷 강의 이후라면 이런 요청을 해석하는 법을 이미 알고 있을 겁니다. 유일한 차이는 이 메서드는 항상 하나이고 “특별하며”, 가장 먼저 수행된다는 점입니다.
3. initialize에서 클라이언트가 보내는 내용
initialize 요청을 부분별로 살펴보겠습니다. 최소한의(강의용으로 단순화한) 예시는 다음과 같습니다:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {
"elicitation": {}
},
"clientInfo": {
"name": "chatgpt-gift-client",
"version": "2.3.0"
}
}
}
이 예시는 MCP 공식 문서에 제시된 내용과 가깝습니다. params의 핵심 필드는 다음과 같습니다:
protocolVersion
MCP 명세 버전을 나타내는 문자열로, 보통 날짜 형식입니다. 예: "2025-06-18". 이는 여러분 앱의 버전이 아니라 프로토콜 자체의 버전입니다. 클라이언트는 “이 버전의 MCP로 대화하겠다”라고 말합니다. 서버는 이를 지원하면 확인해 주고, 모르는 버전이면 오류를 반환해야 합니다.
이는 “클라이언트는 이렇게 생각하고, 서버는 저렇게 구현했다”라는 상황에 대한 방어막입니다. 공통 버전을 찾지 못하면, 호환되지 않는 메시지를 주고받는 것보다 연결을 정직하게 끊는 편이 낫습니다.
capabilities (클라이언트)
클라이언트가 스스로 어떤 MCP 기능을 지원하는지를 선언하는 객체입니다. 예를 들어 ChatGPT 클라이언트는 자주 elicitation 키를 지정해, 사용자에게 추가 입력이나 확인을 요청하는 상호작용을 처리할 수 있음을 신호합니다.
예시:
"capabilities": {
"elicitation": {},
"sampling": {}
}
서버는 이를 참고해 어떤 확장 기능을 실제로 사용할 수 있는지 판단할 수 있습니다. 예컨대 elicitation은 클라이언트(ChatGPT)가 사용자에게 추가 질문을 하거나 추가 데이터를 요청할 수 있음을 의미합니다.
clientInfo
간단한 메타정보로, 클라이언트의 이름과 버전입니다.
"clientInfo": {
"name": "ChatGPT",
"version": "2.0.0"
}
서버 개발자 관점에서는 로그에 금과 같은 정보입니다. 현재 접속한 클라이언트가 정확히 누구인지 — ChatGPT인지, MCP Inspector인지, 자체 테스트 클라이언트인지 — 그리고 그 버전이 무엇인지 항상 확인할 수 있습니다.
4. 서버의 응답: initialize result
initialize에 대한 응답은 동일한 id를 담은 평범한 JSON-RPC 결과이며, result 필드에 서버가 지원하는 기능 설명이 들어갑니다.
요청에서는 클라이언트가 스스로 지원하는 capabilities를 보았습니다. 이제는 응답에서의 짝 객체, 즉 서버의 capabilities를 분석합니다. 개념적으로는 다음과 같습니다:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {},
"prompts": {},
"logging": {}
},
"serverInfo": {
"name": "gift-genius-backend",
"version": "0.1.0"
}
}
}
비슷한 구조를 프로토콜 공식 설명이나 SDK 문서에서도 보게 될 것입니다. 주요 부분은 다음과 같습니다:
응답의 protocolVersion
서버는 클라이언트가 제안한 버전을 그대로 반복하거나(일반적), 이론적으로는 여러 공통 버전 중 하나를 선택할 수도 있습니다. 보통 구현에서는 서버가 해당 버전을 지원하면 그대로 확인합니다. 지원하지 않으면 서버는 오류를 반환하고 통신을 종료해야 합니다.
serverInfo
서버의 메타정보: 이름과 버전입니다.
"serverInfo": {
"name": "gift-genius-backend",
"version": "0.1.0"
}
지루해 보일 수 있지만, 실제로는 나중에 로그에서 “왜 버전 X의 ChatGPT가 우리 버전 Y 서버와 합의하지 못하는가”를 추적할 때 이 데이터로 필터링하고 찾아보게 됩니다.
서버의 capabilities
가장 흥미로운 필드입니다. 여기서 서버는 tools/*, resources/*, prompts/*를 처리할 수 있는지, 목록 변경 알림을 보낼 수 있는지 등의 MCP 프리미티브와 확장들을 선언합니다.
만약 capabilities에 tools 섹션이 없다면, 제대로 구현된 어떤 클라이언트도 tools/list나 tools/call을 호출하지 않을 것입니다. 마찬가지로 resources가 없다면 클라이언트는 resources/list와 resources/read를 보내지 않습니다.
이처럼 capabilities는 “이 서버에 대해 무엇을 할 수 있고, 무엇을 할 수 없는지”를 알려주는 가벼운 계약입니다.
5. Capabilities를 “능력 목록”처럼 이해하기
이제부터는 서버의 capabilities만 관심 대상입니다 — initialize 응답으로 오며, 이 서버가 어떤 MCP 프리미티브를 지원하는지 결정합니다.
그 구조를 좀 더 자세히 보겠습니다. (명세에 가까우나 단순화한) 예시는 다음과 같습니다:
{
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": false
},
"logging": {}
}
이런 예시는 MCP 공식 아키텍처에서 다뤄집니다. 섹션별로 풀어보겠습니다.
Capabilities.tools
tools 키가 존재한다는 것은 서버가 tools/list와 tools/call 메서드에 응답할 수 있음을 뜻합니다. 여기에 listChanged: true 플래그가 있으면, 앞으로 도구 목록이 바뀔 때 tools/list_changed 알림을 보낼 수 있다는 의미입니다.
ChatGPT 입장에서는 유용합니다. 도구 목록을 캐시해 두었다가 list_changed를 받으면 전체 재연결 없이 갱신할 수 있기 때문입니다.
Capabilities.resources
resources 섹션은 서버가 리소스 작업 — resources/list, resources/read, 때로는 검색 — 을 지원함을 선언합니다. 내부 플래그는 다음과 같습니다:
- subscribe: true — 클라이언트가 리소스 변경에 구독할 수 있습니다(예: 라이브 로그나 파일 업데이트).
- listChanged: true — 리소스가 추가/삭제되면 서버가 resources/list_changed 알림을 보낼 수 있습니다.
이는 대규모 디렉터리나 지속적으로 변하는 “라이브” 데이터에는 특히 중요합니다.
Capabilities.prompts
서버가 사전 등록된 프롬프트(예: 여러분 도메인에 맞춘 모델 호출 템플릿)를 제공한다면 capabilities에 prompts 키가 나타납니다. 이 안에도 listChanged 플래그가 있을 수 있습니다.
클라이언트는 이 섹션을 보고 prompts/list 및 필요하다면 prompts/get을 사용할 수 있음을 이해합니다.
Capabilities.logging 및 기타
일부 서버 구현은 logging도 선언합니다 — 이는 서버가 디버깅용으로 MCP를 통해 구조화된 로그를 클라이언트에 보낼 수 있음을 뜻합니다.
그 밖에 sampling 같은 다른 섹션이나 특정 확장이 나타날 수 있습니다. 프로토콜은 처음부터 확장 가능하도록 설계되었기에, 여러분은 capabilities에 새 키를 추가할 수 있고, 오래된 클라이언트는 이를 모르면 그냥 무시합니다.
Insight
실험적으로 확인된 바에 따르면, ChatGPT App은 전달된 listChanged 메시지를 무시합니다. 현재 시점에서는 앱을 작성할 때, 한 번에 한 세트의 tools만 선언할 수 있고 그 뒤에 tools를 추가/제거하는 것은 불가능합니다. MCP 프로토콜 자체는 이를 허용하지만 말이죠.
이 강의를 작성하는 시점의 상황은 다음과 같습니다: ChatGPT Store에 여러분의 앱을 등록하는 순간, ChatGPT는 앱의 tools 및 resources 목록을 요청해 영구 캐시합니다. 2026년 중에 상황이 바뀔 가능성은 큽니다만, 2026년 1분기 안에 바뀔 가능성은 낮습니다.
6. 핸드셰이크 이후의 discovery: 도구와 리소스 목록을 얻는 방법
핸드셰이크는 “서버가 무엇을 할 수 있는가”에 대한 답을 줍니다. 다음 단계는 이른바 discovery로, 클라이언트가 구체적인 메서드를 통해 세부 정보 — 정확히 어떤 도구가 있고, 어떤 리소스가 사용 가능하며, 어떤 프롬프트가 내장돼 있는지 — 를 가져옵니다.
이를 위해 discovery 메서드, 즉 tools/list, resources/list, prompts/list가 사용됩니다. MCP 아키텍처 문서에서도 “핸드셰이크 → 디스커버리 → 도구 호출”로 설명합니다.
요청 예시 tools/list:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}
서버의 응답에는 도구 배열이 담깁니다: 이름, 설명, 인자에 대한 JSON Schema, 그리고 때로는 카테고리나 아이콘 같은 메타데이터가 포함됩니다.
이후 ChatGPT(또는 다른 클라이언트)는 목록을 캐시해, 대화 중에 이를 활용하여:
- 사용자 과제에 맞는 적절한 도구를 선택하고;
- 도구 이름이 존재하는지 확인하며;
- tools/call을 보내기 전에 인자를 검증합니다.
리소스도 유사하지만, resources/list는 종종 커서 기반 페이지네이션을 지원해 한 번에 수많은 레코드를 가져오지 않도록 합니다. 이는 MCP 명세에 설명되어 있으며, 대규모 디렉터리에 대한 전형적인 사례로 다뤄집니다.
7. 우리 앱 GiftGen으로 보는 핸드셰이크와 capabilities
앞선 모듈에서 선물 아이디어를 골라주는 학습용 애플리케이션을 만들었습니다. 위젯이 있고, 백엔드에는 suggest_gifts 도구가 있으며, 선물 카탈로그도 어느 정도 갖춰져 있습니다. 이제 MCP 서버 gift-genius의 핸드셰이크가 어떻게 보일지 상상해 봅시다.
GiftGen 핸드셰이크 예시
클라이언트 요청:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {
"elicitation": {}
},
"clientInfo": {
"name": "ChatGPT",
"version": "2.1.0"
}
}
}
우리 서버의 응답:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": { "listChanged": true },
"resources": { "listChanged": true },
"prompts": {},
"logging": {}
},
"serverInfo": {
"name": "gift-genius-backend",
"version": "0.2.0"
}
}
}
사실상 MCP 공식 아키텍처의 예시를 거의 그대로 따르되, 앱 이름만 우리 것에 맞게 조정했습니다.
이 응답에서 클라이언트가 알게 되는 것:
- tools가 있으며, 목록이 동적으로 바뀔 수 있습니다(listChanged: true).
- 리소스가 있습니다(우리의 선물 카탈로그가 파일이나 DB에 저장되어 있을 수 있음).
- 프롬프트가 있습니다(예: “사용자 N을 위한 짧은 선물 설명을 작성하라”는 템플릿).
- 서버가 로그를 전송할 수 있습니다(인스펙터와 디버깅에 유용).
그다음 클라이언트는 tools/list를 호출하며, 예를 들어 다음과 같은 도구를 보게 됩니다:
{
"name": "suggest_gifts",
"description": "수신자 프로필에 맞춰 선물 아이디어를 제안합니다.",
"inputSchema": {
"type": "object",
"properties": {
"age": { "type": "integer" },
"relationship": { "type": "string" },
"budget": { "type": "number" }
},
"required": ["age", "relationship"]
}
}
이제 사용자가 “여동생에게 줄 선물 추천해 줘. 25살이고, 예산은 50달러까지.”라고 말하면, 모델은 suggest_gifts라는 도구가 있고 어떤 인자를 받는지 알고 있으므로 tools/call로 호출할 수 있습니다.
8. SDK가 핸드셰이크를 감춰 주지만, 그래도 이해해야 하는 이유
다음 강의에서 사용할 MCP용 TypeScript SDK는 initialize와 notifications/initialized의 과정을 connect 메서드 안에 숨겨 둡니다. 대략적인 코드는 다음과 같습니다:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({
name: "gift-genius",
version: "1.0.0",
});
// 도구 등록 – SDK가 이를 바탕으로 capabilities.tools를 자동 설정합니다
server.tool(
"suggest_gifts",
{
description: "선물 아이디어를 제안합니다.",
inputSchema: {
type: "object",
properties: {
age: { type: "integer" },
relationship: { type: "string" },
budget: { type: "number" },
},
required: ["age", "relationship"],
},
},
async (input) => {
// ... 선물 추천 로직 ...
return { suggestions: [] };
},
);
const transport = new StdioServerTransport();
// 여기서 SDK는 다음을 수행합니다:
// 1) 클라이언트의 initialize를 수신,
// 2) serverInfo와 capabilities로 응답,
// 3) notifications/initialized를 대기,
// 4) 이후 tools/* 호출을 처리 시작.
await server.connect(transport);
SDK는 여러분이 등록한 내용에 따라 capabilities를 자동으로 구성합니다. server.tool(...)이 하나라도 있으면 capabilities에 tools 섹션을 추가합니다. 리소스나 프롬프트를 등록하면 resources와 prompts가 생깁니다.
핸드셰이크와 capabilities를 이해하는 이유는 JSON을 손코딩하기 위해서가 아닙니다(절대 그렇게 하지 마세요). 대신 다음을 위해서입니다:
- MCP 로그를 읽고, 클라이언트가 왜 여러분의 도구를 “보지 못하는지” 이해하기 위해;
- 프로토콜 버전 불일치를 진단하기 위해;
- 필요 시 커스텀 서버나 비표준 트랜스포트를 구현하기 위해.
9. 프로토콜 버전과 역량의 진화
핸드셰이크의 protocolVersion 필드는 장식이 아닙니다. MCP 명세는 명확히 강조합니다: 이는 호환 가능한 프로토콜 버전에 합의하는 방법이며, 공통 버전이 없으면 연결을 종료하는 편이 낫습니다.
전형적인 시나리오:
- MCP 서버를 프로덕션에 배포하며, SDK가 MCP "2025-06-18" 버전을 구현하고 있습니다.
- 시간이 지나 MCP 새 버전이 나오고, 클라이언트를 업데이트했지만 서버는 아직 구버전입니다.
- 클라이언트가 protocolVersion으로 "2026-02-01"을 보냈을 때, 서버가 그 버전을 모르면 invalid protocol version(또는 유사한) 오류를 반환합니다.
실무에서는 이 필드를 무시했다가, 연결이 왜 성립하지 않는지 의아해하는 경우가 자주 발생합니다.
버전에 대한 올바른 태도:
- 여러분의 SDK가 어떤 MCP 버전을 지원하는지 항상 파악해 두세요(보통 문서/릴리스 노트에 명시).
- SDK를 업데이트할 때는 프로토콜 버전도 의식적으로 업데이트하세요.
- 로그와 모니터링에서 protocolVersion 불일치로 인한 초기화 오류가 명확히 보이도록 하세요.
capabilities를 통한 기능 확장도 진화와 맞물려 있습니다. MCP의 새로운 기능은 capabilities에 새로운 키로 추가됩니다. 오래된 클라이언트는 이를 무시하고, 새로운 클라이언트는 활용할 수 있습니다. 이는 공식 문서에서 역호환을 유지하는 방법으로 설명되는 패턴입니다.
10. ChatGPT와 인스펙터의 시각에서 본 핸드셰이크
MCP 연결 시 ChatGPT가 하는 일
Dev Mode에서 MCP 서버를 ChatGPT에 연결하면, 플랫폼은 내부적으로 대략 다음을 수행합니다:
- 트랜스포트를 엽니다(보통 /mcp에 대한 HTTP/stream).
- initialize를 보내며, protocolVersion, capabilities, clientInfo(예: “ChatGPT Enterprise, 버전 ○○”)를 포함합니다.
- 응답을 받고, 서버의 capabilities를 캐시합니다.
- capabilities에 따라 tools/list, resources/list, prompts/list를 호출합니다.
- 대화 도중 모델이 도구 호출을 결정할 때, 이 캐시를 참고해 해당 도구가 있는지, 인자 스키마가 무엇인지, 어떻게 호출 형식을 갖춰야 하는지를 확인합니다.
서버의 capabilities에 tools가 없으면, ChatGPT는 여러분의 App을 도구로 제안하려고도 하지 않습니다. resources가 있지만 listChanged 플래그가 없다면, ChatGPT는 리소스 목록을 캐시하고 변경 알림을 기다리지 않을 수 있습니다.
인스펙터와 MCP Jam이 디버깅에 주는 도움
MCP Jam / MCP Inspector 같은 도구들도 사실상 동일한 일을 합니다. 연결을 수립하고, 핸드셰이크를 수행하며, 서버의 capabilities를 보여주고, tools/list, tools/call 등을 수동으로 호출할 수 있게 해 줍니다.
개발자 입장에서는 필수 도구입니다:
- 서버가 실제로 어떤 protocolVersion을 반환했는지 보입니다.
- capabilities에 tools, resources, prompts가 있는지 즉시 확인할 수 있습니다.
- 왜 ChatGPT가 도구를 보지 못하는지(capabilities 미선언 또는 핸드셰이크 실패) 파악할 수 있습니다.
이 모듈의 마지막 강의에서는 이런 도구들을 더 밀도 있게 사용할 것입니다. 하지만 지금도 이들이 바로 우리가 살펴본 핸드셰이크 위에서 작동한다는 사실을 이해해 두면 유익합니다.
11. 핸드셰이크와 capabilities에서 흔히 겪는 실수
이론적으로는 꽤 단순해 보이지만, 실무에서는 바로 이 핸드셰이크와 capabilities 선언이 흔한 초보적인 버그의 근원이 됩니다 — 특히 Dev Mode나 MCP Inspector에서 말이죠. 아래에는 여러분이 거의 확실히 겪게 될(혹은 동료의 로그에서 보게 될) 대표적인 실수들을 정리했습니다.
오류 №1: initialize 요청 포맷이 잘못됨.
SDK 없이 MCP 서버를 수동 구현할 때 자주 발생합니다 — JSON-RPC의 필수 필드를 하나 잃어버리는 식입니다. 예를 들어 jsonrpc: "2.0"을 빠뜨리거나, method를 "initialize"가 아닌 "init"으로 잘못 쓰거나, capabilities를 객체가 아닌 불리언으로 만드는 경우입니다. MCP 명세는 엄격한 포맷을 기대하며, 어떤 일탈도 파싱 오류와 연결 해제로 이어집니다. 문서와 실습 가이드는 다른 걸 보기 전에 initialize가 명세를 정확히 따르는지부터 확인하라고 권장합니다.
오류 №2: protocolVersion을 무시함.
때로 개발자는 문서의 예시를 복사해 임의의 문자열을 넣고, SDK 지원 여부를 확인하지 않습니다. 그 결과 클라이언트와 서버가 서로 다른 MCP 버전으로 대화하려 해 연결이 성립되지 않습니다. 이 오류는 “클라이언트가 아예 연결하지 않는다”로 위장되기도 합니다. protocolVersion을 진짜 계약으로 취급하고, 프런트/에이전트 플랫폼 팀과 MCP 서버 팀 간에 이 버전을 합의해야 합니다.
오류 №3: capabilities 누락.
전형적인 상황입니다. 서버에 도구를 등록했지만, 핸드셰이크를 수동 구현하는 과정에서 initialize 응답의 capabilities에 "tools": {}를 추가하는 걸 잊었습니다. Inspector에서는 도구가 보이는데, ChatGPT는 “No tools available”을 보여줍니다 — capabilities를 신뢰해 tools 섹션이 없으면 tools/list를 호출하지 않기 때문입니다. Apps SDK의 트러블슈팅 가이드에서도 강조합니다: ChatGPT가 도구를 보지 못한다면, 제일 먼저 capabilities를 확인하세요.
오류 №4: capabilities에 선언되지 않은 메서드를 사용하려 함.
예를 들어 학생들이 실험하다가, capabilities에 resources 섹션이 없는 서버에 resources/list를 보내는 경우가 있습니다. 형식적으로 서버는 Method not found로 응답할 수 있지만, 애초에 그런 메서드를 호출하지 않는 것이 바람직합니다. MCP는 이런 시도를 막기 위해 capabilities를 도입했습니다. 클라이언트는 먼저 capabilities에 해당 섹션이 있는지 확인하고, 그다음에 메서드를 호출해야 합니다.
오류 №5: notifications/initialized 전에 서버가 “수다”를 시작함.
서버가 initialize 응답 직후, notifications/initialized를 기다리지도 않고 클라이언트로 로그나 알림을 보내기 시작하면, 일부 클라이언트는 이를 무시하거나 심지어 연결을 끊을 수 있습니다. MCP 공식 아키텍처에서는 먼저 핸드셰이크가 완료되고, 초기화 알림 이후에야 “업무”가 시작된다고 강조합니다.
오류 №6: 목록 변경 신호 없이 도구 스키마를 변경함.
도구의 JSON Schema를 변경(필수 필드 추가, 인자 이름 변경 등)했지만 서버를 재시작하지 않거나 목록 변경 알림을 보내지 않으면, 클라이언트의 캐시에 예전 스키마가 남아 이상한 검증 오류가 발생합니다. 명세는 listChanged 플래그와 tools/list_changed, resources/list_changed 알림을 사용해 캐시 갱신을 돕도록 제안합니다.
오류 №7: capabilities를 둘러싼 성급한 최적화와 “마법”.
기본 메커니즘을 충분히 이해하지 못한 채, 동적으로 capabilities를 생성하거나 클라이언트별 버저닝 등 복잡한 설계를 시도하는 경우가 있습니다. 초반에는 서버가 할 수 있는 것(tools, resources, prompts, logging)을 있는 그대로 선언하는 것으로 충분합니다. capabilities 확장은 실제 필요가 생길 때 하세요. 이는 순수 프로토콜 오류라기보다 조직적 안티패턴이지만, 실전 프로젝트에서 매우 자주 보입니다.
GO TO FULL VERSION