Phase 13 - Lesson 12

Roots e Elicitação — Escopo e Entrada do Usuário no Meio da Execução

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

Caminhos definidos no código (hard-coded) quebram no momento em que o usuário abre um projeto diferente. Argumentos de ferramentas pré-preenchidos quebram quando o usuário não especifica o suficiente. Roots limitam o escopo do servidor a um conjunto de URIs controlado pelo usuário; a elicitação (elicitation) pausa no meio da chamada da ferramenta para pedir ao usuário uma entrada estruturada por meio de um formulário ou URL. Duas primitivas de cliente, duas correções para modos de falha comuns do MCP. A SEP-1036 (elicitação em modo URL, 2025-11-25) é experimental até o primeiro semestre de 2026 — verifique as versões do SDK antes de depender dela.

Tipo: Build Idiomas: Python (stdlib, demo de roots + elicitação) Pré-requisitos: Fase 13 · 07 (servidor MCP) Tempo: ~45 minutos

Objetivos de Aprendizado

  • Declarar roots e responder a notifications/roots/list_changed.
  • Restringir operações de arquivo do servidor a URIs dentro do conjunto de roots declarado.
  • Usar elicitation/create para solicitar ao usuário uma confirmação ou entrada estruturada no meio da chamada da ferramenta.
  • Escolher entre a elicitação em modo formulário (form-mode) e modo URL (o último é experimental; risco de divergência observado).

O Problema

Duas falhas concretas que um servidor MCP de notas encontra em produção.

Assunção de caminho incorreta. O servidor é escrito considerando ~/notes. Um usuário em uma máquina diferente com notas em ~/Documents/Notes obtém uma chamada de ferramenta que falha silenciosamente (nenhum arquivo encontrado) ou, pior, grava no local errado.

Argumento ausente que o usuário saberia. O usuário solicita "excluir a nota antiga do relatório TPS". O modelo chama notes_delete(title: "TPS report"), mas existem três notas correspondentes de 2023, 2024 e 2025. A ferramenta não tem como adivinhar. Falhar com "ambíguo" é irritante; executar nas três é catastrófico.

As roots corrigem o primeiro problema: o cliente declara no initialize o conjunto de URIs que o servidor pode tocar. A elicitação corrige o segundo: o servidor pausa a chamada da ferramenta e envia elicitation/create para pedir ao usuário que escolha qual delas.

O Conceito

Roots

O cliente declara uma lista de roots no initialize:

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

O servidor pode então chamar roots/list:

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

Os servidores DEVEM tratar as roots como o limite: qualquer leitura ou gravação de arquivo fora do conjunto de roots é rejeitada. Isso não é imposto pelo cliente (o servidor ainda é um código no qual o usuário confiou), mas os servidores compatíveis com a especificação o respeitam.

Quando o usuário adiciona ou remove uma raiz, o cliente envia notifications/roots/list_changed. O servidor chama roots/list novamente e atualiza seu limite.

Por que as roots são uma primitiva de cliente

As roots são declaradas pelo cliente porque representam o modelo de consentimento do usuário. O usuário disse ao Claude Desktop "dê a este servidor de notas acesso a estes dois diretórios". O servidor não pode ampliar esse escopo.

Elicitação: o padrão do modo formulário (form-mode)

O método elicitation/create recebe um esquema de formulário e um prompt em linguagem natural:

{
  "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"]
    }
  }
}

O cliente renderiza um formulário, coleta a resposta do usuário e retorna:

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

Três ações possíveis: accept (usuário preencheu), decline (usuário fechou), cancel (usuário abortou toda a chamada de ferramenta).

Os esquemas de formulário são planos (flat) — objetos aninhados não são suportados na v1. Os SDKs normalmente rejeitam qualquer coisa mais complexa do que uma única camada.

Elicitação: modo URL (SEP-1036, experimental)

Novidade em 2025-11-25. Em vez de um esquema, o servidor envia uma URL:

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

O cliente abre a URL em um navegador, aguarda a conclusão e retorna quando o usuário volta. Útil para fluxos de OAuth, autorização de pagamento e assinatura de documentos onde um formulário é insuficiente.

Nota sobre risco de divergência (drift-risk): o formato da resposta da SEP-1036 ainda está se definindo; alguns SDKs retornam a URL de retorno de chamada (callback URL), outros retornam um token de conclusão. Leia as notas de lançamento do seu SDK antes de usar o modo URL em produção.

