Skip to content

feat(mcp): optional path argument on grab_widget#4270

Merged
jensenpat merged 1 commit into
aethersdr:mainfrom
skerker:feat/mcp-grab-widget-path
Jul 17, 2026
Merged

feat(mcp): optional path argument on grab_widget#4270
jensenpat merged 1 commit into
aethersdr:mainfrom
skerker:feat/mcp-grab-widget-path

Conversation

@skerker

@skerker skerker commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Adds the optional path property to the grab_widget MCP tool, wired through to the
raw grab verb's existing [path] argument, so callers can say where the PNG lands
instead of always getting aether-mcp-grab-<epoch>.png in the temp dir.

Implements the shape proposed in #4249's triage comment:

  • Schema — optional path property on grab_widget (tools/aether_mcp.py),
    with a note that the returned JSON's path stays authoritative (the app may
    normalize the requested path).
  • Handlerout = args.get("path") or <temp default>; the request dict, the
    resp.get("path", out) resolution, and the inline/too-large branches are unchanged.
  • Tests — two new field-mapping cases in tools/test_aether_mcp.py: a
    caller-supplied path is emitted verbatim to the bridge, and the no-path default
    still lands under tempfile.gettempdir().
  • Docs — one line in docs/automation-bridge.md's tool list.

Verification (macOS, UI-only, no radio):

  • python3 tools/test_aether_mcp.py — all 36 checks pass (34 existing + 2 new).
  • Live against AetherSDR 26.7.2 with AETHER_AUTOMATION=1 +
    AETHER_AUTOMATION_NO_AUTOCONNECT=1: the same grab_widget call with a path
    wrote the PNG exactly there and returned it inline; without path it still landed
    as aether-mcp-grab-<epoch>.png in the temp dir. Returned JSON path matched the
    on-disk file in both cases — transcript below.
Verification transcript (paths sanitized: home → ~, macOS per-user temp → $TMPDIR)
$ python3 tools/test_aether_mcp.py   (exit 0)
  ok   grab_widget sends cmd=grab + target
  ok   grab_widget default path lands in the temp dir
  ok   grab_widget honors caller-supplied path (#4249)
  ... all MCP field-mapping/protocol checks passed

grab_widget {"target": "MainWindow", "path": "~/Desktop/claude-code-work/screenshots/aethersdr-grab-4249-demo.png"}
  -> {"bytes": 572568, "class": "MainWindow", "height": 1527, "ok": true, "path": "~/Desktop/claude-code-work/screenshots/aethersdr-grab-4249-demo.png", "target": "MainWindow", "width": 2770}

grab_widget {"target": "MainWindow"}   (no path)
  -> {"bytes": 572272, "class": "MainWindow", "height": 1527, "ok": true, "path": "$TMPDIR/aether-mcp-grab-1784219529.png", "target": "MainWindow", "width": 2770}

$ ls -la <both>
-rw-r--r-- 1 user  staff 572568 Jul 16 09:32 ~/Desktop/claude-code-work/screenshots/aethersdr-grab-4249-demo.png
-rw-r--r-- 1 user  staff 572272 Jul 16 09:32 $TMPDIR/aether-mcp-grab-1784219529.png

Fixes #4249

— authored by agent (Claude Code) on behalf of @skerker

🤖 Generated with Claude Code

The grab_widget MCP tool always wrote its PNG to a generated name
under tempfile.gettempdir(), even though the underlying raw grab verb
already accepts a path. Screenshots are evidence the human operator
wants to find later (before/after grabs, bug-report captures), so the
assistant needed an extra shell step to copy each capture out of the
temp dir.

Add an optional 'path' property to the grab_widget input schema and
use it in place of the generated temp path when supplied. Everything
downstream is unchanged: the request dict, the resp.get("path", out)
resolution (the app-side reply stays authoritative if the app
normalizes the path), and the inline/too-large branches.

Test: two new field-mapping cases — caller-supplied path is emitted
verbatim, and the no-path default still lands under the temp dir.

Fixes aethersdr#4249

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@skerker
skerker marked this pull request as ready for review July 16, 2026 16:45
@skerker
skerker requested review from a team as code owners July 16, 2026 16:45

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, tightly-scoped change — the optional path threads straight through to the raw grab verb's existing [path] argument, the request dict / resp.get("path", out) resolution / inline-vs-too-large branches are all untouched, and the existing except OSError around the read already covers an unwritable caller path. The schema note that the returned JSON's path stays authoritative is exactly right, and the two new field-mapping tests (verbatim path + temp-dir default) cover both branches. Ran python3 tools/test_aether_mcp.py locally on the branch: all checks pass.

Nothing blocking. One optional note below; take it or leave it.

Non-blocking notes

  • tools/aether_mcp.py — a caller-supplied ~/... path is forwarded verbatim (no expanduser), so a literal ~ would land relative to the cwd rather than $HOME. This matches the raw grab verb's behavior and the authoritative-return-path note covers it, so it's fine as-is — just worth a mention if you want to be friendlier to hand-typed paths later.

Thanks for the thorough writeup and the live-verification transcript — nice work.


🤖 aethersdr-agent · cost: $4.4810 · model: claude-opus-4-8

@jensenpat jensenpat self-assigned this Jul 17, 2026

@jensenpat jensenpat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the exact head and its clean combined merge with current main. The optional path is schema-typed, forwarded unchanged to the existing grab verb, and the app-returned path remains authoritative; existing inline-size and read-error behavior is unchanged. Python compilation passed and all 36 MCP field-mapping/protocol checks passed. No blockers.

@jensenpat
jensenpat merged commit f8ba3de into aethersdr:main Jul 17, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP server: optional path argument on grab_widget (raw grab already has it)

2 participants