use anyhow::Result;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
use rmcp::{
handler::server::wrapper::Parameters,
schemars,
serde,
service::ServiceExt,
tool,
tool_router,
transport::stdio,
};
#[derive(Debug, Clone, serde::Deserialize, schemars::JsonSchema)]
struct OcrImageParams {
#[schemars(description = "Absolute path to the image file")]
image_path: String,
}
#[derive(Debug, Clone, serde::Deserialize, schemars::JsonSchema)]
struct OcrBase64Params {
#[schemars(description = "Base64-encoded image data (raw base64 or data URI)")]
image_base64: String,
}
#[derive(Clone)]
struct OcrServer;
#[tool_router(server_handler)]
impl OcrServer {
#[tool(description = "Perform OCR on an image file and return the extracted text with confidence. English text only.")]
fn ocr_image(
&self,
Parameters(params): Parameters<OcrImageParams>,
) -> String {
let engine = ocr::OcrEngine::new();
match engine.recognize_file(std::path::Path::new(¶ms.image_path)) {
Ok(result) => format_result(&result),
Err(e) => format!("Error: {}", e),
}
}
#[tool(description = "Perform OCR on a base64-encoded image and return the extracted text. English text only.")]
fn ocr_base64(
&self,
Parameters(params): Parameters<OcrBase64Params>,
) -> String {
let image_data = match decode_base64(¶ms.image_base64) {
Ok(data) => data,
Err(e) => return format!("Error decoding base64: {}", e),
};
let engine = ocr::OcrEngine::new();
match engine.recognize_bytes(&image_data) {
Ok(result) => format_result(&result),
Err(e) => format!("Error: {}", e),
}
}
}
fn format_result(result: &ocr::OcrResult) -> String {
let mut output = result.text.clone();
if !result.words.is_empty() {
output.push_str(&format!(
"\n\n---\nConfidence: {:.1}% | Words: {}",
result.confidence,
result.words.len(),
));
}
output
}
fn decode_base64(input: &str) -> Result<Vec<u8>, String> {
let cleaned = input
.trim()
.strip_prefix("data:image/")
.and_then(|s| s.split_once(";base64,"))
.map(|(_, data)| data)
.unwrap_or(input.trim());
BASE64.decode(cleaned).map_err(|e| format!("{}", e))
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_target(false)
.init();
tracing::info!("Starting ocr-rs MCP server");
let service = OcrServer.serve(stdio()).await?;
service.waiting().await?;
Ok(())
}