Quando a elicitação é a ferramenta certa

  • Confirmação do usuário antes de ações destrutivas (dica destrutiva + elicitação).
  • Resolução de ambiguidade (escolher uma de N correspondências).
  • Configuração inicial na primeira execução (chaves de API, diretórios, preferências).
  • Fluxos estilo OAuth (modo URL).

Quando a elicitação está incorreta

  • Preencher argumentos obrigatórios de uma ferramenta que o modelo poderia ter solicitado em prosa. Use um novo prompt normal, não uma caixa de diálogo de elicitação.
  • Chamadas de alta frequência. A elicitação interrompe a conversa; não a execute dentro de um loop.
  • Qualquer coisa que o servidor possa validar após o fato. Valide, retorne um erro e deixe o modelo perguntar ao usuário por texto.

Ponte para o "Human-in-the-loop"

A elicitação combinada com o sampling ativam o modelo "human-in-the-loop" (humano no circuito) do MCP. O loop de agente de um servidor pode pausar para entrada do usuário (elicitação) ou para raciocínio do modelo (sampling). A Fase 13 · 11 cobriu o sampling; esta lição cobre a elicitação. Junte-os para ter controle total no meio do loop.

Use

O arquivo code/main.py estende o servidor de notas com:

  • Resposta de roots/list que o servidor consulta novamente após notificações de alteração na lista de roots.
  • Uma ferramenta notes_delete que usa elicitation/create para desambiguar quando várias notas correspondem.
  • Uma ferramenta notes_setup que usa elicitação em modo URL para abrir uma página de configuração de primeira execução (simulada).
  • Uma verificação de limite que recusa operações em URIs fora das roots declaradas.

A demonstração executa três cenários: caminho feliz (uma correspondência), desambiguação (três correspondências, a elicitação é disparada), gravação fora da raiz (rejeitada).

Entregue

Esta lição produz o arquivo outputs/skill-elicitation-form-designer.md. Dada uma ferramenta que pode precisar de confirmação ou desambiguação do usuário, a habilidade (skill) projeta o esquema do formulário de elicitação e o modelo de mensagem.

Exercícios

  1. Execute code/main.py. Dispare o caminho de desambiguação; confirme se a resposta simulada do usuário é enviada de volta para a ferramenta.

  2. Adicione uma nova ferramenta notes_archive que requer confirmação de elicitação todas as vezes (dica destrutiva). Verifique a experiência do usuário (UX): como isso se compara ao modelo perguntando novamente em texto?

  3. Implemente a elicitação em modo URL para um fluxo de OAuth na primeira execução. Observe o risco de divergência e adicione uma proteção de versão do SDK.

  4. Estenda o tratamento de roots/list: quando uma notificação chegar, o servidor deve reler e verificar novamente de forma atômica os manipuladores de arquivos abertos que agora podem estar fora do escopo.

  5. Leia o tópico de discussão da issue SEP-1036 no GitHub. Identifique uma questão aberta que afeta como os servidores devem lidar com retornos de chamada (callbacks) em modo URL.

Termos-Chave

Termo O que as pessoas dizem O que realmente significa
Root "Limite de consentimento" URI que o cliente permitiu que o servidor acessasse
roots/list "O servidor solicita o escopo" O cliente retorna o conjunto atual de roots
notifications/roots/list_changed "O usuário alterou o escopo" O cliente sinaliza que o conjunto de roots foi modificado
Elicitação "Perguntar ao usuário no meio da chamada" Solicitação iniciada pelo servidor para entrada estruturada do usuário
elicitation/create "O método" Método JSON-RPC para solicitações de elicitação
Modo Formulário "Formulário baseado em esquema" JSON Schema plano (flat) renderizado como um formulário na interface do usuário do cliente
Modo URL "Redirecionamento do navegador" SEP-1036 experimental; abre uma URL e aguarda
accept / decline / cancel "Resultados de resposta do usuário" Três ramificações que o servidor trata
Desambiguação "Escolha um" Caso de uso comum de elicitação quando uma ferramenta possui N candidatos
Formulário plano (flat) "Apenas propriedades de nível superior" Esquemas de elicitação não podem ser aninhados

Leitura Adicional

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).