제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
서론
MCP(Model Context Protocol)는 LLM이 외부 시스템과 상호작용할 수 있도록 표준화된 인터페이스를 제공한다.
지금까지 MCP의 주요 기능은 다음과 같았다.
- Tools: MCP Server의 기능 실행
- Resources: MCP Server가 제공하는 데이터 조회
- Prompts: 재사용 가능한 프롬프트 제공
- Sampling: MCP Server가 Client에 모델 추론 요청
- MCP Apps: MCP Tool을 인터랙티브 UI로 확장
그런데 실제 MCP Server를 개발하다 보면 한 가지 문제가 발생한다.
Tool을 실행하는 데 필요한 정보가 부족하면 어떻게 해야 할까?
예를 들어 Git Commit Analyzer MCP Server를 개발했다고 가정해보자.
사용자가 다음과 같이 요청한다.
최근 커밋을 분석해줘.
하지만 MCP Server가 실제 분석을 수행하려면 추가 정보가 필요하다.
- 어떤 Git Repository를 분석해야 할까?
- 어떤 Branch를 기준으로 조회해야 할까?
- 최근 며칠간의 Commit을 분석해야 할까?
이때 Server가 임의의 값을 사용하면 사용자의 의도와 다른 결과를 반환할 수 있다.
반대로 오류를 반환하고 사용자에게 다시 요청하도록 하는 방식은 자연스럽지 않다.
이 문제를 해결하기 위한 MCP의 기능이 Elicitation이다.
Elicitation을 이용하면 MCP Server는 작업 도중 필요한 정보를 Client를 통해 사용자에게 요청할 수 있다.
이번 글에서는 MCP Specification의 Multi Round-Trip Requests(MRTR) 방식을 기준으로 Elicitation의 등장 배경과 내부 동작, JSON-RPC 메시지 구조, 보안 모델, 실습 방법을 살펴본다.
Elicitation의 등장배경
기존 MCP Tool의 한계.
기존 MCP Tool 실행 흐름은 다음과 같다.

일반적으로 Clinet가 Tool을 호출하면 Server는 해당 요청을 처리하고 결과를 반환한다.
그런데 Tool을 실행하는 중에 Argument가 없어 실행을 할 수 없다면 어떻게 할까? 아마 다음과 같은 두가지의 방식이 있을 것이다.
방법 A. 오류를 반환한다.
{
"isError": true,
"content": [
{
"type": "text",
"text": "Repository 정보가 필요합니다."
}
]
}
이후 LLM이 사용자에게 다시 질문하고, 입력을 받은 뒤 Tool을 재호출한다.
이 방식은 통신 과정이 한번 더 일어나며, MCP Tool마다의 추가 입력을 어떤 구조로 받아야하는지 표준화된 형태로 표현하기 어렵다.
방법 B. Tool의 필요한 입력을 필수로 지정한다.
{
"repository": "my-project",
"branch": "main",
"days": 7
}
이 경우 LLM이 모든 값을 확보해야 Tool을 실행할 수 있다.
하지만 실행하기 전에는 어떤 정보가 필요한지 결정할 수 없는 경우도 있다.
예를 들어 Repository에 여러 Branch가 존재하는지 확인한 후에야 사용자에게 Branch 선택을 요청할 수도 있다.
즉, 문제의 핵심은 작업을 시작한 후에야 추가 입력의 필요성을 알 수 있다는 것이다.
Elicitation이 해결하려는 문제
Elicitation은 이러한 동적 입력 요구를 프로토콜 수준에서 표현한다.

