Skip to content

feat(desktop): add list_targets MCP tool — fixes 'AI can't enumerate windows' gap - #19

Merged
rrader26 merged 1 commit into
mainfrom
feat/desktop-list-targets-mcp
May 11, 2026
Merged

feat(desktop): add list_targets MCP tool — fixes 'AI can't enumerate windows' gap#19
rrader26 merged 1 commit into
mainfrom
feat/desktop-list-targets-mcp

Conversation

@rrader26

Copy link
Copy Markdown
Contributor

Summary

Caught live while testing AgentMark MCP on the Windows VM with Claude Code. The AI opened a desktop session, captured the focused window (Claude itself), and then had no way to discover other open windows to capture. The bridge supports it (`list_windows` JSON-RPC method), the backend knows about it internally, but it was never exposed on the MCP surface.

This PR closes the gap with a new `agentmark_desktop_list_targets` tool. Independent of PR #16-#18 — branches off main, lands the missing piece for the already-live Windows path.

The AI's actual failure mode (before this fix)

```
Used agentmark: agentmark desktop open
Session opened. Let me try a snapshot...

Used agentmark: agentmark desktop snapshot
Desktop session is open (dt_5150d7c157fa, backend windows_uia), but I
have to flag a limitation: the agentmark MCP doesn't expose an
"enumerate all windows" call. agentmark_desktop_snapshot captures one
window at a time — either the focused one or a specific target...

The only window I can see right now is the focused one:
Claude (process claude, PID 11900...)

Or if you'd prefer a true window list, that needs to come from a
different tool (e.g. PowerShell Get-Process | ...)
```

After this PR, the AI just calls `agentmark_desktop_list_targets(desktop_id)` and gets the full list.

What changed

`src/desktop/types.ts`

  • New `listTargets(): Promise<DesktopTargetSummary[]>` method on the `DesktopCaptureBackend` interface.
  • New `DesktopTargetSummary` shape: `window_id`, `process_name`, `process_id`, `window_title`, `window_class`, `has_focus`. No element tree (cheap to call repeatedly).

`src/desktop/fixture-backend.ts`

  • `listTargets()` enumerates the preset map (excel_blank, nowcerts_customer, etc.).

`src/desktop/windows-uia-backend.ts`

  • `listTargets()` calls the bridge's existing `list_windows` JSON-RPC method, maps camelCase wire fields to the snake_case interface.

`src/mcp/tool-defs.ts`

  • New `agentmark_desktop_list_targets` tool registered in `ALL_TOOLS`. Single `desktop_id` param. Returns `{ windows: [...] }`.

`src/mcp/dispatcher.ts`

  • Routes `agentmark_desktop_list_targets` → `backend.listTargets()`.

Tests

307 pass, 0 fail, 10 skipped (pre-existing).

  • New: `listTargets` maps the bridge response correctly (Windows backend)
  • New: dispatcher returns fixture preset windows
  • New: dispatcher errors on unknown `desktop_id`
  • Updated: tool-count assertion (4 → 5)

After this lands (Windows-side)

The user's AI exchange becomes the natural flow:

```
You: list my open windows
AI: [calls agentmark_desktop_list_targets] → I see Notepad, Excel, Chrome, ...

You: capture Notepad
AI: [calls agentmark_desktop_snapshot with that window_id] → here's the tree

You: type "Hello" into the editor
AI: [calls agentmark_desktop_execute] → done
```

Note on macOS

PR #18 (Node-side macOS backend) will need the same `listTargets()` implementation. I'll add it as a follow-up commit on the `feat/desktop-macos-axapi-backend` branch after #18 merges, rather than stacking deeper dependencies here.

🤖 Generated with Claude Code

Real product gap caught while testing live: the AI on the Windows VM
opened a desktop session and tried to "list windows", but there was no
MCP tool for that. The bridge has `list_windows` and the backend
internals know about windows, but the agent-facing surface only had
`open` / `close` / `snapshot` / `execute` — so the AI was forced to
guess process_names to feed `snapshot` blindly.

Fix: expose enumeration as a first-class operation.

## Changes

`src/desktop/types.ts`:
  - Add `listTargets(): Promise<DesktopTargetSummary[]>` to the
    `DesktopCaptureBackend` interface.
  - New `DesktopTargetSummary` shape — lightweight per-window record
    with window_id, process_name, process_id, window_title,
    window_class, has_focus. No element tree (cheap call).

`src/desktop/fixture-backend.ts`:
  - Implements `listTargets()` by enumerating the preset map.

`src/desktop/windows-uia-backend.ts`:
  - Implements `listTargets()` by calling the bridge's existing
    `list_windows` JSON-RPC method and mapping camelCase wire fields
    to the snake_case interface.

`src/mcp/tool-defs.ts`:
  - New tool `agentmark_desktop_list_targets`. Single param:
    `desktop_id`. Returns `{ windows: [...] }`.

`src/mcp/dispatcher.ts`:
  - Routes `agentmark_desktop_list_targets` -> backend.listTargets().

## Tests (307 pass, 0 fail)

`test/desktop/windows-uia-backend.test.ts`:
  - Asserts listTargets maps the bridge `windows` array to
    snake_case DesktopTargetSummary correctly.

`test/mcp/desktop-dispatcher.test.ts`:
  - Updates tool-count assertion (4 -> 5).
  - New test: dispatcher returns fixture preset windows.
  - New test: errors on unknown desktop_id.

## After this lands

The AI's exchange that prompted this fix becomes:

  agentmark_desktop_open(backend=windows_uia) -> desktop_id
  agentmark_desktop_list_targets(desktop_id)   -> all real windows
  agentmark_desktop_snapshot(desktop_id, windowId=...) -> chosen window
  agentmark_desktop_execute(...)               -> drive it

Instead of:

  agentmark_desktop_open(backend=windows_uia) -> desktop_id
  agentmark_desktop_snapshot(desktop_id)      -> focused window only
  [agent has no way to discover other windows]

## Note on MacosAxapiBackend

PR #18 (Node-side macOS backend) will need the same `listTargets()`
implementation. Will update that PR as a follow-up commit on the
feat/desktop-macos-axapi-backend branch once it merges, rather than
stacking deeper dependencies here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rrader26
rrader26 merged commit e02a001 into main May 11, 2026
5 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.

2 participants