Build and publish / Test and build (darwin) (push) Failing after 1m42s
Build and publish / Test and build (linux) (push) Failing after 1m53s
Build and publish / Test and build (windows) (push) Failing after 2m50s
Build and publish / Build and publish Docker image (push) Skipped
Go rewrite of the Python Emby.MCP server with tool parity (20 tools), plus a streamable-HTTP transport with per-request Emby authentication (Basic / Bearer token passthrough), a multi-arch Docker build, and a Gitea CI workflow.
91 lines
4.7 KiB
Go
91 lines
4.7 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/gordon/emby-mcp/internal/state"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
func registerPlayerTools(s *mcp.Server, st *state.State) {
|
|
mcp.AddTool(s, &mcp.Tool{
|
|
Name: "retrieve_player_list",
|
|
Description: `Retrieve a list of media players that we can use with the supplied media type in JSON format.
|
|
A human may use any JSON field to identify a player, but do not display the 'device_id' or 'session_id'
|
|
You must only supply the 'session_id' field to identify the player when using the control_media_player or retrieve_player_queue tools.
|
|
|
|
Args:
|
|
media_type (str, optional): List only players of this media type (one of: 'Audio', 'Video', 'Photo') or an empty string to list all players
|
|
|
|
Returns:
|
|
List of dicts as JSON with keys including client_name, session_id, device_id, device_name,
|
|
device_ip_address, local_to_media_server, media_types, and now_playing_* fields.`,
|
|
}, func(ctx context.Context, req *mcp.CallToolRequest, in struct {
|
|
MediaType string `json:"media_type" jsonschema:"List only players of this media type (one of: 'Audio', 'Video', 'Photo') or empty for all"`
|
|
}) (*mcp.CallToolResult, any, error) {
|
|
sessions, err := st.Client.GetPlayerSessions(ctx, st.UserID, in.MediaType)
|
|
if err != nil {
|
|
return textResult(errf("ERROR: failed to retrieve player list because: %v", err)), nil, nil
|
|
}
|
|
return jsonResult(sessions), nil, nil
|
|
})
|
|
|
|
mcp.AddTool(s, &mcp.Tool{
|
|
Name: "retrieve_player_queue",
|
|
Description: `Retrieve a list of items in the play queue of a media player in JSON format.
|
|
|
|
Args:
|
|
session_id (str): The ID of the player session to query, obtained from tool retrieve_player_list
|
|
|
|
Returns:
|
|
List of dicts as JSON with keys including title, artists, album, genres, overview,
|
|
media_type, bitrate, run_time, item_id, playlist_item_id.`,
|
|
}, func(ctx context.Context, req *mcp.CallToolRequest, in struct {
|
|
SessionID string `json:"session_id" jsonschema:"The ID of the player session to query, obtained from tool retrieve_player_list"`
|
|
}) (*mcp.CallToolResult, any, error) {
|
|
items, err := st.Client.GetPlayQueueItems(ctx, in.SessionID)
|
|
if err != nil {
|
|
return textResult(errf("ERROR: failed to retrieve player queue items because: %v", err)), nil, nil
|
|
}
|
|
return jsonResult(items), nil, nil
|
|
})
|
|
|
|
mcp.AddTool(s, &mcp.Tool{
|
|
Name: "control_media_player",
|
|
Description: `Control the media player identified as 'session_id' by sending it a 'command'.
|
|
Valid commands are: 'PlayNow', 'Stop', 'Pause', 'Unpause', 'NextTrack', 'PreviousTrack', 'Seek', 'Rewind', 'FastForward'.
|
|
The PlayNow command requires 'item_ids' contain one or more comma separated 'item_id' obtained from the retrieve_item_list_by_genre tool.
|
|
Seek, Rewind, FastForward can specify a time in milliseconds. The 'session_id' is obtained from the retrieve_player_list tool.
|
|
|
|
Args:
|
|
session_id (str): The ID of the player session to control, obtained from tool retrieve_player_list
|
|
command (str): One of 'PlayNow', 'Stop', 'Pause', 'Unpause', 'NextTrack', 'PreviousTrack', 'Seek', 'Rewind', 'FastForward'.
|
|
item_ids (str, optional): The ID of one or more items obtained from tool search_for_item to add to the play queue as a comma separated list. Required for command 'PlayNow'.
|
|
time_milliseconds (int, optional): The time in milliseconds for commands 'Seek', 'Rewind', 'FastForward'. If 0 or None then defaults will be used.
|
|
|
|
Returns:
|
|
Str: success messsage or error message.`,
|
|
}, func(ctx context.Context, req *mcp.CallToolRequest, in struct {
|
|
SessionID string `json:"session_id" jsonschema:"The ID of the player session to control"`
|
|
Command string `json:"command" jsonschema:"One of 'PlayNow', 'Stop', 'Pause', 'Unpause', 'NextTrack', 'PreviousTrack', 'Seek', 'Rewind', 'FastForward'"`
|
|
ItemIDs string `json:"item_ids" jsonschema:"Comma separated item IDs; required for 'PlayNow'"`
|
|
TimeMilliseconds int64 `json:"time_milliseconds" jsonschema:"Time in ms for 'Seek', 'Rewind', 'FastForward'"`
|
|
}) (*mcp.CallToolResult, any, error) {
|
|
if in.SessionID == "" {
|
|
return textResult("ERROR: no session_id was supplied. Obtain session_id from tool retrieve_player_list"), nil, nil
|
|
}
|
|
if in.Command == "" {
|
|
return textResult("ERROR: no command was supplied. Valid commands are: 'PlayNow', 'Stop', 'Pause', 'Unpause', 'NextTrack', 'PreviousTrack', 'Seek', 'Rewind', 'FastForward'."), nil, nil
|
|
}
|
|
command := in.Command
|
|
if strings.EqualFold(command, "play") {
|
|
command = "PlayNow"
|
|
}
|
|
if err := st.Client.SendPlayerCommand(ctx, in.SessionID, command, in.ItemIDs, st.UserID, in.TimeMilliseconds); err != nil {
|
|
return textResult(errf("ERROR: failed to control the player because: %v", err)), nil, nil
|
|
}
|
|
return textResult("Success"), nil, nil
|
|
})
|
|
}
|