이를 통해 Server는 필요한 입력의 종류와 형식을 직접 정의할 수 있다.
Client는 해당 요청을 적절한 UI로 표현하고, 사용자가 입력한 값을 Server에 전달한다.
여기서 중요한 점은 Server가 사용자 인터페이스를 직접 소유하지 않는다는 것이다.
Server는 어떤 정보가 필요한지 선언하고, 실제 사용자와의 상호작용은 Client가 담당한다.
Elicitation 란
Elicitation은 MCP Server가 Client를 통해 사용자에게 추가 정보를 요청하는 기능이다.
공식 규격에서는 두 가지 모드를 제공한다.
| 구분 | Form Mode | URL Mode |
| 목적 | 구조화된 일반 정보 수집 | 민감한 정보 입력 및 외부 인증 |
| 사용자 입력 위치 | MCP Client UI | 외부 웹페이지 |
| 입력 데이터가 Client를 통과하는가 | 예 | 아니요 |
| 데이터 형식 | 제한된 JSON Schema | 외부 서비스가 정의 |
| 대표 사례 | Branch 선택, 날짜 입력 | API Key 등록, OAuth, 결제 |
| 민감한 인증 정보 입력 | 금지 | 허용되는 방식 |
예를 들어 Branch 이름을 선택하는 작업에는 Form Mode가 적절할 것이며, Github API Token과 같은 민감한 정보를 등록해야한다면 URL Mode가 적절할 것이다.

여기에서 MCP 버전에 따라 구현하는 방식이 다르다.
2026-07-28을 기준으로 달라진 통신 구조
기존 방식: Server-Initiated Request
이전 MCP에서는 Server가 Client로 직접 요청을 전송하는 방식이었다.

Server는 Tool 실행을 중단하지 않은 상태에서 사용자 입력을 기다린다.
이는 JavaScript의 await 키워드와 유사하다. await가 완료되려면 Client가 사용자 입력을 수집하고 응답을 받아야한다.
이 방식은 직관적이지만 Server와 Client 사이의 양방향 통신과 요청 처리 상태 유지가 중요해진다.
새로운 방식: Multi Round-Trip Requests
2026-07-28 규격에서는 Multi Round-Trip Requests(MRTR) 방식이 도입되었다.
이 방식에서는 Server가 별도의 역방향 요청을 전송하지 않는다.
대신 현재 요청에 대해 InputRequiredResult를 반환한다.
Client는 추가 정보를 수집한 뒤 원래 요청을 새로운 JSON-RPC 요청으로 재시도한다.

