punchlist-mcp
v0.8.0
Published
MCP companion server for Punchlist — discover a Punchlist iOS device on your LAN and pull voice-narrated screen sessions (screenshots + synchronized transcripts) straight into Claude or any MCP client.
Maintainers
Readme
punchlist-mcp
Talk through your app. Ship the fixes.
punchlist-mcp is the companion Model Context Protocol
server for Punchlist — the iOS app
that records your screen while you narrate, then hands your AI a clean, synchronized
list of screenshots + transcripts to fix.
This server runs on your computer, discovers your Punchlist phone on the local network over Bonjour/mDNS, and exposes MCP tools so Claude (Desktop or Code) — or any MCP client — can say "grab the latest punchlist from my phone" and iterate.
Nothing touches a DoMore Tech server — there is no account and no backend. By
default everything stays on your LAN; optionally, connect_icloud adds your
own private CloudKit database as a second source so sessions synced from the
phone can be pulled even when it's off-LAN (on cellular).
Requirements
- Node.js 20+
- A phone running Punchlist with the in-app LAN server enabled, on the same Wi-Fi network as this computer.
Install / run
You don't need to install anything globally — MCP clients launch it via npx:
npx -y punchlist-mcpThat command starts the server on stdio. You normally never run it by hand; you point your MCP client at it (see Client configuration).
Pairing walkthrough
Pairing happens once per device. The token is stored on both ends.
In Punchlist on your phone, open the Connect tab and flip on Turn on sharing (step 2 of the guided flow); the card shows a 6-digit pairing code.
In your MCP client, discover the phone:
"List my Punchlist devices."
This calls
list_devicesand returns something like:{ "count": 1, "devices": [ { "name": "Johns-iPhone", "host": "Johns-iPhone.local", "port": 7443, "addresses": ["192.168.1.42"], "txt": { "device": "Johns-iPhone", "version": "1", "tls": "1", "fp": "…" }, "paired": false } ] }Pair using the code from step 1:
"Pair with Johns-iPhone, the code is 482137."
This calls
pair_devicewith{ device_name: "Johns-iPhone", code: "482137" }. On success the token is written to~/.punchlist-mcp/config.json(along with the device's TLS certificate fingerprint, pinned trust-on-first-use) and the device becomes the default for the other tools.
Now you can:
"What punchlists are waiting on my phone? Fetch the newest one and walk each item."
Tool reference
| Tool | Input | What it does |
|---|---|---|
| list_devices | timeout_ms? | mDNS scan of _punchlist._tcp; returns devices + whether each is paired. |
| pair_device | code, device_name?, host?, port? | Exchanges the app's 6-digit code for a token (and pins the device's TLS fingerprint). Give device_name (resolved via mDNS) or an explicit host. |
| set_preferred_device | device | Sets the default device used when device is omitted. |
| connect_icloud | environment?, api_token?, container?, disable? | Opens the Apple ID sign-in in your browser and adds your private CloudKit database as a second source (works off-LAN). |
| export_icloud_credentials | environment?, api_token?, container? | Same sign-in flow, but mints a separate token to set as env vars in a remote environment (see below). |
| list_pending_punchlists | device?, app? | Unprocessed sessions (GET /mcp/pending), optionally scoped to one app. |
| list_apps | device? | Apps registered on the device, with session/pending counts and standing instructions. |
| add_app | app_name, icon_path?, instructions?, instruction_files?, device? | Registers an app on the device's Apps tab (optionally with icon + instructions in one call). |
| set_app_instructions | app_name, instructions? or instruction_files?, append?, keep_synced?, device? | Standing processing instructions for an app; can sync from local .md files. |
| upload_app_icon | app_name, icon_path, keep_synced?, device? | Pushes the app's icon PNG so the device's picker shows the real icon. |
| list_punchlists | device? | All sessions (GET /punchlists). |
| get_punchlist_manifest | punchlist_id, device? | Just the manifest.json, no media download. |
| fetch_punchlist | punchlist_id, device?, include_video? | Downloads + unzips into the cache, returns the manifest plus absolute local paths for every screenshot (and the video if include_video: true). |
| mark_punchlist_processed | punchlist_id, device?, resolution_note?, user_confirmed | Marks a session consumed (POST /punchlists/{id}/processed) — requires the user's explicit yes. For tester feedback sessions, resolution_note is a short line shown in the tester's app next to the Reviewed status. |
| diagnose_processed_sync | punchlist_id? | Debugging aid: traces why a processed mark isn't reaching the phone/cloud. |
device? accepts a paired device name. When omitted, the preferred device is
used (or the only paired device). Every session has a stable five-character
code (also shown in the app's session list; derived from the id, never
stored) — the punchlist_id params accept either the code or the full id.
fetch_punchlist defaults to the lightweight screenshots-only bundle; pass
include_video: true to also pull recording.mp4. When iCloud is connected,
the punchlist tools fall back to the cloud source automatically if the phone
isn't reachable on the LAN.
Typical flow
list_devices ──► pair_device (once) ──► list_pending_punchlists
│
▼
fetch_punchlist ──► (read screenshots + manifest)
│
▼
mark_punchlist_processedClient configuration
Claude Code — .mcp.json
Add to .mcp.json in your project root (or ~/.claude.json):
{
"mcpServers": {
"punchlist": {
"command": "npx",
"args": ["-y", "punchlist-mcp"]
}
}
}Claude Desktop — claude_desktop_config.json
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"punchlist": {
"command": "npx",
"args": ["-y", "punchlist-mcp"]
}
}
}Restart the client after editing. On first launch npx downloads the package;
subsequent starts are instant.
Remote sessions (cloud sandboxes)
The repo root carries a .mcp.json with the config above, so any cloud sandbox
or remote session (e.g. a Claude remote session / CI) that clones this repo gets
the MCP installed automatically. Remote environments have no LAN, so only the
iCloud source works there — the LAN tools (list_devices, pair_device, …)
are unavailable remotely by nature.
To give a remote environment iCloud access, run the export_icloud_credentials
tool on your local machine (it opens the Apple ID sign-in in your browser and
mints a token independent of your local one), then set the returned values as
environment variables in the remote environment:
PUNCHLIST_ICLOUD_TOKEN— the web-auth token (account-scoped; treat it like a password).PUNCHLIST_ICLOUD_ENV— set todevelopmentfor phones running an Xcode-installed build; the default isproduction(TestFlight / App Store). Needed until the TestFlight/App Store build ships.PUNCHLIST_ICLOUD_API_TOKEN/PUNCHLIST_ICLOUD_CONTAINER— only needed for non-default tokens/containers.
The MCP seeds its config from these variables on first use and keeps the token fresh (CloudKit rotates it) for the life of the sandbox.
Config file format
State lives in ~/.punchlist-mcp/:
~/.punchlist-mcp/
├── config.json # paired devices + tokens (chmod 0600)
└── cache/ # unzipped punchlists, per device/session
├── Johns-iPhone/
│ └── 2026-07-08_1315/
│ ├── manifest.json
│ ├── 001_screen-a.png
│ └── ...
└── cloud/ # sessions fetched from the iCloud source
└── 2026-07-08_1315/…config.json:
{
"devices": {
"Johns-iPhone": {
"name": "Johns-iPhone",
"host": "Johns-iPhone.local",
"port": 7443,
"token": "…pairing token…",
"scheme": "https",
"certFingerprint": "…pinned sha-256 of the device's TLS cert…",
"addresses": ["192.168.1.42"],
"pairedAt": "2026-07-08T13:20:00.000Z"
}
},
"preferredDevice": "Johns-iPhone"
}Running connect_icloud adds a cloud section (container, environment, and
the rotating web-auth token); offline mark_punchlist_processed calls queue
under pendingMarks and flush on the next contact with the phone.
The file is written 0600 because it contains pairing tokens. You can safely
hand-edit it (e.g. to remove a device) or delete it to reset all pairings.
Troubleshooting
No devices found (list_devices returns nothing).
- Confirm Punchlist is open and the LAN server is enabled in the app.
- Confirm the phone and this computer are on the same Wi-Fi (not a guest network, and not one with "client isolation" / "AP isolation" enabled).
- Windows firewall frequently blocks mDNS (UDP 5353). Allow Node.js through
the firewall for private networks, or add an inbound rule for UDP 5353. Some
corporate networks block multicast entirely — in that case pair by explicit
host:
pair_devicewithhost: "<phone-ip>"(the app shows its IP). - VPNs and virtual adapters can hijack the multicast interface; disconnect the VPN while pairing.
iOS local-network permission.
- Punchlist must be granted Local Network access on the phone (iOS prompts on first use). If you denied it, enable it under Settings → Punchlist → Local Network, then reopen the app.
Found the device but requests fail / time out.
- The phone may have slept or dropped the server. Reopen Punchlist.
.localmDNS name resolution can be flaky on some Windows setups; pair using the phone's IP address ashostinstead.
Pairing code rejected (HTTP 403).
- Check the code against the one currently shown on the Connect tab — it changes
when the user taps Rotate (which also invalidates old tokens). Repeated wrong
codes rate-limit
/pair(HTTP 429) for up to 15 minutes.
Wrong device selected.
- With multiple paired devices, pass
device: "<name>"explicitly, or editpreferredDeviceinconfig.json.
License
MIT © DoMore Tech LLC
