Documentación

API HTTP

laya-serve expone Laya sobre el protocolo de cable TypeSafe Jev /v1/systemone. Un cliente escrito contra Jev – hs-jev, typesafe-sdk, o el tuyo propio – puede apuntar su URL base a este servidor y seguir funcionando: la salida de predict() de Laya ya es compatible con el esquema, y el servidor solo añade la superficie HTTP: una ruta de decisión, una sonda de salud, una comprobación de bearer opcional y límites de solicitud.

Existe un cliente para PHP que apunta a laya-serve en lugar de a la API de Jev: marcreichel/laya-php es un SDK de Composer (PHP 8.4+) que mapea una clase de enums y atributos a preguntas y devuelve una instancia, lee GET /health para una comprobación de despliegue y trae un doble de prueba para que quien llama pueda hacer pruebas unitarias sin un servidor en marcha.

pip install "laya[serve]"
laya-serve            # http://0.0.0.0:8000

El mismo punto de entrada se ejecuta embebido en cualquier servidor ASGI: laya.serve.create_app() construye la aplicación FastAPI, opcionalmente con un Router que inyectas (create_app(router)) en lugar de uno construido desde el entorno.

Configuración

Todo son variables de entorno, así que una sola imagen sirve una ejecución de desarrollo en un portátil y una unidad de systemd.

variable de entorno significado valor por defecto
LAYA_HOST dirección de enlace 0.0.0.0
LAYA_PORT puerto de enlace 8000
LAYA_ROOT_PATH prefijo de URL público cuando se sirve detrás de un proxy inverso vacío
LAYA_DEVICE unidad de torch para todos los checkpoints auto
LAYA_PRELOAD construir los checkpoints al arrancar, no de forma perezosa 1
LAYA_MODELS lista separada por comas a precargar (english,multilingual,typed-decisions); vacío = todos todos
LAYA_THREADS limita los hilos intra-op de torch en CPU; mantenlo <= núcleos físicos – sobresuscribir los núcleos lógicos es una regresión grande valor por defecto de torch
LAYA_AUTO_TASK enrutar automáticamente al checkpoint typed-decisions 0
LAYA_IDLE_UNLOAD_SECONDS descarga los checkpoints residentes tras tantos segundos de inactividad; la siguiente solicitud vuelve a cargar su checkpoint. Cero desactiva la descarga 0
LAYA_DEFAULT_MODEL checkpoint al que recurre un estado sin evidencia de idioma; los alias como ml se resuelven igual que los resuelve core, y un nombre no resoluble detiene el servidor al arrancar english
LAYA_API_KEY si se define, exige Authorization: Bearer <key> ninguno
LAYA_LOG_LEVEL nivel de registro de uvicorn info
LAYA_MAX_CONCURRENT solicitudes admitidas a la vez tras la autenticación; el exceso recibe 503 16
LAYA_MAX_BATCH_TOKENS tokens que una PASADA HACIA ADELANTE de /v1/systemone/batch puede agrupar (states x preguntas x ancho de fila); un lote mayor se divide en varias pasadas, no se rechaza 131072
LAYA_JEV_STRICT sirve el contrato de cable estricto de Jev: sin routing raíz, sin action / answer_confidence por respuesta, sin confidence en las respuestas noul, y usage reducido a input_tokens + output_tokens. Para clientes que validan la respuesta contra el contrato de Jev sin campos extra 0

Para un despliegue publicado bajo un prefijo como /laya, define LAYA_ROOT_PATH=/laya. FastAPI lo usa al generar las URL de OpenAPI y Swagger UI. Configura el proxy inverso para que elimine /laya antes de reenviar las solicitudes a Laya; las rutas de la aplicación siguen siendo /health y /v1/systemone de forma interna.

Para contenedores, incluidas las imágenes CUDA y ARM64, consulta Inicio rápido con Docker.

Para un uso local a ráfagas, define LAYA_IDLE_UNLOAD_SECONDS=300. La inferencia y la descarga se ejecutan en el mismo worker, y la ventana de inactividad vuelve a empezar cuando termina una pasada hacia adelante individual o por lotes, incluidas las solicitudes fallidas. La siguiente predicción paga una carga en frío. La descarga libera referencias de modelo y cachés de dispositivo, incluida Metal; el asignador del proceso puede retener páginas de RAM, así que el RSS del proceso no tiene por qué bajar en la medida del checkpoint.