이 구조의 특징은 다음과 같다.
- Server는 입력을 기다리며 기존 요청을 계속 열어둘 필요가 없다.
- Client는 추가 정보를 수집한 후 원래 작업을 다시 요청한다.
- 최초 요청과 재시도 요청은 서로 독립적인 JSON-RPC 요청이다.
- 따라서 JSON-RPC Request ID도 달라야한다.
- 필요한 작업 상태는 requestState로 전달할 수 있다.
왜 이렇게 발전했을까?
기존 방식에서는 Server가 Client의 응답을 기다리는 동안 요청 컨텍스트를 유지해야 할 수 있다.
이것은 다음과 같은 환경에 부담이 될 수 있다.
- Stateless HTTP Server
- 여러 인스턴스로 구성된 MCP Server
- Load Balancer를 사용하는 환경
- Serverless 환경
- 긴 사용자 입력 대기 시간
MRTR에서는 Server가 InputRequiredResult를 반환하고 현재 요청 처리를 종료할 수 있다.
이후 Client가 다시 요청하면 다른 Server 인스턴스가 처리하더라도 필요한 정보가 요청에 포함되어 있다면 작업을 이어갈 수 있다.
이 구조는 Stateless Server 설계와 잘 맞는다.
다만 모든 상태가 자동으로 해결되는 것은 아니다. 외부 인증 진행 상태나 일회성 처리 여부처럼 서버가 반드시 기억해야 하는 정보는 별도의 저장소가 필요할 수 있다.
호출 흐름 분석
만약 클라이언트로 넘어온 값이 정보가 부족하면, MCP Server는 일반적인 Tool Result 대신 InputRequiredResult를 반환한다.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "input_required",
"inputRequests": {
"branch_selection": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "분석할 Branch를 선택해주세요.",
"requestedSchema": {
"type": "object",
"properties": {
"branch": {
"type": "string",
"title": "Git Branch",
"enum": [
"main",
"develop",
"release"
]
}
},
"required": ["branch"]
}
}
}
}
}
}
이 메시지에서 핵심 필드는 다음과 같다.
| 필드 | 의미 |
| resultType | 추가 입력이 필요하다는 결과 유형 |
| inputRequests | Client가 처리해야 하는 입력 요청 목록 |
| branch_selection | 해당 입력 요청의 식별 키 |
| method | elicitation/create |
| mode | 입력 수집 방식 |
| message | 사용자에게 표시할 설명 |
| requestedSchema | 입력 데이터의 구조와 검증 조건 |
여기서 branch_selection은 임의의 문자열이지만, 다음 요청에서 응답을 연결하는 데 사용되므로 일관되게 유지해야 한다.
Client가 사용자 입력을 수집
Client는 Schema를 확인하고 Branch 선택 UI를 표시할 수 있다.
사용자가 develop을 선택했다고 가정하자.
Elicitation 응답은 다음과 같은 구조다.
{
"action": "accept",
"content": {
"branch": "develop"
}
}
Client가 원래 요청을 재시도
Client는 최초 Tool 호출을 다시 전송하면서 수집한 입력을 inputResponses에 포함한다.
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "analyze_commits",
"arguments": {
"repository": "my-project"
},
"inputResponses": {
"branch_selection": {
"action": "accept",
"content": {
"branch": "develop"
}
}
}
}
}
이때 최초 요청의 ID는 1, 재시도 요청의 ID는 2다.
왜냐하면 MRTR에서 두 요청은 별도의 JSON-RPC 요청이기 때문이다.
Server는 inputResponses.branch_selection을 읽어 사용자 선택을 확인하고 Commit 분석을 진행한다.
최종 Tool Result
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "develop Branch 분석 완료"
}
]
}
}
이것이 2026-07-28 규격에서 Elicitation이 처리되는 기본적인 방식이다.
Elicitation의 두가지 모드
위에서 Elicitation을 구현하는 데에 두가지 모드가 존재하는 것을 확인했다. 하나씩 알아보자.
Form Mode - JSON Schema 사용
Form Mode는 사용자가 일반적인 구조화 데이터를 입력하도록 요청하는 기능이다.
MCP Server는 requestedSchema를 통해 필요한 데이터 형식을 선언한다.
{
"type": "object",
"properties": {
"branch": {
"type": "string",
"title": "Branch",
"enum": ["main", "develop", "release"]
},
"days": {
"type": "integer",
"title": "분석 기간",
"minimum": 1,
"maximum": 30,
"default": 7
},
"includeMergeCommits": {
"type": "boolean",
"title": "Merge Commit 포함",
"default": false
}
},
"required": ["branch", "days"]
}
Client는 이 Schema를 이용해 적절한 입력 UI를 구성할 수 있다.
예를 들어 Branch는 선택 메뉴, 분석 기간은 숫자 입력, Merge Commit 포함 여부는 체크박스로 표현할 수 있다.
하지만 프로토콜이 특정 UI 컴포넌트를 강제하는 것은 아니다.
또한, JSON 문법 내에 지원되는 유형이 정해져있다.
- string
- number
- integer
- boolean
- 단일 선택 Enum
- 문자열 기반 다중 선택 Enum
복잡한 중첩 객체나 일반적인 객체 배열을 자유롭게 사용할 수 있는 것은 아니다.
이것은 의도적인 제약이다.
만약 Server가 복잡한 중첩 Schema를 자유롭게 요청할 수 있다면 Host는 매우 복잡한 동적 입력 UI를 구현해야 할 수 있다.
따라서 Form Mode는 범용 프론트엔드 프레임워크가 아니라, 추가 입력을 수집하기 위한 제한적인 인터페이스로 이해하는 것이 적절하다.
URL Mode - 민감한 정보를 추가로 입력하는 방법
이 부분은 Elicitation에서 가장 중요한 보안 설계 중 하나다.
예를 들어 Git Commit Analyzer가 비공개 GitHub Repository를 분석해야 한다고 가정하자.
이때 GitHub API 접근 권한이 필요할 수 있다.
사용자에게 다음과 같이 요청하는 방법을 생각할 수 있다.
GitHub API Token을 입력해주세요.
하지만 MCP Specification에서는 Form Mode를 통해 API Key, Password, Access Token, 결제 자격 증명 등 민감한 인증 정보를 요청하는 것을 금지한다.
이러한 정보는 URL Mode를 사용해야 한다.
URL Mode 요청
{
"method": "elicitation/create",
"params": {
"mode": "url",
"url": "https://mcp.example.com/connect/github",
"message": "비공개 Repository 분석을 위해 GitHub 계정을 연결해주세요."
}
}
2026-07-28에서는 이 요청 객체가 InputRequiredResult.inputRequests 내부에 포함된다.
Client는 사용자에게 대상 URL과 요청 목적을 표시하고, 사용자의 동의를 받은 뒤 외부 페이지로 이동하도록 한다.

