pub mod metadata; pub mod tools; use std::sync::Arc; use rmcp::transport::streamable_http_server::{ session::local::LocalSessionManager, StreamableHttpServerConfig, StreamableHttpService, }; use rmcp::{model::*, tool_handler, ServerHandler}; use tauri::{AppHandle, Manager}; use tokio::sync::{oneshot, Mutex}; use tokio_util::sync::CancellationToken; use tools::NuclearMcpServer; const MCP_PORT_START: u16 = 8800; const MCP_PORT_END: u16 = 8809; #[tool_handler] impl ServerHandler for NuclearMcpServer { fn get_info(&self) -> ServerInfo { ServerInfo { instructions: Some( "Nuclear Music Player MCP server. Use list_methods to discover domains, method_details for parameter info, describe_type for data type shapes, and call to execute methods.".into(), ), capabilities: ServerCapabilities::builder().enable_tools().build(), ..Default::default() } } } struct RunningServer { task: tauri::async_runtime::JoinHandle<()>, cancellation_token: CancellationToken, port: u16, } pub struct McpState { running: Arc>>, } impl McpState { fn new() -> Self { Self { running: Arc::new(Mutex::new(None)), } } } async fn start_server( bridge: crate::bridge::bridge::Bridge, ct: CancellationToken, ready: oneshot::Sender>, ) { let service = StreamableHttpService::new( move || Ok(NuclearMcpServer::new(bridge.clone())), LocalSessionManager::default().into(), StreamableHttpServerConfig { cancellation_token: ct.child_token(), ..Default::default() }, ); let router = axum::Router::new().nest_service("/mcp", service); let tcp_listener = match crate::net::bind_first_available_port( "127.0.0.1", MCP_PORT_START, MCP_PORT_END, ) .await { Ok(listener) => listener, Err(message) => { log::error!("Failed to bind MCP server: {message}"); let _ = ready.send(Err(message)); return; } }; let bound_port = tcp_listener.local_addr().unwrap().port(); log::info!("MCP server listening on http://127.0.0.1:{bound_port}/mcp"); let _ = ready.send(Ok(bound_port)); let _ = axum::serve(tcp_listener, router) .with_graceful_shutdown(async move { ct.cancelled().await; }) .await; log::info!("MCP server stopped"); } pub fn init_mcp(app_handle: AppHandle) { let state = McpState::new(); app_handle.manage(state); } #[tauri::command] pub async fn mcp_start( state: tauri::State<'_, McpState>, bridge: tauri::State<'_, crate::bridge::bridge::Bridge>, ) -> Result { let mut guard = state.running.lock().await; if let Some(server) = guard.as_ref() { log::info!("MCP server already running on port {}", server.port); return Ok(server.port); } log::info!("Starting MCP server"); let ct = CancellationToken::new(); let (ready_tx, ready_rx) = oneshot::channel(); let task = tauri::async_runtime::spawn(start_server( bridge.inner().clone(), ct.clone(), ready_tx, )); match ready_rx.await { Ok(Ok(port)) => { *guard = Some(RunningServer { task, cancellation_token: ct, port, }); Ok(port) } Ok(Err(message)) => Err(message), Err(_) => Err("MCP server task exited before reporting ready".into()), } } #[tauri::command] pub async fn mcp_stop(state: tauri::State<'_, McpState>) -> Result<(), String> { let mut guard = state.running.lock().await; if let Some(server) = guard.take() { log::info!("Stopping MCP server"); server.cancellation_token.cancel(); let _ = server.task.await; } else { log::info!("MCP server already stopped"); } Ok(()) }