#![forbid(unsafe_code)]
mod error;
mod runtime;
mod schema;
use rmcp::{
ServerHandler, ServiceExt,
handler::server::{router::tool::ToolRouter, wrapper::Json, wrapper::Parameters},
model::{ServerCapabilities, ServerInfo},
tool, tool_handler, tool_router,
};
pub use schema::{AnalyzeImageOutput, CandidateRankingSummary, ManualRankOutput, ReadinessOutput};
#[derive(Debug, Clone)]
pub struct ChromaFrameMcpServer {
runtime: runtime::ChromaFrameRuntime,
tool_router: ToolRouter<Self>,
}
impl ChromaFrameMcpServer {
#[must_use]
pub fn from_env() -> Self {
Self::from_runtime(runtime::ChromaFrameRuntime::from_env())
}
#[must_use]
fn from_runtime(runtime: runtime::ChromaFrameRuntime) -> Self {
Self {
runtime,
tool_router: Self::tool_router(),
}
}
pub async fn serve_stdio(self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let running = self.serve(rmcp::transport::stdio()).await?;
let _ = running.waiting().await?;
Ok(())
}
}
#[tool_router]
impl ChromaFrameMcpServer {
#[tool(
name = "chromaframe_readiness",
description = "Report sanitized ChromaFrame SDK and local vision-helper readiness.",
annotations(read_only_hint = true, idempotent_hint = true)
)]
async fn chromaframe_readiness(
&self,
Parameters(input): Parameters<schema::ReadinessInput>,
) -> Result<Json<schema::ReadinessOutput>, String> {
self.runtime
.readiness(&input)
.map(Json)
.map_err(|error| error.message().to_string())
}
#[tool(
name = "chromaframe_rank_candidates",
description = "Rank candidate colors from manual Lab subject measurements with deterministic SDK scoring.",
annotations(read_only_hint = true, idempotent_hint = true)
)]
async fn chromaframe_rank_candidates(
&self,
Parameters(input): Parameters<schema::ManualRankInput>,
) -> Result<Json<schema::ManualRankOutput>, String> {
self.runtime
.rank_candidates(&input)
.map(Json)
.map_err(|error| error.message().to_string())
}
#[tool(
name = "chromaframe_analyze_image",
description = "Analyze a local image with the vision helper and return sanitized deterministic rankings.",
annotations(read_only_hint = true, idempotent_hint = true)
)]
async fn chromaframe_analyze_image(
&self,
Parameters(input): Parameters<schema::AnalyzeImageInput>,
) -> Result<Json<schema::AnalyzeImageOutput>, String> {
self.runtime
.analyze_image(&input)
.await
.map(Json)
.map_err(|error| error.message().to_string())
}
}
#[tool_handler(router = self.tool_router)]
impl ServerHandler for ChromaFrameMcpServer {
fn get_info(&self) -> ServerInfo {
ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
}
}
#[cfg(test)]
mod tests;