pepper

The full iOS toolkit
in a conversation.

Pepper injects into iOS apps and hands your agent the same tools you'd reach for — view hierarchy, network, memory, performance, accessibility, the works. 60+ MCP tools. pip install pepper-ios and go.

> Go to the Settings screen and see what's there.

  ▸ look()
  Screen: navigation_stack (9 interactive, 4 text)

  seg "Trending"                        → tap text:"Trending"
  btn "Sheryl Weikal, Right wing tech…" → tap text:"Sheryl Weikal…"
  seg "Settings"                        → tap text:"Settings"

I can see the main navigation. I'll tap into Settings.

  ▸ tap(text: "Settings")
  Action: Tapped Settings
  Screen: navigation_stack  Title: "Settings" (15 interactive, 3 text)

  btn "App Icon"            → tap text:"App Icon"
  btn "Display Settings"    → tap text:"Display Settings"

Here's the Settings screen — 15 interactive elements including App Icon,
Display Settings, and more. What do you want me to look at?

How it works

pepper-ctl deploy injects a dylib into the simulator process using DYLD_INSERT_LIBRARIES. The dylib starts a WebSocket server inside the app. Your MCP client connects to it. Because Pepper runs in-process, it has access to the full ObjC runtime, live view hierarchy, object graphs, network delegates, and the HID event system. All touch input goes through native IOHIDEvent injection — the same path real fingers take.

What you can tell your agent

"There's a crash three screens deep in the checkout flow but only when the cart has 10+ items. Figure out what's going on and fix it."

"Build the new profile editor screen. Make sure empty fields, validation errors, and the success state all look right before you're done."

"Users are reporting a white flash on launch in dark mode. Find it and fix it."

"The feed is dropping frames on scroll. Find the bottleneck and fix it."

"Somebody shipped a retain cycle in the messaging flow. Track it down, fix it, and make sure it's actually gone."

"Go through every screen in the settings tab and fix any accessibility violations."

> Users are reporting the app freezes after you post a reply in a long
  thread. I can't reproduce it with short threads. Figure it out.

I'll open a long thread and try to reproduce it.

  ▸ tap(text: "Trending")
  ▸ tap(text: "Show this thread")
  ▸ scroll(direction: "down", distance: 8000)
  ▸ tap(text: "Reply")
  ▸ input_text(text: "Testing reply")
  ▸ tap(text: "Post")

Reproduced — the UI is frozen. Let me check what's blocking main thread.

  ▸ hangs()
  ⚠ Main thread blocked 4.2s
  ThreadListViewController.reloadEntireThread()
  — re-fetching all 847 replies synchronously after post

  ▸ network()
  GET /api/v1/threads/90281/replies — 3.9s, 847 items
  Called from reloadEntireThread() on main queue

Found it. After posting a reply, reloadEntireThread() re-fetches every reply
synchronously on main. Short threads are fast enough to mask it. I need to
make it fetch on a background queue and only insert the new reply locally.

  ▸ Read ThreadListViewController.swift
  ▸ Edit ThreadListViewController.swift
  ▸ build_sim()
  ▸ deploy_sim()

Rebuilt and deployed. Re-running the same steps to verify.

  ▸ scroll(direction: "down", distance: 8000)
  ▸ tap(text: "Reply")
  ▸ input_text(text: "Testing reply")
  ▸ tap(text: "Post")
  ▸ hangs()
  ✓ No main thread hangs detected

Fixed. The post callback was calling reloadEntireThread() which fetched all
847 replies synchronously on main. Changed it to append the new reply to
the data source and do a single row insert. Verified on the same long
thread — no hang after posting.

60+ tools

look · tap · scroll · swipe · gesture · input_text · toggle · navigate · back · dismiss · find · verify · screenshot · snapshot · diff · heap · layers · console · network · timeline · crash_log · vars_inspect · constraints · accessibility_audit · responder_chain · renders · perf · hangs · animations · concurrency · defaults · clipboard · keychain · cookies · coredata · storage · sandbox · flags · locale · push · orientation · appearance · dynamic_type · status · lifecycle · notifications · wait_for · wait_idle · record · script · hook · build_sim · deploy_sim

Works with Claude Code, Cursor, Claude Desktop, or any MCP client.