use anyhow::Result; use dtrack_core::DtrackClient; use rmcp::{ handler::server::router::tool::ToolRouter, handler::server::wrapper::Parameters, model::{ CallToolResult, Content, Implementation, ProtocolVersion, ServerCapabilities, ServerInfo, }, tool, tool_handler, tool_router, transport::stdio, ErrorData as McpError, ServerHandler, ServiceExt, }; use schemars::JsonSchema; use serde::Deserialize; /// MCP-Server fuer Dependency-Track. Haelt den DT-Client + den vom Macro /// generierten ToolRouter. #[derive(Clone)] pub struct DtrackServer { client: DtrackClient, tool_router: ToolRouter, } /// Argument fuer projektbezogene Tools. #[derive(Debug, Deserialize, JsonSchema)] pub struct ProjectArg { /// UUID des Projekts (aus `list_projects`). pub uuid: String, } #[tool_router] impl DtrackServer { pub fn new(client: DtrackClient) -> Self { Self { client, tool_router: Self::tool_router(), } } #[tool(description = "Listet aktive Dependency-Track-Projekte auf (Name, Version, UUID).")] async fn list_projects(&self) -> Result { let body = self .client .list_projects() .await .map_err(|e| McpError::internal_error(e.to_string(), None))?; Ok(CallToolResult::success(vec![Content::text(body)])) } #[tool(description = "Findings (Schwachstellen) eines Projekts anhand seiner UUID.")] async fn project_findings( &self, Parameters(arg): Parameters, ) -> Result { let body = self .client .project_findings(&arg.uuid) .await .map_err(|e| McpError::internal_error(e.to_string(), None))?; Ok(CallToolResult::success(vec![Content::text(body)])) } #[tool(description = "Aktuelle Sicherheits-Metriken eines Projekts anhand seiner UUID.")] async fn project_metrics( &self, Parameters(arg): Parameters, ) -> Result { let body = self .client .project_metrics(&arg.uuid) .await .map_err(|e| McpError::internal_error(e.to_string(), None))?; Ok(CallToolResult::success(vec![Content::text(body)])) } } #[tool_handler] impl ServerHandler for DtrackServer { fn get_info(&self) -> ServerInfo { ServerInfo { protocol_version: ProtocolVersion::V_2025_06_18, capabilities: ServerCapabilities::builder().enable_tools().build(), server_info: Implementation::from_build_env(), instructions: Some( "Read-only Zugriff auf Dependency-Track (Projekte, Findings, Metriken)." .to_string(), ), ..Default::default() } } } #[tokio::main] async fn main() -> Result<()> { // WICHTIG: Logs NUR auf stderr -- stdout ist der JSON-RPC-Kanal des // stdio-Transports. println!() wuerde den Stream korrumpieren. tracing_subscriber::fmt() .with_writer(std::io::stderr) .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .init(); let client = DtrackClient::from_env()?; let service = DtrackServer::new(client).serve(stdio()).await?; service.waiting().await?; Ok(()) }