Endpoints

GET /health

Siempre abierto (sin autenticación), y sigue respondiendo durante la inferencia porque la pasada hacia adelante ligada a la CPU se ejecuta en su propio worker, no en el bucle de eventos. Los campos por debajo de liveness no están abiertos en un despliegue que definió LAYA_API_KEY: sin el bearer, /health responde {"status": "ok"} y nada más, porque el resto nombra los checkpoints residentes, sus SHAs de revisión exactas, el estado del dispositivo y el último motivo de reserva de cada checkpoint, que cita hardware del host. Una sonda solo necesita el 200, así que una comprobación de salud no se ve afectada y un bearer incorrecto sigue siendo un 200 en lugar de un 401. Sin LAYA_API_KEY definida, todos los que llaman reciben la carga completa que se muestra aquí.

{"status": "ok", "loaded": ["english", "multilingual"], "revisions": {"english": "...", "multilingual": "..."},
 "device": "cuda", "device_is_preference": false,
 "checkpoint_devices": {"english": "cuda", "multilingual": "cuda"},
 "cpu_fallbacks": {"english": {"count": 0, "last_reason": null}, "multilingual": {"count": 0, "last_reason": null}}}

La respuesta de un solo servidor, así que los bloques concuerdan entre sí: cada clave de revisions, checkpoint_devices y cpu_fallbacks es un nombre en loaded. tests/test_serve.py mantiene este ejemplo contra el manejador que lo produce, campo por campo.

Con la descarga por inactividad activada, las respuestas de salud autenticadas también incluyen idle_unload_seconds (la ventana configurada) e idle_seconds (tiempo desde la última solicitud de inferencia o su finalización). Las sondas de salud no reinician ese reloj. Una lista loaded vacía es normal tras una descarga por inactividad.

  • status es ok siempre que el proceso responda. No dice nada sobre los checkpoints.
  • loaded lista los checkpoints residentes en memoria. Está vacío hasta que una solicitud construye uno, que es lo que LAYA_PRELOAD=0 deja haciendo al proceso.
  • revisions es la revisión del artefacto desde la que se cargó cada checkpoint residente, indexada por los mismos nombres que loaded, así que un despliegue puede confirmar qué está sirviendo realmente.
  • device es el dispositivo en el que realmente calcula un checkpoint residente, que no siempre es lo que pidió LAYA_DEVICE: un checkpoint que quiere una GPU que no puede obtener recae en CPU de forma silenciosa y aun así responde correctamente. Sin nada residente es la preferencia configurada.
  • device_is_preference es true exactamente mientras no hay nada residente, y false en cuanto el manejador puede medir. Esa es la diferencia entre un servidor que informa de su configuración y un servidor que informa de dónde está su trabajo: uno que perdió su GPU en silencio dice false con device cpu, en lugar de seguir respondiendo cuda.
  • checkpoint_devices da la medición por checkpoint, indexada por los nombres en loaded; device es el primero de esos valores.
  • cpu_fallbacks cuenta, por checkpoint residente, las solicitudes que agotaron la memoria de GPU y se reintentaron una vez en CPU: count desde que arrancó el proceso, y last_reason con el texto de error de la última. La degradación está acotada a la solicitud que falló, así que un checkpoint construido en CPU porque la GPU nunca estuvo disponible no es una reserva y cuenta 0 aquí – eso se ve en device.

POST /v1/systemone

Una solicitud lleva un state y cualquier número de preguntas sobre él:

