루트(Roots)와 유도 입력(Elicitation) — 범위 제한과 실행 중 사용자 입력

하드코딩된 경로는 사용자가 다른 프로젝트를 여는 순간 깨집니다. 미리 채워 둔 도구(tool) 인자는 사용자의 요청이 불충분하게 지정되었을 때 무너집니다. 루트(Roots)는 서버가 다룰 수 있는 범위를 사용자가 제어하는 URI 집합으로 제한합니다. 유도 입력(Elicitation)은 도구 호출 도중에 흐름을 멈춰 폼(form)이나 URL을 통해 사용자에게 구조화된 입력을 요청합니다. 이 두 가지 클라이언트 기본 요소(client primitive)는 MCP(Model Context Protocol)에서 자주 발생하는 두 가지 실패 모드를 함께 해결합니다. SEP-1036(URL 모드 유도 입력, 2025-11-25 도입)은 2026년 상반기까지 실험 단계이므로, 의존하기 전에 사용하는 SDK 버전을 반드시 확인해야 합니다.

유형(Type): Build
언어(Languages): Python (표준 라이브러리, 루트와 유도 입력 데모)
선수 학습(Prerequisites): Phase 13 · 07 (MCP 서버)
예상 시간(Time): 약 45분

학습 목표

  • roots를 선언하고 notifications/roots/list_changed 알림에 응답합니다.
  • 서버의 파일 작업을 선언된 루트 집합 안의 URI로 제한합니다.
  • elicitation/create를 사용해 도구 호출 도중에 사용자에게 확인이나 구조화된 입력을 요청합니다.
  • 폼 모드(form mode)와 URL 모드(URL mode) 유도 입력 중 어느 쪽이 적합한지 선택합니다. URL 모드는 실험 단계이며 사양 표류(drift) 위험이 따른다는 점을 함께 이해합니다.

문제

실제 운영 환경에서 노트 MCP 서버가 자주 마주치는 구체적인 두 가지 실패 사례를 살펴봅니다.

잘못된 경로 가정. 서버가 ~/notes 경로를 전제로 작성되어 있다고 가정합니다. 그런데 다른 컴퓨터에서 노트를 ~/Documents/Notes에 보관하는 사용자가 같은 도구를 호출하면, 도구는 조용히 실패하거나(파일을 찾지 못함), 더 나쁘게는 의도하지 않은 위치에 파일을 쓰는 문제가 발생합니다.

사용자는 알지만 인자로 전달되지 않은 정보. 사용자가 "오래된 TPS report 노트를 삭제해줘"라고 요청한다고 가정해 봅니다. 모델은 notes_delete(title: "TPS report")를 호출하지만 2023년, 2024년, 2025년에 작성된 일치 노트가 세 개나 존재합니다. 도구가 어느 것을 의미하는지 추측할 수는 없습니다. "모호함" 오류로 실패하는 것도 사용자 경험상 거슬리지만, 세 개 모두를 일괄 삭제해 버리는 것은 회복 불가능한 사고로 이어집니다.

첫 번째 문제는 루트가 해결합니다. 클라이언트는 initialize 단계에서 서버가 접근할 수 있는 URI 집합을 선언합니다. 두 번째 문제는 유도 입력이 해결합니다. 서버는 도구 호출을 잠시 중단하고, 사용자가 어떤 항목을 고를지 직접 선택할 수 있도록 elicitation/create 요청을 보냅니다.

사전 테스트

2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요

1.MCP 루트와 이끌어내기가 해결하는 핵심 과제는?

2.MCP 루트와 이끌어내기 이전의 주요 한계는?

0/2 답변 완료

개념

루트(Roots)

클라이언트는 initialize 단계에서 루트 목록을 다음과 같이 선언합니다.

{
  "capabilities": {"roots": {"listChanged": true}}
}

그 다음 서버는 roots/list를 호출해 현재 루트 집합을 가져올 수 있습니다.

{"roots": [{"uri": "file:///Users/alice/Documents/Notes", "name": "Notes"}]}

서버는 반드시 루트를 경계(boundary)로 취급해야 합니다. 즉 루트 집합 밖의 파일 읽기나 쓰기 시도는 거부합니다. 클라이언트가 이 규칙을 강제하지는 않습니다. 서버는 여전히 사용자가 신뢰해 설치한 코드이기 때문입니다. 그러나 사양(spec)을 준수하는 서버라면 이 경계를 스스로 지켜야 합니다.

사용자가 루트를 추가하거나 제거하면 클라이언트는 notifications/roots/list_changed 알림을 보냅니다. 이때 서버는 roots/list를 다시 호출해 최신 집합을 받아오고, 자신의 경계를 갱신해야 합니다.

