Skip to content

feat: add push_notification, set_permission, and get_clipboard tools#69

Open
saen-ai wants to merge 12 commits into
joshuayoes:mainfrom
saen-ai:feat/push-permissions-clipboard
Open

feat: add push_notification, set_permission, and get_clipboard tools#69
saen-ai wants to merge 12 commits into
joshuayoes:mainfrom
saen-ai:feat/push-permissions-clipboard

Conversation

@saen-ai
Copy link
Copy Markdown
Contributor

@saen-ai saen-ai commented Apr 21, 2026

Summary

Adds three new tools that cover common testing scenarios that were previously impossible with this MCP server.

New Tools

push_notification

Send a simulated APNs push notification to any app on the simulator.

Send a push notification to co.getsnippet.tea with title "New snippet shared" and body "John shared a snippet with you"
  • Accepts title, body, badge, and optional user_info for custom data payloads (deep link routing etc.)
  • Writes payload to a temp file, validates 4096-byte APNs limit, cleans up after sending
  • Uses xcrun simctl push under the hood

set_permission

Grant, revoke, or reset any iOS privacy permission without touching the UI.

Grant notification and photo permissions to co.getsnippet.tea
Reset all permissions for co.getsnippet.tea to test first-launch experience

Supports all 13 services: all, calendar, contacts-limited, contacts, location, location-always, photos-add, photos, media-library, microphone, motion, reminders, siri

Uses xcrun simctl privacy.

get_clipboard

Read what's currently on the simulator clipboard — essential for testing copy behaviour.

Tap the "Copy snippet" button in Tea, then read the clipboard to verify the correct text was copied

Uses xcrun simctl pbpaste.

Test plan

  • push_notification with title + body → notification appears on simulator
  • push_notification with user_info → custom data included in payload
  • push_notification with payload > 4096 bytes → returns error before sending
  • set_permission with action: "grant" → permission granted without system dialog
  • set_permission with action: "revoke" → permission denied
  • set_permission with action: "reset" → app prompts again on next use
  • set_permission with action: "grant" and no bundle_id → returns validation error
  • get_clipboard after copying text in app → returns correct clipboard content
  • get_clipboard with empty clipboard → returns "(clipboard is empty)"

saen-ai and others added 12 commits April 21, 2026 10:41
…dep CVEs

- record_video: was hardcoded to "booted", ignoring the udid param — now
  uses getBootedDeviceId() consistently with all other tools; also adds
  udid to the tool schema so callers can target a specific simulator

- ui_view: JSON.parse on idb output had no error handling — server would
  crash on malformed output; wrapped in try/catch with a clear error
  message; also validates frame dimensions are positive numbers before use

- ui_view: temp PNG/JPEG files now deleted immediately after reading
  instead of accumulating until server exit; file names include a random
  suffix to prevent collisions on rapid successive calls

- record_video: improved start detection — now rejects properly if the
  process exits early, increased timeout from 3s to 5s, tracks resolved
  state to avoid double-settling the promise

- deps: updated @modelcontextprotocol/sdk to latest (fixes CVE ReDoS,
  cross-client data leak, DNS rebinding — all high severity); ran
  npm audit fix for 6 additional moderate/low vulns in ajv, body-parser,
  minimatch, path-to-regexp, qs, diff

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
idb ui text only supports ASCII keycodes and throws 'No keycode found'
for any emoji or non-ASCII character. This adds a new ui_paste tool that
works around the limitation using the macOS pasteboard:

1. Copies text to the Mac clipboard via pbcopy
2. Syncs it to the simulator pasteboard via xcrun simctl pbsync
3. Long-presses at the given coordinates to trigger the paste menu
4. Finds the Paste button in the accessibility tree and taps it

This enables typing emoji, Arabic, Chinese, and any Unicode text into
simulator inputs — essential for testing apps with international users
or emoji-heavy content.

ui_type is unchanged and remains the right tool for ASCII text.
1.5s was triggering iOS system gestures (app switcher / home screen),
dismissing the app before the paste menu appeared. 0.8s is long enough
to trigger the contextual paste menu without conflicting with system gestures.
idb ui tap requires integer x/y values — passing floats like 55.166...
causes 'invalid int value' error. Round the calculated center coordinates.
terminate_app: kills a running app by bundle ID without having to
relaunch it — useful for testing cold-start flows and crash recovery

open_url: opens any URL or deep link in the simulator — essential for
testing universal links, custom URL schemes, and OAuth redirect flows

