205 lines
6.6 KiB
Rust
205 lines
6.6 KiB
Rust
use std::sync::Arc;
|
|
|
|
use application::{
|
|
channels::{ChannelCommandDeps, ChannelQueryDeps},
|
|
library::LibraryQueryDeps,
|
|
schedule::ScheduleDeps,
|
|
};
|
|
use rmcp::{
|
|
ServerHandler,
|
|
model::{Implementation, ProtocolVersion, ServerCapabilities, ServerInfo},
|
|
tool,
|
|
};
|
|
use schemars::JsonSchema;
|
|
use serde::Deserialize;
|
|
use uuid::Uuid;
|
|
|
|
use crate::tools::{channels, library, schedule};
|
|
|
|
#[derive(Clone)]
|
|
pub struct KTvMcpServer {
|
|
pub channel_cmd_deps: Arc<ChannelCommandDeps>,
|
|
pub channel_query_deps: Arc<ChannelQueryDeps>,
|
|
pub schedule_deps: Arc<ScheduleDeps>,
|
|
pub library_query_deps: Arc<LibraryQueryDeps>,
|
|
pub owner_id: Uuid,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, JsonSchema)]
|
|
pub struct GetChannelParams {
|
|
pub id: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, JsonSchema)]
|
|
pub struct CreateChannelParams {
|
|
pub name: String,
|
|
pub timezone: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, JsonSchema)]
|
|
pub struct UpdateChannelParams {
|
|
pub id: String,
|
|
pub name: Option<String>,
|
|
pub timezone: Option<String>,
|
|
pub description: Option<String>,
|
|
pub schedule_config_json: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, JsonSchema)]
|
|
pub struct DeleteChannelParams {
|
|
pub id: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, JsonSchema)]
|
|
pub struct ChannelIdParam {
|
|
pub channel_id: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, JsonSchema)]
|
|
pub struct SearchMediaParams {
|
|
pub content_type: Option<String>,
|
|
pub genres: Option<Vec<String>>,
|
|
pub search_term: Option<String>,
|
|
pub series_names: Option<Vec<String>>,
|
|
pub collections: Option<Vec<String>>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, JsonSchema)]
|
|
pub struct ListGenresParams {
|
|
pub content_type: Option<String>,
|
|
}
|
|
|
|
fn parse_uuid(s: &str) -> Result<Uuid, String> {
|
|
s.parse::<Uuid>()
|
|
.map_err(|_| serde_json::json!({"error": format!("invalid UUID: {s}")}).to_string())
|
|
}
|
|
|
|
#[tool(tool_box)]
|
|
impl KTvMcpServer {
|
|
#[tool(description = "List all channels owned by the configured user")]
|
|
async fn list_channels(&self) -> String {
|
|
channels::list_channels(&self.channel_query_deps, self.owner_id).await
|
|
}
|
|
|
|
#[tool(description = "Get a channel by UUID")]
|
|
async fn get_channel(&self, #[tool(aggr)] p: GetChannelParams) -> String {
|
|
match parse_uuid(&p.id) {
|
|
Ok(id) => channels::get_channel(&self.channel_query_deps, id).await,
|
|
Err(e) => e,
|
|
}
|
|
}
|
|
|
|
#[tool(description = "Create a new channel with a name and IANA timezone")]
|
|
async fn create_channel(&self, #[tool(aggr)] p: CreateChannelParams) -> String {
|
|
channels::create_channel(&self.channel_cmd_deps, self.owner_id, &p.name, &p.timezone).await
|
|
}
|
|
|
|
#[tool(description = "Update channel name, timezone, description, and/or schedule config")]
|
|
async fn update_channel(&self, #[tool(aggr)] p: UpdateChannelParams) -> String {
|
|
let id = match parse_uuid(&p.id) {
|
|
Ok(id) => id,
|
|
Err(e) => return e,
|
|
};
|
|
let schedule_config = match p.schedule_config_json {
|
|
Some(json) => match serde_json::from_str(&json) {
|
|
Ok(c) => Some(c),
|
|
Err(e) => {
|
|
return serde_json::json!({"error": format!("invalid schedule_config_json: {e}")})
|
|
.to_string()
|
|
}
|
|
},
|
|
None => None,
|
|
};
|
|
channels::update_channel(
|
|
&self.channel_cmd_deps,
|
|
id,
|
|
self.owner_id,
|
|
p.name,
|
|
p.timezone,
|
|
p.description,
|
|
schedule_config,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[tool(description = "Delete a channel (must be owned by the configured user)")]
|
|
async fn delete_channel(&self, #[tool(aggr)] p: DeleteChannelParams) -> String {
|
|
match parse_uuid(&p.id) {
|
|
Ok(id) => channels::delete_channel(&self.channel_cmd_deps, id, self.owner_id).await,
|
|
Err(e) => e,
|
|
}
|
|
}
|
|
|
|
#[tool(description = "Generate a fresh schedule for the given channel")]
|
|
async fn generate_schedule(&self, #[tool(aggr)] p: ChannelIdParam) -> String {
|
|
match parse_uuid(&p.channel_id) {
|
|
Ok(id) => schedule::generate_schedule(&self.schedule_deps, id).await,
|
|
Err(e) => e,
|
|
}
|
|
}
|
|
|
|
#[tool(description = "Get the currently active schedule for a channel (returns null if none)")]
|
|
async fn get_active_schedule(&self, #[tool(aggr)] p: ChannelIdParam) -> String {
|
|
match parse_uuid(&p.channel_id) {
|
|
Ok(id) => schedule::get_active_schedule(&self.schedule_deps, id).await,
|
|
Err(e) => e,
|
|
}
|
|
}
|
|
|
|
#[tool(
|
|
description = "Get what is currently broadcasting on a channel (returns null if in a gap or no schedule)"
|
|
)]
|
|
async fn get_current_broadcast(&self, #[tool(aggr)] p: ChannelIdParam) -> String {
|
|
match parse_uuid(&p.channel_id) {
|
|
Ok(id) => schedule::get_current_broadcast(&self.schedule_deps, id).await,
|
|
Err(e) => e,
|
|
}
|
|
}
|
|
|
|
#[tool(description = "List media collections/libraries available in the library")]
|
|
async fn list_collections(&self) -> String {
|
|
library::list_collections(&self.library_query_deps).await
|
|
}
|
|
|
|
#[tool(
|
|
description = "List genres available in the library, optionally filtered by content type (movie/episode/short)"
|
|
)]
|
|
async fn list_genres(&self, #[tool(aggr)] p: ListGenresParams) -> String {
|
|
library::list_genres(&self.library_query_deps, p.content_type).await
|
|
}
|
|
|
|
#[tool(
|
|
description = "Search media items. content_type: movie|episode|short. Returns JSON array of LibraryItem."
|
|
)]
|
|
async fn search_media(&self, #[tool(aggr)] p: SearchMediaParams) -> String {
|
|
library::search_media(
|
|
&self.library_query_deps,
|
|
p.content_type,
|
|
p.genres.unwrap_or_default(),
|
|
p.search_term,
|
|
p.series_names.unwrap_or_default(),
|
|
p.collections.unwrap_or_default(),
|
|
)
|
|
.await
|
|
}
|
|
}
|
|
|
|
#[tool(tool_box)]
|
|
impl ServerHandler for KTvMcpServer {
|
|
fn get_info(&self) -> ServerInfo {
|
|
ServerInfo {
|
|
protocol_version: ProtocolVersion::V_2024_11_05,
|
|
capabilities: ServerCapabilities::builder().enable_tools().build(),
|
|
server_info: Implementation {
|
|
name: "k-tv-mcp".into(),
|
|
version: "0.1.0".into(),
|
|
},
|
|
instructions: Some(
|
|
"K-TV MCP server. Create channels, define programming blocks, generate schedules. \
|
|
All operations run as the user configured via MCP_USER_ID."
|
|
.into(),
|
|
),
|
|
}
|
|
}
|
|
}
|