루트가 클라이언트 기본 요소(client primitive)인 이유

루트는 사용자의 동의 모델(consent model)을 표현하기 때문에 클라이언트 쪽에서 선언합니다. 예를 들어 사용자는 Claude Desktop에 "이 노트 서버가 이 두 디렉터리에만 접근하도록 허용한다"고 지정한 것입니다. 서버는 자신의 의지로 이 범위를 임의로 확장할 수 없습니다.

유도 입력(Elicitation): 기본인 폼 모드(form mode)

elicitation/create 메서드는 자연어 메시지와 함께 폼 스키마(form schema)를 인자로 받습니다.

{
  "method": "elicitation/create",
  "params": {
    "message": "Delete 'TPS report'? Multiple notes match; pick one.",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "note_id": {
          "type": "string",
          "enum": ["note-3", "note-7", "note-14"]
        },
        "confirm": {"type": "boolean"}
      },
      "required": ["note_id", "confirm"]
    }
  }
}

클라이언트는 이 스키마를 폼으로 렌더링한 뒤 사용자가 응답한 값을 모아서 다음과 같이 반환합니다.

{
  "action": "accept",
  "content": {"note_id": "note-14", "confirm": true}
}

가능한 응답 동작(action)은 세 가지입니다. accept는 사용자가 입력을 채워 제출한 경우, decline은 사용자가 입력 없이 닫은 경우, cancel은 사용자가 도구 호출 전체를 중단한 경우를 의미합니다.

폼 스키마는 평면(flat) 구조여야 합니다. v1에서는 중첩된 객체(nested object)를 지원하지 않으며, SDK 대부분은 단일 계층보다 복잡한 스키마를 거부합니다.

유도 입력(Elicitation): URL 모드(SEP-1036, 실험적)

2025-11-25 사양 개정에서 새로 추가된 방식입니다. 이 모드에서는 서버가 스키마 대신 URL을 보냅니다.

{
  "method": "elicitation/create",
  "params": {
    "message": "Sign in to GitHub",
    "url": "https://github.com/login/oauth/authorize?client_id=..."
  }
}

클라이언트는 브라우저에서 해당 URL을 열고 사용자가 작업을 마칠 때까지 기다린 뒤, 사용자가 돌아오면 결과를 반환합니다. 폼만으로는 다루기 어려운 OAuth 흐름, 결제 승인, 문서 서명 같은 시나리오에 유용합니다.

사양 표류(drift) 위험에 관한 메모: SEP-1036의 응답 구조는 아직 안정화되지 않은 상태입니다. 일부 SDK는 콜백 URL(callback URL)을 반환하고, 다른 SDK는 완료 토큰(completion token)을 반환하는 식으로 서로 다릅니다. 따라서 실제 운영 환경에서 URL 모드를 사용하기 전에는 사용하는 SDK의 릴리스 노트(release notes)를 꼭 확인해야 합니다.

유도 입력이 적절한 경우

  • 파괴적 작업(destructive action) 직전에 사용자 확인이 필요할 때. 파괴적 힌트(destructive hint)와 유도 입력을 함께 사용합니다.
  • 모호성 해소(disambiguation)가 필요할 때. N개의 후보 중 하나를 사용자가 직접 고르게 합니다.
  • 첫 실행 설정(first-run setup)이 필요할 때. API 키, 디렉터리, 선호도 같은 정보를 받습니다.
  • OAuth 스타일 인증 흐름이 필요할 때(URL 모드).

유도 입력이 적절하지 않은 경우

  • 모델이 자연어로 다시 물어볼 수 있었던 필수 도구 인자를 채우려는 경우입니다. 이때는 유도 입력 대화상자가 아니라 일반적인 재요청(re-prompt)을 사용하는 편이 낫습니다.
  • 호출 빈도가 매우 높은 경우입니다. 유도 입력은 대화 흐름을 끊기 때문에, 반복 루프 안에서는 절대 발생시키지 않습니다.
  • 서버가 사후 검증으로 처리할 수 있는 항목입니다. 이런 경우에는 검증을 수행하고, 오류를 반환한 뒤, 모델이 사용자에게 텍스트로 다시 물어보도록 맡깁니다.

사람 개입(Human-in-the-loop)으로 이어 주는 다리

유도 입력과 샘플링(sampling)을 함께 사용하면 MCP의 사람 개입(human-in-the-loop) 모델을 실현할 수 있습니다. 서버의 에이전트 루프(agent loop)는 사용자 입력(유도 입력)이나 모델 추론(샘플링) 결과를 기다리기 위해 잠시 멈출 수 있습니다. Phase 13 · 11에서는 샘플링을 다루었고, 이번 lesson에서는 유도 입력을 다룹니다. 두 가지를 함께 사용하면 도구 호출 도중에 들어오는 입력을 완전히 제어할 수 있습니다.

