Skip to content

Releases: george-bobby/mcp-modal

0.5.0 — bounded log search, and one module per concern

Choose a tag to compare

@github-actions github-actions released this 09 Sep 10:08

bounded log search, and one module per concern

search_modal_logs
A log search could only be given a start time, so "what happened in
those three seconds" was not expressible: since alone puts the CLI
in range mode and fetches every entry from then until now. Measured on
a busy app, --since 6h is 664KB against a 40k budget and a 30s fetch
deadline, so the search came back truncated and capped; the same query
bounded with --until is kilobytes.

  • until bounds the window at both ends.
  • prefilter=True pushes the pattern down to Modal as a server-side
    substring filter (modal app logs --search, 1.5+), so non-matching
    lines are never fetched — the lever for logs too large to drain.
    Requires regex=False, and context lines then show only other
    matches, so the response flags prefiltered and says so.
  • Truncation and zero-match messages now say to narrow the window
    rather than to raise timeout_seconds, which only fetches more.
  • returned counts matched lines actually shown, with the block count
    moved to returned_blocks. It counted blocks before, so 158 merged
    matches reported as "showing 1 of 158" and invited a wider re-query.

A window the CLI rejects (reversed range, over 35 days, tail over
20000) now returns Modal's own message instead of "exit 2", and the
bounds it can check locally are checked before spawning a process.

get_modal_logs gets the same treatment: the range-mode caveat is
documented, and follow combined with since/until/tail is refused up
front instead of failing as a CLI usage error.

debug_modal_app teaches the bounded-window workflow (know the time ->
since+until; do not know it -> prefilter to locate, then re-query for
context), and adds a step to check whether sibling apps were hit in the
same window before reporting an incident as isolated to one app.

Module layout
server.py was 2092 lines. It is now the assembly point that imports
the pieces and re-exports the public names, with app.py (the FastMCP
instance and annotation policy), command.py (argv helpers and the two
runners), output.py (caps and the response envelope), logsearch.py and
billing.py (the pure local logic), prompts.py, and one module per tool
group under tools/. The dependency graph is one-directional and
acyclic. from mcp_modal.server import ... still resolves everything.

Every function is byte-identical to 0.4.0 except the three named
above; verified by diffing each tool, prompt and helper against the
previous release, and by comparing the full tool and prompt inventory.

Release plumbing
publish.yml now cuts the GitHub Release for a version tag, attaching
the same artifacts the PyPI job uploaded rather than a rebuild, and is
idempotent so a re-pushed tag refreshes an existing release. Every
earlier version has been given a tag and a release with its published
wheel and sdist, including 0.2.3, which was on PyPI with no tag at all.

Docs
README drops the 0.2.x upgrade table and the stale annotation counts
(four read-only tools and eight mutating, two of them non-destructive)
and documents the new log-search arguments. CLAUDE.md describes the
module layout and the log volume behavior. GUIDELINES.md covers the
release step.


Install

uvx mcp-modal@0.5.0          # run this exact version
claude mcp add mcp-modal -- uvx mcp-modal@0.5.0

Also on PyPI: mcp-modal 0.5.0.
The .whl and .tar.gz below are the artifacts published to PyPI for this version.

0.2.3 — security hardening

Choose a tag to compare

@george-bobby george-bobby released this 09 Sep 07:21

security hardening

  • Redact secret values from ALL returned fields (command/stdout/stderr/error)
    and the log line, not just the happy-path command. Fixes plaintext leak on a
    failed modal secret create (e.g. secret exists without --force).
  • Add -- end-of-options before every user-supplied name/path so a value
    starting with - can't be parsed as a modal CLI flag (option injection).
    Also lets exec_modal_container pass the command's own flags through.
  • Bound ReDoS in log search: cap pattern length, clamp context/max_matches,
    match each line against a 16KB slice.
  • Close stdin (DEVNULL) on both subprocess runners so an unexpected prompt
    fails fast instead of hanging.
  • Add opt-in MCP_MODAL_ALLOWED_LOCAL_PATHS allowlist confining volume put/get
    local paths; document confused-deputy tools in a README Security section.

Install

