Shells out to your local Modal CLI to manage serverless infrastructure without juggling extra tokens. You get 26 tools spanning deployments (modal deploy/run), app lifecycle (logs, rollbacks, stop), container inspection and exec, plus volume and secret CRUD. Requires uv and an authenticated Modal profile. The deploy and run tools expect your project to use uv with modal installed. Watch out for the security surface: put_modal_volume_file can exfiltrate arbitrary local files, get_modal_volume_file can overwrite shell profiles, and exec_modal_container runs commands inside live containers. Set MCP_MODAL_ALLOWED_LOCAL_PATHS to restrict filesystem access if you're nervous about prompt injection. Useful when you want Claude to ship code to Modal or debug a running app without leaving the editor.
An MCP server for managing Modal — apps, containers, volumes, and secrets — and for deploying & running Modal apps directly from Claude Code and other MCP clients.
Every tool shells out to your local modal CLI, so it operates against whatever Modal profile and credentials are configured on your machine. There are no extra tokens to manage.
The server is published on PyPI as mcp-modal. No manual install is needed — the recommended way to run it is with uvx, which fetches and launches it on demand. Just point your MCP client at the command below (see Configuration).
This server uses your local Modal credentials. If you haven't authenticated yet, run:
modal setup
This opens a browser to log in and stores a token in ~/.modal.toml. Already logged in elsewhere? Check with modal profile current.
Add the server to Claude Code with the claude mcp CLI:
claude mcp add mcp-modal -- uvx mcp-modal
Or add it to a .mcp.json file in your project root:
{
"mcpServers": {
"mcp-modal": {
"command": "uvx",
"args": ["mcp-modal"]
}
}
}
To pin a specific release, use uvx mcp-modal@0.2.0.
uv (provides uvx)modal setup)uv for dependency managementmodal must be installed in that project's virtual environmentThis server shells out to your local modal CLI using whatever credentials are in
~/.modal.toml. A few tools are powerful by design — if the MCP client driving the server
is ever prompt-injected (for example by malicious text inside logs it fetched), these are
the escalation paths and should stay behind your client's tool-approval prompts rather
than being auto-approved:
deploy_modal_app / run_modal_app — execute arbitrary local Python on the host
(modal deploy imports the app file; uv run resolves and installs the target project's
dependencies).put_modal_volume_file — can read any local file (e.g. ~/.ssh/id_rsa, ~/.modal.toml)
and upload it to a cloud volume (a data-exfiltration primitive).get_modal_volume_file with force=True — can overwrite any local path (e.g. ~/.zshrc
or a shell profile, a persistence primitive).exec_modal_container — runs arbitrary commands inside a container, by design.To contain the two filesystem-touching volume tools, set the
MCP_MODAL_ALLOWED_LOCAL_PATHS environment variable to an
os.pathsep-separated list of
directories (: on macOS/Linux). When it is set, put_modal_volume_file (its local_path)
and get_modal_volume_file (its local_destination) are refused unless the resolved path —
after expanding ~ and collapsing ../symlinks — falls inside one of those roots. The
download target "-" (stream to stdout) is exempt because nothing is written to disk.
When the variable is unset (the default) there is no restriction, so existing setups are unaffected. Configure it in your MCP client, e.g.:
{
"mcpServers": {
"mcp-modal": {
"command": "uvx",
"args": ["mcp-modal"],
"env": { "MCP_MODAL_ALLOWED_LOCAL_PATHS": "/Users/me/modal-workspace:/tmp/modal" }
}
}
}
All tools also pass user-supplied names/paths after a -- end-of-options separator, so a
value beginning with - is always treated as data, never as a modal CLI flag. Secret
values handed to create_modal_secret are redacted from the echoed command, logs, and any
error output.
26 tools, grouped by area. Account-scoped tools accept an optional env argument to
target a specific Modal environment; if
omitted, they use the profile's default (or MODAL_ENVIRONMENT).
Deploy Modal App (deploy_modal_app)
modal deploy). Deployed web endpoints persist, so any links
in the output are live and shareable (returned in urls).absolute_path_to_app (required), env, name, tag,
strategy (rolling/recreate), stream_logsuv with modal installed in its virtualenv.Run Modal App (run_modal_app)
modal run).absolute_path_to_app (required), function_name, env,
detach, timeout_seconds (default 120)truncated: true if the run is still going at the timeout.
Pass detach=True to keep long jobs alive on Modal past the timeout.Why no
modal servetool?modal serveonly keeps its endpoints alive while the blocking process runs — an MCP tool that returns would tear them down immediately, handing back a dead URL. Usedeploy_modal_appfor a persistent, shareable endpoint.
List Modal Apps (list_modal_apps)
envGet Modal App Logs (get_modal_app_logs)
modal app logs).app_identifier (required), timeout_seconds (default 30), env,
since, until, tail, search, source (stdout/stderr/system),
timestamps (prefix each line with its wall-clock time), followfollow=True, logs stream until the app stops or timeout_seconds is reached,
returning a snapshot with truncated: true.Stop Modal App (stop_modal_app)
modal app stop).app_identifier (required), envRoll Back Modal App (rollback_modal_app)
modal app rollback).app_identifier (required), version (optional — defaults to the
previous version), envGet Modal App History (get_modal_app_history)
modal app history). Use it to find a
version for rollback.app_identifier (required), envList Modal Containers (list_modal_containers)
modal container list).app_id (optional filter), envGet Modal Container Logs (get_modal_container_logs)
modal container logs).container_id (required), timeout_seconds (default 30),
since, until, tail, search, source, timestamps, followExec in Modal Container (exec_modal_container)
modal container exec --no-pty).container_id (required), command (list of args, e.g.
["python", "-c", "print('hi')"]), timeout_seconds (default 60)Stop Modal Container (stop_modal_container)
modal container stop).container_id (required)search_modal_logs)
search argument on the log tools) you get
context, regex, case control, and match counts, not just the bare matching line.identifier (required — app name/ID or container ID), pattern (required),
target (app/container, default app), regex, case_sensitive,
context_lines (default 3), max_matches (default 50), since, tail
(defaults to the last 1000 entries), source (stdout/stderr/system),
exclude (drop noise lines before searching, e.g. "queue put failed"),
timestamps (default true — carry each line's wall-clock time into the result),
timeout_seconds, envmatch_count and matches: timestamped, line-numbered context blocks where
matched lines are prefixed with >, e.g. > 8: 2026-06-04T... ValueError: bad input.
Reports excluded_lines when exclude is used.list_modal_volumes) — lists all volumes. Parameters: none.list_modal_volume_contents) — volume_name, path (default /). Sets empty: true with a message when the listing genuinely returns nothing, so an empty directory is distinguishable from an error or a wrong path.copy_modal_volume_files) — volume_name, paths (last is destination).remove_modal_volume_file) — volume_name, remote_path, recursive.put_modal_volume_file) — volume_name, local_path, remote_path, force.get_modal_volume_file) — volume_name, remote_path, local_destination, force. Use - as the destination to stream contents to stdout.create_modal_volume) — creates a named persistent volume. Parameters: volume_name, env.delete_modal_volume) — deletes a volume and all its data (irreversible). Parameters: volume_name, env.rename_modal_volume) — Parameters: old_name, new_name, env.List Secrets (list_modal_secrets)
envCreate Secret (create_modal_secret)
modal secret create).
Secret values are redacted from the returned command.secret_name (required), key_values (dict), from_dotenv (path),
from_json (path), force, env. Provide at least one of key_values,
from_dotenv, or from_json.Delete Secret (delete_modal_secret) — Parameters: secret_name, env.
Get Modal Profile (get_modal_profile)
List Modal Environments (list_modal_environments)
env
arguments for the other tools. Parameters: none.All tools return responses in a standardized format, with slight variations depending on the operation type:
# JSON / list operations (apps, containers, volumes, secrets, history, ...):
{
"success": True,
"apps": [...] # or "containers", "volumes", "secrets", "history", "environments"
}
# Action operations (deploy, stop, create, delete, rename, copy, 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
"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
}
This project is licensed under the MIT License - see the LICENSE file for details.