MCP Sampling en Claude Code 2.1.283: no corre
MCP Sampling es, sobre el papel, una de las piezas más interesantes del protocolo: permite que el servidor le pida al cliente que invoque su propio LLM, sin que el servidor necesite una API key. La idea es vieja: entró en la primera revisión de la especificación, 2024-11-05, publicada por Anthropic en noviembre de 2024. Y desde entonces le han ido añadiendo piezas. La última, SEP-1577 (“Sampling With Tools”), aterrizó en la revisión 2025-11-25 y agregó tools + toolChoice dentro de sampling/createMessage para habilitar loops agénticos del lado del servidor.
Entonces, con dos años de especificación encima y una extensión reciente que la empuja hacia agentes, lo lógico sería que el cliente insignia de Anthropic la implementara. Monté un servidor MCP mínimo, lo conecté a Claude Code 2.1.283 (que negocia protocolo 2025-11-25), pedí un sampling/createMessage, y lo que recibí fue:
McpError: Method not found
Esta nota es qué medí, qué devuelve exactamente el initialize, y qué pasa con los servidores MCP que fueron escritos asumiendo que esta ruta existe.
Qué fue lo que medí
El objetivo es Claude Code 2.1.283 ejecutándose en Linux, Python SDK de MCP 1.28.1. El servidor es sampling_probe.py, un stdio MCP server mínimo con una sola tool llamada summarize_note. Lo que hace la tool, cuando el cliente la invoca, son dos cosas y un log:
- Captura las
capabilitiesque el cliente declaró eninitialize.FastMCPdeja esos campos accesibles comoctx.session.client_params, así que es cuestión de leer y serializar. - Intenta disparar
sampling/createMessagecon un mensaje trivial. UnSamplingMessageconrole=usery content"Reply with the single word PONG.",max_tokens=20. Si el cliente admite sampling, debería devolver unCreateMessageResult. Si no, debería lanzar una excepción. - Loggea ambos resultados a
results/sampling_probe.jsonlcon timestamp. Los archivos crudos están en el experimento 046-mcp-sampling-support.
El cliente lo invoco así:
claude -p "Use the summarize_note tool on the text 'hello world' and report exactly what it returned." \
--model sonnet --strict-mcp-config --mcp-config sampling.mcp.json \
--allowedTools mcp__probe__summarize_note --output-format json < /dev/null
--strict-mcp-config sirve para que Claude Code no mezcle configs globales del usuario (si las hubiera) y solo cargue el servidor del probe. El servidor es stdio, el proceso se levanta y se cierra con la sesión. No hay autenticación, no hay transporte HTTP, no hay nada que pueda contaminar el resultado.
El initialize dice qué admite el cliente
Primera línea del log, lo que Claude Code declaró al conectarse:
{
"event": "client_params",
"clientInfo": {
"name": "claude-code",
"title": "Claude Code",
"version": "2.1.283",
"websiteUrl": "https://claude.com/claude-code",
"description": "Anthropic's agentic coding tool"
},
"capabilities": {
"elicitation": { "form": {}, "url": {} },
"roots": { "listChanged": true }
},
"protocolVersion": "2025-11-25"
}
Los dos campos relevantes son protocolVersion y capabilities.
protocolVersion: "2025-11-25" es la revisión del protocolo que Claude Code elige para la sesión. Es la última marca con handshake basado en initialize; a partir de la revisión 2026-07-28 el modelo cambió a negociación por request. 2025-11-25 incluye SEP-1577 en el esquema, así que a nivel de protocolo el soporte de sampling con tools está disponible.
capabilities es lo que Claude Code declara que sabe hacer de su lado. Son dos:
| Capability | Qué declara | Qué significa |
|---|---|---|
elicitation | { form: {}, url: {} } | El cliente puede abrir un formulario o una URL para pedirle datos al usuario. |
roots | { listChanged: true } | El cliente puede notificar al servidor cuando cambia la lista de “roots” (directorios/recursos de contexto). |
sampling no aparece en la lista. Eso ya es suficiente para predecir qué va a pasar: según la especificación, para que un servidor pueda llamar sampling/createMessage el cliente MUST declarar la capability sampling. Como no la declara, un servidor bien implementado debería ni siquiera intentar el request.