curl -s localhost:8000/v1/systemone -H 'content-type: application/json' -d '{
  "state": "I was charged twice this month, I want my money back",
  "questions": {
    "queue":   {"type": "choice", "instructions": "Which team?",
                "criteria": {"billing": "billing and refunds", "tech": "login and app issues",
                             "other": "everything else"}},
    "urgency": {"type": "score",  "instructions": "How urgent?",
                "criteria": ["calm", "firm", "angry", "furious"]}
  }
}'
campo obligatorio significado
state sí texto, correo, ticket o documento JSON sobre el que decidir; un estado ausente o null es un 400
questions sí objeto indexado por id de pregunta; cada pregunta es choice / score / noul con instructions y criteria
model no nombra un checkpoint; una ruta o un id de Hub no publicado es un 422, cualquier otra cosa se ignora (ver abajo)
task no fuerza un checkpoint por nombre de flujo de trabajo en lugar de dejar que el enrutamiento decida; un nombre desconocido es un 422 que lo nombra
lang no un código de idioma (de, en-US) que omite la detección cuando nombra un idioma; un código en blanco o no reconocido recae en la detección
lang_guess no un código de idioma del propio identificador del cliente, consultado después de lang y antes de la detección; cualquier código no inglés enruta al checkpoint multilingüe
max_len no ventana total de tokens para esta solicitud, limitada por LAYA_MAX_TOKEN_BUDGET
head_max_len no ventana de tokens que comparte el prompt de opciones, mismo tope; consulta Ampliar el presupuesto de tokens para saber cuándo lo necesita una pregunta
min_confidence no umbral de abstención en [0.0, 1.0]; una respuesta cuyo answer_confidence caiga por debajo vuelve marcada como low_confidence, y la respuesta en sí se conserva

model, task, lang, lang_guess, max_len, head_max_len y min_confidence son los argumentos que toma Router.predict y que un cuerpo JSON puede indicar; cada uno se reenvía solo cuando la solicitud lo envía, así que uno ausente deja al propio ajuste Router(...) del despliegue al mando. Los cinco argumentos de hook que predict también toma – hooks, on_predict_start, on_predict_end, hooks_raise, hooks_timeout – se rechazan con un 422 en lugar de descartarse: un hook es un callable que se ejecuta dentro del proceso del servidor, y los dos últimos dicen cómo se ejecutan los hooks que instaló un despliegue, así que ningún valor que envíe un llamador tiene significado aquí. Los mismos cinco se rechazan del lado del cliente por un nodo de LangChain con un base_url (laya.integrations.langchain), así que una cadena y un cliente HTTP en bruto ahora reciben la misma respuesta.

model se acepta para que un cliente de Jev pueda seguir enviando uno. Los ids públicos de Hugging Face (convaiinnovations/laya-multilingual, convaiinnovations/laya-typed-decisions), los nombres de checkpoint (english, multilingual, typed-decisions) y sus alias seleccionan un checkpoint. convaiinnovations/laya, y cualquier otro valor que no sea una ruta ni un id de repo de Hub – incluido un id de Jev como jev-1 – significa “deja que el router elija”, y el bloque routing de la respuesta registra lo elegido y por qué. Un valor que parece una ruta del sistema de archivos o un id de Hub no publicado (/path/to/checkpoint, org/repo, ~/ckpt, .\ckpt) es un 422 tanto en /v1/systemone como en /v1/systemone/batch: este servidor no puede cargarlo, y responder con otro checkpoint lo ocultaría. El detail es el mismo texto unknown model que lanza core, más el recordatorio de omitir model para que el router elija.

Respuesta

{
  "model": "laya-rl-agent",
  "answers": {
    "queue": {"type": "choice", "choice": "billing",
              "probabilities": {"billing": 0.9519, "tech": 0.0327, "other": 0.0154},
              "confidence": 0.797, "answer_confidence": 0.9519,
              "action": {"act_probability": 1.0}},
    "urgency": {"type": "score", "score": 1.6994,
                "legend": {"0": "calm", "1": "firm", "2": "angry", "3": "furious"},
                "probabilities": {"0": 0.0249, "1": 0.4136, "2": 0.3985, "3": 0.1629},
                "confidence": 0.1925, "answer_confidence": 0.4136,
                "action": {"act_probability": 1.0}}
  },
  "usage": {"input_tokens": 83, "output_tokens": 0, "state_tokens": 12,
            "state_tokens_dropped": 0, "truncated": false, "truncated_questions": []},
  "routing": {"model": "english", "repo": "convaiinnovations/laya", "reason": "English Latin text",
              "detection": {"script": "latin", "script_profile": {"latin": 1.0}, "language": "en",
                            "is_english": true, "language_undecided": false, "diacritic_rate": 0.0,
                            "non_latin_fraction": 0.0, "mixed_segment": null},
              "workflow": null}
}

El ejemplo es una respuesta que dio este servidor, literal: la solicitud de arriba, el checkpoint english en caché sobre CPU. answers y usage son las claves que decodifican los clientes de Jev; model es el nombre constante de la cabeza de decisión, y el checkpoint que respondió está en routing.

