Modal MCP
Um servidor MCP para gerenciar aplicativos, containers, volumes e segredos do Modal. Também ajuda a implantar e executar aplicativos Modal diretamente do Claude Code e de outros clientes MCP.
Documentação
Servidor MCP Modal
Um servidor MCP para gerenciar Modal — apps, contêineres, volumes e segredos — e para implantar e executar apps Modal diretamente do Claude Code e de outros clientes MCP.
Cada ferramenta executa comandos na sua CLI modal local, então opera com o perfil e as credenciais Modal configurados na sua máquina. Não há tokens extras para gerenciar.
Instalação
O servidor é publicado no PyPI como mcp-modal. Não é necessária instalação manual — a forma recomendada de executá-lo é com uvx, que o busca e inicia sob demanda. Basta apontar seu cliente MCP para o comando abaixo (veja Configuração).
Cada versão também é marcada e publicada na
página de Releases, com notas de versão e os
mesmos .whl / .tar.gz que o PyPI serve anexados — útil para fixar versões, instalações
isoladas ou para ler o que mudou entre duas versões.
Fazendo login no Modal
Este servidor usa suas credenciais locais do Modal. Se você ainda não se autenticou, execute:
modal setup
Isso abre um navegador para login e armazena um token em ~/.modal.toml. Já está logado em outro lugar? Verifique com modal profile current.
Configuração
Adicione o servidor ao Claude Code com a CLI claude mcp:
claude mcp add mcp-modal -- uvx mcp-modal@latest
Ou adicione-o a um arquivo .mcp.json na raiz do seu projeto, que é a melhor opção para um time
— todos que abrirem o repositório recebem a mesma configuração:
{
"mcpServers": {
"mcp-modal": {
"command": "uvx",
"args": ["mcp-modal@latest"]
}
}
}
Por que @latest, e quando fixar versão
uvx armazena em cache o ambiente que ele cria na primeira execução e não verifica o PyPI novamente:
"uvx usará a versão mais recente disponível da ferramenta solicitada na primeira invocação. Depois disso, uvx usará a versão em cache da ferramenta, a menos que uma versão diferente seja solicitada, o cache seja limpo ou o cache seja atualizado." — documentação do uv
Então um uvx mcp-modal simples significa mais recente no momento da instalação, congelado para sempre depois — reiniciar
o cliente ou o sistema não muda nada, porque o cache fica no disco. Pessoas diferentes
acabam em versões diferentes dependendo de quando executaram pela primeira vez, sem aviso.
mcp-modal@latestre-resolve a cada inicialização, então uma reinicialização pega novos lançamentos. Custa uma ida à rede na inicialização. Use enquanto a superfície da ferramenta ainda está mudando.mcp-modal@0.4.0(uma versão explícita) é reproduzível e atualizações se tornam uma mudança deliberada de uma linha. Use quando quiser estabilidade, ou para um público maior.
Para mover uma máquina que já está presa em um build antigo em cache, mudar para qualquer uma das formas
acima é suficiente — solicitar uma versão invalida o cache. Caso contrário,
uv cache clean mcp-modal força uma atualização.
Requisitos
- Python 3.11 ou superior
uv(forneceuvx)- CLI Modal 1.5 ou mais recente, configurada com credenciais válidas (
modal setup) — 1.5 é ondemodal billing summary/rateschegaram e onde o relatório de cobrança mudou para colunas snake_case; a ferramenta de custo lê ambas as grafias, mas precisa da 1.5 para essas duas visualizações - Para suporte a deploy e run do Modal:
- O projeto sendo implantado/executado deve usar
uvpara gerenciamento de dependências modaldeve estar instalado no ambiente virtual desse projeto
- O projeto sendo implantado/executado deve usar
Segurança
Este servidor executa comandos na sua CLI modal local usando as credenciais que estão em
~/.modal.toml. Algumas ferramentas são poderosas por design — se o cliente MCP que dirige o servidor
for vítima de injeção de prompt (por exemplo, por texto malicioso dentro de logs que ele buscou), estes são
os caminhos de escalada e devem permanecer atrás dos prompts de aprovação de ferramentas do seu cliente em vez
de serem aprovados automaticamente:
deploy_modal_app/run_modal_app— executam Python local arbitrário no host (modal deployimporta o arquivo do app;uv runresolve e instala as dependências do projeto alvo).modal_volume_filescomaction="put"— pode ler qualquer arquivo local (ex.:~/.ssh/id_rsa,~/.modal.toml) e enviá-lo para um volume na nuvem (uma primitiva de exfiltração de dados).modal_volume_filescomaction="get"eforce=True— pode sobrescrever qualquer caminho local (ex.:~/.zshrcou um perfil de shell, uma primitiva de persistência).manage_modal_containercomaction="exec"— executa comandos arbitrários dentro de um contêiner, por design.
Cada ferramenta declara anotações de ferramentas MCP,
para que um cliente possa distinguir as quatro ferramentas somente leitura (list_modal_resources,
get_modal_logs, search_modal_logs, analyze_modal_costs — todas readOnlyHint: true)
das oito que alteram estado remoto ou iniciam computação. Seis dessas oito são
destructiveHint: true; as exceções são run_modal_app e inspect_modal_secret,
que iniciam computação sem remover ou sobrescrever nada. Aprovação automática para as leituras; mantenha
o resto atrás de um prompt.
Lista de permissões opcional de caminhos locais
Para conter as duas ferramentas de volume que tocam no sistema de arquivos, defina a
variável de ambiente MCP_MODAL_ALLOWED_LOCAL_PATHS para uma lista de
diretórios separada por os.pathsep
(: no macOS/Linux). Quando definida, modal_volume_files é recusada para qualquer
caminho local — local_path em action="put", o destino em action="get" — a menos que o
caminho resolvido, após expandir ~ e colapsar ../symlinks, caia dentro de uma dessas
raízes. O destino de download "-" (retornar conteúdo em vez de escrever um arquivo) é isento
porque nada é gravado no disco.
Quando a variável está não definida (o padrão) não há restrição, então configurações existentes não são afetadas. Configure-a no seu cliente MCP, ex.:
{
"mcpServers": {
"mcp-modal": {
"command": "uvx",
"args": ["mcp-modal"],
"env": { "MCP_MODAL_ALLOWED_LOCAL_PATHS": "/Users/me/modal-workspace:/tmp/modal" }
}
}
}
Todas as ferramentas também passam nomes/caminhos fornecidos pelo usuário após um separador de fim de opções --, então um
valor começando com - é sempre tratado como dado, nunca como uma flag da CLI modal. Valores de
segredos passados para manage_modal_secret são ocultados do comando ecoado, logs e qualquer
saída de erro.
Ferramentas Suportadas
12 ferramentas. Operações relacionadas são agrupadas atrás de um argumento action/resource em vez de
divididas uma-por-subcomando-CLI: cada esquema de ferramenta é carregado no contexto do modelo para a
sessão inteira, então uma superfície menor deixa mais espaço para o seu trabalho real (e dá ao
modelo menos ferramentas quase idênticas para escolher).
Ferramentas que falam com recursos com escopo de ambiente aceitam um argumento opcional env para mirar um
ambiente Modal específico; se omitido, elas
usam o padrão do perfil (ou MODAL_ENVIRONMENT). A exceção é manage_modal_container
e logs de contêiner — um ID de contêiner é globalmente único e a CLI não aceita ambiente
ali.
Somente leitura
-
Listar Recursos Modal (
list_modal_resources) — uma consulta para a conta inteira.- Parâmetros:
resource(obrigatório),name,path(padrão/),env - Valores de
resource:valor retorna namesignificaappsapps implantados/em execução/parados recentemente — app_historyversões de implantação de um app (para rollback) nome/ID do app containerscontêineres em execução ( ta-...)ID do app para filtrar volumesvolumes nomeados — volume_filesarquivos dentro de um volume (com path)nome do volume secretsnomes de segredos (valores nunca são expostos) — environmentsvalores válidos de envpara este workspace— profileperfil ativo + todos os perfis — volume_filesdefineempty: truecom uma mensagem quando uma listagem genuinamente retorna nada, para que um diretório vazio seja distinguível de um caminho errado.- Listagens acima de 200 entradas são limitadas, com
omitted_itemsdando o número descartado.
- Parâmetros:
-
Obter Logs Modal (
get_modal_logs) — busca ou transmite logs de um app ou um contêiner.- Parâmetros:
identifier(obrigatório),target(auto/app/container, padrãoauto— qualquer coisa começando comta-é um contêiner),timeout_seconds(padrão 30),env,since,until,tail,source(stdout/stderr/system),timestamps,follow sincesemtailbusca todas as entradas no intervalo; passeuntiltambém (intervalo máximo 35 dias,tailmáximo 20.000) para manter a saída de um app movimentado limitada.- Com
follow=True, os logs são transmitidos até o app/contêiner parar outimeout_secondsser alcançado, retornando um instantâneo comtruncated: true. - Cobre apenas os fluxos stdout/stderr/sistema; algumas falhas (ex.: uma falha relatada como "... saiu com ...") são eventos do painel Modal, não linhas de log, e não aparecerão aqui.
- Parâmetros:
-
Pesquisar Logs Modal (
search_modal_logs) — busca em logs e obtém cada ocorrência com as linhas ao redor, feito para depuração de "onde deu errado?". Os logs são buscados uma vez e pesquisados localmente, então você obtém contexto, regex, controle de maiúsculas/minúsculas e contagens exatas de correspondências.- Parâmetros:
identifier(obrigatório),pattern(obrigatório),target(padrãoauto),regex,case_sensitive,context_lines(padrão 3),max_matches(padrão 50),since,until,tail(padrão para as últimas 1000 entradas),source,exclude(remove linhas de ruído antes de pesquisar, ex.:"queue put failed"),prefilter,timestamps(padrãotrue),timeout_seconds,env - Limite a janela em um app movimentado.
sincesozinho busca tudo desde então até agora — centenas de KB por hora em um app tagarela, que a busca de 30s corta (logs_truncated: true) e o orçamento de saída reduz.sinceeuntilem torno do minuto que você quer é a correção, e geralmente são kilobytes. prefilter=Trueempurrapatternpara o Modal como um filtro de substring no lado do servidor (modal app logs --search), então linhas sem correspondência nunca são buscadas — a alavanca para logs grandes demais para drenar. Requerregex=False, e linhas de contexto mostram então apenas outras correspondências, então use para localizar a janela e re-consultar comprefilter=False.- Retorna
match_countematches: blocos de contexto com timestamp e número de linha onde linhas correspondentes são prefixadas com>, ex.:> 8: 2026-06-04T... ValueError: bad input. Todo o log buscado é sempre pesquisado, entãomatch_countpermanece exato mesmo quando menos blocos são retornados.returnedé quantas correspondências vieram (correspondências adjacentes se fundem em um bloco, contadas porreturned_blocks). Relataexcluded_linesquandoexcludeé usado. - Uma janela que a CLI rejeita (intervalo invertido, acima de 35 dias,
tailacima de 20.000) retorna comosuccess: falsecom a mensagem do próprio Modal, não um código de saída simples. - Mesma ressalva de stdout/stderr/sistema apenas que
get_modal_logs.
- Parâmetros:
Implantar e executar
-
Implantar App Modal (
deploy_modal_app)- Implanta um app Modal (
modal deploy). Endpoints web implantados persistem, então qualquer link na saída está ativo e compartilhável (retornado emurls). - Parâmetros:
absolute_path_to_app(obrigatório),env,name,tag,strategy(rolling/recreate),stream_logs - O diretório do app deve usar
uvcommodalinstalado em seu virtualenv.
- Implanta um app Modal (
-
Executar App Modal (
run_modal_app)- Executa uma função ou entrypoint local uma vez e coleta sua saída (
modal run). - Parâmetros:
absolute_path_to_app(obrigatório),function_name,env,detach,timeout_seconds(padrão 120) - Retorna um snapshot com
truncated: truese a execução ainda estiver em andamento no timeout. Passedetach=Truepara manter jobs longos ativos no Modal além do timeout.
- Executa uma função ou entrypoint local uma vez e coleta sua saída (
Por que não há ferramenta
modal serve?modal servesó mantém seus endpoints ativos enquanto o processo de bloqueio está em execução — uma ferramenta MCP que retorna os derrubaria imediatamente, devolvendo uma URL morta. Usedeploy_modal_apppara um endpoint persistente e compartilhável.
Mudanças de estado
-
Gerenciar App Modal (
manage_modal_app) —actionéstop(desligar o app e encerrar seus contêineres) ourollback(reimplantar uma versão anterior).- Parâmetros:
action(obrigatório),app_identifier(obrigatório),version(somente rollback — padrão é a versão imediatamente anterior),env
- Parâmetros:
-
Gerenciar Contêiner Modal (
manage_modal_container) —actionéexec(executar um comando dentro de um contêiner em execução,modal container exec --no-pty) oustop(encerrá-lo).- Parâmetros:
action(obrigatório),container_id(obrigatório),command(somente exec — uma lista de argumentos, ex.:["python", "-c", "print('hi')"]),timeout_seconds(padrão 60)
- Parâmetros:
-
Gerenciar Volume Modal (
manage_modal_volume) —actionécreate,delete(o volume e todos os seus dados, irreversível), ourename.- Parâmetros:
action(obrigatório),volume_name(obrigatório),new_name(somente renomear),env
- Parâmetros:
-
Arquivos de Volume Modal (
modal_volume_files) — operações de escrita nos arquivos de um volume:actionéput(upload),get(download),cp(copiar dentro do volume), ourm.- Parâmetros:
action(obrigatório),volume_name(obrigatório),local_path,remote_path,paths(paracp: origens e depois destino),recursive,force,env action="get"comlocal_path="-"retorna o conteúdo do arquivo em vez de gravar um arquivo.- Para listar o conteúdo de um volume, use
list_modal_resources(resource="volume_files").
- Parâmetros:
-
Gerenciar Segredo Modal (
manage_modal_secret) —actionécreateoudelete.- Parâmetros:
action(obrigatório),secret_name(obrigatório),key_values(dict),from_dotenv(caminho),from_json(caminho),force,env. A criação exige pelo menos um dekey_values,from_dotenvoufrom_json. - Os valores dos segredos são ocultados de todos os campos retornados, incluindo saída de erro.
- Para listar nomes de segredos, use
list_modal_resources(resource="secrets").
- Parâmetros:
Custos
- Analisar Custos Modal (
analyze_modal_costs) — somente leitura. Buscamodal billinguma vez e agrega localmente, para que você obtenha totais classificados e mudanças período a período em vez de centenas de linhas brutas.- Parâmetros:
view(padrãoby_app),period,start,end,resolution(d/h),timezone,app,environment,top_n(padrão 10),tag_names - Valores de
view:valor responde by_app"qual é o meu app mais caro?" — apps classificados por gasto, com participação % timeline"por que segunda-feira foi cara?" — custo por intervalo, além de um explanationque compara o intervalo de pico com o anterior e classifica quais apps cresceramby_environmentpara qual ambiente o dinheiro vai by_resourceCPU vs classe GPU vs memória vs armazenamento summarycusto faturado vs medido para um ciclo mensal, com ajustes de créditos/plano ratespreços unitários atuais total_costsempre cobre todas as linhas no intervalo, mesmo quandogroupsé reduzido paratop_n— cite-o em vez de somar as linhas visíveis.- O faturamento é por workspace (a CLI não aceita
-e), então isso reporta em todos os ambientes;environmentfiltra as linhas depois. - O Modal reporta apenas intervalos completos, então um dia parcialmente decorrido aparece menor.
- Parâmetros:
Segredos — inspeção
- Inspecionar Segredo Modal (
inspect_modal_secret) — lista os nomes das chaves dentro de um segredo, nunca os valores.- Parâmetros:
secret_name(obrigatório),env,image,timeout_seconds(padrão 300) - O Modal não expõe nenhuma API para isso por design: nem a CLI, nem o SDK, nem a camada gRPC.
A única maneira de ver quais chaves um segredo define é montá-lo em um contêiner
e listar o ambiente. Então esta ferramenta executa
modal shell --secret <name>comcompgen -e(um builtin do bash que imprime apenas os nomes das variáveis exportadas — nenhum valor é jamais impresso, mesmo dentro do contêiner) e depois subtrai as variáveis que a imagem e o runtime do Modal definem de qualquer forma — 23 nomes conhecidos mais qualquer coisa sob seis prefixos (MODAL_,PYTHON,PIP_,NVIDIA_,CUDA_,LD_LIBRARY_PATH), o que também cobre as credenciaisMODAL_TOKEN_*que existem em todo contêiner. - Esta única chamada inicia computação remota, então custa alguns centavos e leva dezenas de
segundos (mais quando a imagem precisa ser construída). Todas as outras leituras neste servidor são
gratuitas; use
list_modal_resources(resource="secrets")para ver quais segredos existem e recorra a esta apenas quando precisar saber o que está dentro de um. - Retorna
keys, além doall_env_namessem filtro, para que uma chave que pareça uma variável de runtime ainda fique visível em vez de ser descartada silenciosamente. - Omita
imagepara usar o padrão do Modal (criado para corresponder ao Python do servidor — a escolha mais confiável). Passe um, ex.:python:3.12-slim, se o construtor de imagens do seu workspace rejeitar essa versão do Python.
- Parâmetros:
Prompts
O servidor também inclui quatro prompts MCP — fluxos de trabalho em várias etapas que seu cliente pode invocar
diretamente (no Claude Code eles aparecem como /mcp__mcp-modal__<name>). Os prompts são buscados sob
demanda, então, ao contrário das ferramentas, não custam nada no contexto por sessão:
debug_modal_app(app_name,symptomopcional) — uma rotina de triagem ordenada: verificar se o app está no ar, buscar logs por tracebacks com contexto, estreitar a janela em vez de alargá-la quando uma busca de log retorna truncada, recorrer ao final do log, verificar se apps irmãos foram afetados na mesma janela, inspecionar contêineres e depois comparar com o histórico de implantação e considerar um rollback.deploy_and_verify(absolute_path_to_app,envopcional) — confirmar o workspace de destino, implantar, reportar as URLs ao vivo e depois verificar se o app está saudável em vez de presumir que está.review_modal_account(envopcional) — um inventário somente leitura que sinaliza apps ociosos, contêineres em execução inexplicáveis e volumes/segredos órfãos, nomeando a chamada exata que limparia cada um sem executá-la.investigate_modal_costs(periodopcional,app) — rastreia um aumento de gasto desde a linha do tempo diária até a hora de pico, a classe de recurso e a implantação ou contêiner ainda em execução por trás dele.
Limites de saída
A saída de logs, execuções e exec é limitada antes de ser retornada, para que um app muito verboso não inunde
sua janela de contexto. O orçamento padrão é de 40.000 caracteres por campo de texto (aproximadamente 10 mil
tokens); quando um campo é truncado, o resultado define output_capped: true e o texto carrega
um marcador informando quanto foi descartado. Um campo limitado mantém seu início e seu fim, então um
banner de inicialização e o traceback no final sobrevivem.
A busca nunca é limitada antes do fato: search_modal_logs percorre todo o log buscado
e só limita quantos blocos de contexto retornam, então match_count é sempre exato.
Aumentar timeout_seconds ou o orçamento raramente é a resposta certa para uma busca de log
truncada — buscar menos é. Limite a janela com since e until, filtre com
source/exclude ou defina prefilter=True para descartar linhas sem correspondência dentro do Modal.
Defina MCP_MODAL_MAX_OUTPUT_CHARS para aumentar ou diminuir o orçamento, ou para 0 para desativar a limitação
completamente:
{
"mcpServers": {
"mcp-modal": {
"command": "uvx",
"args": ["mcp-modal"],
"env": { "MCP_MODAL_MAX_OUTPUT_CHARS": "80000" }
}
}
}
Formato de resposta
Todas as ferramentas retornam respostas em um formato padronizado, com pequenas variações dependendo do tipo de operação:
# 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
}
Licença
Este projeto é licenciado sob a Licença MIT — consulte o arquivo LICENSE para detalhes.