mcp-server-shieldtv
Provides tools for controlling an NVIDIA Shield TV over the Android TV Remote protocol v2: pressing an allow-listed set of remote keys (D-pad, media, volume), launching allow-listed apps by friendly name, waking or sleeping the device, and reading its power state, foreground app, volume, and device info.
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., "@mcp-server-shieldtvwake up the Shield and launch Netflix"
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.
mcp-server-shieldtv
An MCP server for the NVIDIA Shield TV, built on the Android TV Remote protocol v2 (the same protocol the Google TV phone app uses). It lets an MCP client such as Claude press remote keys, launch apps, wake or sleep the Shield, and read its power state and foreground app.
Status. Pairing,
get_status, keys,launch_app,set_powerand the ADB tools were verified on a real Shield (remote service 7.00) in 2026-10. The features added since (pairing retries, reconnect and rediscovery hardening, dry run,doctor, resources and prompts, HTTP, the Alpine service) are verified against the simulator only, a wire-level fake that the real protocol library pairs and connects with. Verification status lists every protocol assumption and whether a Shield has confirmed it; HARDWARE_VALIDATION.md is the checklist.
It's one of four sibling servers with the same conventions (dry run, doctor, typed results,
Streamable HTTP behind Cloudflare Access):
mcp-server-onkyo,
mcp-server-harmony and
mcp-server-sofabaton.
This is not an official NVIDIA project, and NVIDIA publishes no MCP server for the Shield.
Why this protocol
Remote protocol v2 (this project) | ADB | |
Setup | One-time pairing with an on-screen code | Enable Network debugging on the Shield |
Good for | D-pad, media keys, app launch, power/app state | Deep device inspection |
Risk if exposed to an LLM | Bounded: it can only press keys | High: |
Three optional ADB tools do what the remote protocol can't: get_now_playing,
get_remotes and reboot_shield. They are off until you run adb-setup, and they run
only fixed commands; the server never exposes a raw shell (see
ADB tools).
Related MCP server: mcp-gtv
Install
git clone https://github.com/SDNick484/mcp-server-shieldtv.git
cd mcp-server-shieldtv
python -m venv .venv && . .venv/bin/activate
pip install -e .Requires Python 3.11+.
Pair with your Shield (once)
The Shield and the machine running the server must be on the same network, and the machine must be able to reach the Shield on these ports:
Port | Used for |
TCP 6466 | Remote control (every tool call) |
TCP 6467 | Pairing (only during |
UDP 5353 (mDNS) |
|
TCP 5555 | ADB, only if you enable the ADB tools with |
A firewall, a guest network, or a separate IoT VLAN between the two will show up as "can't reach the Shield".
mcp-server-shieldtv discover # optional: list Android TV devices via mDNS
mcp-server-shieldtv pair # finds the Shield, or: pair --host 192.168.1.50A code appears on the TV; type it into the terminal. A typo, a rejected code or Cancel on
the TV starts over with a new code (three tries). Pairing generates a client certificate
and key under ~/.config/mcp-server-shieldtv/ (override with SHIELDTV_CONFIG_DIR) and
saves the Shield's address, name and MAC in config.json. The MAC is how the server finds
the Shield again if its address changes. Unusable old credentials are set aside
(cert.pem.broken) and replaced. A running server notices a new pairing and reconnects
with it; it doesn't need a restart.
Then check everything between this machine and the Shield:
mcp-server-shieldtv doctorShield 192.168.1.50
ok config paired with SHIELD Android TV (MAC xx:xx:xx:xx:B2:C3) at x.x.x.50; credentials load and are private
ok tcp ports 6466 and 6467 accept connections
ok identity SHIELD Android TV, MAC xx:xx:xx:xx:B2:C3
ok session NVIDIA SHIELD Android TV (remote service <version>); on, com.google.android.tvlauncher in front, ...doctor checks each layer in order and stops at the first failure, saying what it means and
which HARDWARE_VALIDATION.md step covers it. It never presses a
key, launches anything or pairs. Output is redacted (addresses, MACs) unless --no-redact.
Treat cert.pem and key.pem like a password. Anyone who has them can control your
Shield. The directory is created 0700 and the files 0600, and .gitignore excludes
them. To revoke access, remove the device from the Shield's settings and re-pair.
WSL2 note: mDNS discovery needs mirrored networking (networkingMode=mirrored in
%UserProfile%\.wslconfig, then wsl --shutdown). Otherwise pass --host.
ADB tools (optional)
The remote protocol knows which app is in front, but not what it's playing, whether your Bluetooth remotes work, or how to reboot. ADB can do all three. To enable the ADB tools:
On the Shield: Settings > Device Preferences > About, select Build seven times, then Developer options > Network debugging on.
Run
mcp-server-shieldtv adb-setup(afterpair). The TV asks "Allow USB debugging?": tick "Always allow from this computer" and choose Allow.
adb-setup creates its own ADB key (adbkey, adbkey.pub, 0600) next to the pairing
files and sets "adb": true in config.json. The ADB key grants a shell on the Shield;
guard it more carefully than the pairing files. To revoke it, use Developer options >
Revoke USB debugging authorizations.
get_now_playing reports, checked on a real Shield: the app, title, subtitle (the artist for music,
the channel for live TV), play state, and position. Live TV (YouTube TV, Sling) reports no
position, since its "position" is an offset into the stream. Music played from YouTube Music
shows up as the YouTube app. Duration and album aren't available this way. A title that
contains ", " can rarely split wrong (the system prints title and artist joined by ", ").
get_remotes lists Bluetooth remotes (a Harmony hub shows up as "Harmony Keyboard")
(every paired input device, even ones that haven't connected since the Shield started)
as working, disconnected (normal when not in use), or stuck: Bluetooth says it is
connected, but Android never created its input device, so its buttons do nothing. This
happened to both remotes on a real Shield after a reboot. The fix for a Harmony hub is to
press Off and start the activity again.
reboot_shield notes which remotes work, restarts the Shield, and waits until it has
booted (about 35 seconds on a real Shield) plus 20 seconds for remotes that reconnect on
their own. It reports any remote that came back stuck, with the fix, and any that worked
before but hasn't reconnected yet. A Harmony hub doesn't reconnect until a button is
pressed, so expect it to be listed that way; if it then does nothing, get_remotes says
whether it's stuck. The whole call takes about a minute. It is marked destructive, so MCP
clients ask before running it.
Use it with an MCP client
{
"mcpServers": {
"shieldtv": { "command": "/path/to/mcp-server-shieldtv/.venv/bin/mcp-server-shieldtv" }
}
}For Claude Code: claude mcp add shieldtv -- /path/to/.venv/bin/mcp-server-shieldtv.
HTTP and Cloudflare Access
To share one server between Claude Code, Claude Desktop and the mobile app, run it as a service and reach it through a Cloudflare Tunnel with Access in front:
CF_ACCESS_TEAM_DOMAIN=<team>.cloudflareaccess.com CF_ACCESS_AUD=<aud tag> \
mcp-server-shieldtv --http --public-host mcp.example.com # 127.0.0.1:8712/shieldtv/mcpFlag | Variable | Default |
|
|
|
|
|
|
|
|
|
|
| none: only loopback Host headers pass |
| none: no Access checks | |
| anyone Access lets in |
With Access configured, every request needs a valid Cf-Access-Jwt-Assertion: the signature
(the team's published keys), audience, issuer, expiry, and the email allow-list if set.
Anything else gets a plain 403. The server refuses to start on a non-loopback address
without Access unless you pass --insecure-no-auth (for trusted-LAN testing only). It also
checks Host and Origin (DNS-rebinding protection), including on --bind 0.0.0.0.
GET /healthz answers without auth. src/shieldtv_mcp/remote.py is identical in all four
servers.
As a service on Alpine (Proxmox LXC)
sh deploy/alpine/install.sh # as root; or: install.sh /path/to/checkout
su -s /bin/sh mcp-shieldtv -c 'SHIELDTV_CONFIG_DIR=/var/lib/mcp-server-shieldtv \
/opt/mcp-server-shieldtv/venv/bin/mcp-server-shieldtv pair --host <shield ip>'
vi /etc/mcp-server-shieldtv/env # HTTP and Access settings
rc-update add mcp-server-shieldtv default && rc-service mcp-server-shieldtv startPath | Owner, mode | Holds |
| root, 0755 | The code |
| root:mcp-shieldtv, 0640 | HTTP and Access settings |
| mcp-shieldtv, 0700 (files 0600) |
|
| mcp-shieldtv, 0750 | Logs, redacted |
| root, 0755 | OpenRC script ( |
The credentials live in /var/lib, owned by the unprivileged mcp-shieldtv user, because
they're the service's own: pair runs as that user, and the server rewrites config.json
when the Shield moves. Every dependency has a musl wheel, so nothing compiles.
deploy/alpine/smoke-test.sh runs the whole install in a python:3.12-alpine container
against the simulator, and CI runs it on every push.
To point it at a particular Shield without editing config.json, pass the host as an
environment variable: claude mcp add shieldtv -e SHIELDTV_HOST=192.168.1.50 -- ....
That Shield must already be paired with the same credentials (pair --host <ip> once per
Shield; note that pair also saves its host as the default in config.json).
Configuration
Setting | Default | What it does |
| (from | The Shield's IP address or hostname |
|
| Where the certificate, key and |
|
| Standard base directory, used when |
| off | Read the Shield, send nothing that changes anything ( |
| off | Log the protocol traffic to stderr ( |
| off | Show LAN addresses and MACs in logs ( |
config.json (written by pair, safe to edit by hand):
Key | What it does |
| The Shield's address, saved by |
| The Shield's name and MAC, read from its certificate by |
| Extra or overriding app names for |
|
|
|
|
Problems in config.json (invalid JSON, a bad app entry) are logged at startup and shown by
doctor, and only the bad part is ignored.
When the host is set in more than one place, the most specific wins: pair --host, then
SHIELDTV_HOST, then config.json.
Tools
Tool | What it does |
| Reachability, power ( |
| The app names |
| Press an allow-listed remote key, optionally repeated 1-10 times (see below) |
| Launch an allow-listed app by friendly name, and confirm it reached the foreground |
| Wake ( |
| App, title, subtitle (artist or channel), play state and position. Needs |
| Each Bluetooth remote and whether it works, with a fix for stuck ones. Needs |
| Restart the Shield, wait until it's back, then check the remotes. Needs |
Every tool returns structured content with a published output schema. The actions
(send_key, launch_app, set_power, and reboot_shield's first fields) return the same
ActionResult shape as the sibling servers:
{"shield": "SHIELD Android TV", "outcome": "done",
"detail": "Launched netflix (com.netflix.ninja is in the foreground)",
"sent": ["app_link https://www.netflix.com/title"], "warnings": []}outcome is done, unchanged (e.g. set_power off while already in standby: nothing is
sent) or dry_run. Failures are MCP errors with a sentence that says what to do.
Dry run (serve --dry-run, call --dry-run or SHIELDTV_DRY_RUN=1) reads the Shield and
validates every call as usual, but sends nothing; results say what would have been sent.
Resources and prompts
Resources are context a client attaches (in Claude Code: @shieldtv:shieldtv://apps). Reading
them sends nothing to the Shield.
Resource | What it holds |
| The apps |
| The keys |
Prompts are workflows you pick (in Claude Code: /mcp__shieldtv__watch netflix).
Prompt | Arguments | Does |
|
| Wakes the Shield, opens the app, navigates with the D-pad while asking you what's on screen |
| Runs |
Movie night across servers
The servers don't know about each other: each works alone, and a client with several connected composes them. With all four connected, "movie night in the theater: Plex on the Shield, receiver in Dolby Surround at 45" becomes (tool names as of these versions):
harmony.start_activity activity="Watch Shield" # TV and receiver on, inputs switched
shieldtv.launch_app app="plex"
onkyo.set_listening_mode receiver="Theater" mode="dolby-surround"
onkyo.set_volume receiver="Theater" level=45 # capped server-side if above the capWhat makes this safe to hand to a model is that limits live in each server, not in the prompt: only allow-listed keys and apps here, and the volume cap and never-guess-a-receiver rule in Onkyo.
Allowed keys: HOME, BACK, MENU, DPAD_UP/DOWN/LEFT/RIGHT/CENTER,
MEDIA_PLAY_PAUSE, MEDIA_PLAY, MEDIA_PAUSE, MEDIA_STOP, MEDIA_NEXT,
MEDIA_PREVIOUS, MEDIA_REWIND, MEDIA_FAST_FORWARD, VOLUME_UP, VOLUME_DOWN,
VOLUME_MUTE.
Apps
Default apps, each checked on a real Shield: youtube, youtube-tv, netflix,
prime-video, disney+, hulu, plex, spotify.
Apps launch through deep links: https links, or an app's own scheme (plex://,
spotify:) where the https link would go to a browser instead. On the Shield (remote service 7.x), a bare
package name is sent as market://launch?id=<package>, which the Shield rejects and then
drops the connection. launch_app waits for the app to reach the foreground (up to 10s)
and returns an error that says what happened if it doesn't: the request was rejected, or it
was accepted but nothing opened (the app isn't installed, or no app handles the link; the
TV shows "You don't have an app that can do this").
Add your own apps
Edit ~/.config/mcp-server-shieldtv/config.json:
{
"host": "192.168.1.50",
"apps": {
"crunchyroll": { "link": "https://www.crunchyroll.com", "package": "com.crunchyroll.crunchyroid" },
"example": "https://example.com/tv"
}
}An entry is either a link, or { "link", "package" }. Adding the package is recommended:
launch_app then confirms that exact app opened, and get_status shows the friendly name.
Without it, any app other than the home screen coming to the front counts as success.
Names are case-insensitive, and an entry with the same name as a default replaces it.
Restart the server (or your MCP client) to pick up changes.
Finding the link and package: open the app on the Shield, then call get_status (or ask
"what app is open on the Shield?"); current_app_package is the package. For the link, try
the service's website address (https://www.<service>.com). With ADB enabled, Android can
tell you which app a link opens without launching anything:
adb shell cmd package query-activities --brief -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE -d 'https://tv.youtube.com'A result of com.google.android.tv.frameworkpackagestubs/.Stubs$BrowserStub means "no app"
(the TV shows "You don't have an app that can do this"). If the stub is listed alongside the
app, the app usually still opens, but its own scheme (if it has one) avoids the ambiguity.
Safety design
Keys are an allow-list. The tool schema is an enum, and the client re-checks it.
POWER,SEARCH(starts voice capture),SETTINGS,MUTE(Android's microphone mute), raw numeric key codes, and the library'stext:typing are not available.Apps are an allow-list. Only names in
list_appscan be launched.No shell and no arbitrary key codes, so a prompt-injected model has a small blast radius. ADB is off by default. When enabled, the ADB tools take no arguments and run only constant commands (
dumpsys media_session,dumpsys bluetooth_manager,dumpsys input,getprop sys.boot_completed,/proc/uptime), plus ADB's own reboot service. Code that tries any other command is refused, so nothing the model writes reaches the Shield's shell.reboot_shieldis the only tool marked destructive: it interrupts playback, so clients should confirm with you first.Credentials are private (
0600) and never logged. Logs go to stderr because stdout belongs to the MCP transport.Tools carry titles and MCP annotations (
readOnlyHint,destructiveHint,idempotentHint,openWorldHint) so clients can decide what needs confirmation.
Behavior notes
The server keeps one long-lived connection and caches pushed state. If the Shield is asleep or offline at startup it retries in the background (1 s doubling to 60 s), and tool calls return a clear "can't reach the Shield" message in the meantime. Each attempt is bounded (15 s), so a Shield that accepts the connection but never starts a session can't hang the server.
While disconnected,
get_statusstill shows the last known values, markedstale: true, withas_of(when they arrived) anderror(why it isn't reachable).If the Shield moves to another address (DHCP), after 60 s unreachable the server looks for it over mDNS and checks each candidate's certificate for the MAC that
pairsaved. Only an exact match is adopted, and saved toconfig.json. Any other device is never guessed at.If the Shield stops trusting the certificate (unpaired on the TV, factory reset), the error says to re-run
pair, and the running server picks up the new pairing by itself.Volume keys act on whatever the Shield is configured to control (the Shield itself, HDMI-CEC, or IR), so results depend on your setup. When volume goes to a TV or receiver over CEC, the Shield doesn't report a level, and
get_statusreturnsvolume: null.
Troubleshooting
The server's errors are written to be actionable, so the model will usually relay one of these:
"Not paired with a Shield yet." No host or credentials were found. Run
mcp-server-shieldtv pair. If you did pair, check that the server and pair use the same
SHIELDTV_CONFIG_DIR (an MCP client may launch the server with a different environment).
"The Shield rejected our pairing." The Shield no longer trusts this certificate,
typically after a factory reset or after removing the device under the Shield's
remote/connected-device settings. Run pair again; a running server reconnects by itself.
"Can't connect: the pairing files ... can't be used." cert.pem or key.pem is
damaged. Run pair again; it sets the old files aside.
"Can't reach the Shield at ..." The server is paired but has no connection. The Shield
may be asleep, rebooting, or off the network, its IP may have changed (a DHCP reservation
helps), or TCP 6466 may be blocked (see the port table above). The server keeps retrying in
the background, so the next call may succeed without a restart. If the address changed and
config.json has the Shield's mac (saved by pair since this version), the server finds
it again by itself; otherwise re-pair with the new address. doctor tells these cases apart.
"The connection to the Shield dropped; try again in a moment." The connection closed during the command. The library reconnects on its own; retry.
"The Shield rejected the launch request ..." The app entry is a bare package name (or a link the Shield refuses). Use an https link; see Apps.
"The Shield accepted ..., but the foreground app didn't change" The app is probably not installed, or nothing on the Shield handles that link. A very slow cold start can also do this; the next call then reports the app as already open.
pair says "No input to read the code from". It was run without a terminal (for
example through a tool that doesn't attach one). Run it in a regular terminal.
discover (or pair without --host) finds nothing. mDNS doesn't cross most VLANs or
guest networks, and on WSL2 it needs mirrored networking (see above). Pass --host <ip>;
the Shield shows its IP in its network/about settings, and your router's client list has
it too.
Server logs (connection attempts, retries, auth failures) go to stderr, which most MCP
clients save in their own logs. Running mcp-server-shieldtv in a terminal shows them
directly; it waits for MCP messages on stdin, so stop it with Ctrl+C.
A session against the simulator
simulate runs a simulated Shield: TLS on two ports, speaking Android TV Remote v2 the way
androidtvremote2 does. The real library pairs and connects with it. Pair with it exactly as
with a real one (the code "on the TV" is printed by the simulator), or pass --paired:
$ mcp-server-shieldtv simulate --config-dir /tmp/sim # terminal 1
...
The TV shows the pairing code: 3B26B3
$ export SHIELDTV_CONFIG_DIR=/tmp/sim # terminal 2
$ mcp-server-shieldtv pair --host 127.0.0.1
Pairing with SHIELD Android TV (00:04:4B:A1:B2:C3). A code will appear on the TV screen.
Enter the code shown on the TV: 3B26B3
Paired and connected: NVIDIA SHIELD Android TV
$ mcp-server-shieldtv call launch_app app=netflix
{ "shield": "SHIELD Android TV", "outcome": "done",
"detail": "Launched netflix (com.netflix.ninja is in the foreground)",
"sent": ["app_link https://www.netflix.com/title"], "warnings": [] }
$ mcp-server-shieldtv call set_power state=off
{ ..., "outcome": "done", "detail": "The Shield is in standby", "sent": ["KEYCODE_SLEEP"] }
$ mcp-server-shieldtv call set_power state=off
{ ..., "outcome": "unchanged", "detail": "The Shield is already in standby", "sent": [] }
$ mcp-server-shieldtv call launch_app app=hulu
Error executing tool launch_app: The Shield accepted https://www.hulu.com/welcome, but the
foreground app didn't change within 10s (still com.netflix.ninja). The app may not be
installed, or no installed app handles that link; the TV may be showing an error. The Shield
is in standby, which may be why: wake it with set_power first.(Lines wrapped, some fields elided with ....) What the simulator does and where each behavior
comes from (the library's client code, the owner's Shield, or invented for a test) is listed
at the top of src/shieldtv_mcp/sim/fake_shield.py.
Development
pip install -e ".[dev]"
pytest -q # ~200 tests, ~15 s, no Shield needed
ruff check . && ruff format --check .
mypy # strict type checking of src/Tests | Cover |
| Allow-lists (checked against the protocol's own key enum), settings, config problems |
| Connection lifecycle and state against |
| The real library against the simulated Shield: pairing (typo, rejection, Cancel, Shield gone), session, drops, garbled frames, sleep, stale state, unpairing, re-pair pickup, an address change (127.0.0.2 to .3) |
| The MCP contract: schemas, annotations, ActionResults, errors, dry run, resources, prompts |
| ADB parsers on real dumpsys output; only constant commands reach the shell |
|
|
| Streamable HTTP end to end; Access JWTs (valid, expired, wrong audience, wrong issuer, missing) with locally generated keys |
| The installed entry point over stdio |
| Every assumption is cited by code, and listed here and in HARDWARE_VALIDATION.md |
CI runs it all on Python 3.11-3.14, plus the Alpine smoke test.
npx @modelcontextprotocol/inspector mcp-server-shieldtv browses tools, resources and prompts
by hand. docs/ADB_TOOLS.md reviews the ADB tools and proposes read-only
additions (design only).
Verification status
Hardware-verified means seen on the owner's Shield (what was seen is in each assumption's
note in src/shieldtv_mcp/assumptions.py). Simulator-only means the simulator implements
it and nothing has confirmed it.
Assumption | Confidence | Status |
| high | hardware-verified |
| high | hardware-verified |
| medium | simulator-only |
| medium | simulator-only |
| high | simulator-only |
| high | hardware-verified |
| medium | simulator-only |
| high | hardware-verified |
| high | hardware-verified |
| high | hardware-verified |
| high | hardware-verified |
| high | hardware-verified |
| high | hardware-verified |
| medium | simulator-only |
| high | hardware-verified |
| high | hardware-verified |
test_assumptions.py fails if this table and assumptions.py disagree.
Roadmap
Hardware validation of this branch (HARDWARE_VALIDATION.md)
The ADB proposals in docs/ADB_TOOLS.md, once their outputs are captured
Several Shields from one server, by name (as the sibling servers do)
Publish to PyPI and the MCP registry
License
MIT. See LICENSE.
Available Tools
8 toolsget_now_playingGet what's playingARead-only
Report what is playing: app, title, subtitle (artist or channel), play state, position in seconds.
state is "idle" when nothing is playing. position_s is null for live TV. Needs ADB, enabled once with
mcp-server-shieldtv adb-setup; without it this returns an error saying so.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| app | Yes | |
| state | Yes | |
| title | Yes | |
| subtitle | Yes | |
| position_s | Yes | |
| app_package | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnlyHint=true, openWorldHint=false), and the description adds real context beyond them: the ADB prerequisite, that a missing setup produces an explicit error, that state is 'idle' when nothing plays, and that position_s is null for live TV. It stops short of describing latency, caching, or polling behavior, so it is strong but not exhaustive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The primary payload (what is reported) is front-loaded in the first sentence, with edge-case semantics and the prerequisite following in a tight second block. Every sentence carries information; nothing is redundant filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a no-argument, read-only probe with an output schema, the description covers everything an agent needs that the structured fields do not: the setup prerequisite, the failure mode, and the two non-obvious value semantics (idle state, null position for live TV). Nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to disambiguate; per the baseline for a 0-parameter schema this lands at 4. The description correctly adds no parameter discussion and instead spends its words on return-value semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb and resource ('Report what is playing') and enumerates the exact fields returned (app, title, subtitle, play state, position in seconds), so the agent knows precisely what it gets back. It does not, however, differentiate itself from the sibling get_status, which could plausibly also report playback state.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: an agent can infer this is the tool for 'what's on right now,' and the idle/live-TV notes hint at when results are meaningful. There is no explicit statement of when to prefer this over get_status or how it relates to the other read-oriented siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_remotesCheck Bluetooth remotesARead-only
List the Shield's Bluetooth remotes (e.g. a Harmony hub) and whether each works.
state is "working", "stuck" (connected but its buttons do nothing; advice says how to fix it), "connecting" or "disconnected" (normal for a remote not in use). Needs ADB (adb-setup).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| advice | Yes | |
| remotes | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the description correctly need not restate safety. It adds genuinely new behavioral context: the ADB (adb-setup) prerequisite and the meaning of a "stuck" remote including that advice on fixing it is returned. It stops short of describing pagination, timing, or failure modes when ADB is unavailable.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The core purpose is front-loaded in the first sentence, and the state glossary is compact and readable. The state enumeration is somewhat long for a tool that also has an output schema, but each state's parenthetical adds diagnostic meaning rather than filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-argument read tool with annotations and an output schema, the description covers what is returned (remote list plus per-remote state), the environment prerequisite (ADB), and how to interpret abnormal states. Only the absence of guidance on what to do next when a remote is disconnected keeps it from being fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline is 4; there is nothing for the description to disambiguate. The schema is empty and fully covered, and the description correctly spends no space on arguments.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ("List") and resource ("the Shield's Bluetooth remotes") plus the key payload ("whether each works"). No sibling tool (get_status, get_now_playing, list_apps) covers Bluetooth remotes, so the scope is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The diagnostic intent is implied by "whether each works" and the state glossary, but the description never says when to reach for this tool versus get_status or what to do with the results. No exclusions or prerequisites beyond ADB are given, so usage is inferred rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_statusGet Shield statusARead-only
Start here. Whether the Shield is reachable, its power state, the foreground app, and volume.
When it isn't reachable, stale is true: power, app and volume are the last known values (as_of says when), and error says why. It does not say what is playing; get_now_playing does (if ADB is set up). dry_run true means actions are reported, not sent.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| host | Yes | |
| name | Yes | |
| as_of | Yes | |
| error | Yes | |
| power | Yes | |
| stale | Yes | |
| device | Yes | |
| paired | Yes | |
| volume | Yes | |
| dry_run | Yes | |
| reachable | Yes | |
| current_app | Yes | |
| current_app_package | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so safety is covered. The description adds genuinely non-obvious behavior: stale=true signaling unreachable state, that power/app/volume are last-known values with as_of timestamping, and that error explains why. The dry_run note is somewhat tangential for a zero-parameter read tool but still adds context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the key orientation phrase "Start here" and stays compact across two short paragraphs. The final dry_run sentence is loosely related to a no-param read tool and slightly dilutes focus.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be restated, and the description still covers the ambiguity the schema cannot express: stale-value semantics, timestamp/error fields, and the distinction from get_now_playing. Nothing needed to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline is 4. There are no parameter semantics to clarify, and nothing in the description misrepresents the empty schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource (get Shield status) and enumerates exactly what is returned: reachability, power state, foreground app, and volume. It also distinguishes itself from the sibling get_now_playing, so an agent can separate the two without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
"Start here" gives explicit entry-point guidance, and the description names the boundary condition and alternative: it does not report what is playing, while get_now_playing does (if ADB is set up). That is an explicit when-to-use plus a when-to-use-instead.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
launch_appLaunch an appAIdempotent
Launch an app by friendly name (see list_apps), e.g. 'netflix' or 'youtube'.
Succeeds only once the app is confirmed in the foreground; otherwise the error says what the Shield did.
| Name | Required | Description | Default |
|---|---|---|---|
| app | Yes | Friendly app name from list_apps, e.g. 'netflix'. |
Output Schema
| Name | Required | Description |
|---|---|---|
| sent | Yes | |
| detail | Yes | |
| shield | Yes | |
| outcome | Yes | |
| warnings | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare idempotent=true and destructive=false, so safety is largely covered. The description adds genuinely new behavioral context: the call only succeeds once the app is actually in the foreground, and failures surface a Shield-specific error message — useful for an agent deciding how to interpret results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences; the identifier requirement and the success/error semantics are both front-loaded with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return values need no explanation, and annotations cover idempotency and safety. The description covers the failure mode and success condition; only minor details (e.g. timeout behavior while waiting for foreground confirmation) are left implicit.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the schema itself already says 'Friendly app name from list_apps, e.g. netflix'. The description's pointer to list_apps and its examples largely restate the schema, so baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('launch') and resource ('app') and immediately scopes the identifier source to friendly names from list_apps. An agent can distinguish it from the sibling list_apps without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It tells the agent to use friendly names 'see list_apps', which is the key prerequisite, and states the success condition (app confirmed in foreground). It doesn't state a when-not case, but for a single-purpose launch tool there is no competing sibling to exclude beyond list_apps.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_appsList launchable appsARead-only
List the app names launch_app accepts (friendly name -> the link it opens).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the safe read-only profile is covered. The description adds that entries are friendly name -> link pairs, which is modestly beyond annotations, but since an output schema exists it largely restates return content rather than behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with zero waste; the purpose and the launch_app linkage appear immediately.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, read-only discovery tool with an output schema and covering annotations, nearly everything needed to call it is present. The only omission is an explicit statement of the intended sequencing relative to launch_app.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so per the rubric baseline is 4. There is nothing for the description to clarify about inputs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb (List) plus a precise resource (the app names launch_app accepts), and it even characterizes the return shape as a friendly-name-to-link mapping. This distinguishes it cleanly from siblings like launch_app, get_status, or get_remotes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
By tying the returned names directly to launch_app's accepted input, it gives clear context for when to call it (discovery before launching). It stops short of an explicit 'call this before launch_app' instruction or any exclusion, so it is clear context rather than full when/when-not guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
reboot_shieldReboot the ShieldADestructive
Restart the Shield. Only do this when the user asks for a reboot: it stops whatever is playing.
Takes about a minute: waits until the Shield is back, then checks the Bluetooth remotes, since a reboot can leave them connected but not working. If so, advice says how to fix it. Needs ADB (adb-setup).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| sent | Yes | |
| advice | Yes | |
| detail | Yes | |
| outcome | Yes | |
| remotes | Yes | |
| back_after_s | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true and non-idempotent, but the description goes well beyond them: it discloses the playback interruption, the ~1 minute duration, the blocking wait for the device to return, the post-reboot Bluetooth remote check with remediation advice, and the ADB (adb-setup) dependency. That is the behavioral context an agent needs before triggering an irreversible action.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the action and the restriction before the mechanics, and every clause carries information (side effect, duration, post-check, prerequisite). The duration/remote-check sentence is slightly dense, but nothing is padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation; the description still covers the two things an agent cannot infer from schema or annotations: the destructive playback side effect and the ADB prerequisite. Nothing needed to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline is 4 and there is no syntax or format detail the description could add. It correctly avoids inventing parameters and instead spends its words on side effects.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Restart the Shield') and immediately scopes it against the sibling family: this is a reboot, not the power control offered by set_power or a key press. An agent can pick it apart from set_power/get_status without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit when-to-use gate ('Only do this when the user asks for a reboot') plus the consequence that selects caution ('it stops whatever is playing'). It does not name the alternative tool to use for plain power-off (set_power), so the routing is clear but not fully closed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
send_keyPress a remote keyA
Press a remote-control key on the Shield, e.g. DPAD_DOWN with repeat=3 to move down three rows.
| Name | Required | Description | Default |
|---|---|---|---|
| key | Yes | ||
| repeat | No | How many times to press the key (1-10). |
Output Schema
| Name | Required | Description |
|---|---|---|
| sent | Yes | |
| detail | Yes | |
| shield | Yes | |
| outcome | Yes | |
| warnings | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare the safety profile (readOnlyHint=false, destructiveHint=false, idempotentHint=false, openWorldHint=false), so the bar is lower. The description adds only that repeat produces repeated presses, without disclosing what receives the key (focused app vs. system), latency/blocking behavior, or any prerequisite state such as the device being powered on.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence that puts the verb and target first, then supplies the example. There is no filler and nothing redundant with the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter input tool with an output schema (so return values need no explanation) and full annotation coverage, the description supplies the essential purpose plus one grounded example. It stops just short of stating when this tool should be preferred over higher-level siblings or whether the target must be focused/awake.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 50% (the 'key' enum has no description) and 'repeat' is minimally documented ('How many times to press the key (1-10).'). The description compensates by tying repeat to an observable effect ('repeat=3 to move down three rows') and by framing the key values as Shield remote-control keys, which is genuine meaning beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Press') and resource ('a remote-control key on the Shield'), making it immediately distinguishable from siblings like launch_app, set_power, and get_status. An agent can tell this is the low-level input-injection tool without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the concrete example (DPAD_DOWN with repeat=3 to move down three rows), which shows the intended navigation scenario, but the description names no alternatives and gives no explicit when-to-use/when-not-to-use guidance. It is adequate but leaves routing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
set_powerWake or sleep the ShieldAIdempotent
Wake the Shield ('on') or put it to sleep ('off'). Uses WAKEUP/SLEEP, not the POWER toggle.
Succeeds only once the Shield reports the new state. Already in that state: nothing is sent.
| Name | Required | Description | Default |
|---|---|---|---|
| state | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| sent | Yes | |
| detail | Yes | |
| shield | Yes | |
| outcome | Yes | |
| warnings | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare idempotency, non-destructiveness, and non-read-only, but the description adds command-level behavior: it uses WAKEUP/SLEEP rather than a POWER toggle and only succeeds after the Shield reports the new state. It also notes no command is sent if the Shield is already in that state, reinforcing the idempotent behavior beyond the annotation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with purpose and mechanism. Every sentence earns its place by clarifying the value mapping, command choice, or success/no-op behavior.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter state setter with output schema and safety annotations present, the description covers purpose, mapping, mechanism, success condition, and no-op behavior. It leaves failure/timeout behavior unstated, and sibling differentiation is only implicit via the command mechanism.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It does so fully by mapping each enum value to the real action ('on' = wake, 'off' = sleep).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names specific verbs (wake/sleep) and the resource (the Shield), and maps them to the 'on'/'off' values. It also distinguishes the operation from a POWER toggle, though it does not explicitly contrast with the reboot_shield sibling.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the verb pair—the tool is for waking or sleeping—and the description adds the no-op condition when already in the desired state. However, it offers no explicit when-to-use/when-not guidance or routing to alternatives like reboot_shield or send_key.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
8 tool updates
v0.1.0- First observed
get_now_playing - First observed
get_remotes - First observed
get_status - First observed
launch_app - First observed
list_apps - First observed
reboot_shield - First observed
send_key - First observed
set_power
TDQS
Scored across 8 tools
Each tool targets a distinct action/resource: input (send_key, launch_app), power (set_power), reads (get_status, get_now_playing, get_remotes, list_apps), and lifecycle (reboot_shield). The only near-overlap, get_status vs get_now_playing, is clearly delineated (device state vs playback content).
All eight tools follow a consistent snake_case verb_noun pattern (send_key, launch_app, set_power, get_status, get_now_playing, get_remotes, reboot_shield, list_apps). No mixing of conventions or vague verbs.
Eight tools is well-scoped for a media-device controller, covering input, power, status, playback, remotes, and reboot without bloat. Each tool earns its place.
Core lifecycle (power, input, launch, status, playback, reboot) is well covered, and volume/pause are reachable via send_key. Minor gaps exist for explicit volume setting or text entry, but no dead ends for typical control workflows.
Maintenance
Related MCP Connectors
Control Android TV from any AI. 38 MCP tools: playback, recap, recommend, smart-home, schedules.
An authenticated remote MCP server for user-owned devices and one-shot capability invocation.
Securely control paired computers with ReMCP.
Control your Tesla - wake it, warm it up, unlock and more. Get your developer token at https://Infoseek.ai/mcp. Also requires your own Tesla developer token which is tied to your car/fleet.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceA Model Context Protocol server that enables AI assistants to control Android TV devices, providing remote control functionality like navigation, playback control, app management, and device status monitoring.10MIT
- AlicenseAqualityCmaintenanceMCP server for controlling Google TV devices via natural language. Enables key presses, text input, and app launching on paired devices.5MIT
- FlicenseAqualityCmaintenanceEnables MCP clients to control Sony Bravia TVs over the local network, including power, HDMI inputs, apps, volume, and mute through the TV's JSON-RPC API.8-
- AlicenseNot gradedqualityBmaintenanceEnables controlling an Android device from an MCP client via ADB, including wireless debugging discovery, UI-tree inspection, and performing taps, swipes, typing, app actions, and shell commands without relying on screen coordinates.2 npmMIT