Modal MCP
Un servidor MCP para gestionar aplicaciones, contenedores, volúmenes y secretos de Modal. También ayuda a desplegar y ejecutar aplicaciones de Modal directamente desde Claude Code y otros clientes MCP.
Documentación
Servidor MCP Modal
Un servidor MCP para gestionar Modal — aplicaciones, contenedores, volúmenes y secretos — y para desplegar y ejecutar aplicaciones Modal directamente desde Claude Code y otros clientes MCP.
Cada herramienta invoca tu CLI local de modal, por lo que opera con el perfil y las credenciales de Modal configurados en tu máquina. No hay tokens adicionales que gestionar.
Instalación
El servidor está publicado en PyPI como mcp-modal. No se necesita instalación manual — la forma recomendada de ejecutarlo es con uvx, que lo descarga y lo lanza bajo demanda. Solo tienes que apuntar tu cliente MCP al comando siguiente (consulta Configuración).
Cada versión también está etiquetada y publicada en la
página de Releases, con notas de la versión y
los mismos .whl / .tar.gz que sirve PyPI adjuntos — útil para fijar versiones, instalaciones
en entornos aislados o para leer qué cambió entre dos versiones.
Iniciar sesión en Modal
Este servidor usa tus credenciales locales de Modal. Si aún no te has autenticado, ejecuta:
modal setup
Esto abre un navegador para iniciar sesión y guarda un token en ~/.modal.toml. ¿Ya has iniciado sesión en otro lugar? Compruébalo con modal profile current.
Configuración
Añade el servidor a Claude Code con la CLI de claude mcp:
claude mcp add mcp-modal -- uvx mcp-modal@latest
O añádelo a un archivo .mcp.json en la raíz de tu proyecto, que es la mejor opción para un equipo
— todos los que abran el repositorio obtienen la misma configuración:
{
"mcpServers": {
"mcp-modal": {
"command": "uvx",
"args": ["mcp-modal@latest"]
}
}
}
Por qué @latest, y cuándo fijar versión en su lugar
uvx almacena en caché el entorno que construye en la primera ejecución y no vuelve a consultar PyPI:
"uvx utilizará la versión más reciente disponible de la herramienta solicitada en la primera invocación. Después, uvx utilizará la versión en caché de la herramienta a menos que se solicite una versión diferente, se limpie la caché o se actualice la caché." — documentación de uv
Así que un uvx mcp-modal simple significa la más reciente en el momento de la instalación, congelada para siempre después — reiniciar
el cliente o el sistema no cambia nada, porque la caché vive en el disco. Personas diferentes
terminan en versiones diferentes según cuándo lo ejecutaron por primera vez, sin ninguna advertencia.
mcp-modal@latestvuelve a resolver en cada lanzamiento, por lo que un reinicio recoge nuevas versiones. Cuesta un viaje de ida y vuelta de red al inicio. Úsalo mientras la superficie de herramientas aún se está moviendo.mcp-modal@0.4.0(una versión explícita) es reproducible y las actualizaciones se convierten en un cambio deliberado de una línea. Úsalo cuando quieras estabilidad, o para una audiencia más amplia.
Para mover una máquina que ya está atascada en una compilación antigua en caché, cambiar a cualquiera de las dos formas
anteriores es suficiente — solicitar una versión invalida la caché. De lo contrario,
uv cache clean mcp-modal fuerza una actualización.
Requisitos
- Python 3.11 o superior
uv(proporcionauvx)- CLI de Modal 1.5 o más reciente, configurada con credenciales válidas (
modal setup) — 1.5 es donde aterrizaronmodal billing summary/ratesy donde el informe de facturación cambió a columnas snake_case; la herramienta de costos lee ambas grafías pero necesita 1.5 para esas dos vistas - Para soporte de deploy y run de Modal:
- El proyecto que se despliega/ejecuta debe usar
uvpara la gestión de dependencias modaldebe estar instalado en el entorno virtual de ese proyecto
- El proyecto que se despliega/ejecuta debe usar
Seguridad
Este servidor invoca tu CLI local de modal usando las credenciales que haya en
~/.modal.toml. Algunas herramientas son potentes por diseño — si el cliente MCP que maneja el servidor
sufre una inyección de prompt (por ejemplo, por texto malicioso dentro de los registros que obtiene),
estas son las vías de escalada y deberían permanecer detrás de los avisos de aprobación de herramientas de tu cliente en lugar
de estar aprobadas automáticamente:
deploy_modal_app/run_modal_app— ejecutan Python local arbitrario en el host (modal deployimporta el archivo de la aplicación;uv runresuelve e instala las dependencias del proyecto objetivo).modal_volume_filesconaction="put"— puede leer cualquier archivo local (p. ej.~/.ssh/id_rsa,~/.modal.toml) y subirlo a un volumen en la nube (una primitiva de exfiltración de datos).modal_volume_filesconaction="get"yforce=True— puede sobrescribir cualquier ruta local (p. ej.~/.zshrco un perfil de shell, una primitiva de persistencia).manage_modal_containerconaction="exec"— ejecuta comandos arbitrarios dentro de un contenedor, por diseño.
Cada herramienta declara anotaciones de herramientas MCP,
para que un cliente pueda distinguir las cuatro herramientas de solo lectura (list_modal_resources,
get_modal_logs, search_modal_logs, analyze_modal_costs — todas readOnlyHint: true)
de las ocho que cambian el estado remoto o inician cómputo. Seis de esas ocho son
destructiveHint: true; las excepciones son run_modal_app y inspect_modal_secret,
que inician cómputo sin eliminar ni sobrescribir nada. Aprueba automáticamente las lecturas; mantén
el resto detrás de un aviso.
Lista blanca de rutas locales opcional
Para contener las dos herramientas de volúmenes que tocan el sistema de archivos, establece la
variable de entorno MCP_MODAL_ALLOWED_LOCAL_PATHS a una
lista separada por os.pathsep de
directorios (: en macOS/Linux). Cuando está establecida, modal_volume_files se rechaza para cualquier
ruta local — local_path en action="put", el destino en action="get" — a menos que la
ruta resuelta, después de expandir ~ y colapsar ../enlaces simbólicos, caiga dentro de una de esas
raíces. El destino de descarga "-" (devolver el contenido en lugar de escribir un archivo) está exento
porque no se escribe nada en el disco.
Cuando la variable está sin establecer (el valor predeterminado) no hay restricción, por lo que las configuraciones existentes no se ven afectadas. Configúrala en tu cliente MCP, p. ej.:
{
"mcpServers": {
"mcp-modal": {
"command": "uvx",
"args": ["mcp-modal"],
"env": { "MCP_MODAL_ALLOWED_LOCAL_PATHS": "/Users/me/modal-workspace:/tmp/modal" }
}
}
}
Todas las herramientas también pasan nombres/rutas proporcionados por el usuario después de un separador de fin de opciones --, por lo que un
valor que comienza con - siempre se trata como datos, nunca como una bandera de CLI de modal. Los valores
secretos entregados a manage_modal_secret se redactan del comando mostrado, los registros y cualquier
salida de error.
Herramientas compatibles
12 herramientas. Las operaciones relacionadas se agrupan detrás de un argumento action/resource en lugar de
dividirse una por subcomando de CLI: cada esquema de herramienta se carga en el contexto del modelo para
toda la sesión, por lo que una superficie más pequeña deja más espacio para tu trabajo real (y le da al
modelo menos herramientas casi idénticas entre las que elegir).
Las herramientas que hablan con recursos de ámbito de entorno toman un argumento opcional env para apuntar a un
entorno de Modal específico; si se omite, usan
el predeterminado del perfil (o MODAL_ENVIRONMENT). La excepción es manage_modal_container
y los registros de contenedores — un ID de contenedor es globalmente único y la CLI no acepta entorno
allí.
Solo lectura
-
Listar recursos de Modal (
list_modal_resources) — una sola consulta para toda la cuenta.- Parámetros:
resource(obligatorio),name,path(predeterminado/),env - Valores de
resource:valor devuelve namesignificaappsaplicaciones desplegadas/ejecutándose/detenidas recientemente — app_historyversiones de despliegue de una aplicación (para rollback) nombre/ID de la aplicación containerscontenedores en ejecución ( ta-...)ID de la aplicación para filtrar volumesvolúmenes con nombre — volume_filesarchivos dentro de un volumen (con path)nombre del volumen secretsnombres de secretos (los valores nunca se exponen) — environmentsvalores válidos de envpara este espacio de trabajo— profileperfil activo + todos los perfiles — volume_filesestableceempty: truecon un mensaje cuando un listado genuinamente devuelve nada, para que un directorio vacío se distinga de una ruta incorrecta.- Los listados de más de 200 entradas se limitan, con
omitted_itemsindicando el número descartado.
- Parámetros:
-
Obtener registros de Modal (
get_modal_logs) — obtener o transmitir registros de una aplicación o un contenedor.- Parámetros:
identifier(obligatorio),target(auto/app/container, predeterminadoauto— cualquier cosa que comience conta-es un contenedor),timeout_seconds(predeterminado 30),env,since,until,tail,source(stdout/stderr/system),timestamps,follow sincesintailobtiene todas las entradas en el rango; pasauntiltambién (rango máximo 35 días,tailmáximo 20,000) para mantener acotada la salida de una aplicación ocupada.- Con
follow=True, los registros se transmiten hasta que la aplicación/contenedor se detiene o se alcanzatimeout_seconds, devolviendo una instantánea contruncated: true. - Solo cubre los flujos stdout/stderr/sistema; algunos fallos (p. ej. un bloqueo reportado como "... salió con ...") son eventos del panel de Modal, no líneas de registro, y no aparecerán aquí.
- Parámetros:
-
Buscar registros de Modal (
search_modal_logs) — busca en los registros y obtén cada coincidencia con las líneas circundantes, diseñado para depurar "¿dónde salió mal?". Los registros se obtienen una vez y se buscan localmente, por lo que obtienes contexto, expresiones regulares, control de mayúsculas y recuentos exactos de coincidencias.- Parámetros:
identifier(obligatorio),pattern(obligatorio),target(predeterminadoauto),regex,case_sensitive,context_lines(predeterminado 3),max_matches(predeterminado 50),since,until,tail(predeterminado a las últimas 1000 entradas),source,exclude(eliminar líneas de ruido antes de buscar, p. ej."queue put failed"),prefilter,timestamps(predeterminadotrue),timeout_seconds,env - Acota la ventana en una aplicación ocupada.
sincepor sí solo obtiene todo desde entonces hasta ahora — cientos de KB por hora en una aplicación locuaz, que la búsqueda de 30s corta (logs_truncated: true) y el presupuesto de salida recorta.sinceyuntilalrededor del minuto que te importa es la solución, y suele ser kilobytes. prefilter=Trueempujapatternhacia Modal como un filtro de subcadena del lado del servidor (modal app logs --search), por lo que las líneas que no coinciden nunca se obtienen — la palanca para registros demasiado grandes para drenar. Requiereregex=False, y las líneas de contexto muestran entonces solo otras coincidencias, así que úsalo para localizar la ventana y vuelve a consultarla conprefilter=False.- Devuelve
match_countymatches: bloques de contexto con marca de tiempo y número de línea donde las líneas coincidentes tienen el prefijo>, p. ej.> 8: 2026-06-04T... ValueError: bad input. Todo el registro obtenido siempre se busca, por lo quematch_countsigue siendo exacto incluso cuando se devuelven menos bloques.returnedes cuántas coincidencias llegaron (las coincidencias adyacentes se fusionan en un bloque, contadas porreturned_blocks). Reportaexcluded_linescuandoexcludese usa. - Una ventana que la CLI rechaza (rango invertido, más de 35 días,
tailmás de 20,000) vuelve comosuccess: falsecon el mensaje propio de Modal, no un código de salida desnudo. - Misma advertencia de solo stdout/stderr/sistema que
get_modal_logs.
- Parámetros:
Desplegar y ejecutar
-
Desplegar aplicación Modal (
deploy_modal_app)- Despliega una aplicación Modal (
modal deploy). Los endpoints web desplegados persisten, por lo que cualquier enlace en la salida está activo y se puede compartir (devueltos enurls). - Parámetros:
absolute_path_to_app(obligatorio),env,name,tag,strategy(rolling/recreate),stream_logs - El directorio de la aplicación debe usar
uvconmodalinstalado en su virtualenv.
- Despliega una aplicación Modal (
-
Ejecutar App de Modal (
run_modal_app)- Ejecuta una función o punto de entrada local una vez y recopila su salida (
modal run). - Parámetros:
absolute_path_to_app(obligatorio),function_name,env,detach,timeout_seconds(predeterminado 120) - Devuelve una instantánea con
truncated: truesi la ejecución aún continúa al agotarse el tiempo de espera. Pasedetach=Truepara mantener trabajos largos activos en Modal más allá del tiempo de espera.
- Ejecuta una función o punto de entrada local una vez y recopila su salida (
¿Por qué no hay una herramienta
modal serve?modal servesolo mantiene sus endpoints activos mientras el proceso de bloqueo se ejecuta — una herramienta MCP que devuelve resultados los eliminaría inmediatamente, entregando una URL muerta. Usedeploy_modal_apppara un endpoint persistente y compartible.
Cambios de estado
-
Gestionar App de Modal (
manage_modal_app) —actionesstop(apagar la app y terminar sus contenedores) orollback(redesplegar una versión anterior).- Parámetros:
action(obligatorio),app_identifier(obligatorio),version(solo reversión — predeterminado a la versión inmediatamente anterior),env
- Parámetros:
-
Gestionar Contenedor de Modal (
manage_modal_container) —actionesexec(ejecutar un comando dentro de un contenedor en ejecución,modal container exec --no-pty) ostop(terminarlo).- Parámetros:
action(obligatorio),container_id(obligatorio),command(solo exec — una lista de argumentos, p. ej.["python", "-c", "print('hi')"]),timeout_seconds(predeterminado 60)
- Parámetros:
-
Gestionar Volumen de Modal (
manage_modal_volume) —actionescreate,delete(el volumen y todos sus datos, irreversible), orename.- Parámetros:
action(obligatorio),volume_name(obligatorio),new_name(solo renombrar),env
- Parámetros:
-
Archivos de Volumen de Modal (
modal_volume_files) — operaciones de escritura en los archivos de un volumen:actionesput(subir),get(descargar),cp(copiar dentro del volumen), orm.- Parámetros:
action(obligatorio),volume_name(obligatorio),local_path,remote_path,paths(paracp: orígenes y luego destino),recursive,force,env action="get"conlocal_path="-"devuelve el contenido del archivo en lugar de escribir un archivo.- Para listar el contenido de un volumen use
list_modal_resources(resource="volume_files").
- Parámetros:
-
Gestionar Secreto de Modal (
manage_modal_secret) —actionescreateodelete.- Parámetros:
action(obligatorio),secret_name(obligatorio),key_values(dict),from_dotenv(ruta),from_json(ruta),force,env. Crear requiere al menos uno dekey_values,from_dotenv, ofrom_json. - Los valores de los secretos se redactan de cada campo devuelto, incluida la salida de errores.
- Para listar nombres de secretos use
list_modal_resources(resource="secrets").
- Parámetros:
Costos
- Analizar Costos de Modal (
analyze_modal_costs) — solo lectura. Obtienemodal billinguna vez y agrega localmente, para que obtenga totales clasificados y cambios de período a período en lugar de cientos de filas sin procesar.- Parámetros:
view(predeterminadoby_app),period,start,end,resolution(d/h),timezone,app,environment,top_n(predeterminado 10),tag_names - Valores de
view:valor responde by_app"¿cuál es mi app más costosa?" — apps clasificadas por gasto, con % de participación timeline"¿por qué el lunes fue caro?" — costo por intervalo, más un explanationque compara el intervalo pico con el anterior y clasifica qué apps crecieronby_environmenta qué entorno va el dinero by_resourceCPU vs clase GPU vs memoria vs almacenamiento summarycosto facturado vs medido para un ciclo mensual, con ajustes de créditos/plan ratesprecios unitarios actuales total_costsiempre cubre cada fila en el rango, incluso cuandogroupsse reduce atop_n— cítelo en lugar de sumar las filas visibles.- La facturación es a nivel de espacio de trabajo (la CLI no acepta
-e), por lo que esto informa en todos los entornos;environmentfiltra las filas después. - Modal informa solo intervalos completos, por lo que un día parcialmente transcurrido se lee bajo.
- Parámetros:
Secretos — inspección
- Inspeccionar Secreto de Modal (
inspect_modal_secret) — lista los nombres de claves dentro de un secreto, nunca los valores.- Parámetros:
secret_name(obligatorio),env,image,timeout_seconds(predeterminado 300) - Modal no expone ninguna API para esto por diseño: ni la CLI, ni el SDK, ni la capa gRPC.
La única forma de ver qué claves define un secreto es montarlo en un contenedor
y listar el entorno. Entonces esta herramienta ejecuta
modal shell --secret <name>concompgen -e(un builtin de bash que imprime solo nombres de variables exportadas — ningún valor se imprime jamás, incluso dentro del contenedor), luego resta las variables que la imagen y el runtime de Modal establecen de todos modos — 23 nombres conocidos más cualquier cosa bajo seis prefijos (MODAL_,PYTHON,PIP_,NVIDIA_,CUDA_,LD_LIBRARY_PATH), que también cubre las credenciales deMODAL_TOKEN_*que viven en cada contenedor. - Esta única llamada inicia cómputo remoto, por lo que cuesta unos centavos y toma decenas de
segundos (más cuando la imagen tiene que construirse). Cada otra lectura en este servidor es
gratuita; use
list_modal_resources(resource="secrets")para ver qué secretos existen y recurra a esto solo cuando necesite saber qué hay dentro de uno. - Devuelve
keys, más elall_env_namessin filtrar para que una clave que parece una variable de runtime siga siendo visible en lugar de descartarse silenciosamente. - Omita
imagepara usar el predeterminado de Modal (diseñado para coincidir con el Python del servidor — la opción más confiable). Pase uno, p. ej.python:3.12-slim, si el constructor de imágenes de su espacio de trabajo rechaza esa versión de Python.
- Parámetros:
Prompts
El servidor también incluye cuatro prompts de MCP — flujos de trabajo de múltiples pasos que su cliente puede invocar
directamente (en Claude Code aparecen como /mcp__mcp-modal__<name>). Los prompts se obtienen bajo
demanda, por lo que a diferencia de las herramientas no cuestan nada en contexto por sesión:
debug_modal_app(app_name, opcionalsymptom) — una rutina de triaje ordenada: verificar que la app esté activa, buscar tracebacks en los registros con contexto, reducir la ventana en lugar de ampliarla cuando una búsqueda de registros regresa truncada, recurrir a la cola de registros, verificar si las apps hermanas fueron afectadas en la misma ventana, inspeccionar contenedores, luego comparar contra el historial de despliegues y considerar una reversión.deploy_and_verify(absolute_path_to_app, opcionalenv) — confirmar el espacio de trabajo objetivo, desplegar, informar las URLs en vivo, luego verificar que la app esté saludable en lugar de asumirlo.review_modal_account(opcionalenv) — un inventario de solo lectura que señala apps inactivas, contenedores en ejecución inexplicables y volúmenes/secretos huérfanos, nombrando la llamada exacta que limpiaría cada uno sin ejecutarla.investigate_modal_costs(opcionalperiod,app) — rastrea un aumento de gasto desde la línea de tiempo diaria hasta la hora pico, la clase de recurso y el despliegue o contenedor aún en ejecución detrás de él.
Límites de salida
La salida de registros, ejecuciones y exec se limita antes de devolverse, para que una app habladora no inunde
su ventana de contexto. El presupuesto predeterminado es de 40,000 caracteres por campo de texto (aproximadamente 10k
tokens); cuando un campo se recorta, el resultado establece output_capped: true y el texto lleva
un marcador que indica cuánto se descartó. Un campo limitado conserva su inicio y su final, por lo que
un banner de inicio y el traceback al final ambos sobreviven.
La búsqueda nunca se limita antes del hecho: search_modal_logs examina todo el registro obtenido
y solo limita cuántos bloques de contexto regresan, por lo que match_count siempre es exacto.
Elevar timeout_seconds o el presupuesto rara vez es la respuesta correcta a una búsqueda de registros
truncada — obtener menos es. Limite la ventana con since y until, filtre con
source/exclude, o establezca prefilter=True para descartar líneas que no coincidan dentro de Modal.
Establezca MCP_MODAL_MAX_OUTPUT_CHARS para subir o bajar el presupuesto, o a 0 para deshabilitar el límite
por completo:
{
"mcpServers": {
"mcp-modal": {
"command": "uvx",
"args": ["mcp-modal"],
"env": { "MCP_MODAL_MAX_OUTPUT_CHARS": "80000" }
}
}
}
Formato de respuesta
Todas las herramientas devuelven respuestas en un formato estandarizado, con ligeras variaciones según el tipo de operación:
# Lookups (list_modal_resources):
{
"success": True,
"apps": [...], # or "containers", "volumes", "contents", "secrets", ...
"omitted_items": 0 # present when the listing was capped at 200 entries
}
# Action operations (deploy, stop, rollback, create, delete, rename, cp, put, get, rm):
{
"success": True,
"message": "Operation successful message",
"command": "executed command string",
"stdout": "command output", # if any
"stderr": "error output" # if any
}
# Log / run / exec operations (snapshot-based):
{
"success": True,
"logs": "...", # or "output" for run/exec
"truncated": False, # True when cut off at timeout_seconds
"output_capped": False, # True when text was trimmed to fit MCP_MODAL_MAX_OUTPUT_CHARS
"command": "executed command string"
}
# Log search (search_modal_logs):
{
"success": True,
"match_count": 12, # exact: the whole fetched log is searched
"returned": 5, # matches actually shown
"returned_blocks": 2, # adjacent matches merge into one context block
"matches": ["> 8: ...", ...],
"logs_truncated": False, # True when the log fetch hit timeout_seconds
"output_capped": False,
"command": "executed command string"
}
# Error case (all operations):
{
"success": False,
"error": "Error message describing what went wrong",
"command": "executed command string",
"stdout": "command output", # if available
"stderr": "error output" # if available
}
Licencia
Este proyecto está licenciado bajo la Licencia MIT — consulte el archivo LICENSE para más detalles.