Phase 13 - Lesson 12
Roots y Elicitación — Alcance y Entrada del Usuario a Mitad de la Ejecución
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Las rutas definidas en código (hard-coded) se rompen en el momento en que un usuario abre un proyecto diferente. Los argumentos de herramientas precompletados se rompen cuando el usuario no especifica lo suficiente. Las roots limitan el alcance del servidor a un conjunto de URIs controlado por el usuario; la elicitación (elicitation) pausa a mitad de la llamada a la herramienta para pedir al usuario una entrada estructurada a través de un formulario o URL. Dos primitivas del cliente, dos soluciones para modos de fallo comunes de MCP. SEP-1036 (elicitación en modo URL, 2025-11-25) es experimental hasta el primer semestre de 2026 — verifique las versiones del SDK antes de depender de ella.
Tipo: Build Lenguajes: Python (stdlib, demo de roots + elicitación) Prerrequisitos: Fase 13 · 07 (servidor MCP) Tiempo: ~45 minutos
Objetivos de Aprendizaje
- Declarar
rootsy responder anotifications/roots/list_changed. - Restringir las operaciones de archivos del servidor a las URIs dentro del conjunto de roots declarado.
- Usar
elicitation/createpara solicitar al usuario una confirmación o entrada estructurada a mitad de la llamada a la herramienta. - Elegir entre la elicitación en modo formulario (form-mode) y en modo URL (este último es experimental; riesgo de desviación observado).
El Problema
Dos fallas concretas que experimenta un servidor MCP de notas en producción.
Suposición de ruta incorrecta. El servidor está escrito pensando en ~/notes. Un usuario en una máquina diferente con notas en ~/Documents/Notes obtiene una llamada a la herramienta que falla silenciosamente (no se encontró el archivo) o, peor aún, escribe en el lugar equivocado.
Argumento faltante que el usuario conocería. El usuario solicita "eliminar la nota antigua del informe TPS". El modelo llama a notes_delete(title: "TPS report") pero hay tres notas coincidentes de 2023, 2024 y 2025. La herramienta no puede adivinar. Fallar con "ambiguo" es molesto; ejecutar en las tres es catastrófico.
Las roots solucionan el primero: el cliente declara en initialize el conjunto de URIs que el servidor puede tocar. La elicitación soluciona el segundo: el servidor pausa la llamada a la herramienta y envía elicitation/create para pedirle al usuario que elija cuál.
El Concepto
Roots
El cliente declara una lista de roots en initialize:
{
"capabilities": {"roots": {"listChanged": true}}
}
El servidor puede entonces llamar a roots/list:
{"roots": [{"uri": "file:///Users/alice/Documents/Notes", "name": "Notes"}]}
Los servidores DEBEN tratar las roots como el límite: cualquier lectura o escritura de archivos fuera del conjunto de roots es rechazada. Esto no es impuesto por el cliente (el servidor sigue siendo código en el que el usuario confió), pero los servidores que cumplen con la especificación lo respetan.
Cuando el usuario añade o elimina una raíz, el cliente envía notifications/roots/list_changed. El servidor vuelve a llamar a roots/list y actualiza su límite.
Por que las roots son una primitiva del cliente
Las roots son declaradas por el cliente porque representan el modelo de consentimiento del usuario. El usuario le dijo a Claude Desktop "dale a este servidor de notas acceso a estos dos directorios". El servidor no puede ampliar ese alcance.
Elicitación: el modo por defecto en formulario (form-mode)
elicitation/create recibe un esquema de formulario más un prompt en lenguaje 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"]
}
}
}
El cliente renderiza un formulario, recopila la respuesta del usuario y devuelve:
{
"action": "accept",
"content": {"note_id": "note-14", "confirm": true}
}
Tres acciones posibles: accept (el usuario lo completó), decline (el usuario lo cerró), cancel (el usuario abortó toda la llamada a la herramienta).
Los esquemas de formulario son planos (flat) — los objetos anidados no son compatibles en la v1. Los SDKs normalmente rechazan cualquier cosa más compleja que una sola capa.
Elicitación: modo URL (SEP-1036, experimental)
Novedad en 2025-11-25. En lugar de un esquema, el servidor envía una URL:
{
"method": "elicitation/create",
"params": {
"message": "Sign in to GitHub",
"url": "https://github.com/login/oauth/authorize?client_id=..."
}
}
El cliente abre la URL en un navegador, espera a que se complete y regresa cuando el usuario vuelve. Útil para flujos de OAuth, autorización de pagos y firma de documentos donde un formulario es insuficiente.
Nota sobre riesgo de desviación (drift-risk): la estructura de respuesta de SEP-1036 aún se está definiendo; algunos SDKs devuelven la URL de retorno (callback URL), otros devuelven un token de finalización. Lea las notas de lanzamiento de su SDK antes de usar el modo URL en producción.
Cuándo la elicitación es la herramienta adecuada
- Confirmación del usuario antes de acciones destructivas (indicación destructiva + elicitación).
- Desambiguación (elegir una de N coincidencias).
- Configuración inicial en la primera ejecución (claves de API, directorios, preferencias).
- Flujos estilo OAuth (modo URL).
Cuándo la elicitación es incorrecta
- Completar los argumentos obligatorios de una herramienta que el modelo podría haber solicitado en prosa. Use una nueva solicitud normal en texto, no un diálogo de elicitación.
- Llamadas de alta frecuencia. La elicitación interrumpe la conversación; no la inicie dentro de un bucle.
- Cualquier cosa que el servidor pueda validar a posteriori. Valide, devuelva un error y deje que el modelo le pregunte al usuario en texto.
Puente para el "Human-in-the-loop"
La elicitación y el sampling juntos habilitan el modelo "human-in-the-loop" (humano en el bucle) de MCP. El bucle del agente de un servidor puede pausarse ya sea para recibir la entrada del usuario (elicitación) o para el razonamiento del modelo (sampling). La Fase 13 · 11 cubrió el sampling; esta lección cubre la elicitación. Junte ambos para obtener un control completo a mitad del bucle.
Uso
code/main.py extiende el servidor de notas con:
- Respuesta
roots/listque el servidor vuelve a consultar después de las notificaciones de cambio en la lista de roots. - Una herramienta
notes_deleteque utilizaelicitation/createpara desambiguar cuando coinciden varias notas. - Una herramienta
notes_setupque utiliza la elicitación en modo URL para abrir una página de configuración de primera ejecución (simulada). - Una comprobación de límites que rechaza operaciones en URIs fuera de las roots declaradas.
La demostración ejecuta tres escenarios: camino feliz (una coincidencia), desambiguación (tres coincidencias, se activa la elicitación) y escritura fuera de la raíz (rechazada).
Entrega
Esta lección produce outputs/skill-elicitation-form-designer.md. Dada una herramienta que podría necesitar confirmación o desambiguación del usuario, la habilidad (skill) diseña el esquema del formulario de elicitación y la plantilla de mensaje.
Ejercicios
Ejecute
code/main.py. Active la ruta de desambiguación; confirme que la respuesta simulada del usuario se dirija de vuelta a la herramienta.Agregue una nueva herramienta
notes_archiveque requiera confirmación de elicitación cada vez (indicación destructiva). Compruebe la experiencia de usuario (UX): ¿cómo se compara esto con que el modelo vuelva a preguntar en texto?Implemente la elicitación en modo URL para un flujo de OAuth de primera ejecución. Tenga en cuenta el riesgo de desviación y agregue una protección de versión del SDK.
Extienda el manejo de
roots/list: cuando llegue una notificación, el servidor debe releer y escanear de nuevo atómicamente los descriptores de archivos abiertos que ahora podrían estar fuera del alcance.Lea el hilo de discusión de la issue SEP-1036 en GitHub. Identifique una pregunta abierta que afecte el modo en que los servidores deben manejar los retornos de llamada (callbacks) en modo URL.
Términos Clave
| Término | Lo que la gente dice | Lo que realmente significa |
|---|---|---|
| Root | "Límite de consentimiento" | URI que el cliente ha permitido que el servidor toque |
roots/list |
"El servidor solicita el alcance" | El cliente devuelve el conjunto de roots actual |
notifications/roots/list_changed |
"El usuario cambió el alcance" | El cliente indica que el conjunto de roots ha mutado |
| Elicitación | "Preguntar al usuario a mitad de llamada" | Solicitud iniciada por el servidor para obtener una entrada de usuario estructurada |
elicitation/create |
"El método" | Método JSON-RPC para solicitudes de elicitación |
| Modo Formulario | "Formulario basado en esquema" | JSON Schema plano (flat) renderizado como formulario en la interfaz de usuario del cliente |
| Modo URL | "Redirecionamiento del navegador" | SEP-1036 experimental; abre una URL y espera |
accept / decline / cancel |
"Resultados de la respuesta del usuario" | Tres ramas que el servidor maneja |
| Desambiguación | "Elige uno" | Caso de uso común de elicitación cuando una herramienta tiene N candidatos |
| Formulario plano (flat) | "Solo propiedades de primer nivel" | Los esquemas de elicitación no se pueden anidar |
Lectura Adicional
- MCP — Client roots spec — referencia canónica de roots
- MCP — Client elicitation spec — referencia canónica de elicitación
- Cisco — What's new in MCP elicitation, structured content, OAuth enhancements — tutorial de las adiciones del 2025-11-25
- MCP — GitHub SEP-1036 — propuesta de elicitación en modo URL (experimental, riesgo de desviación)
- The New Stack — How elicitation brings human-in-the-loop to AI tools — tutorial de experiencia de usuario (UX)