uvx mcp-modal@0.2.3          # run this exact version
claude mcp add mcp-modal -- uvx mcp-modal@0.2.3

Also on PyPI: mcp-modal 0.2.3. The .whl and
.tar.gz attached below are the exact artifacts published to PyPI for this version.

Full changelog: v0.2.2...v0.2.3

0.4.0 — cost analysis and secret key inspection

Choose a tag to compare

@george-bobby george-bobby released this 09 Sep 07:22

cost analysis and secret key inspection

analyze_modal_costs (read-only)
Fetches modal billing once and aggregates locally, the same way
search_modal_logs does: a week on a busy workspace is 1500+ flat rows,
one per app per interval, and adding those up is not work to hand back
to the caller. Views: by_app, timeline, by_environment, by_resource,
summary, rates. Decimal arithmetic throughout — summing hundreds of
8-decimal strings as floats drifts.

"what is my costliest app?" -> view="by_app"
"why was Monday expensive?" -> view="timeline", whose explanation
finds the peak interval, diffs it against the one before, and ranks
apps by how much they grew into it.

Two client-version landmines, both handled: modal 1.4.x names the report
columns in Title Case ("Object ID") while 1.5+ uses snake_case, so every
field is read through an alias table — reading one spelling silently
yields a report full of zeros. And billing summary/rates only exist
in 1.5+, where an old client fails with a bare "No such command"; that
becomes an upgrade hint, and pyproject now pins modal>=1.5.

inspect_modal_secret
Lists the KEY NAMES in a secret, never the values. Modal exposes this
nowhere — not the CLI, not Secret.info(), not the gRPC SecretMetadata
message — so the only route is to mount the secret in a container and
list the environment, subtracting the ~35 variables the image and the
Modal runtime set anyway (including the MODAL_TOKEN_* credentials that
live there).

The probe is compgen -e, a bash builtin that prints exported variable
names only, so no value is printed even inside the container. It has to
be quote- and paren-free: modal shell -c shlex-splits the string,
re-joins it with spaces and runs it under bash -c, so a python -c
probe arrives mangled.

Unlike every other read here this call starts remote compute, costs a
few cents and takes tens of seconds, so it is annotated non-read-only
and deliberately kept out of list_modal_resources.

Also

  • New prompt: investigate_modal_costs.
  • Redact longest values first. With one secret value a substring of
    another ("dummy" inside "dummy2"), replacing the short one first left
    the remainder visible as "***2".

Verified against a live workspace: the cost views on both modal 1.4.3 and
1.5.5 (identical totals), and the secret probe end-to-end via a throwaway
secret with two dummy keys — both returned, 35 runtime variables filtered,
nothing leaked, secret deleted afterwards.


Install

uvx mcp-modal@0.4.0          # run this exact version
claude mcp add mcp-modal -- uvx mcp-modal@0.4.0

Also on PyPI: mcp-modal 0.4.0. The .whl and
.tar.gz attached below are the exact artifacts published to PyPI for this version.

Full changelog: v0.3.1...v0.4.0

0.3.1 — refresh the PyPI page's install instructions

Choose a tag to compare

@george-bobby george-bobby released this 09 Sep 07:21

refresh the PyPI page's install instructions

Docs-only. The PyPI project page embeds the README from the release, so
it still tells people to run uvx mcp-modal — the unpinned form that
resolves once and then freezes on that version forever, which is the
drift the README now warns about. Publishing 0.3.1 replaces that page
with the @latest guidance. No code changes.


Install

uvx mcp-modal@0.3.1          # run this exact version
claude mcp add mcp-modal -- uvx mcp-modal@0.3.1

Also on PyPI: mcp-modal 0.3.1. The .whl and
.tar.gz attached below are the exact artifacts published to PyPI for this version.

Full changelog: v0.3.0...v0.3.1

0.3.0 — grouped tools, output caps, annotations, prompts

Choose a tag to compare

@george-bobby george-bobby released this 09 Sep 07:21

grouped tools, output caps, annotations, prompts

Cuts the tool surface from 26 tools to 10 and adds the three things a
client needs to use them safely and cheaply.

