gronod 50bde0846d
Build and publish / Build and publish Docker image (push) Successful in 2m38s
Build and publish / Test and build (darwin) (push) Successful in 2m11s
Build and publish / Test and build (linux) (push) Successful in 2m42s
Build and publish / Test and build (windows) (push) Successful in 2m52s
ci: publish unprefixed 1.0.0 image tag from v1.0.0
2026-09-21 16:51:55 +01:00
2026-09-21 16:51:49 +01:00
2026-09-20 22:52:46 +01:00
2026-09-20 22:52:46 +01:00
2026-09-21 16:51:52 +01:00
2026-09-21 16:51:47 +01:00

emby-mcp (Go)

Go rewrite of the Python Emby.MCP server — a Model Context Protocol (MCP) server that connects an Emby media server to an AI client such as Claude Desktop.

The original 20 tools match the Python version's parameter names and JSON output shapes; additional Go-only tools add library browsing, next-episode resolution, and subtitle/audio-track control.

Features

  • Log on to / log out of an Emby media server (username/password, or API key)
  • List libraries, select a library, list genres
  • Search items by title/album, artist, genre, release years, lyrics, and item type (item_types, e.g. Series) — with chunked results for large libraries
  • Browse an item's children, a series' seasons, and a series' episodes (retrieve_item_children, retrieve_season_list, retrieve_episode_list), including per-user played state and resume positions
  • Resolve the next episode to play (retrieve_next_episode, modes next_unplayed/latest) without a library selection
  • Create/modify playlists, add/remove/reorder items, share playlists
  • List player sessions, retrieve play queues, control playback (play/pause/seek/skip, PlayNow)
  • Inspect the now-playing item's audio/subtitle tracks (retrieve_now_playing) and switch tracks (set_subtitle, set_audio_track) — mid-playback where the player supports it, else restart-at-position

Build

go build -o emby-mcp ./cmd/emby-mcp

Requires Go 1.27+.

Configuration

Create a .env file (in the working directory or next to the binary):

EMBY_SERVER_URL = "http://localhost:8096"

# Either username/password:
EMBY_USERNAME = "user"
EMBY_PASSWORD = "pass"

# ...or an API key plus the user ID it should act as:
# EMBY_API_KEY = "..."
# EMBY_USER_ID = "..."

# Set to False to skip SSL certificate verification (e.g. self-signed).
EMBY_VERIFY_SSL = True
# Max items per search-result chunk; 0 = no limit.
LLM_MAX_ITEMS = 100

# Transport: "stdio" (default) or "http" (streamable HTTP at /mcp).
MCP_TRANSPORT = stdio
# HTTP mode: bind address and idle-session timeout.
MCP_LISTEN_ADDR = "127.0.0.1:8080"
MCP_SESSION_TIMEOUT = 30m

In http mode the EMBY_USERNAME/EMBY_PASSWORD/EMBY_API_KEY variables are not needed — every request authenticates against Emby (see below).

Usage

Startup checks only (login + list libraries, then exit):

./emby-mcp -check

Run the MCP server on stdio:

./emby-mcp

HTTP mode

MCP_TRANSPORT=http MCP_LISTEN_ADDR=0.0.0.0:8080 ./emby-mcp

Serves the MCP streamable HTTP transport (SSE streaming) at POST http://<addr>/mcp. /healthz is an unauthenticated liveness endpoint.

Every request must carry Emby credentials:

  • Authorization: Basic base64(user:pass) — exchanged for an Emby access token via AuthenticateByName (cached ~15 min).
  • Authorization: Bearer <token> — an Emby access token or API key used directly. The token's owning user is resolved automatically via GET /Sessions; server-wide API keys and other ambiguous tokens require a disambiguation header:
    • X-Emby-User-Id: <id> — the Emby user to act as (validated), or
    • X-Emby-Username: <name> — looked up via the public user list.

Each MCP session is isolated: it gets its own library selection and search-chunking state, and requests to a session are rejected if they authenticate as a different user.

Plain HTTP only — put the server behind a reverse proxy for TLS.

Docker

docker run -e EMBY_SERVER_URL="http://emby:8096" -p 8080:8080 \
    git.i3omb.com/gronod/emby-mcp:1.0.0

The image defaults to MCP_TRANSPORT=http on 0.0.0.0:8080.

Docker Compose / TrueNAS SCALE

The YAML below is a Compose v2 file suitable for a TrueNAS SCALE Custom App (Apps → Discover → Custom App / Install via YAML) as well as plain docker compose up -d.

