8000 GitHub - devAdrec/googlecast-mcp: MCP server that finds Google speakers on the LAN and speaks Vietnamese text on them · GitHub
[go: up one dir, main page]

Skip to content

About

MCP server that finds Google speakers on the LAN and speaks Vietnamese text on them

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

googlecast-mcp

An MCP server that finds the Google speakers on your local network and speaks text out loud on them, in Vietnamese, built on pychromecast and edge-tts.

say("Cơm đã chín rồi", target="Kitchen speaker")

It also exposes the usual Cast media controls (play a URL, pause, volume, …).

Requirements

  • Python ≥ 3.11
  • uv (recommended) or pip
  • The machine running the server must be on the same local network as the Cast devices (discovery uses mDNS/zeroconf).

Install & run

uv sync

# stdio transport (default — for Claude Desktop/Code and most MCP clients)
uv run googlecast-mcp

# streamable HTTP transport
uv run googlecast-mcp --transport http --host 0.0.0.0 --port 8000

# legacy SSE transport
uv run googlecast-mcp --transport sse

Running as a service

On a home server that stays on, run it as a systemd service over HTTP so clients on other machines can use it:

./scripts/service.sh install     # write the unit, enable at boot, start it
./scripts/service.sh status      # is it running?
./scripts/service.sh logs        # follow the journal
./scripts/service.sh stop        # / start / restart
./scripts/service.sh remove      # stop, disable, delete the unit

install needs sudo (it writes /etc/systemd/system/) and defaults to MCP on port 8765 and audio on port 8766. Override with environment variables:

MCP_PORT=9000 MEDIA_PORT=9001 ./scripts/service.sh install

The service must run on a machine on the same LAN as the speakers: discovery uses mDNS, and the speakers fetch the audio back from it. Allow both ports through the firewall if one is active.

The deployed command

The install currently in use, serving Claude Desktop over https and a browser-based client at http://192.168.1.99:8383:

MCP_EXTRA_ARGS="--allow-host google-cast.adrec.cloud --json-response --stateless --cors-origin http://192.168.1.99:8383" \
  ./scripts/service.sh install

Each flag earns its place — drop one and a client breaks:

Flag Without it
--allow-host <domain> Requests through the reverse proxy get 421 Misdirected Request.
--json-response Replies are SSE-framed; clients that parse only JSON fail.
--stateless Clients that drop Mcp-Session-Id fail after the first call.
--cors-origin <origin> A browser client's preflight fails: "Failed to fetch".

--cors-origin must match the browser's address bar exactly — scheme, host and port, no path. Repeat the flag for more origins. A page served on port 80 or 443 sends no port in its origin (http://192.168.1.99, not …:80).

Always confirm the change took effect:

./scripts/service.sh status     # check the "Running command line" line

Client configuration (remote, over HTTP)

Point the client at the service's LAN address:

http://<server-ip>:8765/mcp

Claude Desktop's Add custom connector field only accepts https URLs, so a plain-HTTP server on the LAN cannot be pasted there. Bridge it with the mcp-remote stdio proxy instead — add this to claude_desktop_config.json (Settings → Developer → Edit Config) on the client machine:

{
  "mcpServers": {
    "googlecast": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://<server-ip>:8765/mcp", "--allow-http"]
    }
  }
}

This needs Node.js on the client machine. Restart Claude Desktop afterwards.

Clients that are stricter than the spec

By default the server answers even a single request with an SSE-framed body (event: message) and hands out an Mcp-Session-Id that clients must echo back. Both are legal, but simpler MCP clients — llama-server's, for one — choke on them and report a bare "protocol error". Two flags relax it:

googlecast-mcp --transport http --json-response --stateless

--json-response replies with application/json; --stateless drops the session requirement, so a lone tools/call works with no handshake. Standard clients (Claude Desktop, Claude Code) work in this mode too, so it is a safe default when several kinds of client share one server:

MCP_EXTRA_ARGS="--json-response --stateless" ./scripts/service.sh install

Behind a reverse proxy (https)

To use the custom-connector field instead, front the server with nginx and a real certificate. A working vhost is in scripts/nginx-googlecast-mcp.conf. Three things are easy to get wrong:

  1. Proxy only the MCP port. The audio port stays LAN-internal — the speakers fetch from it directly by IP.

  2. Turn off buffering (proxy_buffering off) and raise the timeouts. Streamable HTTP holds the response open and pushes events; nginx would otherwise buffer them and the client just hangs.

  3. Trust the domain, or the server answers 421 Misdirected Request:

    MCP_EXTRA_ARGS="--allow-host cast.example.com" ./scripts/service.sh install

The hostname must not contain an underscore — public CAs refuse to issue certificates for such names, so no https URL is possible for one.

