Saltar a contenido

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:

server.py
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_code recibe un language. Un usuario no debería tener que adivinar qué formas de escribirlo aceptas.
  • github_repo recibe un owner y un repo. Dos campos de texto libre hacen un mal formulario.

El handler de autocompletado

Añade una función decorada con @mcp.completion():

server.py
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, como PromptReference o ResourceTemplateReference. Con isinstance los distingues.
  • argument: argument.name es el argumento que se está completando, argument.value es lo que el usuario ha escrito hasta ahora.
  • context: los argumentos ya resueltos. Ignóralo por ahora.
  • Devuelves un Completion(values=[...]), o None cuando 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']
  • ref es el mismo tipo de referencia que recibe tu handler.
  • argument es un dict normal con exactamente dos claves, name y value.

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:

server.py
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 repo de la plantilla.
  • context.arguments es un dict[str, str] | None con los valores elegidos hasta ahora (aquí, owner).
  • Si todavía no hay owner, no hay sugerencias sensatas, así que el handler devuelve None.

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. Es async def (ref, argument, context) -> Completion | None.
  • Decide según isinstance(ref, ...) y argument.name. Filtra por argument.value tú mismo.
  • None se convierte en una lista vacía. Nunca es un error.
  • context.arguments contiene los valores ya resueltos; el cliente los proporciona como context_arguments=.
  • La capacidad completions aparece en cuanto registras el handler. Sin él, la solicitud da Method 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.