Replace the placeholder values. On SCALE, map the same keys in the app environment UI if you prefer not to bake them into the file. Use the NAS host/IP (or an internal container DNS name) for EMBY_SERVER_URL so the app can reach Emby; host networking is an alternative if Emby is also on the same TrueNAS box.

In HTTP mode (MCP_TRANSPORT=http, the image default) per-request Emby auth is used and EMBY_USERNAME / EMBY_PASSWORD / EMBY_API_KEY are optional. They are included so the same file also works if you switch the container to stdio or want a default identity.

# emby-mcp — TrueNAS SCALE Custom App / docker compose
# Placeholders: replace every CHANGE_ME_* value before starting.

services:
  emby-mcp:
    image: git.i3omb.com/gronod/emby-mcp:1.0.0
    container_name: emby-mcp
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      EMBY_SERVER_URL: "http://CHANGE_ME_EMBY_HOST:8096"
      # Username/password login (leave empty if using an API key):
      EMBY_USERNAME: "CHANGE_ME_EMBY_USERNAME"
      EMBY_PASSWORD: "CHANGE_ME_EMBY_PASSWORD"
      # API-key login (leave empty if using username/password).
      # EMBY_USER_ID is required when EMBY_API_KEY is set:
      EMBY_API_KEY: "CHANGE_ME_EMBY_API_KEY"
      EMBY_USER_ID: "CHANGE_ME_EMBY_USER_ID"
      EMBY_VERIFY_SSL: "true"
      LLM_MAX_ITEMS: "100"
      MCP_TRANSPORT: "http"
      MCP_LISTEN_ADDR: "0.0.0.0:8080"
      MCP_SESSION_TIMEOUT: "30m"
    # Scratch image has no shell/curl — omit healthcheck, or front with a proxy.
    # networks: [host]   # uncomment instead of ports: if Emby is on the same NAS

MCP endpoint after start: POST http://<truenas-host>:8080/mcp
Liveness: GET http://<truenas-host>:8080/healthz

Claude Desktop example (stdio)

{
  "mcpServers": {
    "Emby": {
      "command": "/path/to/emby-mcp",
      "args": ["-env", "/path/to/.env"]
    }
  }
}

Streamable HTTP example (mcpServers)

Point an MCP client at the container's /mcp endpoint. Every request must carry Emby credentials in headers (see HTTP mode above). Replace the placeholders.

API key (add X-Emby-User-Id or X-Emby-Username if the key is server-wide):

{
  "mcpServers": {
    "Emby": {
      "type": "http",
      "url": "http://CHANGE_ME_MCP_HOST:8080/mcp",
      "headers": {
        "Authorization": "Bearer CHANGE_ME_EMBY_API_KEY",
        "X-Emby-User-Id": "CHANGE_ME_EMBY_USER_ID"
      }
    }
  }
}

Username and password (sent as HTTP Basic; the server exchanges them for an Emby access token):

{
  "mcpServers": {
    "Emby": {
      "type": "http",
      "url": "http://CHANGE_ME_MCP_HOST:8080/mcp",
      "headers": {
        "Authorization": "Basic CHANGE_ME_BASE64_USER_COLON_PASS"
      }
    }
  }
}

CHANGE_ME_MCP_HOST is the host where emby-mcp is published (TrueNAS IP or hostname), not the Emby server. CHANGE_ME_BASE64_USER_COLON_PASS is base64("<username>:<password>"). Clients that do not understand "type": "http" may accept the same block with only "url" and "headers".

Development

go build ./...
go vet ./...
go test ./...

Layout:

  • cmd/emby-mcp — entry point, config loading, startup checks
  • internal/emby — Emby REST client (auth, libraries, items, playlists, sessions)
  • internal/server — MCP tool handlers (official go-sdk)
  • internal/mcphttp — streamable-HTTP transport with per-request Emby auth
  • internal/state — shared session state (library selection, search chunking)
  • internal/config — .env parsing
  • internal/textutil — Unicode→ASCII folding for lyrics search
  • docs/api/emby_openapi.json — Emby REST API spec (reference)

Upstream endpoint conformance is checked by internal/emby/spec_conformance_test.go; see docs/api/endpoint_validation.md for the audit report. Planned features live in docs/ROADMAP.md.

License

GPL v3 — see LICENCE.md (same license as the upstream angelltek/Emby.MCP project).

S
Description
No description provided
Readme
728 KiB
Languages
Go 99.6%
Dockerfile 0.4%