The endpoint has no authentication. Anyone who can reach /mcp can play audio in the house. On a publicly resolvable domain, restrict it in nginx (allow 192.168.0.0/16; deny all;) unless remote access is genuinely wanted.

Client configuration (local, stdio)

Add to your MCP client config (e.g. Claude Desktop claude_desktop_config.json):

{
  "mcpServers": {
    "googlecast": {
      "command": "uv",
      "args": ["run", "--directory", "/storage/apps/mcp/googlecast_mcp", "googlecast-mcp"]
    }
  }
}

Tools

Tool Purpose
say(text, target?, voice="female", rate="+0%") Speak text out loud. Omit target to be asked which speaker.
discover_devices(timeout=5.0) Scan the network for Cast devices and save them.
list_speakers() Saved speakers and speaker groups only (no TVs); scans if empty.
list_devices() All known devices, live + saved.
get_status(target) Current app + media status of a device.
play_media(target, url, content_type?, title?) Cast a media URL and start playback.
play(target) / pause(target) / stop(target) Playback control.
seek(target, position_seconds) Seek to an absolute position.
set_volume(target, level) Set volume (0.0–1.0, clamped).
set_muted(target, muted) Mute / unmute.
quit_app(target) Stop the running app; return device to idle.

target is a device friendly name (e.g. "Living Room TV") or its uuid, as returned by discover_devices.

Speaking text (say)

  • target accepts one speaker, several separated by commas, or "all" / "tất cả" for every speaker. "all" covers the individual speakers only — a speaker group plays through its members, so including it as well would send two streams to the same device. Name a group directly to use one.
  • Omitting target never plays anything. The tool returns the speaker list and asks the client to have the user pick one.
  • voice is "female" (vi-VN-HoaiMyNeural, default), "male" (vi-VN-NamMinhNeural), or any full edge-tts voice id.
  • Synthesis needs internet access (Microsoft Edge neural voices, no API key). Rendered mp3s are cached, so repeating a phrase is instant.
  • The speaker fetches the audio from a small HTTP server this process starts on your LAN address. The port is random by default; pin it with --media-port (or GOOGLECAST_MCP_MEDIA_PORT) so a firewall rule can be written once.

Notes

  • play_media URLs must be publicly reachable by the Cast device — the device fetches the media itself, not this server.
  • content_type is guessed from the URL extension when omitted; pass it explicitly for streams without a recognizable extension (e.g. HLS behind a query string).

Troubleshooting

Symptom Cause and fix
Not Acceptable: Client must accept text/event-stream You opened /mcp in a browser. Expected — the endpoint is not browsable, and this reply means the server is healthy.
421 Misdirected Request The Host header is not trusted. Add the name with --allow-host.
Execution of wait timed out after 10 s for one device That device is not accepting cast connections. Check with nc -z <ip> 8009; a Nest Hub in this state comes back after a reboot.
Client connects but calls hang A proxy is buffering the stream. Set proxy_buffering off.
A client reports a generic "protocol error" It likely cannot parse SSE framing or does not carry the session header. Start the server with --json-response --stateless.
say returns needs_speaker_selection Working as intended: no target was given, so nothing was played.
No speaker found The server is not on the speakers' LAN, or mDNS is blocked between VLANs.
Speaker accepts the cast but stays silent It cannot reach the audio port. Check the firewall, and that the advertised URL uses a LAN address.
Screen flashes, audio cuts short Poll get_status while it plays. player_state=PLAYING for the full duration ending in idle_reason=FINISHED means the cast side is fine — suspect the device (volume, or an unstable state after a reboot).

Architecture

See docs/architecture.md for the request flow, the design decisions behind each module, and the deployment notes.

  • cast_manager.py — thread-safe wrapper over pychromecast; owns discovery, the connected-device cache, and synchronous control methods.
  • speaker_store.py — persists discovered devices to ~/.googlecast-mcp/speakers.json (override with GOOGLECAST_MCP_STORE), so a fresh process still knows the speakers.
  • tts.py — Vietnamese text → mp3 via edge-tts, cached on disk (override the cache location with GOOGLECAST_MCP_CACHE).
  • media_server.py — background HTTP server on the LAN address; Cast devices cannot read local file paths, so the audio must be served to them.
  • server.py — FastMCP tool definitions; offloads each blocking call to a worker thread via 56AA asyncio.to_thread.
  • __main__.py — CLI entrypoint; selects transport, widens the SDK's trusted hosts to the LAN, and handles clean shutdown.
  • scripts/service.sh — systemd install/remove/start/stop/restart/status/logs.

How this was built

_dong-goi/method-note.md records the reasoning behind the design and the dead ends hit along the way — most of it applies to any MCP server that has to serve several kinds of client, not just this one. _dong-goi/reproduction-prompt.md is enough to rebuild an equivalent server from scratch.

License

MIT

About

MCP server that finds Google speakers on the LAN and speaks Vietnamese text on them

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

0