Modal MCP
The Modal MCP server lets you manage Modal cloud resources and deploy/run Modal apps directly from MCP clients using your local Modal CLI configuration.
Deploy & Run
Deploy apps: Deploy Modal apps persistently; returns live web endpoint URLs
Run apps: Execute a Modal function once and stream output; supports detach mode for long-running jobs
App Management
List deployed/running apps, fetch or stream logs, stop apps, roll back to previous versions, and view deployment history
Container Management
List running containers, fetch/stream container logs, execute commands inside containers, and stop containers
Log Search
Search app or container logs with grep, supporting regex, case control, context lines, and time filtering
Volume File Operations
List volumes, browse contents, copy/move/remove files, upload local files to a volume, and download files from a volume
Volume Lifecycle
Create, delete (irreversibly), and rename named persistent volumes
Secrets Management
List secret names (values never exposed), create secrets from inline key-values,
.envfiles, or JSON files, and delete secrets
Account & Discovery
Show the active Modal profile and all configured profiles; list available environments (e.g. "dev", "production") in the workspace
Provides tools for managing Modal apps, containers, volumes, and secrets, enabling deployment and execution of Modal apps directly from MCP clients.
MCP Modal Server
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.
Installation
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).
Every version is also tagged and published on the
Releases page, with release notes and
the same .whl / .tar.gz that PyPI serves attached — useful for pinning, air-gapped
installs, or reading what changed between two versions.
Related MCP server: MCP Server Modal
Logging in to Modal
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.
Configuration
Add the server to Claude Code with the claude mcp CLI:
claude mcp add mcp-modal -- uvx mcp-modal@latestOr add it to a .mcp.json file in your project root, which is the better option for a team
— everyone who opens the repo gets the same configuration:
{
"mcpServers": {
"mcp-modal": {
"command": "uvx",
"args": ["mcp-modal@latest"]
}
}
}Why @latest, and when to pin instead
uvx caches the environment it builds on the first run and does not check PyPI again:
"uvx will use the latest available version of the requested tool on the first invocation. After that, uvx will use the cached version of the tool unless a different version is requested, the cache is pruned, or the cache is refreshed." — uv docs
So a plain uvx mcp-modal means latest at install time, frozen forever after — restarting
the client or rebooting changes nothing, because the cache lives on disk. Different people
end up on different versions depending on when they first ran it, with no warning.
mcp-modal@latestre-resolves on every launch, so a restart picks up new releases. Costs one network round-trip at startup. Use it while the tool surface is still moving.mcp-modal@0.4.0(an explicit version) is reproducible and upgrades become a deliberate one-line change. Use it once you want stability, or for a wider audience.
To move a machine that is already stuck on an old cached build, switching it to either form
above is enough — requesting a version invalidates the cache. Otherwise
uv cache clean mcp-modal forces a refresh.
Requirements
Python 3.11 or higher
uv(providesuvx)Modal CLI 1.5 or newer, configured with valid credentials (
modal setup) — 1.5 is wheremodal billing summary/rateslanded and where the billing report switched to snake_case columns; the cost tool reads both spellings but needs 1.5 for those two viewsFor Modal deploy and run support:
The project being deployed/run must use
uvfor dependency managementmodalmust be installed in that project's virtual environment
Security
This 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 deployimports the app file;uv runresolves and installs the target project's dependencies).modal_volume_fileswithaction="put"— can read any local file (e.g.~/.ssh/id_rsa,~/.modal.toml) and upload it to a cloud volume (a data-exfiltration primitive).modal_volume_fileswithaction="get"andforce=True— can overwrite any local path (e.g.~/.zshrcor a shell profile, a persistence primitive).manage_modal_containerwithaction="exec"— runs arbitrary commands inside a container, by design.
Every tool declares MCP tool annotations,
so a client can distinguish the four read-only tools (list_modal_resources,
get_modal_logs, search_modal_logs, analyze_modal_costs — all readOnlyHint: true)
from the eight that change remote state or start compute. Six of those eight are
destructiveHint: true; the exceptions are run_modal_app and inspect_modal_secret,
which start compute without removing or overwriting anything. Auto-approve the reads; keep
the rest behind a prompt.
Optional local-path allowlist
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, modal_volume_files is refused for any
local path — local_path on action="put", the destination on action="get" — unless the
resolved path, after expanding ~ and collapsing ../symlinks, falls inside one of those
roots. The download target "-" (return contents instead of writing a file) 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 manage_modal_secret are redacted from the echoed command, logs, and any
error output.
Supported Tools
12 tools. Related operations are grouped behind an action/resource argument rather than
split one-per-CLI-subcommand: every tool schema is loaded into the model's context for the
whole session, so a smaller surface leaves more room for your actual work (and gives the
model fewer near-identical tools to choose between).
Tools that talk to environment-scoped resources take an optional env argument to target a
specific Modal environment; if omitted, they
use the profile's default (or MODAL_ENVIRONMENT). The exception is manage_modal_container
and container logs — a container ID is globally unique and the CLI accepts no environment
there.
Read-only
List Modal Resources (
list_modal_resources) — one lookup for the whole account.Parameters:
resource(required),name,path(default/),envresourcevalues:value
returns
namemeansappsdeployed/running/recently-stopped apps
—
app_historyone app's deployment versions (for rollback)
app name/ID
containersrunning containers (
ta-...)app ID to filter by
volumesnamed volumes
—
volume_filesfiles inside a volume (with
path)volume name
secretssecret names (values are never exposed)
—
environmentsvalid
envvalues for this workspace—
profileactive profile + all profiles
—
volume_filessetsempty: truewith a message when a listing genuinely returns nothing, so an empty directory is distinguishable from a wrong path.Listings over 200 entries are capped, with
omitted_itemsgiving the number dropped.
Get Modal Logs (
get_modal_logs) — fetch or stream logs for an app or a container.Parameters:
identifier(required),target(auto/app/container, defaultauto— anything startingta-is a container),timeout_seconds(default 30),env,since,until,tail,source(stdout/stderr/system),timestamps,followsincewithouttailfetches every entry in the range; passuntilas well (max range 35 days,tailmax 20,000) to keep a busy app's output bounded.With
follow=True, logs stream until the app/container stops ortimeout_secondsis reached, returning a snapshot withtruncated: true.Only covers the stdout/stderr/system streams; some failures (e.g. a crash reported as "... exited with ...") are Modal dashboard events, not log lines, and won't appear here.
Search Modal Logs (
search_modal_logs) — grep logs and get each hit with the surrounding lines, built for "where did it go wrong?" debugging. Logs are fetched once and searched locally, so you get context, regex, case control, and exact match counts.Parameters:
identifier(required),pattern(required),target(defaultauto),regex,case_sensitive,context_lines(default 3),max_matches(default 50),since,until,tail(defaults to the last 1000 entries),source,exclude(drop noise lines before searching, e.g."queue put failed"),prefilter,timestamps(defaulttrue),timeout_seconds,envBound the window on a busy app.
sinceon its own fetches everything from then until now — hundreds of KB per hour on a chatty app, which the 30s fetch cuts off (logs_truncated: true) and the output budget trims.sinceanduntilaround the minute you care about is the fix, and is usually kilobytes.prefilter=Truepushespatterndown to Modal as a server-side substring filter (modal app logs --search), so non-matching lines are never fetched — the lever for logs too large to drain. Requiresregex=False, and context lines then show only other matches, so use it to locate the window and re-query it withprefilter=False.Returns
match_countandmatches: timestamped, line-numbered context blocks where matched lines are prefixed with>, e.g.> 8: 2026-06-04T... ValueError: bad input. The whole fetched log is always searched, somatch_countstays exact even when fewer blocks are returned.returnedis how many matches came back (adjacent matches merge into one block, counted byreturned_blocks). Reportsexcluded_lineswhenexcludeis used.A window the CLI rejects (reversed range, over 35 days,
tailover 20,000) comes back assuccess: falsewith Modal's own message, not a bare exit code.Same stdout/stderr/system-only caveat as
get_modal_logs.
Deploy & run
Deploy Modal App (
deploy_modal_app)Deploys a Modal app (
modal deploy). Deployed web endpoints persist, so any links in the output are live and shareable (returned inurls).Parameters:
absolute_path_to_app(required),env,name,tag,strategy(rolling/recreate),stream_logsThe app's directory must use
uvwithmodalinstalled in its virtualenv.
Run Modal App (
run_modal_app)Runs a function or local entrypoint once and collects its output (
modal run).Parameters:
absolute_path_to_app(required),function_name,env,detach,timeout_seconds(default 120)Returns a snapshot with
truncated: trueif the run is still going at the timeout. Passdetach=Trueto 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.
State changes
Manage Modal App (
manage_modal_app) —actionisstop(shut the app down and terminate its containers) orrollback(redeploy a previous version).Parameters:
action(required),app_identifier(required),version(rollback only — defaults to the immediately preceding version),env
Manage Modal Container (
manage_modal_container) —actionisexec(run a command inside a running container,modal container exec --no-pty) orstop(terminate it).Parameters:
action(required),container_id(required),command(exec only — a list of args, e.g.["python", "-c", "print('hi')"]),timeout_seconds(default 60)
Manage Modal Volume (
manage_modal_volume) —actioniscreate,delete(the volume and all its data, irreversible), orrename.Parameters:
action(required),volume_name(required),new_name(rename only),env
Modal Volume Files (
modal_volume_files) — write operations on a volume's files:actionisput(upload),get(download),cp(copy inside the volume), orrm.Parameters:
action(required),volume_name(required),local_path,remote_path,paths(forcp: sources then destination),recursive,force,envaction="get"withlocal_path="-"returns the file contents instead of writing a file.To list a volume's contents use
list_modal_resources(resource="volume_files").
Manage Modal Secret (
manage_modal_secret) —actioniscreateordelete.Parameters:
action(required),secret_name(required),key_values(dict),from_dotenv(path),from_json(path),force,env. Creating requires at least one ofkey_values,from_dotenv, orfrom_json.Secret values are redacted from every field returned, including error output.
To list secret names use
list_modal_resources(resource="secrets").
Costs
Analyze Modal Costs (
analyze_modal_costs) — read-only. Fetchesmodal billingonce and aggregates locally, so you get ranked totals and period-over-period changes instead of hundreds of raw rows.Parameters:
view(defaultby_app),period,start,end,resolution(d/h),timezone,app,environment,top_n(default 10),tag_namesviewvalues:value
answers
by_app"what is my costliest app?" — apps ranked by spend, with % share
timeline"why was Monday expensive?" — cost per interval, plus an
explanationthat diffs the peak interval against the one before and ranks which apps grewby_environmentwhich environment the money goes to
by_resourceCPU vs GPU class vs memory vs storage
summarybilled vs metered cost for a month cycle, with credits/plan adjustments
ratescurrent unit prices
total_costalways covers every row in range, even whengroupsis cut totop_n— quote it rather than summing the visible rows.Billing is workspace-wide (the CLI takes no
-e), so this reports across all environments;environmentfilters the rows afterwards.Modal reports whole intervals only, so a partially elapsed day reads low.
Secrets — inspection
Inspect Modal Secret (
inspect_modal_secret) — lists the key names inside a secret, never the values.Parameters:
secret_name(required),env,image,timeout_seconds(default 300)Modal exposes no API for this by design: not the CLI, not the SDK, not the gRPC layer. The only way to see which keys a secret defines is to mount it in a container and list the environment. So this tool runs
modal shell --secret <name>withcompgen -e(a bash builtin that prints exported variable names only — no value is ever printed, even inside the container), then subtracts the variables the image and Modal runtime set anyway — 23 known names plus anything under six prefixes (MODAL_,PYTHON,PIP_,NVIDIA_,CUDA_,LD_LIBRARY_PATH), which also covers theMODAL_TOKEN_*credentials that live in every container.This one call starts remote compute, so it costs a few cents and takes tens of seconds (longer when the image has to build). Every other read in this server is free; use
list_modal_resources(resource="secrets")to see which secrets exist and reach for this only when you need to know what is inside one.Returns
keys, plus the unfilteredall_env_namesso a key that looks like a runtime variable is still visible rather than silently dropped.Omit
imageto use Modal's default (built to match the server's Python — the most reliable choice). Pass one, e.g.python:3.12-slim, if your workspace's image builder rejects that Python version.
Prompts
The server also ships four MCP prompts — multi-step workflows your client can invoke
directly (in Claude Code they appear as /mcp__mcp-modal__<name>). Prompts are fetched on
demand, so unlike tools they cost nothing in per-session context:
debug_modal_app(app_name, optionalsymptom) — an ordered triage routine: check the app is up, search logs for tracebacks with context, narrow the window instead of widening it when a log fetch comes back truncated, fall back to the log tail, check whether sibling apps were hit in the same window, inspect containers, then compare against deployment history and consider a rollback.deploy_and_verify(absolute_path_to_app, optionalenv) — confirm the target workspace, deploy, report the live URLs, then verify the app is healthy instead of assuming it.review_modal_account(optionalenv) — a read-only inventory that flags idle apps, unexplained running containers, and orphaned volumes/secrets, naming the exact call that would clean each one up without running it.investigate_modal_costs(optionalperiod,app) — traces a spend increase from the daily timeline down to the peak hour, the resource class, and the deploy or still-running container behind it.
Output caps
Log, run, and exec output is capped before it is returned, so one chatty app can't flood
your context window. The default budget is 40,000 characters per text field (roughly 10k
tokens); when a field is trimmed the result sets output_capped: true and the text carries
a marker naming how much was dropped. A capped field keeps its head and its tail, so a
startup banner and the traceback at the end both survive.
Searching is never capped before the fact: search_modal_logs greps the whole fetched log
and only limits how many context blocks come back, so match_count is always exact.
Raising timeout_seconds or the budget is rarely the right answer to a truncated log
search — fetching less is. Bound the window with since and until, filter with
source/exclude, or set prefilter=True to drop non-matching lines inside Modal.
Set MCP_MODAL_MAX_OUTPUT_CHARS to raise or lower the budget, or to 0 to disable capping
entirely:
{
"mcpServers": {
"mcp-modal": {
"command": "uvx",
"args": ["mcp-modal"],
"env": { "MCP_MODAL_MAX_OUTPUT_CHARS": "80000" }
}
}
}Response Format
All tools return responses in a standardized format, with slight variations depending on the operation type:
# 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
}License
This project is licensed under the MIT License - see the LICENSE file for details.
Available Tools
12 toolsanalyze_modal_costsARead-onlyIdempotent
Break down what the workspace is spending (`modal billing`). Costs are fetched once
and aggregated locally, so you get ranked totals and period-over-period changes
rather than hundreds of raw rows.
Answering common questions:
"what is my costliest app?" -> view="by_app", period="this month"
"why was Monday expensive?" -> view="timeline", period="last week" (the
`explanation` field diffs the peak day against
the day before and ranks which apps grew)
"what did that day cost hourly?" -> view="timeline", start="2026-08-31",
end="2026-09-01", resolution="h"
"where does the money go?" -> view="by_resource" (CPU / GPU / memory / ...)
"what is the bill this cycle?" -> view="summary"
Billing is workspace-wide, so this reports across every environment; use
`environment` to narrow it after the fact.
Args:
view: "by_app" (default), "timeline" (per interval, with an explanation of the
peak), "by_environment", "by_resource", "summary" (billed vs metered for a
month cycle), or "rates" (current unit prices).
period: Convenience range — "today", "yesterday", "this week", "last week",
"this month", "last month". For "summary" also accepts "YYYY-MM".
start / end: Explicit range instead of `period` — ISO dates ("2026-08-31") or
relative ("3 days ago"). Start is inclusive, end exclusive; end defaults to now.
resolution: "d" (daily, default) or "h" (hourly). Hourly is what you want when
drilling into a single day.
timezone: Timezone for interpreting dates — "local", an offset ("+05:30"), or an
IANA name. Requires resolution="h".
app: Only include apps whose name or ID contains this string (case-insensitive).
environment: Only include rows from this Modal environment.
top_n: How many groups/movers to return. Default 10.
tag_names: Comma-separated cost-attribution tag names to include.
Returns: {total_cost, groups | intervals, explanation (for timeline), row_count}.
Costs are strings of US dollars with 4 decimals. `total_cost` always covers every
row in range, even when `groups` is cut to top_n.
| Name | Required | Description | Default |
|---|---|---|---|
| app | No | ||
| end | No | ||
| view | No | by_app | |
| start | No | ||
| top_n | No | ||
| period | No | ||
| timezone | No | ||
| tag_names | No | ||
| resolution | No | d | |
| environment | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, and the description adds meaningful behavior beyond that: 'Costs are fetched once and aggregated locally', 'total_cost always covers every row in range, even when groups is cut to top_n', and costs are strings with 4 decimals. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Long but tightly organized: purpose, example-driven usage, workspace-wide caveat, Args list, and Returns. For a tool with 10 parameters, the length is justified and every sentence adds operational value; no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers all 10 optional parameters, return shape, cost formatting, date-range edge cases, and view-specific explanation behavior. The description is complete enough for an agent to invoke this tool correctly without external documentation, even with an output schema present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, but the description documents every parameter: view values, period named ranges, start/end inclusivity, resolution alternatives, timezone requirements, app filtering, environment scoping, top_n default, and tag_names format. This fully compensates for the schema's lack of property descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Break down what the workspace is spending ... modal billing') and enumerates distinct views. It is unambiguous and clearly differentiated from sibling tools like deploy, run, logs, and resource management, none of which cover cost analytics.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit common-question mappings ('what is my costliest app?' -> view="by_app", period="this month"') and explains scope ('Billing is workspace-wide... use environment to narrow it after the fact'). This gives an agent concrete decision rules for selecting the right view and parameters.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
deploy_modal_appADestructive
Deploy a Modal app (`modal deploy`). Deployed endpoints persist after this returns,
so any URLs in the result are live, shareable links.
Args:
absolute_path_to_app: Absolute path to the app file. Its directory must use `uv`
and have `modal` installed in its virtualenv.
env: Modal environment to deploy into.
name: Deployment name (`--name`).
tag: Version tag (`--tag`).
strategy: Rollout strategy — "rolling" or "recreate".
stream_logs: Stream the app's logs after deploying.
Returns: {message, urls (live endpoints), stdout, stderr}.
| Name | Required | Description | Default |
|---|---|---|---|
| env | No | ||
| tag | No | ||
| name | No | ||
| strategy | No | ||
| stream_logs | No | ||
| absolute_path_to_app | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and idempotentHint=false, so the agent knows deployment is a mutating operation. The description adds useful context by stating that deployed endpoints persist and that returned URLs are live shareable links. It does not disclose what can be overwritten or replaced during deployment, which would have strengthened transparency given the destructive hint.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and well-structured: a one-sentence purpose with the most important behavioral fact, followed by a succinct argument list and a return summary. Every sentence earns its place and there is no filler or repetition of schema defaults.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with six parameters, an output schema, and annotations, the description covers the key call-time prerequisites, parameter semantics, and return fields. It could mention failure/conflict behavior or rollback semantics, but the output schema and annotations already cover safety and return structure, so the description is reasonably complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the explanatory burden for parameters. It documents all six arguments, including the absolute path precondition (uv + modal in virtualenv), the mapping of name/tag to `--name`/`--tag`, the valid strategy values, and stream_logs behavior. It could add more detail on env naming or tag format, but it already adds substantial meaning beyond the raw schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource: 'Deploy a Modal app', and reinforces it with the underlying command `modal deploy`. It also distinguishes deployment from ephemeral execution by noting that endpoints persist after the call returns. However, it does not explicitly contrast itself with siblings like run_modal_app or manage_modal_app.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies persistent deployment through the phrase 'endpoints persist after this returns', but it gives no explicit guidance on when to choose deploy over run_modal_app or manage_modal_app. There are no stated exclusions, prerequisites beyond the app's directory, or alternative tools to consider.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_modal_logsARead-onlyIdempotent
Fetch logs for an app or container (`modal app logs` / `modal container logs`).
To find where something went wrong, prefer search_modal_logs — it returns matches
with surrounding context instead of a raw tail.
Covers the stdout/stderr/system streams ONLY. Crash events shown on the Modal
dashboard (e.g. "... exited with ...") are not log lines and never appear here.
Args:
identifier: App name/ID ("my-app", "ap-...") or container ID ("ta-...").
target: "auto" (default — "ta-..." is a container), "app", or "container".
timeout_seconds: Max seconds to collect. Default 30.
env: Modal environment. Apps only — container logs take no environment.
since / until: Time range, ISO 8601 or relative ("2h", "30m", "1d"). Max 35 days.
`since` without `tail` fetches EVERY entry in the range — pass `until` too
(or a `tail`) to bound the volume on a busy app.
tail: Only the last N entries (max 20000).
source: "stdout", "stderr", or "system".
timestamps: Prefix each line with its wall-clock timestamp.
follow: Live-stream until the app/container stops or the timeout hits.
Returns: {logs, truncated (still streaming at the timeout), output_capped (text
trimmed to fit context — narrow with tail/since/source)}.
| Name | Required | Description | Default |
|---|---|---|---|
| env | No | ||
| tail | No | ||
| since | No | ||
| until | No | ||
| follow | No | ||
| source | No | ||
| target | No | auto | |
| identifier | Yes | ||
| timestamps | No | ||
| timeout_seconds | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark readOnly, idempotent, and non-destructive, and the description adds genuinely new behavior knowledge beyond those: only stdout/stderr/system streams are covered, dashboard crash events never appear, `since` without `tail` fetches every entry in range, and returns a `truncated`/`output_capped` flag. This is the kind of context agents need to avoid misreading results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long, but for a 10-parameter tool with subtle behaviors, every sentence adds necessary information. It is front-loaded with purpose and direction, uses a clean Args list, and closes with the Returns shape. No filler or repeated schema details.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has 10 parameters and an output schema, this description provides crucial contextual constraints: volume pitfalls, stream coverage, crash-event exclusion, defaults, maximum bounds (35 days, tail 20000), and return flag meanings (truncated, output_capped). It is complete enough to invoke correctly without clicking outside docs.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the entire burden for parameter semantics. It fully compensates by explaining identifier formats ('my-app', 'ap-...', 'ta-...'), the `target` auto resolution logic, `timeout_seconds` default, environment scope, ISO/relative and max 35 days for `since`/`until`, `tail` max, allowed sources, `follow` behavior, and `timestamps`. Every parameter in the schema is addressed meaningfully.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource ('Fetch logs for an app or container') and maps it to the underlying CLI commands. It explicitly differentiates itself from the sibling search_modal_logs by contrasting raw logs with matches plus surrounding context, so an agent can tell them apart immediately.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states when not to use this tool: 'To find where something went wrong, prefer search_modal_logs... instead of a raw tail.' It also excludes crash events, which is a clear boundary for agent decision-making. This is explicit when/when-not guidance plus an alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
inspect_modal_secretA
List the KEY NAMES inside a Modal secret — never the values.
Modal exposes no API for this: neither the CLI, the SDK, nor the gRPC layer can read
a secret's contents, by design. The only way to see which keys a secret defines is to
mount it in a container and look at the environment variable names. So this tool
starts a short-lived container (`modal shell --secret ...`), prints the variable NAMES
only, and subtracts the ones the image and the Modal runtime would have set anyway.
That means, unlike every other read in this server, a call here **starts remote
compute and costs a few cents** (and takes tens of seconds — longer on the first run
for a given image, which has to be built). It is not a free lookup: use
list_modal_resources(resource="secrets") to see which secrets exist, and reach for
this only when you need to know what is inside one.
Values never leave the container: the probe is `compgen -e`, a bash builtin that
prints exported variable NAMES only, so no value is ever printed or read.
Args:
secret_name: Name of the secret, from list_modal_resources(resource="secrets").
env: Modal environment the secret lives in.
image: Optional container image. Omit it to use Modal's default image, which is
built to match this server's Python — that is the most reliable choice. Pass one
(e.g. "python:3.12-slim") if the workspace's image builder rejects that Python.
timeout_seconds: Max seconds to wait, including image build. Default 300.
Returns: {keys: [...names...], all_env_names: [...], filtered_out: n}. `all_env_names`
is the unfiltered list, so a key that looks like a runtime variable (e.g. one
literally named "PATH") is still visible rather than silently dropped.
| Name | Required | Description | Default |
|---|---|---|---|
| env | No | ||
| image | No | ||
| secret_name | Yes | ||
| timeout_seconds | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes well beyond the annotations by disclosing that a call starts remote compute, costs money, takes tens of seconds, and may trigger an image build on first run. It also explains the probe mechanism and assures that values never leave the container. These are critical behavioral traits not captured by readOnlyHint=false or destructiveHint=false. There is no contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Although the description is long, it is front-loaded with the most critical information (keys only, not values) and the cost warning. The explanation of why no API exists, the specifically scoped probe detail, and the per-parameter Args all earn their place. The structure is clean and skimmable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers return value semantics even beyond the output schema, explaining filtered vs unfiltered results. It also addresses cost, latency, prerequisites, alternatives, and parameter provenance. For a tool with remote side effects and unusual constraints, nothing important is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With schema description coverage at 0%, the description fully compensates via an Args section. Each parameter is explained: secret_name's source, env's purpose, image's optionality and recommended fallback, and timeout_seconds' default and meaning. This addresses the gaps left by the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific action and scope: 'List the KEY NAMES inside a Modal secret — never the values.' It clearly names the resource and differentiates this tool from sibling tools like list_modal_resources and manage_modal_secret by emphasizing it inspects secret contents, not resource existence or management.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to use the tool versus alternatives: 'use list_modal_resources(resource="secrets") to see which secrets exist, and reach for this only when you need to know what is inside one.' It also warns that this is 'not a free lookup' and should be used sparingly, giving clear decision guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_modal_resourcesARead-onlyIdempotent
Read-only lookup of everything in the Modal account. Start here to find the app name,
container ID or volume name that the other tools take.
Args:
resource: One of:
"apps" — deployed/running/recently-stopped apps.
"app_history" — one app's deployment versions (`name` = app name/ID); use it
to pick a version for manage_modal_app(action="rollback").
"containers" — running containers ("ta-..."); `name` = app ID to filter.
"volumes" — named volumes.
"volume_files" — files in a volume (`name` = volume, plus `path`).
"secrets" — secret names only; values are never returned.
"environments" — valid values for every `env` argument.
"profile" — active profile + all profiles (which account am I?).
name: App name/ID, app ID filter, or volume name — see `resource`.
path: Path inside the volume for "volume_files". Default "/".
env: Modal environment. Ignored for "environments"/"profile".
Returns: {<resource key>: [...]} — e.g. "apps", "containers", "contents". Listings
over 200 entries are capped, with `omitted_items` giving the count dropped.
| Name | Required | Description | Default |
|---|---|---|---|
| env | No | ||
| name | No | ||
| path | No | / | |
| resource | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and non-destructive behavior, and the description adds substantial behavioral context beyond that: 200-entry cap with omitted_items, the return shape, that secret values are never returned, and that env is ignored for 'environments'/'profile'. This fully discloses what happens when the tool is invoked.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is detailed yet tightly structured with a front-loaded purpose line and a bulleted resource list. Every sentence adds operational value; no filler or redundant restatement of the name/schema exists.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a broad read-only lookup tool, the description covers purpose, all parameter behaviors, return format, pagination truncation, and caveats like secret value redaction. An agent has enough context to select the right resource and correctly interpret results without opening the output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, but the description compensates completely by documenting every parameter: the full set of resource enum values, the polymorphic meaning of name, the path default, and when env applies. It adds meaning far beyond the raw schema properties.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource ('Read-only lookup of everything in the Modal account') and enumerates eight clearly differentiated resource types. It distinguishes this discovery tool from the sibling action tools by positioning it as the starting point to find identifiers those tools consume.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says 'Start here to find the app name, container ID or volume name that the other tools take,' and each resource option includes its intended use, such as using 'app_history' to pick a version for manage_modal_app(action='rollback'). It also gives exclusions, e.g. secrets return names only and env is ignored for certain resources.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage_modal_appADestructive
Change a deployed app's state. Both actions affect live traffic.
Args:
action: "stop" — shut the app down, ending web endpoints (`modal app stop`).
"rollback" — restore a previous deployment (`modal app rollback`).
app_identifier: App name ("my-app") or ID ("ap-...").
version: Rollback target; omit for the immediately preceding version. List valid
versions with list_modal_resources(resource="app_history", name=...).
env: Modal environment to target.
Returns: {message, stdout, stderr} or {error}.
| Name | Required | Description | Default |
|---|---|---|---|
| env | No | ||
| action | Yes | ||
| version | No | ||
| app_identifier | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark destructiveHint=true, readOnlyHint=false, idempotentHint=false. The description adds concrete behavioral detail: stop 'shut the app down, ending web endpoints' and rollback 'restore a previous deployment,' plus the warning that both actions affect live traffic. This goes beyond the structured hints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact: an opening one-line summary, a tidy Args list, and a Returns line. There is no redundant prose, and the live-traffic warning is front-loaded for safety.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the tool's purpose, both actions, all four parameters, return shape ({message, stdout, stderr} or {error}), and a cross-reference to list valid rollback targets. It doesn't discuss failure cases or reversibility, but output schema and destructive annotations cover part of that, making this adequate.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must carry all parameter meaning. It explains action with both permitted values, app_identifier with name/ID formats, version with omission semantics and a pointer to list_modal_resources, and env as the Modal environment to target. The env entry is brief, but every parameter receives usable semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Change a deployed app's state,' naming a specific verb and resource, then enumerates two concrete actions (stop and rollback) with their effects. This clearly distinguishes it from siblings like deploy_modal_app or run_modal_app.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states that both actions affect live traffic, framing when this tool is relevant, and explicitly directs users to list_modal_resources(resource='app_history', name=...) to find valid versions. It does not list exclusions vs. deploy/run, but the action-specific guidance is strong context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage_modal_containerADestructive
Act on one running container. Find IDs with
list_modal_resources(resource="containers").
Args:
action: "exec" — run a command inside the container (`modal container exec`).
This is arbitrary remote code execution: treat it like SSH, not a lookup.
"stop" — terminate it (`modal container stop`); in-flight inputs are
cancelled and rescheduled elsewhere.
container_id: Container ID ("ta-..."). Unique across environments, so no `env`
argument is needed (the CLI accepts none for these subcommands).
command: For "exec": argv list, e.g. ["python", "-c", "print('hi')"] or
["ls", "-la", "/"].
timeout_seconds: For "exec": max seconds to wait. Default 60.
Returns: exec → {output, returncode, truncated, output_capped}; stop → {message}.
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | ||
| command | No | ||
| container_id | Yes | ||
| timeout_seconds | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the destructiveHint annotation, it discloses that stop cancels and reschedules in-flight inputs, that exec is equivalent to SSH, and that container IDs are environment-unique so no env arg is needed. This meaningfully surfaces risk and runtime behavior the agent could not infer from annotations alone.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with what the tool does, then organized as Args/Returns. Every sentence adds value, including examples, defaults, and the SSH warning, with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with two modes, four parameters, and meaningful destructive/execution risk, the description covers inputs, behavior, risks, ID discovery, and return shapes. The output schema is also available, but the description already explains exec and stop return structures.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 0% schema description coverage, the description carries full parameter documentation: action values, container_id format ('ta-...'), command shape for exec, and timeout_seconds semantics with default. It fully compensates for the schema's bare property definitions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Act on one running container' and immediately enumerates the two concrete actions, 'exec' and 'stop', with exact CLI commands. This clearly distinguishes the tool from sibling app/volume/log tools by resource type and operation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It tells the agent to discover container IDs via list_modal_resources(resource="containers"), and warns that exec is 'arbitrary remote code execution... not a lookup.' This is clear, practical guidance, though it doesn't explicitly contrast every sibling tool such as manage_modal_app or get_modal_logs.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage_modal_secretADestructive
Create or delete a secret. To list secret names use
list_modal_resources(resource="secrets") — values are never readable.
Values are redacted from every field returned (command, stdout, stderr, error), so
they cannot leak back into the transcript on failure.
Args:
action: "create" or "delete".
secret_name: Secret name.
key_values: For "create": {"API_KEY": "abc", ...}.
from_dotenv / from_json: For "create": load key/values from a local file instead.
force: For "create": overwrite an existing secret.
env: Modal environment to target.
Returns: {message, stdout, stderr} or {error}, with values redacted.
| Name | Required | Description | Default |
|---|---|---|---|
| env | No | ||
| force | No | ||
| action | Yes | ||
| from_json | No | ||
| key_values | No | ||
| from_dotenv | No | ||
| secret_name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool as destructive and not read-only, and the description aligns with those flags. It adds valuable context by stating that values are never readable and are redacted from every returned field, including error fields, preventing transcript leaks.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is tight and well-structured: a one-line purpose, a critical security note, a compact Args list, and a return-shape line. Every sentence adds useful information and no space is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, an output schema exists, and the description covers all parameters, the create/delete distinction, file-based alternatives, overwrite behavior, environment targeting, and security-redaction behavior. Nothing essential is missing for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the full burden for all seven parameters. It explains action, secret_name, key_values, from_dotenv/from_json, force, and env, including which parameters apply to 'create' and the shape of key_values.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Create or delete a secret,' naming the specific verb and resource. It also distinguishes itself from the listing path by directing users to list_modal_resources(resource='secrets'), making its scope unmistakable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly tells users to use list_modal_resources for listing secret names, which is the main alternative. It does not exhaustively enumerate when to choose inspect_modal_secret or other siblings, but the create/delete scope is clear enough for appropriate routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage_modal_volumeADestructive
Volume lifecycle. For the files inside a volume use modal_volume_files (writes) or
list_modal_resources(resource="volume_files") (reads).
Args:
action: "create", "delete" (removes the volume and ALL its data — irreversible),
or "rename".
volume_name: Volume name (the current name, for "rename").
new_name: Required for "rename".
env: Modal environment. Volumes are environment-scoped, so this must match the
environment the volume lives in.
Returns: {message, stdout, stderr} or {error}.
| Name | Required | Description | Default |
|---|---|---|---|
| env | No | ||
| action | Yes | ||
| new_name | No | ||
| volume_name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and readOnlyHint=false. The description adds value by specifying 'removes the volume and ALL its data — irreversible' for the delete action, and by disclosing the return format '{message, stdout, stderr} or {error}'. These details enrich the behavioral picture beyond what structured hints provide, though it could mention reversibility of create/rename or permission requirements.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is efficient: a one-line summary, a sibling-routing sentence, a concise Args block, and a Returns line. It is front-loaded with purpose and the most important caveat (irreversibility) appears in the action list. Slightly verbose with the 'Returns' line but not bloated; every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the 4 parameters, existence of an output schema, and destructive annotations, the description covers the essentials: action semantics, parameter requirements, environment constraint, and return type. It could be more explicit about what 'create' does regarding naming (e.g., that volume_name serves as the new name) and whether rename affects existing file references, but overall it is sufficiently complete for an agent to use the tool safely.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has zero description coverage, so the description carries the full burden. It explains every parameter: action with allowed values and the irreversibility note for delete, volume_name as the current name for rename, new_name as required for rename, and env with the environment-scoping rule. This fully compensates for the lack of schema documentation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states 'Volume lifecycle' and explicitly lists the three actions (create, delete, rename), making the tool's purpose unambiguous. It also differentiates from sibling tools by directing file-level operations to modal_volume_files and list_modal_resources, so an agent can distinguish it from those alternatives without opening their schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides explicit guidance on when to use this tool versus the siblings: 'For the files inside a volume use modal_volume_files (writes) or list_modal_resources(resource="volume_files") (reads).' It also adds a requirement that 'env' must match the volume's environment, which is a concrete precondition for correct invocation. This goes beyond mere context to actionable routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
modal_volume_filesADestructive
Write operations on a volume's files. To LIST a volume's contents use
list_modal_resources(resource="volume_files").
Args:
action: "put" (upload local_path → remote_path), "get" (download remote_path →
local_path; "-" returns the contents instead of writing a file), "cp" (copy
inside the volume, using `paths`), "rm" (delete remote_path).
volume_name: Volume name.
local_path: Local source ("put") or destination ("get", default ".").
remote_path: In-volume destination ("put", default "/", trailing "/" keeps the
filename), source ("get"), or target ("rm").
paths: For "cp": sources followed by the destination, e.g. ["a.txt", "dest/"].
recursive: Needed to "rm" or "cp" a directory.
force: Overwrite existing files ("put"/"get").
env: Modal environment the volume lives in.
Returns: {message, stdout, stderr} or {error}. When MCP_MODAL_ALLOWED_LOCAL_PATHS is
set, "put"/"get" are refused for local paths outside the allowlist.
| Name | Required | Description | Default |
|---|---|---|---|
| env | No | ||
| force | No | ||
| paths | No | ||
| action | Yes | ||
| recursive | No | ||
| local_path | No | ||
| remote_path | No | ||
| volume_name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the destructiveHint annotation, the description discloses meaningful behavior: rm deletes remote files, force overwrites existing files, get with '-' returns contents instead of writing, and put/get are refused for paths outside the MCP_MODAL_ALLOWED_LOCAL_PATHS allowlist. It also documents the return envelope as {message, stdout, stderr} or {error}.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description front-loads the core purpose and sibling distinction in its first two sentences, then uses a compact Args list where every line adds needed information. It is appropriately detailed for an 8-parameter tool without being padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a multi-action tool with eight parameters and no enum constraints, the description covers all action modes, path conventions, directory handling, overwrite semantics, allowlist restrictions, and return value shape. The presence of an output schema further reduces the need to explain return details.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must fully carry parameter meaning, and it does. Every parameter is explained with functional semantics: action values and their roles, local/remote path direction, paths for cp, recursive and force behavior, and env context for the volume.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Write operations on a volume's files' and enumerates the specific actions put/get/cp/rm, making the tool's purpose concrete. It also immediately distinguishes itself from list_modal_resources, so an agent can tell which tool handles file listing versus file mutation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says 'To LIST a volume's contents use list_modal_resources(resource="volume_files")', giving a clear when-not-to-use condition and naming the alternative. It also provides operational guidance for each action, including when recursive or force flags are needed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
run_modal_appA
Run a Modal function or local entrypoint once and collect its output (`modal run`).
Use this to test on Modal compute; use deploy_modal_app to publish.
Args:
absolute_path_to_app: Absolute path to the app file. Its directory must use `uv`
and have `modal` installed in its virtualenv.
function_name: Function/entrypoint name, e.g. "main". Omit if the module has
exactly one.
env: Modal environment to target.
detach: Keep the run alive on Modal past this call (`--detach`) — for long jobs.
timeout_seconds: Max seconds to collect output. Default 120.
Returns: {output, urls, truncated (still running at the timeout), output_capped}.
| Name | Required | Description | Default |
|---|---|---|---|
| env | No | ||
| detach | No | ||
| function_name | No | ||
| timeout_seconds | No | ||
| absolute_path_to_app | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations are sparse (no readOnly, no idempotency), so the description carries the disclosure burden. It explains one-shot execution, output collection, detach persistence, and timeout truncation behavior—valuable details beyond the structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded with purpose and alternative routing, followed by a scannable Args list and a sharp Returns line. Every sentence adds functional value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite no schema descriptions, the definition covers prerequisites, parameter behavior, return shape, timeout handling, and the publish alternative. It is complete enough for an agent to invoke the tool correctly without external information.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, and the description fully compensates by explaining each parameter: absolute path requirements (uv + modal installed), function_name disambiguation, env target, detach semantics, and timeout default. This adds meaning the schema alone does not provide.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Run') and resource ('Modal function or local entrypoint') and explicitly contrasts with deploy_modal_app ('use deploy_modal_app to publish'). This clearly differentiates the tool from its main sibling.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It says 'Use this to test on Modal compute; use deploy_modal_app to publish,' giving direct when-to-use and when-not-to-use guidance. It also notes detach for long jobs, helping agents select the right mode for the task.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_modal_logsARead-onlyIdempotent
Search an app's or container's logs and return each hit WITH surrounding context —
the fastest way to find a traceback, an error or a request ID. Logs are fetched once
and grepped locally, so you get the lines around each match, not just the match.
Covers the stdout/stderr/system streams ONLY. Crash events shown on the Modal
dashboard (e.g. "... exited with ...") are not log lines, so a search for them
returns 0 matches even though the failure is real — check the dashboard instead.
Args:
identifier: App name/ID ("my-app", "ap-...") or container ID ("ta-...").
pattern: Text to find, or a Python regex when regex=True.
target: "auto" (default — "ta-..." is a container), "app", or "container".
regex / case_sensitive: Match mode. Both default False.
context_lines: Lines of context each side of a match. Default 3.
max_matches: Cap on match blocks returned. Default 50.
since / until: Time range, ISO 8601 or relative ("2h", "30m", "1d"). PREFER a
bounded range (both ends) when you know roughly when something happened —
`since` alone fetches every entry from then until now, which on a busy app
is megabytes and gets cut off at the timeout. Range must be <= 35 days.
tail: Search only the last N entries (max 20000) instead of a whole range.
With no since/until/tail, defaults to the last 1000 entries.
source: Search only "stdout", "stderr", or "system".
exclude: Drop lines matching this BEFORE searching, to strip repeated noise.
prefilter: Push `pattern` down to Modal as a server-side substring filter, so
non-matching lines are never fetched. The big lever for huge logs, but it
requires regex=False and leaves `context_lines` showing only other matching
lines — use it to find *where* something is, then re-query that window.
timestamps: Prefix lines with their timestamp. Default True.
timeout_seconds: Max seconds spent fetching logs. Default 30.
env: Modal environment (apps only).
Returns: {match_count (exact, whole log searched), returned (matches actually shown),
returned_blocks, matches (context blocks, matched lines prefixed ">"), excluded_lines,
output_capped}.
| Name | Required | Description | Default |
|---|---|---|---|
| env | No | ||
| tail | No | ||
| regex | No | ||
| since | No | ||
| until | No | ||
| source | No | ||
| target | No | auto | |
| exclude | No | ||
| pattern | Yes | ||
| prefilter | No | ||
| identifier | Yes | ||
| timestamps | No | ||
| max_matches | No | ||
| context_lines | No | ||
| case_sensitive | No | ||
| timeout_seconds | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, open-world, and idempotent behavior, but the description adds substantial behavioral context beyond structured data: logs are fetched once and grepped locally, crash events are absent from log streams, a single bounded 'since' range can fetch megabytes and be cut off by timeout, and prefilter changes what context_lines shows. These are meaningful, non-obvious behaviors an agent needs to know.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The structure is purposeful: a front-loaded purpose statement, a critical stream-coverage caveat early, and then parameter documentation that earns its place by adding real semantic and operational value. Despite being long, there is little redundancy, and the return-value summary is compact.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 16-parameter tool with 0% schema coverage, the description covers every parameter's semantics, gives default behaviors, failure modes, performance trade-offs, and explains the return shape. It is complete enough for an agent to select and invoke the tool correctly with no additional information.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the full burden, and it succeeds: every parameter is explained with non-schema semantics. identifier gets app/container ID prefixes, target gets an 'auto' rule, since/until gets a format warning and a 35-day cap, tail gets a max and default, prefilter gets a clear server-side behavior, and exclude' is defined as pre-search filtering.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('Search an app's or container's logs') and clarifies the unique selling point: returns each hit with surrounding context to find tracebacks, errors, or request IDs. It also distinguishes itself from sibling tools by scoping coverage to stdout/stderr/system streams and explicitly excluding Modal dashboard crash events.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use guidance ('fastest way to find a traceback, an error or a request ID') and a clear when-not-to-use case: crash events are not log lines and should be checked on the dashboard. It also gives operational guidance for large logs, such as preferring bounded ranges, using prefilter for huge logs, and re-querying a window after locating the hit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.5.0- Changed
search_modal_logs2 fields changed- added
Input schema / properties / prefilterAdded value: +{ + "default": false, + "title": "Prefilter", + "type": "boolean" +} - added
Input schema / properties / untilAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Until" +}
33 tool updates
v0.2.2- Added
analyze_modal_costs - Removed
copy_modal_volume_files - Removed
create_modal_secret - Removed
create_modal_volume - Removed
delete_modal_secret - Removed
delete_modal_volume - Removed
exec_modal_container - Removed
get_modal_app_history - Removed
get_modal_app_logs - Removed
get_modal_container_logs - Added
get_modal_logs - Removed
get_modal_profile - Removed
get_modal_volume_file - Added
inspect_modal_secret - Removed
list_modal_apps - Removed
list_modal_containers - Removed
list_modal_environments - Added
list_modal_resources - Removed
list_modal_secrets - Removed
list_modal_volume_contents - Removed
list_modal_volumes - Added
manage_modal_app - Added
manage_modal_container - Added
manage_modal_secret - Added
manage_modal_volume - Added
modal_volume_files - Removed
put_modal_volume_file - Removed
remove_modal_volume_file - Removed
rename_modal_volume - Removed
rollback_modal_app - Changed
search_modal_logs4 fields changed- added
Input schema / properties / excludeAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Exclude" +} - added
Input schema / properties / sourceAdded value: +{ + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source" +} - changed
Input schema / properties / target / defaultPrevious value: -"app"New value: +"auto" - added
Input schema / properties / timestampsAdded value: +{ + "default": true, + "title": "Timestamps", + "type": "boolean" +}
- Removed
stop_modal_app - Removed
stop_modal_container
26 tool updates
v0.1.0- First observed
copy_modal_volume_files - First observed
create_modal_secret - First observed
create_modal_volume - First observed
delete_modal_secret - First observed
delete_modal_volume - First observed
deploy_modal_app - First observed
exec_modal_container - First observed
get_modal_app_history - First observed
get_modal_app_logs - First observed
get_modal_container_logs - First observed
get_modal_profile - First observed
get_modal_volume_file - First observed
list_modal_apps - First observed
list_modal_containers - First observed
list_modal_environments - First observed
list_modal_secrets - First observed
list_modal_volume_contents - First observed
list_modal_volumes - First observed
put_modal_volume_file - First observed
remove_modal_volume_file - First observed
rename_modal_volume - First observed
rollback_modal_app - First observed
run_modal_app - First observed
search_modal_logs - First observed
stop_modal_app - First observed
stop_modal_container
TDQS
Scored across 12 tools
Every tool targets a distinct resource-action pair: logs are split into raw retrieval and context search with explicit cross-references, app and container management are separated, and secret/volume operations are cleanly divided into listing, inspecting, and mutating. The only close pair (get vs search logs) is clearly disambiguated by pointing out which to prefer.
Most tools follow a clear [verb]_modal_[noun] pattern (manage_modal_app, deploy_modal_app, list_modal_resources), with consistent snake_case including the 'modal' brand. The outlier is 'modal_volume_files', which drops the verb prefix and reads as a resource path rather than an action, breaking the otherwise predictable naming convention.
Twelve tools cover the full Modal surface — apps, containers, logs, volumes, secrets, billing, and resource discovery — without redundancy or bloat. Each tool addresses a significant workflow, so the count feels naturally scoped for the domain.
The toolset provides full lifecycle coverage: deploy/run apps, manage their state (stop/rollback), execute/stop containers, read and write volume files, create/delete secrets plus read key names, cost analytics, and broad resource discovery. Useful missing operations (like editing a secret) are handled via supported actions such as create-with-force, so there are no obvious dead ends.
Maintenance
Related MCP Connectors
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
- SupabaseOAuthcom.supabase
MCP server for interacting with the Supabase platform
MCP server for Superserve sandboxes: create, exec, and manage Firecracker microVMs
- mcpOAuthcom.airtable
Official Airtable MCP server — database and operations layer for agents.
Related MCP Servers
- AlicenseBqualityCmaintenanceFacilitates running Python code in a sandbox and generating images using the FLUX model via an MCP server compatible with clients like Goose and the Claude Desktop App.225MIT
- FlicenseDqualityFmaintenanceAn MCP server that allows users to deploy Python scripts to Modal directly from Claude, providing a link to the deployed application that can be shared with others.14-
- AlicenseAqualityDmaintenanceAn MCP server that enables AI agents to interact with Modal, allowing them to deploy apps and run functions in a serverless cloud environment.73MIT
- AlicenseAqualityDmaintenanceA Model Context Protocol server that enables LLMs and AI assistants to create, manage, and interact with isolated cloud-based Python environments with GPU support on Modal.com.111MIT