Mcp Modal — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Mcp Modal (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<!-- mcp-name: io.github.george-bobby/mcp-modal -->
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 setupThis 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-modalOr 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 [email protected].
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:
(modal deploy imports the app file; uv run resolves and installs the target project's dependencies).
~/.ssh/id_rsa, ~/.modal.toml)and upload it to a cloud volume (a data-exfiltration primitive).
force=True — can overwrite any local path (e.g. ~/.zshrcor a shell profile, a persistence primitive).
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)modal deploy). Deployed web endpoints persist, so any linksin the output are live and shareable (returned in urls).
absolute_path_to_app (required), env, name, tag,strategy (rolling/recreate), stream_logs
uv with modal installed in its virtualenv.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 serve` tool?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)app name/ID for the other app tools.
envget_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), follow
follow=True, logs stream until the app stops or timeout_seconds is reached,returning a snapshot with truncated: true.
"... exited with ...") are Modal dashboard events, not log lines, and won't appear here.
stop_modal_app)modal app stop).app_identifier (required), envrollback_modal_app)modal app rollback).app_identifier (required), version (optional — defaults to theprevious version), env
get_modal_app_history)modal app history). Use it to find aversion for rollback.
app_identifier (required), envlist_modal_containers)modal container list).app_id (optional filter), envget_modal_container_logs)modal container logs).container_id (required), timeout_seconds (default 30),since, until, tail, search, source, timestamps, follow
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)modal container stop).container_id (required)search_modal_logs)surrounding lines** — built for "where did it go wrong?" debugging. Logs are fetched once and searched locally, so (unlike the 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, env
match_count and matches: timestamped, line-numbered context blocks wherematched lines are prefixed with >, e.g. > 8: 2026-06-04T... ValueError: bad input. Reports excluded_lines when exclude is used.
events (e.g. "... exited with ...") return 0 matches even when the failure is real.
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_modal_secrets)envcreate_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_modal_secret) — Parameters: secret_name, env.get_modal_profile)workspace/account the server is authenticated as. Parameters: none.
list_modal_environments)envarguments 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.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.