Files
gronod 42570915ed
Build and publish / Test and build (darwin) (push) Successful in 1m43s
Build and publish / Test and build (linux) (push) Successful in 2m30s
Build and publish / Test and build (windows) (push) Successful in 3m5s
Build and publish / Build and publish Docker image (push) Successful in 1m46s
Add browse, next-episode, and track-control tools
Implements the three roadmap feature sections:

- Browse: retrieve_item_children, retrieve_season_list, retrieve_episode_list;
  search_for_item gains item_types (e.g. "Series") so shows are findable
  without a selected library. Items now expose item_type, series_name, and
  per-user played state/resume position.
- retrieve_next_episode: next_unplayed (Emby NextUp, resume counts) or latest
  (most recent present episode; virtual placeholders skipped).
- retrieve_now_playing + set_subtitle/set_audio_track: stream listing and
  track switching via SetSubtitleStreamIndex/SetAudioStreamIndex GeneralCommands,
  with a PlayNow restart-at-position fallback for sessions that cannot switch
  mid-play (DLNA/Chromecast bridges).

Conformance manifest grows to 34 call sites with three new documented spec
gaps (StartPositionTicks on PlayRequest, missing /Shows/{Id}/Episodes response
schema, undeclared Fields/EnableUserData on /Users/{UserId}/Items/{Id}).
2026-09-21 00:09:49 +01:00

60 lines
1.8 KiB
Go

// Package server wires the Emby tools into an MCP server using the official
// modelcontextprotocol go-sdk.
package server
import (
"encoding/json"
"fmt"
"log"
"github.com/gordon/emby-mcp/internal/state"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
const (
Name = "Emby.MCP"
Version = "1.0.2"
Purpose = `These MCP tools allow you to control an Emby media server. Using them you can retrieve
a list of libraries, genres, playlists, audio & video items, and player sessions.
You can browse series, seasons and episodes, find the next episode to watch,
inspect a player's now-playing audio/subtitle tracks, add items to playlists,
and play, pause, seek and stop items on a player session.`
)
// New builds the MCP server and registers all tools against the shared state.
func New(st *state.State) *mcp.Server {
s := mcp.NewServer(
&mcp.Implementation{Name: Name, Version: Version},
&mcp.ServerOptions{Instructions: Purpose},
)
registerUserTools(s, st)
registerLibraryTools(s, st)
registerItemTools(s, st)
registerBrowseTools(s, st)
registerPlaylistTools(s, st)
registerPlayerTools(s, st)
return s
}
// errf logs to stderr and returns the message in the Python "ERROR: ..." style.
func errf(format string, args ...any) string {
msg := fmt.Sprintf(format, args...)
log.Printf("%s", msg)
return msg
}
// textResult wraps a string as MCP text content (the Python tools all return
// bare strings — JSON documents or error messages).
func textResult(s string) *mcp.CallToolResult {
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: s}}}
}
// jsonResult marshals v to JSON text; on failure returns an error string.
func jsonResult(v any) *mcp.CallToolResult {
b, err := json.Marshal(v)
if err != nil {
return textResult(errf("ERROR: failed to encode result as JSON: %v", err))
}
return textResult(string(b))
}