Saltar al contenido

Capítulo 3 de 18

Herramientas con contratos y permisos

Diseña acciones pequeñas y comprobables, valida argumentos y separa los datos del modelo de la autoridad del servidor.

#Diseñar una herramienta es diseñar una API

Una herramienta convierte una intención en una operación definida. hacer_cosas(texto) obliga al modelo a adivinar; buscar_documentos(query, limit) declara qué acción existe y qué controla el llamante. La descripción debe explicar cuándo usarla, qué devuelve y qué límites tiene.

Antes de escribir una función, decide su efecto. Una búsqueda consulta; un borrado modifica; un envío puede ser irreversible. Esa clasificación determina permisos, confirmaciones y recuperación. Es preferible ofrecer crear_borrador y enviar_borrador por separado cuando la revisión es necesaria: así la autorización puede referirse al borrador concreto.

#Forma, significado y autorización

Un contrato de entrada puede expresarse con JSON Schema, un lenguaje para describir la forma de un objeto JSON. No ejecuta la herramienta ni establece permisos.

{
  "type": "object",
  "properties": {
    "document_id": {"type": "string", "minLength": 1, "maxLength": 100}
  },
  "required": ["document_id"],
  "additionalProperties": false
}

required obliga a incluir la clave; additionalProperties: false rechaza campos no declarados. Esto detecta document_id: 42, pero no demuestra que el documento exista o pertenezca al usuario.

Hay tres comprobaciones diferentes:

Comprobación Pregunta Responsable
Forma ¿Es un texto dentro del tamaño permitido? Validador de entrada
Significado ¿Existe ese documento y esa versión? Lógica de herramienta
Autorización ¿Esta ejecución puede leerlo? Política del servidor

La identidad del usuario o tenant debe proceder de la autenticación del servidor. No aceptes tenant_id enviado por el modelo como autoridad. Puede aparecer como filtro solicitado, pero siempre debe quedar restringido por la identidad real.

#Una lectura autorizada y versionada

El siguiente fragmento muestra el núcleo de una herramienta de lectura; documents es un diccionario controlado por la aplicación y allowed_ids se calcula a partir de la identidad autenticada. No son argumentos que el modelo pueda modificar.

import hashlib


def leer_documento(arguments, documents, allowed_ids):
    if not isinstance(arguments, dict) or set(arguments) != {"document_id"}:
        raise ValueError("Se requiere document_id")
    document_id = arguments["document_id"]
    if not isinstance(document_id, str) or not 1 <= len(document_id) <= 100:
        raise ValueError("document_id inválido")
    if document_id not in allowed_ids:
        raise PermissionError("Documento no disponible para esta ejecución")
    text = documents[document_id]
    encoded = text.encode("utf-8")
    if len(encoded) > 64_000:
        raise ValueError("Usa lectura por fragmentos para este documento")
    return {
        "document_id": document_id,
        "sha256": hashlib.sha256(encoded).hexdigest(),
        "text": text,
        "truncated": False,
    }

hashlib pertenece a Python y calcula resúmenes criptográficos. El hash SHA-256 identifica el contenido: si cambia un carácter, normalmente cambia su hash. No prueba quién escribió el documento ni que sea verdadero. Sirve para relacionar una decisión con la versión leída.

Limitar el tamaño evita que una herramienta llene toda la ventana de contexto. Si implementas truncamiento, devuélvelo explícitamente junto a un cursor o rango para continuar. Una respuesta recortada presentada como completa puede producir un diagnóstico falso.

#Si necesitas leer rutas del filesystem

Un identificador lógico es más fácil de autorizar que una ruta libre. Cuando necesites rutas, una comprobación inicial consiste en resolverlas y exigir que queden dentro de una raíz:

from pathlib import Path


def resolver_ruta(root, relative):
    root = Path(root).resolve(strict=True)
    path = (root / relative).resolve(strict=True)
    if not path.is_relative_to(root) or not path.is_file():
        raise PermissionError("Ruta fuera del workspace")
    return path

resolve resuelve componentes como .. y enlaces simbólicos. Comparar cadenas con startswith sería incorrecto: /datos/usuario2 empieza por /datos/usuario y no pertenece a ese directorio.

Esta comprobación es adecuada para un árbol que no cambia de forma adversaria entre validar y abrir. Si otro proceso puede sustituir un enlace justo después, existe una carrera entre comprobación y uso. Para código no confiable necesitas aislamiento del sistema operativo y aperturas relativas seguras según la plataforma. La validación de rutas no convierte un proceso en un sandbox.

#El resultado debe permitir decidir

Un resultado útil no es solo «correcto». Para ejecutar pruebas, devuelve código de salida, duración, versión del código y ubicación del log. Para escribir, devuelve versión anterior y nueva. Para una API remota, conserva el identificador de la operación del proveedor.

{
  "ok": false,
  "error": {"code": "VERSION_CONFLICT", "retryable": false},
  "expected_version": "v7",
  "current_version": "v8"
}

El modelo puede leer otra vez y replantear el cambio. El harness no debe repetir automáticamente una escritura sobre v8 solo porque falló sobre v7: estaría aplicando una intención antigua a un estado distinto. Las escrituras condicionales son una protección contra sobrescribir trabajo concurrente.

#Herramientas grandes, pequeñas y transaccionales

Una herramienta por cada operación mínima genera demasiadas vueltas: abrir documento, contar líneas, obtener título, leer primera línea. Una herramienta gigantesca como «investigar y reparar todo» oculta decisiones y permisos. El límite útil coincide con una acción de negocio comprensible y verificable.

Si dos cambios deben ocurrir juntos, puede tener sentido una única herramienta transaccional. Por ejemplo, guardar un informe y registrar su referencia en la ejecución. Si cada mitad se expone por separado, el modelo puede olvidar la segunda o caer entre ambas.

Las funciones puras, que calculan un resultado sin modificar estado, son fáciles de repetir. Las herramientas con efectos requieren una identidad de operación y un plan frente a timeout. Se desarrollan en fallos e idempotencia.

#Qué aporta MCP y qué no

Model Context Protocol estandariza cómo un cliente descubre herramientas de un servidor y las invoca. En la versión 2025-11-25, tools/list devuelve definiciones y tools/call realiza una llamada. Los resultados pueden contener texto y structuredContent; un fallo de ejecución puede expresarse con isError.

MCP permite reutilizar integraciones sin escribir un formato nuevo para cada servidor. No decide si tu usuario puede emitir una factura, ni aísla el proceso del servidor, ni valida que el resultado sea cierto. La autenticación del transporte tampoco sustituye a comprobar autorización sobre cada recurso.

Conviene incorporar MCP cuando necesitas integrar servidores existentes o exponer tus herramientas a varios clientes. Para aprender el mecanismo, un registro de funciones local permite inspeccionar el contrato completo con menos piezas. La referencia enlazada está versionada; antes de implementar un cliente nuevo, revisa la versión negociada por ambos extremos.

#Un fallo que merece una prueba

Dos usuarios tienen documentos con el mismo título. La búsqueda devuelve una coincidencia del otro usuario. Aunque la lectura final la deniegue, ya has filtrado metadatos. El filtro de permisos debe aplicarse también al buscar, contar y generar snippets. Una prueba con dos tenants y títulos idénticos detecta este problema mejor que comprobar únicamente el código de respuesta de leer_documento.

#Referencias

    ↑ ↓ para recorrer · Intro para abrir · Ctrl K para buscar