여기서 중요한 점은 GitHub Token이 MCP Client를 거치지 않는다는 것이다.
Token은 OAuth 절차를 통해 MCP Server가 직접 획득하고 관리한다.
이렇게 하면 LLM의 Context나 MCP Client 로그에 인증 정보가 포함될 위험을 줄일 수 있다.
URL Mode에서 다음 응답을 받았다고 가정하자.
{
"action": "accept"
}
이것은 사용자가 해당 상호작용에 동의했다는 의미다.
외부 웹페이지에서 인증이 성공적으로 완료되었다는 뜻은 아니다.
따라서 Server는 재시도된 요청을 처리할 때 실제 인증 완료 여부를 별도로 확인해야 한다.
아직 인증이 완료되지 않았다면 다시 InputRequiredResult를 반환할 수 있다.
이 구분을 이해하지 못하면 사용자가 URL을 열기만 했는데도 인증에 성공한 것으로 처리하는 보안 문제가 발생할 수 있다.
requestState와 Stateless Server 설계
MRTR에서 추가 입력을 요청한 뒤 Server가 기존 요청을 종료한다면, 작업 상태는 어떻게 유지할까?
예를 들어 다음과 같은 두 단계 입력을 생각해보자.
1단계: Repository 선택
2단계: Branch 선택
3단계: Commit 분석
첫 번째 입력을 수집한 뒤 두 번째 입력을 요청할 때는 이전에 선택한 Repository 정보를 기억해야 한다.
이때 사용할 수 있는 것이 requestState다.
requestState란
Server가 생성하는 보이지 않는 문자열이다.
Client는 해당 값을 해석하지 않고 다음 요청에서 그대로 돌려준다.
{
"resultType": "input_required",
"requestState": "opaque-server-generated-state",
"inputRequests": {
"branch_selection": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Branch를 선택해주세요.",
"requestedSchema": {
"type": "object",
"properties": {
"branch": {
"type": "string"
}
},
"required": ["branch"]
}
}
}
}
}
이후 Client는 재시도 요청에 동일한 requestState를 포함한다.
requestState를 통해 MCP Server는 Stateless한 설계를 할 수 있으며, 필요한 정보를 복원할 수 있다.
응답 상태
Elicitation은 세 가지 응답 상태를 제공한다.
| Action | 의미 |
| accept | 사용자가 요청을 승인하고 입력을 제출 |
| decline | 사용자가 명시적으로 거절 |
| cancel | 명시적인 승인이나 거절 없이 종료 |
처음에는 accept와 나머지 실패 상태만 구분해도 충분해 보일 수 있다.
하지만 실제 애플리케이션에서는 세 상태의 의미가 다르다.
예를 들어 사용자가 Branch 선택 요청을 받았다고 가정하자.
Accept
- 사용자가 develop을 선택했다.
- Server는 선택된 Branch를 기준으로 분석을 진행한다.
Decline
- 사용자가 추가 정보 제공을 거절했다.
- Server는 작업을 중단하거나, 가능한 경우 기본 분석 방식 등 대안을 안내할 수 있다.
Cancel
- 사용자가 입력 창을 닫았다.
- 이는 명시적인 거절과 다르다.
- 사용자가 나중에 작업을 다시 진행할 수도 있다.
- 따라서 Server는 모든 경우를 동일한 오류로 처리하기보다는 사용자의 의도에 맞게 구분하는 것이 좋다.
- 또한 Client가 응답하지 않거나 요청을 재시도하지 않을 가능성도 고려해야 한다.
주의
requestState는 Client를 거쳐 다시 돌아오는 값이므로 신뢰할 수 없다.
해당 값을 악의적인 Client값으로 디코딩하고 수정한 뒤 다시 인코딩할 수 있다. 따라서 requestState가 권한이나 비즈니스 로직에 영향을 준다면 무결성을 보호해야 한다. 대표적으로 HMAC 또는 AEAD를 사용할 수 있다.
또한 Replay Attack을 줄이기 위해 다음 정보의 포함을 고려해야 한다.
- 인증된 사용자 식별자
- 만료 시간
- 원래 요청의 식별 정보
- 중요한 요청 파라미터의 Digest
일회성 처리가 필요한 작업이라면 서버 측에서 이미 사용된 상태인지 별도로 관리해야 한다.
Elicitation을 남용하면 안됨.
Server가 작은 정보 하나를 얻기 위해 매번 사용자 입력을 요구한다면 UX가 나빠질 수 있다.
예를 들어 Repository, Branch, 기간을 각각 별도의 Elicitation으로 요청한다면 사용자는 세 번의 상호작용을 수행해야 한다.
반면 필요한 정보를 한 번의 Form Mode 요청으로 수집하면 상호작용 횟수를 줄일 수 있다
하지만 실행 과정에 따라 필요한 정보가 달라진다면 단계별 요청이 적절할 수도 있다.
따라서 Elicitation 설계에서는 한 번에 수집할 정보와 실행 중 동적으로 결정할 정보를 구분해야 한다.
정리
Elicitation은 MCP Server가 작업을 수행하는 도중 사용자에게 추가 정보를 요청할 수 있도록 하는 기능이다.
이 기능의 핵심은 단순히 입력 창을 표시하는 것이 아니다.
Server가 필요한 정보를 선언하고, Client가 사용자 상호작용을 담당하며, Server가 수집된 입력을 검증해 작업을 이어가는 구조를 표준화한다는 점이다.
특히 2026-07-28 규격에서는 MRTR이 도입되면서 기존 Server-Initiated Request 방식에서 InputRequiredResult 기반의 재시도 방식으로 변경되었다. 이 변경을 이해하면 Elicitation뿐 아니라 Stateless Server 설계, 요청 상태 관리, 비동기 작업, 사용자 인증 및 보안 경계까지 함께 이해할 수 있다.
개발자 입장에서 Elicitation을 구현할 때 기억해야 할 핵심은 다음과 같다.
- 일반적인 구조화 입력은 Form Mode를 사용한다.
- 인증 정보와 결제 자격 증명 등 민감한 입력은 URL Mode를 사용한다.
- 2026-07-28에서는 InputRequiredResult와 MRTR을 사용한다.
- requestState는 무결성 검증이 필요한 비신뢰 입력으로 취급한다.
- accept, decline, cancel을 구분한다.
- Client Capability를 확인한다.
- 요청 재시도에 따른 중복 실행과 멱등성을 고려한다.
- URL Mode에서 사용자의 동의와 외부 인증 완료를 혼동하지 않는다.
결국 Elicitation은 LLM이 외부 기능을 실행하는 과정에 사람의 입력을 안전하고 구조적으로 포함시키기 위한 프로토콜 기능이라고 이해할 수 있다.
참조
'AI > MCP' 카테고리의 다른 글
| [MCP] MCP Apps - Interactive UI (1) | 2026.10.13 |
|---|---|
| [MCP] MCP Transport - SSE와 Streamable HTTP (0) | 2026.10.08 |
| [MCP] MCP에서 JSON-RPC를 채택한 이유 (0) | 2026.10.06 |
| [MCP] MCP를 이해하기 위한 JSON과 JSON-RPC 2.0 차이 (0) | 2026.10.06 |