사용해 보기

code/main.py에서는 노트 서버를 다음과 같은 기능으로 확장합니다.

  • 루트 목록 변경 알림(notifications/roots/list_changed)을 받은 뒤 서버가 다시 질의하는 roots/list 응답 처리
  • 여러 노트가 동시에 일치할 때 elicitation/create로 모호성을 해소하는 notes_delete 도구
  • 첫 실행 설정 페이지를 여는 URL 모드 유도 입력을 사용하는 notes_setup 도구(시뮬레이션)
  • 선언된 루트 밖에 있는 URI에 대한 작업을 거부하는 경계 검사 로직

데모는 세 가지 시나리오를 차례로 실행합니다. 정상 경로(하나만 일치하는 경우), 모호성 해소(세 개가 일치해 유도 입력이 발생하는 경우), 루트 밖 쓰기(거부되는 경우)입니다.

산출물 만들기

이 lesson에서는 outputs/skill-elicitation-form-designer.md 스킬(skill)을 만듭니다. 사용자 확인이나 모호성 해소가 필요할 수 있는 도구가 주어지면, 이 스킬은 그에 맞는 유도 입력 폼 스키마와 메시지 템플릿을 설계해 줍니다.

연습문제

  1. (쉬움) code/main.py를 실행해 봅니다. 모호성 해소 경로를 발생시키고, 시뮬레이션된 사용자 답변이 다시 도구로 라우팅되는지 확인합니다.

  2. (중간) 매 호출마다 유도 입력 확인을 거치는 새 도구 notes_archive를 추가합니다. 이때 파괴적 힌트(destructive hint)를 사용합니다. 모델이 자연어로 다시 묻는 방식과 비교했을 때 사용자 경험이 어떻게 달라지는지 살펴봅니다.

  3. (중간) 첫 실행 OAuth 흐름을 위한 URL 모드 유도 입력을 구현합니다. 사양 표류 위험에 대한 주의 사항을 메모로 남기고, SDK 버전을 확인하는 가드(guard)를 추가합니다.

  4. (어려움) roots/list 처리 로직을 확장합니다. 알림이 도착하면 서버는 새로운 범위 밖에 있을 수 있는 열린 파일 핸들을 원자적으로(atomically) 다시 읽고 재스캔해야 합니다.

  5. (어려움) GitHub에 있는 SEP-1036 이슈 토론 스레드를 읽어 봅니다. 서버가 URL 모드 콜백을 어떻게 처리해야 할지에 영향을 주는 열린 질문 하나를 식별합니다.

핵심 용어

용어흔한 설명실제 의미
루트(Root)"동의 경계"클라이언트가 서버에 접근을 허용한 URI
roots/list"서버가 범위를 요청함"클라이언트가 현재 루트 집합을 응답으로 돌려줌
notifications/roots/list_changed"사용자가 범위를 바꿈"클라이언트가 루트 집합이 바뀌었음을 알리는 신호
유도 입력(Elicitation)"호출 중간에 사용자에게 물음"구조화된 사용자 입력을 받기 위한 서버 주도 요청
elicitation/create"그 메서드"유도 입력 요청을 보낼 때 사용하는 JSON-RPC 메서드
폼 모드(Form mode)"스키마 기반 폼"클라이언트 UI에서 폼으로 렌더링되는 평면 구조의 JSON Schema
URL 모드(URL mode)"브라우저 리다이렉트"SEP-1036 실험 기능. URL을 열고 사용자의 완료를 기다림
accept / decline / cancel"사용자 응답 결과"서버가 처리해야 하는 세 가지 응답 분기
모호성 해소(Disambiguation)"하나 고르기"도구에 N개의 후보가 있을 때 흔히 쓰이는 유도 입력 사례
평면 폼(Flat form)"최상위 속성만"유도 입력 스키마는 중첩 구조를 허용하지 않음

더 읽을거리

실습 코드

이 강의의 실습 코드 1개

main
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 1개

elicitation-form-designer

Design the elicitation form schema and message template for a tool that needs mid-call user confirmation or disambiguation.

Skill

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.프로덕션에서 MCP 루트와 이끌어내기의 가장 중요한 설계 원칙은?

2.MCP 루트와 이끌어내기가 올바른 선택이 아닌 경우는?

3.MCP 루트와 이끌어내기는 AI 생태계에 어떻게 들어맞나요?

0/3 답변 완료

추가 문제 풀기

AI가 강의 내용을 바탕으로 새로운 문제를 생성합니다