Tools (26 -> 10, ~39% smaller schema payload)
Every tool schema sits in the model's context for the whole session, so
related CLI subcommands are now grouped behind an action/resource
argument instead of one tool per subcommand. Nothing was dropped:
list_modal_resources covers all eight read-only lookups, and the
manage_* tools cover app stop/rollback, container exec/stop, volume
create/delete/rename, volume file put/get/cp/rm, and secret
create/delete. See the upgrade table in the README for the mapping.

Output caps
The streaming runner was bounded by time, not volume, so a chatty app
could return megabytes of logs into the caller's context in one result.
Text fields are now capped (head + tail, line-snapped, with a marker
naming what was dropped) at MCP_MODAL_MAX_OUTPUT_CHARS, default 40000,
0 to disable; JSON listings cap at 200 entries with omitted_items.
Capping happens at the response boundary only: JSON is parsed intact,
and search_modal_logs greps the whole log before capping the returned
blocks, so match_count stays exact.

Tool annotations
Every tool declares readOnlyHint / destructiveHint / idempotentHint, so
clients can auto-approve the three read-only tools and keep prompting
for volume deletes and container exec.

Prompts
Adds debug_modal_app, deploy_and_verify and review_modal_account.
Clients fetch prompts on demand, so multi-step guidance costs nothing
in per-session tool schema.

Fixes

  • Volume file operations now accept env. Volumes are environment-scoped
    and every modal volume subcommand takes -e, but ls/put/get/cp/rm
    silently used the default environment. Container logs/exec/stop
    correctly still take no -e; the CLI accepts none there.
  • modal_volume_files supports recursive for cp.
  • Log tools accept target="auto", resolving app vs container from the
    ta- ID prefix.
  • Pin mcp>=1.9.2,<2. mcp 2.x renames FastMCP to MCPServer, and uvx
    resolves from PyPI metadata rather than uv.lock, so an unbounded
    specifier breaks new installs.
  • Reissue LICENSE with the correct copyright holder.

Install

uvx mcp-modal@0.3.0          # run this exact version
claude mcp add mcp-modal -- uvx mcp-modal@0.3.0

Also on PyPI: mcp-modal 0.3.0. The .whl and
.tar.gz attached below are the exact artifacts published to PyPI for this version.

Full changelog: v0.2.4...v0.3.0

0.2.4 — bump CI actions to Node 24-compatible versions

Choose a tag to compare

@george-bobby george-bobby released this 09 Sep 07:21

bump CI actions to Node 24-compatible versions

actions/checkout@v4 -> @v5 and astral-sh/setup-uv@v5 -> @v6 ahead of GitHub's
2026-06-16 forced Node 24 migration. CI-only change; package code is unchanged
from 0.2.3. Doubles as the first release exercising the tag-triggered pipeline.


Install

uvx mcp-modal@0.2.4          # run this exact version
claude mcp add mcp-modal -- uvx mcp-modal@0.2.4

Also on PyPI: mcp-modal 0.2.4. The .whl and
.tar.gz attached below are the exact artifacts published to PyPI for this version.

Full changelog: v0.2.3...v0.2.4

0.2.2 — log timestamps, noise filters, volume empty/error clarity

Choose a tag to compare

@george-bobby george-bobby released this 09 Sep 07:21

log timestamps, noise filters, volume empty/error clarity

  • search_modal_logs: carry per-line wall-clock timestamps into context blocks
    (--timestamps, default on); add source and exclude filters to cut log noise
    (e.g. repeated "queue put failed" spam); report excluded_lines.
  • get_modal_app_logs / get_modal_container_logs: opt-in timestamps; document
    that crashes surfaced as Modal dashboard events never appear in log streams.
  • list_modal_volume_contents: distinguish a genuinely empty listing (empty=true
    • message) from an error; echo volume_name/path/command.
  • Add GUIDELINES.md publishing checklist; bump version 0.2.1 -> 0.2.2.

Install

uvx mcp-modal@0.2.2          # run this exact version
claude mcp add mcp-modal -- uvx mcp-modal@0.2.2

Also on PyPI: mcp-modal 0.2.2. The .whl and
.tar.gz attached below are the exact artifacts published to PyPI for this version.