tipo de respuesta claves
choice choice (la opción argmax), probabilities por opción
score score (índice de nivel esperado, puede caer entre niveles), probabilities indexadas "0".. "k-1", legend que asigna índice al texto del nivel
noul noul, la probabilidad de la opción sí
todas confidence, answer_confidence y action.act_probability
gate abstention, abstention_threshold y low_confidence, escritos por la puerta de abstención – ver abajo

La fila gate es el informe de abstención (#361), y es la única forma en que quien llama puede ver que la puerta por la que pagó se ejecutó. Una solicitud que define min_confidence lo obtiene; una que no lo hace no obtiene ninguna de las tres claves. abstention es uno de tres estados, escrito en todas las respuestas de una solicitud con puerta: passed (su confianza superó el umbral), abstained (cayó por debajo, y low_confidence es true exactamente en esas respuestas), o unevaluated (la respuesta no llevaba una confianza utilizable, así que la puerta no pudo decidir – informar eso como un aprobado sería la misma mentira que informarlo como un flag). abstention_threshold devuelve el umbral contra el que se midieron esos estados, que es lo que hace que una ejecución por lotes con umbrales por clase se pueda volver a dividir a posteriori. Sin min_confidence definido no aparece ninguna de las tres claves en ninguna respuesta: la ausencia es el informe, no un cuarto estado, y es como quien llama distingue una ejecución sin puerta de una puerta superada. Un min_confidence de exactamente 0.0 sí se definió, así que se informan los estados, y nada puede caer por debajo, así que cada respuesta se lee passed – el 0.0 devuelto es lo que distingue eso de un aprobado con un umbral real. La respuesta en sí se conserva en todos los estados; la puerta marca, no descarta.

usage informa de con qué se construyó la pasada hacia adelante. Cuánto de un estado lee el modelo es un presupuesto de tokens, no un recuento de caracteres, y el presupuesto se mueve con max_len, head_max_len y el prompt de opciones de cada pregunta (#174), así que estas claves son el único lugar donde ese hecho es visible:

clave de usage significado
input_tokens tokens no de relleno de las filas del estado – una fila por pregunta, así que crece con las preguntas en vez de ser una longitud de contexto
output_tokens siempre 0 – la cabeza responde en una pasada, no genera nada
state_tokens tokens que necesita el estado serializado completo
state_tokens_dropped tokens de eso que al menos una pregunta no recibió: el peor caso entre las preguntas, ya que cada una deja al estado un espacio distinto
truncated true cuando ese peor caso descartó algo
truncated_questions los ids de las preguntas cuyo propio margen se recortó, [] cuando ninguna
options presente solo cuando las opciones de alguna pregunta ya no tienen cada una un tramo de tokens: indexado por id de pregunta, con total (las opciones que esa pregunta define), distinct (los tramos que llegaron a la secuencia) y tokens_per_option

Una respuesta truncada sigue siendo una respuesta – la cabeza decide con la evidencia que se le dio – pero quien llama que dimensiona los estados por recuento de caracteres no puede ver el recorte en ningún otro lugar de la respuesta.

routing registra qué checkpoint respondió y por qué:

clave de routing significado
model el checkpoint que respondió: english, multilingual o typed-decisions
repo su id público de Hugging Face
reason la frase de la elección, que nombra la evidencia sobre la que actuó
detection laya.lang.analyse() sobre el estado – script, script_profile, language, is_english, language_undecided, diacritic_rate, non_latin_fraction, mixed_segment – o null cuando la ruta decidió antes de leer el texto
workflow el flujo de trabajo de typed-decisions al que coinciden los ids de pregunta, o null

detection es null en toda ruta que decide sin leer el estado: una forzada por model o task, una respondida por lang o lang_guess, o una que coincidió con un flujo de trabajo de typed-decisions por los ids de pregunta. Un lang_guess no deja ninguna clave propia – la pista sobre la que actuó se nombra en reason. Las ramas model y task también informan workflow como null, porque responden antes de que se lean los ids de pregunta.

Confianza: dos números, no intercambiables

  • answer_confidence es la masa de probabilidad sobre la respuesta informada (max(p)). Es la cantidad que ajusta el escalado por temperatura y sobre la que se calculan las cifras de ECE de este repo, así que lleva la propiedad de gating en la que se apoya la página Benchmarks y límites conocidos – pero solo para un checkpoint cuya temperatura ajustada se haya validado con tu tráfico.
  • confidence significa algo distinto según el tipo: entropía normalizada 1 - H(p)/log(k) en choice y score, y max(p_yes, p_no) en noul (donde equivale a answer_confidence).

Nunca compares las dos contra un mismo umbral. Ten en cuenta también la diferencia al portar desde Jev: TypeSafe define la confianza como (n*p_max - 1)/(n - 1), así que un umbral traído de un despliegue de Jev aplica el gating de forma distinta sobre el valor de entropía de Laya.

Contrato estricto de Jev: LAYA_JEV_STRICT

La carga de arriba es la carga completa de Laya. El contrato de Jev al que un cliente puede someterla define menos: tres campos de nivel superior (model, answers, usage), las claves contratadas en cada respuesta y nada más, y un usage de los dos recuentos de tokens. Un cliente que valida la respuesta contra ese contrato sin campos extra – el plugin de proveedor TypeSafe de OpenClaw es uno – rechaza la carga completa, así que LAYA_JEV_STRICT=1 proyecta la respuesta sobre el contrato antes de responder, tanto en /v1/systemone como en /v1/systemone/batch:

  • la raíz conserva solo model, answers y usage; routing no se envía;
  • una respuesta choice conserva choice, probabilities y confidence;
  • una respuesta score conserva score, probabilities, confidence y legend;
  • una respuesta noul conserva solo noul;
  • usage conserva input_tokens y output_tokens; los hechos de truncado y el techo de opciones colapsadas no se envían.

La proyección conserva solo las claves contratadas y no recalcula nada: cada valor es el que el resultado ya lleva, así que las probabilidades y puntuaciones que lee un cliente estricto son idénticas a las que informa la carga completa. El valor por defecto sigue siendo la carga completa, y un despliegue que activa el flag pierde la visibilidad de truncado que ofrece usage – un estado recortado se ve entonces en los registros, no en la respuesta. El criteria de score debe seguir siendo cadenas simples bajo el contrato estricto: un cliente estricto compara el legend devuelto con los criterios que envió, y Laya renderiza un criterio estructurado con el JSON de Python, que un llamador de JavaScript que convierte a cadena sus propios criterios puede no igualar byte por byte.

Las respuestas correctas también llevan Server-Timing: inference;dur=<ms> y X-Inference-Time-Ms.

Límites

Los guardarraíles de solicitud se comprueban antes de la tokenización, así que una solicitud sobredimensionada no le cuesta al servidor más que los bytes que leyó. Todos ellos son un 413; el detail dice qué límite se alcanzó.

límite valor
cuerpo de la solicitud 2 MiB, aplicado mientras se transmite – un Content-Length troceado o subestimado no puede evitarlo
state 50,000 caracteres del texto que recibe el modelo – la propia cadena para un estado de tipo cadena, json.dumps(state, ensure_ascii=False) para un objeto o array
preguntas por solicitud 64
states por solicitud por lotes 64
opciones por pregunta choice 100
niveles por pregunta score 32
opciones entre todas las preguntas 512
solicitudes admitidas concurrentes LAYA_MAX_CONCURRENT (16)

/v1/systemone/batch está acotado de otra forma, y no por un rechazo. Tokeniza cada estado una vez por pregunta y agrupa cada fila en un único tensor, así que los topes de campo se multiplican: 64 estados de 64 preguntas son 4096 filas, que cualquier otro límite de esta página permite. Lo que cuesta una fila es su ancho, y max_len es en sí mismo un campo de solicitud, así que el coste de un lote es states x questions x width.

En lugar de rechazar un lote grande, el endpoint lo divide: cuando ese producto supera LAYA_MAX_BATCH_TOKENS (131.072 por defecto) elige un batch_size para que cada pasada hacia adelante se mantenga dentro del presupuesto, y Router.predict_batch ejecuta el lote en varias pasadas. Cada estado se sigue respondiendo y la respuesta no cambia. Una solicitud cuyas filas ya caben no recibe ningún batch_size, así que se comporta exactamente como antes – lo cual importa porque la forma del lote puede mover resultados de punto flotante. Un batch_size que envía quien llama siempre gana: pidió una forma.

En el valor por defecto, 256 filas pasan en una sola pasada – 64 estados de 4 preguntas, u 8 de 32. Los lotes mayores se dividen, y subir max_len hace cada pasada más estrecha en lugar de costar 16 veces el trabajo. Lo que esto no acota es cuánto tiempo ocupa una solicitud al servidor; eso es LAYA_MAX_CONCURRENT y el único worker de inferencia, y ya es cierto para una solicitud /v1/systemone sobre un estado de 50.000 caracteres.

Los topes de opciones son guardas de amplificación solo de HTTP; el propio modelo encaja los tokens de opciones en una ventana head_max_len=192, así que una pregunta dentro de los topes HTTP todavía puede rechazarse como un 422 cuando los textos de las opciones juntos superan ese presupuesto. El Harness de evaluación ejecuta las mismas solicitudes en proceso sin la capa HTTP.

Errores

estado cuándo detail del cuerpo
400 el cuerpo no es JSON válido, no es un objeto, no tiene questions, state falta o es null, questions no es un objeto, o una cadena en cualquier parte del cuerpo contiene un escape sustituto \udXXX sin pareja qué está mal
401 LAYA_API_KEY está definida y el token bearer falta o es incorrecto invalid or missing bearer token
413 cualquier límite de arriba qué límite y por cuánto
422 la pregunta es JSON bien formado pero inválido para Laya (tipo desconocido, opciones por encima del presupuesto de cabeza), o un control de solicitud (lang, min_confidence, un argumento de hook) no tiene la forma que este endpoint acepta nombra la pregunta o el campo y qué corregir
500 la inferencia falló por cualquier otra razón inference failed – siempre esta cadena, para que las rutas, los pesos y el estado de memoria nunca se filtren; la causa está en el registro del servidor
503 ya hay LAYA_MAX_CONCURRENT solicitudes en vuelo server busy, try again later

El 400 de sustituto sin pareja es el que parece inusual. \udXXX sin pareja es JSON legal, pero el carácter que nombra no se puede codificar en UTF-8, así que el tokenizador lanza un TypeError – la propia cadena de quien llama llegando como un fallo del servidor, con un traceback por solicitud. Ambas rutas de decisión recorren por tanto el cuerpo ya analizado en busca de sustitutos solitarios y rechazan uno antes de que llegue a la inferencia. El recorrido se ejecuta después de las comprobaciones de tamaño, así que un cuerpo sobredimensionado sigue rechazándose primero y los límites de caracteres y preguntas acotan lo que puede alcanzar. Un sustituto con pareja es un carácter astral ordinario cuando el analizador termina, así que un emoji en un estado no se ve afectado.

La carga por encima del tope se rechaza, no se pone en cola: los clientes que ocupan una plaza de admisión mientras transmiten un cuerpo lento no pueden dejar sin recursos a /health, y un reintento puede tomar la plaza que dejó un cliente rechazado.

Modelo de concurrencia

La inferencia es una llamada síncrona de torch que tarda de cientos de milisegundos a segundos en CPU, así que nunca se ejecuta en el bucle de eventos: las solicitudes se entregan a un ejecutor de un solo worker, lo que significa una pasada hacia adelante a la vez – la forma que quiere un solo checkpoint en una sola unidad. La admisión (el semáforo LAYA_MAX_CONCURRENT) se comprueba antes de leer ningún byte del cuerpo y se mantiene durante toda la inferencia; la puerta de inferencia se une solo cuando el cuerpo está completo, así que un cliente lento ocupa una plaza de admisión pero nunca una de inferencia.

Lo que (todavía) no está aquí

Este servidor habla un solo protocolo a propósito. No hay endpoint compatible con OpenAI; ejecuta varias preguntas en una sola solicitud en su lugar, ya que comparten una única pasada hacia adelante por conjunto de preguntas. La otra ruta es POST /v1/systemone/batch, que responde un conjunto de questions sobre un array de states. Todavía no tiene sección en esta página – su forma de solicitud está en la sección de autoalojamiento del README – y todas las comprobaciones de arriba se le aplican igual que a POST /v1/systemone: los mismos 400 de forma, el mismo rechazo de sustituto sin pareja, la misma auth, admisión, límites de tamaño, validación de controles del cuerpo y mapeo de 500. La CLI laya y el servidor MCP cubren el uso local – consulta el README.