list_apps: lists all installed apps with their bundle IDs and display
names, sorted alphabetically — removes the need to manually look up
bundle IDs before calling launch_app or terminate_app
- max_size: resizes screenshot proportionally using sips when the image
  exceeds the given pixel dimension (width or height). Solves the Claude
  2000px API limit issue (joshuayoes#42).
- force: prevents silent overwrites by erroring when the output file
  already exists. Defaults to false (joshuayoes#19).

Fixes joshuayoes#42, Fixes joshuayoes#19
Instead of pkill-ing all simctl recordVideo processes, we now store
each recording's ChildProcess in a Map keyed by UDID. stop_recording
sends SIGINT to that specific PID, leaving any other simulators or
concurrent idb operations untouched.

Falls back to pkill if no tracked process is found (e.g. server
restarted mid-recording) so behaviour is never worse than before.

Also adds an optional udid param to stop_recording for multi-simulator
setups.

Fixes joshuayoes#20
Adds an optional timeout (1–3600 seconds) to record_video. When set,
a setTimeout fires after the given duration and sends SIGINT to the
tracked recording process — same targeted kill used by stop_recording.

If omitted, behaviour is unchanged: recording runs until stop_recording
is called.

Fixes joshuayoes#5
Adds a rotate_device tool that rotates the iOS Simulator left
(counter-clockwise) or right (clockwise) using the Simulator app's
built-in keyboard shortcuts via osascript.

Supports an optional `times` param (1–3) for multi-increment rotations
without multiple tool calls. A 500ms delay between increments lets the
simulator animate each step.

Contributes to joshuayoes#49
Replaces the generic prompt list with five concrete end-to-end flows
that reflect how the tool is actually used: bug reproduction recording,
post-implementation feature validation, React Native Redbox debugging,
Unicode/emoji text input testing, and rotation-based layout checks.

Also adds missing tool entries to the Tools section for ui_paste,
rotate_device, terminate_app, open_url, and list_apps.

Fixes joshuayoes#40
push_notification: sends a simulated APNs push to any app via
xcrun simctl push. Accepts title, body, badge, and optional custom
data payload. Writes payload to a temp file (cleaned up after use)
and validates the 4096-byte limit before sending.

set_permission: grants, revokes, or resets any iOS privacy permission
via xcrun simctl privacy. Supports all 13 services (camera, photos,
location, microphone, etc). Validates that bundle_id is provided for
grant/revoke actions.

get_clipboard: reads the simulator clipboard via xcrun simctl pbpaste.
Useful for verifying copy behaviour in apps like snippet managers
where clipboard correctness is a core feature.
@joshuayoes
Copy link
Copy Markdown
Owner

Thanks — all three tools are well-scoped wrappers that follow the existing patterns (getBootedDeviceId, errorWithTroubleshooting, Zod-validated schemas). Reviewing the leaf commit 8109dd2f:

  1. Stacking. Same as feat: add max_size and force params to screenshot tool #63docs: rewrite use cases section with real developer workflows #67: your branch feat/push-permissions-clipboard was cut from your fork's main, so it drags all ancestor commits along. Please rebase onto upstream/main with just 8109dd2f:

    git fetch upstream
    git checkout -B feat/push-permissions-clipboard upstream/main
    git cherry-pick 8109dd2f
    git push --force-with-lease origin feat/push-permissions-clipboard
    

    Per the stacking note in feat: unicode support, recording fixes, screenshot improvements, device rotation & docs #68, also consider resetting your fork's main to match upstream so this stops happening.

  2. -- separator is missing on all three tools. Existing tools consistently use -- to prevent user-provided positional args from being parsed as flags. Please add it:

    • push_notification: ["simctl", "push", "--", actualUdid, bundle_id, tmpFile] (confirm simctl push accepts --)
    • set_permission: ["simctl", "privacy", "--", actualUdid, action, service, ...(bundle_id ? [bundle_id] : [])]
    • get_clipboard: ["simctl", "pbpaste", "--", actualUdid]

    If any of those subcommands rejects --, that's fine — but the input values need to be validated tightly enough that positional-flag confusion can't happen. UDID_REGEX covers that; bundle_id and tmpFile need the same scrutiny.

  3. push_notification temp file. Uses push_${Date.now()}.json which can collide on rapid successive calls. Same fix as the ui_view random-suffix pattern introduced in fix: record_video ignores udid, ui_view JSON crash, temp file leaks, dep CVEs #59: push_${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json.

  4. set_permission grammar. The description string produces "granted", "revoked", but also "reseted". Either change the interpolation ({action === "reset" ? "reset" : action + "ed"}) or rephrase the message.

  5. set_permission services enum. Hardcoding the 13 services means the enum needs updating whenever Apple adds one (unlikely but possible). That's actually the right trade — tighter validation at the schema level beats "pass through and hope simctl errors well" — just flagging that this one needs occasional maintenance. No change required.

  6. Demo recording. ## Demo with a quick showcase of each tool — push with title/body/badge showing on the lock screen, set_permission grant camera, and get_clipboard after ui_paste. See Releases and feat: add ui_find_element tool #51 for format.

Content-wise this is the most straightforward of your open PRs. Once rebased and the nits addressed, I'm ready to land it.

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