Autocompletado
Traducción automática
Esta página se tradujo automáticamente a partir de la documentación en inglés, y la página en inglés es la versión de referencia. Si algo no se lee bien, Traducciones explica cómo avisarnos.
Un cliente que construye una interfaz sobre tu servidor quiere autocompletar los valores de los argumentos mientras el usuario escribe: nombres de lenguajes, nombres de repositorios, rutas de archivos.
El autocompletado (completions) es la forma en que tu servidor proporciona esas sugerencias.
Algo que valga la pena completar
El autocompletado se aplica exactamente a dos cosas: los argumentos de un prompt y los parámetros de una plantilla de recurso. Así que empieza con un servidor que tenga uno de cada:
from mcp.server import MCPServer
mcp = MCPServer("GitHub Explorer")
@mcp.resource("github://repos/{owner}/{repo}")
def github_repo(owner: str, repo: str) -> str:
"""A GitHub repository."""
return f"Repository: {owner}/{repo}"
@mcp.prompt()
def review_code(language: str, code: str) -> str:
"""Review a snippet of code."""
return f"Review this {language} code:\n{code}"
Aquí todavía no hay nada de autocompletado.
review_coderecibe unlanguage. Un usuario no debería tener que adivinar qué formas de escribirlo aceptas.github_reporecibe unownery unrepo. Dos campos de texto libre hacen un mal formulario.
El handler de autocompletado
Añade una función decorada con @mcp.completion():
from mcp.server import MCPServer
from mcp.types import Completion, CompletionArgument, CompletionContext, PromptReference, ResourceTemplateReference
mcp = MCPServer("GitHub Explorer")
LANGUAGES = ["go", "javascript", "python", "rust", "typescript"]
@mcp.resource("github://repos/{owner}/{repo}")
def github_repo(owner: str, repo: str) -> str:
"""A GitHub repository."""
return f"Repository: {owner}/{repo}"
@mcp.prompt()
def review_code(language: str, code: str) -> str:
"""Review a snippet of code."""
return f"Review this {language} code:\n{code}"
@mcp.completion()
async def handle_completion(
ref: PromptReference | ResourceTemplateReference,
argument: CompletionArgument,
context: CompletionContext | None,
) -> Completion | None:
if isinstance(ref, PromptReference) and argument.name == "language":
return Completion(values=[lang for lang in LANGUAGES if lang.startswith(argument.value)])
return None
- Hay un handler por servidor. Todas las solicitudes de autocompletado llegan aquí, y tú decides qué hacer según lo que se esté completando.
- Debe ser
async def: el SDK lo espera con await. - Recibe tres argumentos:
ref: qué prompt o plantilla de recurso, comoPromptReferenceoResourceTemplateReference. Conisinstancelos distingues.argument:argument.namees el argumento que se está completando,argument.valuees lo que el usuario ha escrito hasta ahora.context: los argumentos ya resueltos. Ignóralo por ahora.- Devuelves un
Completion(values=[...]), oNonecuando no tienes nada que ofrecer.
Tip
argument.value es el prefijo que el usuario ha escrito. El SDK no filtra por ti: lo que
pongas en values es lo que muestra la interfaz. El startswith lo escribes tú.
Pruébalo
Manéjalo con el Client en memoria de Pruebas. Llama a
client.complete() con ref=PromptReference(name="review_code") y
argument={"name": "language", "value": "py"}:
result.completion.values # ['python']
refes el mismo tipo de referencia que recibe tu handler.argumentes un dict normal con exactamente dos claves,nameyvalue.
Envía un value vacío y te devuelve la lista completa. lang.startswith("") es verdadero para todos los lenguajes:
result.completion.values # ['go', 'javascript', 'python', 'rust', 'typescript']
Pregunta por code (un argumento que tu handler no reconoce) y devuelve None, que el SDK convierte en una lista vacía:
result.completion.values # []
None significa "sin sugerencias", nunca un error. La interfaz recurre a un campo de texto normal.
Una capacidad que nunca declaraste
Registrar el handler es la declaración. Conecta un cliente y mira:
client.server_capabilities.completions # CompletionsCapability()
No escribiste completions en ninguna parte. El SDK vio el handler y declaró la capacidad por ti. Todas las capacidades opcionales funcionan así: el handler es la declaración. (Las tres primitivas no son opcionales: MCPServer siempre las declara, haya handlers o no.)
Check
Vuelve al primer server.py (el que no tiene handler) y pregúntale de todos modos. La llamada
falla con un error JSON-RPC:
Method not found
Y client.server_capabilities.completions es None. Ese es el sentido de la capacidad: un
cliente bien hecho la comprueba y nunca envía la solicitud que no puedes responder.
Argumentos dependientes
github://repos/{owner}/{repo} tiene dos parámetros, y los valores útiles para repo dependen de qué owner se eligió primero.
Para eso sirve context. Lleva los argumentos que el usuario ya ha resuelto:
from mcp.server import MCPServer
from mcp.types import Completion, CompletionArgument, CompletionContext, PromptReference, ResourceTemplateReference
mcp = MCPServer("GitHub Explorer")
LANGUAGES = ["go", "javascript", "python", "rust", "typescript"]
REPOS_BY_OWNER = {
"modelcontextprotocol": ["python-sdk", "typescript-sdk", "inspector"],
"pydantic": ["pydantic", "pydantic-ai", "logfire"],
}
@mcp.resource("github://repos/{owner}/{repo}")
def github_repo(owner: str, repo: str) -> str:
"""A GitHub repository."""
return f"Repository: {owner}/{repo}"
@mcp.prompt()
def review_code(language: str, code: str) -> str:
"""Review a snippet of code."""
return f"Review this {language} code:\n{code}"
@mcp.completion()
async def handle_completion(
ref: PromptReference | ResourceTemplateReference,
argument: CompletionArgument,
context: CompletionContext | None,
) -> Completion | None:
if isinstance(ref, PromptReference) and argument.name == "language":
return Completion(values=[lang for lang in LANGUAGES if lang.startswith(argument.value)])
if isinstance(ref, ResourceTemplateReference) and argument.name == "repo":
if context is None or context.arguments is None:
return None
repos = REPOS_BY_OWNER.get(context.arguments.get("owner", ""), [])
return Completion(values=[repo for repo in repos if repo.startswith(argument.value)])
return None
- La nueva rama se activa para el parámetro
repode la plantilla. context.argumentses undict[str, str] | Nonecon los valores elegidos hasta ahora (aquí,owner).- Si todavía no hay
owner, no hay sugerencias sensatas, así que el handler devuelveNone.
El cliente envía esos valores resueltos con context_arguments=. Esta vez ref es un
ResourceTemplateReference(uri="github://repos/{owner}/{repo}"). Pide repo con un
value vacío y pasa context_arguments={"owner": "modelcontextprotocol"}:
result.completion.values # ['python-sdk', 'typescript-sdk', 'inspector']
Quita context_arguments= y la misma llamada devuelve []. El handler no puede saber qué repositorios ofrecer hasta que conoce el propietario.
Info
Completion también acepta total= y has_more=. Úsalos cuando values sea un fragmento de
una lista más larga, para que la interfaz pueda mostrar "y 200 más". La mayoría de los
handlers nunca los necesitan.
Resumen
- El autocompletado son sugerencias para argumentos de prompts y parámetros de plantillas de recurso. Nada más.
@mcp.completion()registra el único handler. Esasync def (ref, argument, context) -> Completion | None.- Decide según
isinstance(ref, ...)yargument.name. Filtra porargument.valuetú mismo. Nonese convierte en una lista vacía. Nunca es un error.context.argumentscontiene los valores ya resueltos; el cliente los proporciona comocontext_arguments=.- La capacidad
completionsaparece en cuanto registras el handler. Sin él, la solicitud daMethod not found.
Las sugerencias ayudan mientras el usuario todavía está rellenando un prompt o una plantilla; para hacerle una pregunta en mitad de una llamada a una herramienta, lo que quieres es Elicitación. Todo lo que una herramienta puede devolver además de texto está en Imágenes, audio e iconos.