# MCP tool reference Every TV capability TVWizard exposes is an MCP tool. The relay resolves the caller's device id from the bearer token — tools never accept `device_id` from the request body, so one token only controls its own TV. Tools are namespaced `tv.*`. There are six in v0.3. --- ## `tv.list_devices` Lists TVs paired with the caller's token. In v0.3 one token = one TV, so this returns one entry; kept as a tool so clients can assume the surface is stable when multi-device lands. ### Input None. ### Output ```json { "devices": [ { "id": "living-room", "online": true } ] } ``` --- ## `tv.send_key` Sends a single key event to the TV. ### Input ```json { "key": "POWER" } ``` Valid keys: `POWER`, `HOME`, `UP`, `DOWN`, `LEFT`, `RIGHT`, `OK`, `BACK`, `VOL_UP`, `VOL_DOWN`. ### Examples *"Turn the volume down three clicks."* → three `tv.send_key VOL_DOWN` calls. *"Go back to the home screen."* → `tv.send_key HOME`. --- ## `tv.launch_app` Opens any Android Intent on the TV — deep links, package launches, arbitrary URLs. ### Input ```json { "data": "https://www.youtube.com/watch?v=jNQXAC9IVRw", "package": "com.google.android.youtube.tv" } ``` `package` is optional — omit to let Android pick the default handler for the URI. ### Common recipes | Target | `data` | `package` | |---|---|---| | YouTube search | `https://www.youtube.com/results?search_query=kurzgesagt` | `com.google.android.youtube.tv` | | Netflix title | `https://www.netflix.com/title/70025587` | `com.netflix.ninja` | | Spotify track | `spotify:track:2M9ro2krNb7nr7HSplklGG` | `com.spotify.tv.android` | | Arbitrary URL | `https://news.ycombinator.com` | *(omit)* | --- ## `tv.list_apps` Enumerates installed apps. ### Input None. ### Output ```json { "apps": [ { "package": "com.netflix.ninja", "label": "Netflix", "leanback": true }, { "package": "com.disneyplus.mea", "label": "Disney+", "leanback": true } ] } ``` `leanback: true` means the app has a proper Android-TV launcher. --- ## `tv.play_title` Resolves a natural-language title to a provider-specific deep link via TMDB → JustWatch → region-aware Android package map, then hands off to `tv.launch_app`. 60-day cache. ### Input ```json { "title": "Stranger Things", "country": "US", "season": 1, "episode": 1, "year": 2016, "provider": "netflix" } ``` All fields except `title` are optional. | Field | Notes | |---|---| | `title` | Natural-language. "Scrubs", "The Bear", "Inception". | | `country` | ISO-3166 alpha-2. Defaults to the relay's `RESOLVE_DEFAULT_COUNTRY`. | | `season` / `episode` | Omit for movies; supply for TV. | | `year` | Strongly recommended when the title is ambiguous (reboots, makings-of, franchises). Routes to TMDB's strict typed search. | | `provider` | Preferred provider name (see below). Omit to pick the first available in the country. Use `"stremio"` to bypass JustWatch entirely. | ### Known providers - `netflix`, `disney_plus`, `prime_video`, `max`, `hulu`, `apple_tv_plus`, `paramount_plus`, `peacock`, `fubo_tv`, `youtube`, `stremio`. ### Output ```json { "launched": true, "resolved": { "source": "justwatch", "provider": "netflix", "package": "com.netflix.ninja", "deep_link": "intent://...", "kind": "direct", "matched": { "tmdb_id": 66732, "media_type": "tv", "title": "Stranger Things", "year": 2016, "season": 1, "episode": 1 } }, "bridge_result": { "ok": true, "message": "" } } ``` ### Failure modes - `resolver disabled: TMDB_API_KEY is not set` — the relay wasn't configured with a TMDB key. Admin-side issue. - `title not found on TMDB` — TMDB couldn't match. Try a more specific title or pass `year`. - `no matching streaming provider for this title in this country` — JustWatch has no playable offer in that country. Try `provider: "stremio"`. --- ## `tv.observe` Reads back what's on the TV's screen. Foreground app, focused node, up to 50 visible clickable nodes with text / content-description. ### Input None. ### Output ```json { "package": "com.netflix.ninja", "activity": "FrameLayout", "focused": { "text": "Play", "content_description": "" }, "visible": [ { "text": "Play", "content_description": "", "clickable": true }, { "text": "My List", "content_description": "", "clickable": true } ] } ``` ### First-run permission `tv.observe` requires Android's AccessibilityService. The first call without it returns: ``` accessibility_not_granted: a setup prompt was just posted on the TV. Ask the user to tap it to grant accessibility, then retry tv.observe. ``` Once the user accepts, subsequent calls work. ### When to use - *"What's on my TV right now?"* — one `tv.observe`. - Auto-advance loops — `tv.send_key OK` + `tv.observe` until the focus lands where you want it. - Verifying that a `tv.launch_app` or `tv.play_title` actually did what you expected. --- ## Auth All tools require `Authorization: Bearer `. The caller's token → device mapping is bbolt-persisted server-side; tools read `auth.DeviceFromContext(ctx)` and never accept a client-supplied `device_id`. Rate-limiting applies at `/pair/init` (pairing) and `/waitlist`. Tool calls themselves are not rate-limited — the cost of a tool call is low and the token gates who can call. ## Caching `tv.play_title` results are cached for 60 days keyed on (title, country, season, episode, year, provider). A disambiguated call (`year` set) does not hit the un-disambiguated cache entry and vice versa. ## See also - [Quickstart](quickstart.md) — end-to-end setup - [claude-desktop.md](claude-desktop.md) — wiring into Claude Desktop - [troubleshooting.md](troubleshooting.md) — when things break