scraps 3.7.0

Scraps is a portable CLI knowledge hub for managing interconnected Markdown documentation with Wiki-link notation.
use crate::ir::loader;
use crate::mcp::json::scrap::ScrapRefJson;
use crate::usecase::frontmatter::usecase::FrontmatterUsecase;
use rmcp::model::ErrorCode;
use rmcp::model::{CallToolResult, ContentBlock};
use rmcp::service::RequestContext;
use rmcp::{ErrorData, RoleServer};
use serde::Serialize;
use serde_json::Value;
use std::path::Path;

#[derive(Debug, Serialize)]
pub struct FrontmatterItemJson {
    pub scrap: ScrapRefJson,
    pub frontmatter: Value,
}

#[derive(Debug, Serialize)]
pub struct ListFrontmatterResponse {
    pub results: Vec<FrontmatterItemJson>,
    pub count: usize,
    pub next: String,
}

pub async fn list_frontmatter(
    scraps_dir: &Path,
    exclude_dirs: &[std::path::PathBuf],
    _context: RequestContext<RoleServer>,
) -> Result<CallToolResult, ErrorData> {
    let scraps = loader::load_scraps(scraps_dir, exclude_dirs).map_err(|e| {
        ErrorData::new(
            ErrorCode(-32003),
            format!("Failed to load scraps: {e}"),
            None,
        )
    })?;

    let frontmatter_usecase = FrontmatterUsecase::new();

    let results = frontmatter_usecase.execute(&scraps).map_err(|e| {
        ErrorData::new(
            ErrorCode(-32004),
            format!("List frontmatter failed: {e}"),
            None,
        )
    })?;

    let items: Vec<FrontmatterItemJson> = results
        .into_iter()
        .map(|result| FrontmatterItemJson {
            scrap: (&result.scrap).into(),
            frontmatter: result.frontmatter,
        })
        .collect();

    let count = items.len();
    let next = if count == 0 {
        "No scrap carries frontmatter. The wiki's tags are all #[[tag]] in scrap bodies — start from list_tags."
    } else {
        "Keys are the author's own; scraps gives them no meaning, except that the strings of a `tags` list are tags (list_tags) and a `type` string is the scrap's kind (list_types). Read the scrap behind an entry with get_scrap {key}."
    };
    let response = ListFrontmatterResponse {
        results: items,
        count,
        next: next.to_string(),
    };

    Ok(CallToolResult::success(vec![ContentBlock::text(
        serde_json::to_string(&response).map_err(|e| {
            ErrorData::new(
                ErrorCode(-32005),
                format!("JSON serialization failed: {e}"),
                None,
            )
        })?,
    )]))
}