frida-ios-mcp
Allows interaction with TikTok app on iOS devices via Frida, enabling automated UI operations such as opening search, typing text, tapping, swiping, and taking screenshots for testing and exploration.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@frida-ios-mcp open TikTok and take a screenshot"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
frida-mcp
TypeScript stdio MCP for Frida iOS exploration — Playwright-style loop:
device_list → app_list → session_open → wait_until_texts (TikTok) / wait → screen_snapshot
→ tap(ref) / swipe / type_text → screen_snapshot → … → session_closeIf session_open hangs or Cursor cancel leaves MCP half-dead: session_status → session_force_unlock → retry one open.
Independent of fleetcontrol (agent JS copied under agent/).
Requirements
Component | Version |
Node.js | ≥ 22 |
pnpm | 9+ |
npm | 17.x (this repo pins |
iOS | same major as host npm |
USB | MVP: native USB only (no wecha TCP yet) |
Python (media only) | 3.x + |
Mismatch between host frida and phone frida-server → inject / session_open fails.
Related MCP server: ya-frida-mcp
Device prerequisites
This MCP targets jailbroken iPhones only. Without a jailbreak and a running frida-server, device_list may still show USB, but inject / touch / UI text collection will fail.
1. Supported jailbreak stacks
Stack | Notes |
Dopamine | Common; works well with RootHide. This repo’s default spawn-only path is built for this class of devices. |
Waterfall / Serotonin-family | Same requirement: a matching |
RootHide | Recommended for TikTok-like apps (reduces inject fingerprints). Do not rely on |
Not supported: stock (non-jailbroken) devices, Developer Mode alone, pymobiledevice3 without Frida, or remote TCP Frida (USB-only MVP).
2. frida-server must be running on the phone
Download
frida-serverfrom Frida releases with the same major as the host npmfridapackage (e.g. hostfrida@17.16.x→ devicefrida-server17.x).Push it to the device and
chmod +x(paths vary by jailbreak; common:/var/jb/usr/sbin/frida-serveror/usr/sbin/frida-server).Start it as root and keep it running, e.g.:
# On-device SSH / terminal (adjust path for your jailbreak)
sudo frida-server -D
# or foreground for debugging:
sudo frida-serverVerify from the PC:
pnpm cli call device_list
# or
npx frida-ps -UOnly then start the MCP. If frida-server stops, later session_open calls will fail or hang.
3. Host machine
Node.js ≥ 22;
pnpm install && pnpm buildin this repoUSB cable + trusted computer
For Photos import: Python with
pymobiledevice3, pointed to byFRIDA_MCP_PYTHON
4. TikTok / touch rules
Approach | Result |
| Touch reliable |
| Unreliable (blocked by default; |
Immediate Accessibility | Triggers anti-debug; this MCP uses safe text collection instead |
Search UI tip: prefer tool tiktok_open_search from For You (taps the top-right magnifier with retries). The top wide field is the text input ([input]). The narrow 搜尋 / 搜索 / Search label on the right is a submit button (tap after typing) — never smart_type_text it.
Spawn-only (this device stack)
RootHide / TikTok / many jailbreak setups cannot use reliable attach (touch _touchesEvent null, or process dies).
Policy | Behavior |
Default | Always |
| Forced to spawn + warning |
Escape hatch |
|
kill old pid → device.spawn(bundleId) suspended → attach(pid) → inject agent → [netEnable?] → resumeDual parallel: App + SpringBoard (+ Photos side channel)
Channel | Session field | Lock | How to open |
App (TikTok) |
|
|
|
SpringBoard |
|
|
|
Photos album |
|
|
|
Stuck open / half-dead MCP: Cursor cancel does not abort server-side Frida. If device_list works but session_open / ping hang forever, check session_status (appLockBusy, appLockWaiters), then call session_force_unlock or restart the MCP process. CLI can still open apps because it is a different Node process with its own locks.
Env | Default | Meaning |
|
| spawn/attach/inject total timeout |
|
| soft timeout for script unload/detach (won't pin the lock) |
|
| max wait to acquire appLock/sbLock |
|
|
|
| unset (on) | Default |
session_open also has a hold timeout (~open+close+5s): if Frida close/spawn hangs but the event loop is alive, the lock is released with APP_LOCK_HOLD_TIMEOUT instead of pinning forever. On hold/open timeout the server best-effort kills inFlightPid / last app pid and sets orphanFridaOpPossible. Soft-close timeout on the app channel also kills that pid (SpringBoard is never killed).
Stuck / orphan recovery: session_status (look for orphanFridaOpPossible / inFlightPid) → session_force_unlock (kills orphan pid, clears flag) → one session_open. Do not immediately re-open while orphan is set.
Held in parallel — App + SB Frida scripts; Photos is a temporary third channel.
RPCs concurrent — separate locks;
dual_ping/Promise.all([app…, sb…])run together.Not multi-app business — one app + SpringBoard; Photos is import/clear only.
photos_ never closes TikTok* — spawn Photos may briefly steal foreground.
Media import (PhotoKit, no fleetcontrol HTTP)
Requires Python 3 with pymobiledevice3 on the interpreter MCP actually runs (AFC).
MCP does not pip install for you. Missing deps → stage: afc within ~5s (preflight), not a 120s hang.
Pin the interpreter (recommended on Windows):
# CLI
set FRIDA_MCP_PYTHON=C:\Users\You\AppData\Local\Programs\Python\Python312\python.exe
"%FRIDA_MCP_PYTHON%" -m pip install pymobiledevice3Cursor / Claude MCP config example:
{
"mcpServers": {
"frida-ios": {
"command": "node",
"args": ["D:/Project/tk/frida-mcp/dist/index.js"],
"env": {
"FRIDA_MCP_PYTHON": "C:\\Users\\You\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"
}
}
}
}# image or small mp4 (prefer no other session_open during video import)
pnpm cli call photos_import_file --localPath D:\path\to\clip.mp4 --mediaType video
pnpm cli call photos_list --mediaType video
pnpm cli call photos_clearVideo tip: concurrent App sessions (e.g. Preferences open via session_open) can delay sqlite verify → needsRetry:true with a valid localIdentifier. Close other sessions and photos_list / re-import; do not treat needsRetry as silent success.
Tool | Role |
| Upload + ensure Photos + PhotoKit import (+ sqlite verify); image or video |
| Split steps for retry |
| Untrashed assets; optional |
| Trash untrashed media (Recently Deleted), optional DCIM cleanup |
AFC helper: scripts/afc_tool.py (preflight / push / list-untrashed / rm-dcim). Host = Photos.app only. SQLite query aligns with fleetcontrol (ZKIND in (0,1,2) + extensions).
session_open { bundleId: TikTok, withSpringBoard: true }
dual_ping # both channels pong at once
# later, can issue app + sb work concurrently if client allowsInstall & build
cd D:\Project\tk\frida-mcp
pnpm install
pnpm buildProduct surface
Surface | Role |
MCP ( | Interactive AI/human probe (main) |
CLI ( | Scripts / CI / one-shot |
Core ( | Shared API — not shared memory by default |
MCP vs CLI sessions (read this)
Embedded MCP = its own Node process + own
sessionStore(Cursor/Grok default).CLI = another process; cannot see the MCP session.
To share one Frida session: run the daemon, then set
FRIDA_MCP_MODE=daemonon both MCP and CLI.Without daemon, open/close in CLI is independent of Cursor.
App acts are serialized in-process (AI parallel tap+swipe will queue, not race).
# CLI (after build) — separate process unless daemon
pnpm cli help
pnpm cli open --bundleId com.ss.iphone.ugc.Ame --withSpringBoard
pnpm cli call wait --ms 4000
pnpm cli snap --limit 20
pnpm cli call net_dump --summaryOnly
pnpm cli closeOpen-source safety (net_dump defaults):
Redact Authorization / Cookie /
*Token*Drop
data:URLs (base64 images)Fold binary / octet-stream body previews
summaryOnly: truefor host counts onlyredact:false/includeDataUrls/includeBinaryBodiesonly on trusted local machines — never paste raw dumps into issues/PRs.
Typing (real input path): Feed → tiktok_open_search (preferred; retries magnifier points) → smart_type_text on the wide [input] search bar → then tap the narrow 搜尋 / 搜索 / Search submit button to run the query.
Never smart_type_text on nav tabs or on the submit 搜尋 button itself.
Nav / composer chips (e.g. “What's on your mind”) are not fields → NOT_INPUT.
SB test alert: sb_alert_trigger → sb_alert_list (hasAlert) → single sb_alert_dismiss (post-settle cleared) or stacked sb_alert_dismiss({ all: true }); if needsRetry re-list / retry all.
Debug tools (rpc_call, dump_modal, set_text_at_point): registered by default in FRIDA_MCP_TOOLS=all (prefixed [debug]). Hide with FRIDA_MCP_ALLOW_DEBUG_TOOLS=0, or hide advanced+debug with FRIDA_MCP_TOOLS=core.
Modes
Embedded (default, recommended for Grok/Cursor trial)
Session lives inside the MCP process. No daemon, no env:
node dist/index.js
# or
pnpm devDaemon + thin MCP (NSSM) — shared session with CLI
Daemon holds Frida session on
127.0.0.1:18765stdio MCP and CLI forward when
FRIDA_MCP_MODE=daemon(orFRIDA_MCP_DAEMON=1)
pnpm start:daemon
# other terminal / Cursor:
set FRIDA_MCP_MODE=daemon
node dist/index.jsGrok config (~/.grok/config.toml)
Why no args? MCP spawns command + optional args. We ship bin/frida-mcp.cmd
which already runs node dist/index.js, so config only needs the launcher path:
[mcp_servers.frida-ios]
command = "D:/Project/tk/frida-mcp/bin/frida-mcp.cmd"
enabled = trueOptional daemon mode (daemon must be running separately):
[mcp_servers.frida-ios]
command = "D:/Project/tk/frida-mcp/bin/frida-mcp.cmd"
enabled = true
[mcp_servers.frida-ios.env]
FRIDA_MCP_MODE = "daemon"Cursor mcp.json
{
"mcpServers": {
"frida-ios": {
"command": "D:/Project/tk/frida-mcp/bin/frida-mcp.cmd"
}
}
}TikTok red lines
Never
dump_tree/find_view/find_buttons/dump_login_gate(MCP gates these).Read UI only via
screen_snapshot→collectTextsWithFrames.Prefer
session_openmode=spawnfor reliable touch.After open,
wait3000–5000 ms before first snapshot.Refs only valid for last snapshot — re-snapshot after UI changes.
Tools
Tool | Purpose |
| Frida devices (default USB-only; |
| Apps + pid. Default |
| spawn | attach + inject |
| lifecycle + stuck-lock recovery ( |
| blind sleep vs poll until text; TikTok use |
| agent liveness |
| simplified |
| texts refs are generation-scoped ( |
| lockdown pixel screenshot (pymobiledevice3); visual assist — not for tap refs |
| act — swipe prefer |
| Recommended probe loop + tool tiers ( |
| Humanized per-char typing into focused field ( |
| Preferred: tap → focus → humanized typing |
| focus / clear / step-gap pause |
| double-tap like at ref/x,y |
| TikTok OTP fill ( |
| coordinate setText (not humanized; debug) |
| mid-screen modal (blocked on TikTok; debug) |
| whitelisted agent RPC ( |
| device processes (pid/name) |
| SpringBoard system alerts |
| in-process NSURLSession + TTNet/Cronet capture (TLS plaintext after app decrypt) |
| Inbox+MR read / composer reply / IM advanced / self posts / MetaSec sign I/O |
Refs expire after tap/swipe and across snapshot generations. Off-screen / zero-size nodes are marked and rejected on tap.
Humanized typing (inputText)
Agent: agent/text_input/comment.js (same approach as fleetcontrol).
MCP tool | fleetcontrol counterpart | Behavior |
|
| Field already focused; per-char |
|
| tap → wait firstResponder → |
|
| Random gap between steps (not inter-key delay) |
Default
perCharDelayMs=90; agent addsrandomDelay(base, jitter≈base)(jitter ≥ 30ms).Insert fallbacks:
insertText→replaceRange→ innerinsertText→setText+ notify.Nav tabs / chips like “Home” or “What's on your mind” are not inputs →
NOT_INPUT(session stays alive).Prefer
smart_type_texton a real field ref; usefirst_responderif unsure (canInsertText).screen_snapshotdefaults:onScreenOnly=true,limit=40; optionalsearch/showDiff.tap/swipe/smart_type_textdefaultresnapshot=true(returnssnapshot).Errors return
{ code, recovery[] }(e.g.SCRIPT_DESTROYED→ respawn).Start probes with
probe_help; prefer first-class tools over[debug]rpc_call(hide debug withFRIDA_MCP_ALLOW_DEBUG_TOOLS=0).
Network capture (reverse-engineering)
# Best: capture launch traffic (hooks before resume)
session_open {
bundleId,
captureNet: true,
netOptions: { captureMode: "all", maxBody: 16384, captureResponse: true, signTrace: true }
}
→ use app (Inbox / Profile)
→ net_dump({ redact:false, dedupe:false, query:"imapi|inbox|/im/|profile/self|security-argus", includeBinaryBodies:true, limit:100 })
→ tiktok_sign({ action: "last" }) # recent sign headers (+ backtrace if signTrace)captureMode:nsurl|ttnet|all(default).Signing model (general capability): MCP does not reimplement offline
X-Gorgon/X-Argus. Instead it uses in-process TTNet (TTNetworkManagerJSON request) so the App's MetaSec signs, andnet_*/tiktok_signcapturex-security-argus/x-Tt-Token/x-metasec-*I/O (+ optionalsignTracebacktrace).iOS sign headers (45.x):
x-security-argus,x-Tt-Token,x-metasec-*, ticket/device-guard — classicX-Gorgon/X-Argusnames are often absent on this build.Responses:
captureResponse:trueuses TTNetonReadResponseData+setIsCompleted. Do not hookonURLFetchComplete(kills script). Many JSON/IM payloads may still arrive empty via this path (protobuf/other channels); binary/CDN bodies usually populate. Prefer ttnetRequest callback JSON for posts/list APIs.DM / works URLs seen on device:
imapi-*.tiktokv.com/v1/message/get_by_user,…/v2/message/get_by_user_init,tiktok/v1/im/inbox_data/get,aweme/v1/user/profile/self, feed/post endpoints.IM inbox/reply:
tiktok_inbox— refresh Inbox + Message Requests and returnusername, real textcontent,conversationId,peerUid.tiktok_reply/tiktok_im send_text— defaultdryRun:true. WithdryRun:false, opens the real chat composer (ChatInputTextView+ 傳送), and returnssent:trueonly after the exact text is re-read from liveAWEIMTextMessagemodels (blank bubbles / network-callback-only success are rejected).transport:"sdk"is disabled.tiktok_im action: messages— list recent messages with real text when available;open_chatacceptsconversationIdorpeerUid.
Posts (
tiktok_posts): self post list via TTNet toaweme/v1/aweme/post/(overrideurl/userIdif needed).Add Phone popup attribution (no dedicated tool):
session_openwithcaptureNet+ reproduce the popupnet_dump({ query: "passport/account|bind_phone|mobile", redact:false, dedupe:false })— avoid barephone(matchesiPhonedevice_type noise)tiktok_im({ action: "phone_status" })— on this build:AWEUserService.sharedService→currentLoginUser:havePhoneNumber/isPhoneBindedfalse + emptybindPhone⇒ account unbound (not just a cosmetic prompt)canShowThirdPartyPhoneBindingPopup/is3pBindPopupRequestShowdistinguish third-party bind UI vs mandatory add-phone
Boot traffic often includes
passport/account/info/v2andpassport/token/beat/v2
RE dump tip:
redact:false,dedupe:false,includeBinaryBodies:true.
Advanced TikTok tools (not core)
Tool | Purpose |
| Read Inbox + Message Requests/notification messages (username/content/conversation ID) |
| Reply via real chat composer; succeeds only after exact text re-read |
| Advanced IM diagnostics / compatibility actions |
| Self works list (in-app signed TTNet) |
|
|
NSSM (only after device tools work)
# Admin
cd D:\Project\tk\frida-mcp
pnpm build
.\scripts\install-nssm-service.ps1
nssm start FridaMcpDaemonUninstall: .\scripts\uninstall-nssm-service.ps1
Logs: logs/daemon.stdout.log, logs/daemon.stderr.log
Typical probe
device_listapp_list→ find TikTok bundlesession_open{ "bundleId": "…", "mode": "spawn" }wait{ "ms": 4000 }ping→pongscreen_snapshot→ refstap{ "ref": "t3" }→screen_snapshotagain
Agent
Source:
agent/agent_main.js(+ imports), includespingOverride:
FRIDA_AGENT_ENTRY=path/to/agent_main.jsCompile: Frida Compiler,
projectRoot= repo root (frida-objc-bridgeinnode_modules)
License
Private / internal use.
Available Tools
53 toolsapp_listA
Enumerate apps: identifier, name, pid. Default userFacing=true filters noisy Apple services. Use runningOnly for live apps, query for name/id substring.
Inputs: udid, query, userFacing, runningOnly.
clear_textA
Clear current firstResponder text field (setText empty).
Inputs: no named parameters.
device_listA
List Frida devices. Default USB-only. Need matching frida-server on phone (17.x with this package).
Inputs: usbOnly.
double_tapC
Double-tap (like) at ref or x,y. gapMs default 140. resnapshot default true.
Inputs: x, y, ref, gapMs, +1 more.
dual_pingA
[advanced] Parallel health: app ping + SpringBoard ping at the same time (proves dual inject + concurrent locks).
Inputs: no named parameters.
dump_modalD
[debug] dumpModalView. BLOCKED on TikTok. Daily probe: do not use.
Inputs: no named parameters.
first_responderA
Current firstResponder info (className/frame/canInsertText). Check focus before type_text.
Inputs: no named parameters.
human_pauseA
Random step gap sleep (fleetcontrol human_pause). Not typing delay — use between actions.
Inputs: maxMs, minMs.
media_uploadC
[advanced] AFC upload PC file to /DCIM/100APPLE/{IMG|VID}_XXXX.ext. Needs Python + pymobiledevice3. stage=upload on failure.
Inputs: localPath (required), mediaType (required), udid.
net_clearB
[advanced] Clear captured network buffer.
Inputs: no named parameters.
net_disableA
[advanced] Stop recording new network entries (buffer retained until net_clear).
Inputs: no named parameters.
net_dumpB
[advanced] Quiet HTTP dump. Entries may include stack=nsurl|ttnet, signHeaders, query, backtrace (if signTrace). rawCount=buffer; returned(=count)=entries after filter. Default: redact, DROP data: URLs, FOLD binary, dedupe method+url. RE tip: redact:false dedupe:false query:"tiktokv" or query:"sign_header". Add Phone: query:"phone|bind|mobile|passport|verify".
Inputs: limit, query, dedupe, redact, +3 more.
net_enableA
[advanced] Start in-process HTTP capture. captureMode: nsurl | ttnet | all (default all). ttnet hooks TikTok TTHttpTaskChromium AFTER request filters (api.tiktokv.com + headers/sign fields). signTrace:true attaches module+offset backtrace on sign_header writes (MetaSec RE). captureResponse wraps TTNet onReadResponseData+setIsCompleted (stable). NSURLSession wrap skipped when TTHttpTaskChromium present. Each call RESETS opts. Typical RE: session_open({captureNet:true, netOptions:{signTrace:true}}) → use app → net_dump / tiktok_sign.
Inputs: maxBody, signTrace, urlFilter, captureMode, +1 more.
net_statusB
[advanced] Network capture status: enabled, hooksInstalled, count, options.
Inputs: no named parameters.
photos_clearA
[advanced] PhotoKit trash all untrashed image/video (Recently Deleted), verify count=0, optional DCIM source cleanup. needsRetry if leftover.
Inputs: udid, clearDcim.
photos_ensureA
[advanced] Spawn+resume Photos.app (com.apple.mobileslideshow), settle ~4s, inject photos agent. Does not close TikTok/app session. May steal foreground briefly.
Inputs: udid, settleMs.
photos_importB
[advanced] PhotoKit import already-on-device file (devicePath or remotePath under /DCIM). Host=Photos.app only. Returns localIdentifier.
Inputs: mediaType (required), udid, devicePath, remotePath, +1 more.
photos_import_fileA
[advanced] One-shot: AFC upload + Photos ensure + PhotoKit import + optional sqlite verify. Preferred for AI. Needs FRIDA_MCP_PYTHON with pymobiledevice3 (no auto pip). Missing deps → stage=afc in ≤5s. Accepts image or small mp4 (mediaType=video). Video: avoid parallel session_open other apps or expect needsRetry + photos_list.
Inputs: localPath (required), mediaType (required), udid, verify.
photos_listB
[advanced] Pull Photos.sqlite via AFC; list untrashed assets (not Recently Deleted). Optional mediaType=image|video and idPrefix/localIdentifier filter. Default = all untrashed. Needs FRIDA_MCP_PYTHON with pymobiledevice3 (fast-fail stage=afc if missing).
Inputs: udid, idPrefix, mediaType, localIdentifier.
pingB
Agent liveness probe (returns pong). Do not use wrong RPC names as probes.
Inputs: no named parameters.
press_homeA
Background current app (suspend) so SpringBoard shows. Session may remain attached to previous app.
Inputs: no named parameters.
probe_helpA
Recommended probe loop and which tools to prefer/avoid. Call first in a new session.
Inputs: no named parameters.
process_listA
[advanced] List device processes (pid, name). query is LITERAL substring only (not regex; SpringBoard ok, a|b is wrong).
Inputs: udid, limit, query.
rpc_callC
[debug] Whitelisted agent RPC. Prefer first-class tools + probe_help.
Inputs: name (required), args.
sb_alert_dismissA
Dismiss SB alert. Default policy=deny. all=false: one layer after ~300ms settle (trust cleared; empty → cleared:true rounds:0). Stacked: all=true (loop until clear or maxRounds=5). Returns cleared/remaining/rounds/needsRetry. If needsRetry or cleared=false after settle → re-list or all:true again. Do not parallel tap+dismiss.
Inputs: all, policy, maxRounds.
sb_alert_listA
List SpringBoard alerts. Live actionViewCount + actionViewCountRaw (raw may be higher; live can undercount stacks). hasAlert if either count path shows UI. After force: do not trust actionViewCount===1 — use sb_alert_dismiss({all:true}). After dismiss → app screen_snapshot.
Inputs: no named parameters.
sb_alert_tapA
[advanced] Tap SpringBoard alert button by title. Then call app screen_snapshot. Do not parallel with dismiss.
Inputs: title (required).
sb_alert_triggerA
[advanced] Create a test system alert (SBAlertItemTestRecipe). Default force=false: skip if alert already present (no stack). force:true stacks another. Next: sb_alert_list → sb_alert_dismiss({all:true}) or sb_alert_tap.
Inputs: force.
sb_closeA
Detach SpringBoard Frida session (app session_open stays open).
Inputs: no named parameters.
sb_ensureA
[advanced] Attach SpringBoard now without listing alerts. Use to warm dual session; then app tools + sb_* can run in parallel.
Inputs: no named parameters.
screen_searchB
Filter last snapshot. query default SUBSTRING; regex:true or a|b for regex. Does not touch device.
Inputs: query (required), regex.
screen_shotA
Pixel screenshot via lockdown ScreenshotService (pymobiledevice3) — NOT Accessibility, not Frida UI dump. Use when texts are sparse / visual layout unclear. Still prefer screen_snapshot for tap refs. Needs FRIDA_MCP_PYTHON + pymobiledevice3 (+ optional Pillow for JPEG). Returns image + meta.
Inputs: udid, quality.
screen_snapshotA
Read screen → generation-scoped refs (g3t8). PRIMARY read tool. Defaults: onScreenOnly=true, limit=40 (token-safe). TikTok: texts only (tree blocked). search= substring by default; a|b auto-enables regex. showDiff=true compares to previous snapshot. Do not parallelize app acts (tap/swipe/type).
Inputs: mode, limit, search, showDiff, +2 more.
screen_windowA
Key window size: {width,height,x,y,cx,cy,className}. Safe on TikTok.
Inputs: no named parameters.
session_closeA
Close app session. Default also closes SpringBoard and clears Photos side channel (photosAlive). closeSpringBoard=false keeps SB; closePhotos=false keeps Photos.
Inputs: closePhotos, closeSpringBoard.
session_force_unlockA
Emergency: reset stuck locks, detach sessions, kill in-flight/last app pid. Use when orphanFridaOpPossible or open hangs. Then ONE session_open.
Inputs: no named parameters.
session_openA
Open long-lived Frida session (spawn-only on this stack). ALWAYS uses mode=spawn: kill → spawn suspended → inject agent → resume. mode=attach is ignored/forced to spawn unless FRIDA_MCP_ALLOW_ATTACH=1 (unreliable: touch/net). TikTok: after open, wait 3–5s before screen_snapshot. Never dump_tree/find_view. spawn restarts the process (login UI state may reset). captureNet=true installs NSURLSession + TTNet hooks before resume (launch traffic). withSpringBoard=true attaches SpringBoard in parallel (dual inject; App+SB concurrent RPCs).
Inputs: bundleId (required), mode, udid, captureNet, +2 more.
session_respawnA
Force spawn+inject+resume for current bundle. Kills the app process. Prefer only when session is dead.
Inputs: no named parameters.
session_statusB
Session health: alive, refsValid/hasSnapshot, lastSnapshotGeneration, openInFlight, appLockBusy/waiters, recovery[].
Inputs: no named parameters.
set_otpA
Fill TikTok OTP (TMVerificationCodeInputView / TUXPinField). Pass full code string e.g. 123456.
Inputs: code (required), source.
set_text_at_pointB
[debug] setText at point — NOT 拟人. Prefer type_text / smart_type_text. Prefer first-class tools for daily probes.
Inputs: text (required), x, y, ref.
smart_type_textA
PREFERRED typing: tap real input (ref|x,y) → wait typable FR → 拟人逐字. Rejects chrome/chips (好友/有什麼好事). TikTok AWESearchBar may canInsertText=false but still types. Prefer wide search-bar [input]; avoid hot-search chips. retryOnFail default false. resnapshot default true.
Inputs: text (required), x, y, ref, +4 more.
swipeA
Swipe direction or path. Prefer durationMs (e.g. 280). Agent uses seconds; duration>10 is treated as ms (avoids 280→280s lock traps). Clamped ~0.15–2.5s. resnapshot default true (feed browse: set false then one snapshot).
Inputs: x0, x1, y0, y1, +4 more.
tapA
Tap by ref (gNtM) or x,y. Default resnapshot=true returns new screen_snapshot in result.snapshot. Set resnapshot=false only when chaining many acts then one snapshot.
Inputs: x, y, ref, resnapshot.
tiktok_imB
[advanced] In-process TikTok IM network runtime. action: status | conversations | inbox | send_text | messages | open_chat | peer_conversation | phone_status. inbox refreshes normal chats and Message Requests/notification pages without UI navigation; returns username + newest content. send_text defaults dryRun:true. With dryRun:false, transport defaults to the real chat composer and succeeds only after the exact text is re-read from live message models. messages: peer nickname/content/time (best-effort). open_chat: open message VC. peer_conversation: build id from peerUid.
Inputs: action (required), text, limit, dryRun, +6 more.
tiktok_inboxA
[advanced] Refresh TikTok Inbox plus Message Requests/notification entries without UI navigation. Returns username, content, conversationId, peerUid, and isMessageRequest.
Inputs: limit, timeoutMs, onlyUnread.
tiktok_open_searchB
From For You feed, open TikTok search landing by tapping the top-right magnifier (retries a few points). Not the narrow 搜尋 submit button. Then smart_type on wide [input], then tap 搜尋 to submit.
Inputs: maxTries.
tiktok_postsC
[advanced] List current user's posts via in-process TTNet (App MetaSec signs). Returns awemeId, desc, createTime, shareUrl, stats.
Inputs: url, count, cursor, userId.
tiktok_replyB
[advanced] Reply through TikTok's real chat composer. With dryRun:false, opens the chat, types into ChatInputTextView, taps 傳送, and succeeds only after the exact text is re-read from live message models.
Inputs: text (required), dryRun, peerUid, conversationId, +1 more.
tiktok_signB
[advanced] MetaSec sign observability (NOT offline Argus recompute). action: last | enable_trace. last → recent sign_header / signHeaders entries (x-security-argus, x-Tt-Token, …). enable_trace → net_enable({signTrace:true, captureMode:ttnet}). Prefer session_open captureNet+signTrace.
Inputs: limit, action.
type_textA
拟人逐字 into ALREADY-FOCUSED field (TypeTextAction). Default perCharDelayMs=90 + jitter. If field not focused → use smart_type_text instead. resnapshot default true.
Inputs: text (required), resnapshot, perCharDelayMs.
waitA
Sleep N milliseconds. Prefer wait_until_texts after TikTok session_open instead of blind wait.
Inputs: ms (required).
wait_until_textsA
Poll screen_snapshot until on-screen text matches pattern or preset (or timeout). TikTok: prefer preset "tiktok_feed" (EN/ZH-Hant/ZH-Hans/JA/KO) — do not hardcode one language. Custom pattern still allowed for page-specific probes.
Inputs: preset, pattern, timeoutMs, intervalMs, +2 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 53 tools
Tools are highly specific with detailed descriptions, making each one clearly distinct. Even overlapping areas like typing (type_text vs smart_type_text) are well-differentiated.
Most tools follow a consistent snake_case convention, but there are minor inconsistencies like screen_snapshot vs screen_shot and verb_noun vs noun_verb order. Prefixes for subdomains (tiktok_, sb_, photos_) aid clarity.
With 53 tools, the set is extensive and covers a broad domain, but the count is high compared to typical MCP servers. While many are advanced or debug-specific, it still feels heavy for a single server.
The tool set covers device management, session handling, UI interaction, network capture, and TikTok-specific features, leaving few obvious gaps. Advanced tools add depth for power users.
Related MCP Connectors
Hosted iOS app intelligence and creative research MCP server.
MCP server for Appcircle mobile CI/CD platform.
Remote MCP server exposing your Node Command fleet (files, shell, processes, browser) to AI hosts.
An authenticated remote MCP server for user-owned devices and one-shot capability invocation.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP-compliant server that enables AI systems to interact with mobile and desktop applications through Frida's dynamic instrumentation capabilities, allowing for process management, device control, JavaScript execution, and script injection.465MIT
- AlicenseAqualityCmaintenanceA comprehensive MCP server for Frida dynamic instrumentation, enabling AI agents to manage devices, processes, scripts, memory, and ADB operations.3937MIT
- AlicenseCqualityDmaintenanceA Frida MCP server for authorized dynamic analysis and application security research, exposing device/session management, script injection, process and memory inspection, and platform-specific workflows.21004MIT
- AlicenseAqualityCmaintenanceMCP server that wraps the Frida dynamic instrumentation toolkit, allowing users to attach to processes, hook functions, enumerate modules and exports, and manage scripts through natural language.101MIT