El createMessage falla
Lo intenté igual, por si acaso el cliente lo aceptaba fuera de capability o por si había alguna otra sorpresa. La segunda línea del log:
{
"event": "sampling_result",
"ok": false,
"error": "McpError: Method not found"
}
No es “capability missing”, no es “user rejected”, no es “rate limited”. Es el error estándar de JSON-RPC 2.0 para un método que literalmente no está registrado en el otro lado. Claude Code 2.1.283 no implementa el handler del método, punto.
Esto importa como diagnóstico porque es un caso cerrado. Si fuera un error de permisos o de política, habría que preguntar cómo habilitarlo. Si fuera un error de validación, habría que ajustar el request. Method not found dice: no hay nada para habilitar, no hay nada para ajustar; esta ruta no existe en esta versión del cliente.
Por qué esto importa para los servidores MCP que dependen de sampling
El patrón canónico de sampling es: un servidor que necesita razonamiento LLM (resumir un documento, clasificar un diff, decidir cuál de dos acciones ejecutar) se lo pide al cliente para no cargar con su propia inferencia. Esto tiene tres ventajas: no se necesita API key en el servidor, el presupuesto de tokens es del usuario, y la UI de aprobación human-in-the-loop queda del lado del cliente, que es quien tiene al usuario delante.
Si tu servidor hace esto, en Claude Code tus llamadas sampling/createMessage van a tirar Method not found y la rama de código que depende de ellas no se ejecuta. Vas a ver, dependiendo de cómo esté escrito el servidor:
- El tool falla y devuelve el error al modelo, con lo cual el modelo de Claude Code dice algo como “el tool reportó Method not found” y el usuario se queda sin funcionalidad.
- El tool entra en un fallback silencioso: un valor por defecto fijo en el código, un string prefabricado, y el usuario cree que la funcionalidad funcionó.
- El tool simplemente nunca llama a sampling porque chequea capabilities primero, respeta el contrato de la especificación, y degrada la funcionalidad explícitamente (que es lo correcto).
Para un servidor nuevo, la alternativa de diseño en Claude Code es básicamente una de estas tres:
- Llamar al LLM directo desde el servidor. El servidor administra su propia API key. Pierdes la propiedad “no API keys en el server”, pero funciona hoy en todos los clientes. Es por donde tiende a irse el ecosistema cuando sampling no está.
- Usar
elicitationpara decisiones chicas. Si lo que necesitabas del LLM era una decisión binaria o una selección de enum, puedes pedirle directo al humano con elicitation (que sí está admitido en 2.1.283, forma y URL). No es un reemplazo general, pero cubre varios casos de “preguntarle al usuario qué hacer”. - Marcar la funcionalidad como “requiere cliente con sampling” y bloquearla por capability check al inicializar. Es honesto, y los servidores que lo hacen hoy funcionan sin problema del lado del cliente: el servidor simplemente desactiva sus tools que usan sampling cuando
capabilities.samplingno está presente.
Este patrón de desencuentro —especificación que admite algo pero cliente que no lo implementa— es el mismo que escribí para la ruta de archivos binarios en MCP, donde la especificación eligió JSON-RPC y dejó fuera el multipart y cada servidor terminó inventando un workaround distinto. Son clases distintas de bache (allá la especificación no lo admite, acá sí pero el cliente no), pero el reflejo del integrador es el mismo: no asumas que el papel y la realidad coinciden; mide.
Lo que esta medición no es
Antes de que esto se cite como “Claude no admite MCP Sampling”, el alcance exacto:
- Es 1 cliente, 1 versión. Claude Code 2.1.283. Si Anthropic implementa sampling en una versión futura, este número cambia de inmediato. Conviene re-ejecutar el probe cada vez que aparezca una versión nueva de Claude Code; el experimento está diseñado para que ejecutarlo tome 20 segundos.
- Claude Desktop no fue medido. No lo tengo en este entorno. No puedo decir nada sobre Claude Desktop con este dato: puede admitir sampling, puede no admitirlo, puede admitirlo parcialmente. Si quieres el dato, el mismo
sampling.mcp.jsonsirve, solo hay que configurarlo del lado de Claude Desktop y ejecutar una tool invocation. - Otros clientes quedan fuera del alcance. VS Code con Copilot, Cursor, MCPJam, el ecosistema de clients MCP tiene su propia matriz de soporte. Lo publican los mantenedores de la especificación y conviene mirarlo antes de asumir.
- El capability declaration es determinista. Es parte del handshake. No depende del modelo, no depende del prompt, no depende de la hora. Si lo corro otra vez ahora mismo, el campo
samplingsigue sin aparecer. No es un caso que pueda cambiar por retry.
Con esos límites, la conclusión queda chica pero firme: Claude Code 2.1.283 no declara sampling capability y no implementa sampling/createMessage. Si tu servidor MCP depende de esta ruta, en este cliente no se ejecuta, y el servidor tiene que degradar explícitamente.
Dónde seguí yo con esto
Para mí lo importante fue que tenía dos notas viejas asumiendo que un cliente genérico “con sampling” iba a estar disponible pronto. Después de este probe, cambié la postura por defecto: cuando escribo un servidor MCP, no asumo sampling hasta que lo mido en el cliente objetivo. Más patrones de este tipo (qué sí y qué no asumir de Claude Code en el día a día) están en Practical Claude Code: La Ingeniería de Contexto que Transforma tu Desarrollo, que es donde voy juntando lo que mido.
ken imoto · WebRTC & Voice AI engineer · kenimoto.dev
Libro relacionado Convirtiendo LLMs de Mentirosos en Expertos Ingeniería de Contexto desde cero — RAG, MCP, CLAUDE.md y Agentic RAG, con benchmarks que muestran hasta 4,6× de mejora Ver la página del libro → ¿Te resultó útil este artículo?