tiktok

SKILL.md

TikTok — Navigation via chrome-bridge MCP

TikTok is a client-rendered React SPA on top of a logged-in Chrome session. The bridge profile owns the cookies. Do not attempt programmatic login. If /profile ends up at /login, stop and tell the user — the session expired.

TikTok's stable selector hooks are data-e2e="..." attributes; CSS class names contain rotating hashes (e.g. epprvxn10) and should only be matched as substrings via [class*="..."].

How this skill is laid out

Each capability has a focused JS file under scripts/. To use one:

  1. Read the script file to load it as a string.
  2. Pass the contents as the code argument to mcp__chrome-bridge__execute_script.
  3. The script returns a structured object — see the header comment at the top of each file for the exact return shape.

Scripts are pure JS, no escaping needed; the comment block at the top of each file documents pre-conditions and return shape so you don't need to grep through the body.

Capability map

Task Pre-condition Script Notes
Verify login navigate('https://www.tiktok.com/profile') + 3-4s scripts/login_check.js URL after redirect is the authoritative signal — do NOT rely on top-login-button absence.
Run a search navigate('https://www.tiktok.com/search?q=' + encodeURIComponent(query)) + 4-5s scripts/search_results.js Direct URL only. The DOM input flow is React-controlled and dom_click on the submit button silently no-ops. Tab variants: /search/video?q=, /search/user?q=.
Video stats + author profile stats (single nav) navigate('https://www.tiktok.com/@<handle>/video/<id>') + 5s scripts/video_metadata.js Reads from rehydration JSON — exact integer counts, includes playCount (not in DOM) and full author stats.
Video stats — DOM fallback same as above scripts/video_metadata_dom.js + scripts/parse_count.js Use only if the rehydration JSON path returns ok:false. Display strings need parseCount.
Author profile stats navigate('https://www.tiktok.com/@<handle>') + 5s scripts/profile_stats.js Skip this nav if you already have a video page open — video_metadata.js returns the same author stats.
Author profile stats — DOM fallback same as above scripts/profile_stats_dom.js + scripts/parse_count.js Display strings; use parse_count.js.
Author latest N videos navigate('https://www.tiktok.com/@<handle>') + 5s scripts/profile_latest_videos.js The post grid is NOT in the rehydration JSON — DOM scrape is the only path. Filters out pinned posts so first 3 are truly latest.
All comments + replies (preferred) navigate('https://www.tiktok.com/@<handle>/video/<id>') + 5s scripts/comments_api.js Calls TikTok's web API directly. No _signature needed; cookies + device_id are enough. ~100× faster than DOM scraping.
All comments — DOM fallback navigate + click [data-e2e="comment-icon"] + 2s scripts/comments_dom_extract.js + scripts/comments_dom_step.js Use only if the API path returns ok:false. Loop pattern below.

DOM comments fallback loop

When comments_api.js returns { ok:false }, drive the DOM fallback like this:

# Pre-condition: comment panel open (dom_click on [data-e2e="comment-icon"], wait ~2s)
seen = {}
plateau = 0
HARD_CAP = 1000
MAX_PASSES = 80

for i in range(MAX_PASSES):
    execute_script(code=<contents of scripts/comments_dom_step.js>)
    # wait 2-3s — too fast and TikTok skips a load batch
    rows = execute_script(code=<contents of scripts/comments_dom_extract.js>)
    before = len(seen)
    for r in rows:
        seen.setdefault(r['key'], r)    # synthesized dedup key
    if len(seen) == before:
        plateau += 1
        if plateau >= 2:                  # two passes with zero new rows → done
            break
    else:
        plateau = 0
    if len(seen) >= HARD_CAP:
        break
comments = list(seen.values())

Each pass adds roughly 20 top-level comments. Coverage check: if the sum of level-2 rows under a parent is less than the parent's replyCountAdvertised, you haven't expanded everything yet — keep stepping.

Why the API path doesn't need _signature

TikTok's web app reads comments through /api/comment/list/ and /api/comment/list/reply/. Both are GET endpoints. No _signature / X-Bogus is required for these read paths — auth rides entirely on session cookies (msToken, ttwid, etc.) plus a single device_id URL param. Because chrome-bridge runs inside a real logged-in Chrome session, fetch(..., credentials: 'include') from execute_script carries those cookies automatically. device_id is read from the rehydration JSON (webapp.app-context.wid), with localStorage.ttwid as fallback.

Verified URL shape:

GET https://www.tiktok.com/api/comment/list/?aweme_id=<id>&cursor=<n>&count=<k>&aid=1988&device_id=<wid>
GET https://www.tiktok.com/api/comment/list/reply/?item_id=<id>&comment_id=<cid>&cursor=<n>&count=<k>&aid=1988&device_id=<wid>

If TikTok ever adds signature gating, the API path will return status_code != 0 — fall back to the DOM path.

Critical rules

  1. Stable selectors only. data-e2e="..." is stable; class names rotate per deploy and must be substring-matched via [class*="..."].
  2. Direct-URL search > input flow. Navigate to /search?q=... rather than typing in the search box.
  3. For comments, prefer the API path. Only fall back to DOM if the API returns status_code != 0.
  4. DOM comments dedup. No comment ID exists in the DOM (verified — wrappers carry only a class attribute). Use the synthesized key from comments_dom_extract.js.
  5. /profile redirect is the login signal. top-login-button is rendered on public video pages even when logged in; ignore it.
  6. Wait 4–6s after navigation. TikTok hydrates slowly, especially the first time.
  7. Never log in programmatically. If /profile lands at /login, stop and tell the user.
  8. Close tabs when done. TikTok pages are heavy (autoplaying video).

Common failures

Signal Cause Fix
Search button click does nothing React form handler expects keypress Navigate to /search?q=... directly
comments_api.js returns status_code != 0 Stale cookies, bot-detection, or signature gating Fall back to the DOM loop
Zero comments via DOM path Comment panel hasn't been opened dom_click('[data-e2e="comment-icon"]') then wait ~2s
DOM comment count plateaus at 20 Comment list isn't being scrolled comments_dom_step.js scrolls the right ancestor; window scroll doesn't work
comment-level-1 text is empty Sticker / image-only comment Expected — keep the row, accept null text
/profile lands on /login Session expired Stop; ask user to re-authenticate Chrome manually
DOM empty right after navigate Page not hydrated yet Wait 5–6s; re-query
Profile page rehydration JSON missing post list TikTok loads it via async API after first paint Use profile_latest_videos.js (DOM scrape) instead

Example: top 5 comments on the first cat-video search result

# 1. Search
navigate(url='https://www.tiktok.com/search/video?q=' + encodeURIComponent('cat videos'))
# wait ~5s
results = execute_script(code=<contents of scripts/search_results.js>)
firstUrl = results[0]['url']

# 2. Open the video and pull all comments via the API
navigate(url=firstUrl)
# wait ~5s
out = execute_script(code=<contents of scripts/comments_api.js>)

if out['ok']:
    top5 = out['topLevel'][:5]
else:
    # API path failed; fall back to DOM loop (see "DOM comments fallback loop" above)
    ...
Installs
2
First Seen
13 days ago