use metis_core::application::services::workspace::WorkspaceDetectionService;
use rust_mcp_sdk::{
macros::{mcp_tool, JsonSchema},
schema::{schema_utils::CallToolError, CallToolResult, TextContent},
};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[mcp_tool(
name = "list_documents",
description = "List documents in a project with optional filtering. Returns document details including unique short codes (format: PREFIX-TYPE-NNNN).",
idempotent_hint = true,
destructive_hint = false,
open_world_hint = false,
read_only_hint = true
)]
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ListDocumentsTool {
pub project_path: String,
}
impl ListDocumentsTool {
pub async fn call_tool(&self) -> std::result::Result<CallToolResult, CallToolError> {
let metis_dir = Path::new(&self.project_path);
let detection_service = WorkspaceDetectionService::new();
let db = detection_service
.prepare_workspace(metis_dir)
.await
.map_err(|e| {
CallToolError::new(std::io::Error::new(
std::io::ErrorKind::Other,
e.to_string(),
))
})?;
let mut repo = db.into_repository();
let documents = self.list_all_documents(&mut repo)?;
let limited_documents = documents;
let document_list: Vec<serde_json::Value> = limited_documents
.iter()
.map(|doc| {
let updated = chrono::DateTime::from_timestamp(doc.updated_at as i64, 0)
.map(|dt| dt.format("%Y-%m-%d %H:%M").to_string())
.unwrap_or_else(|| "Unknown".to_string());
serde_json::json!({
"id": doc.id,
"title": doc.title,
"document_type": doc.document_type,
"phase": doc.phase,
"filepath": doc.filepath,
"updated_at": updated,
"archived": doc.archived,
"short_code": doc.short_code
})
})
.collect();
let response = serde_json::json!({
"documents": document_list,
"total_count": limited_documents.len()
});
Ok(CallToolResult::text_content(vec![TextContent::from(
serde_json::to_string_pretty(&response).map_err(CallToolError::new)?,
)]))
}
fn list_all_documents(
&self,
repo: &mut metis_core::dal::database::repository::DocumentRepository,
) -> Result<Vec<metis_core::dal::database::models::Document>, CallToolError> {
let mut all_docs = Vec::new();
for doc_type in ["vision", "strategy", "initiative", "task", "adr"] {
let mut docs = repo.find_by_type(doc_type).map_err(|e| {
CallToolError::new(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to query {} documents: {}", doc_type, e),
))
})?;
all_docs.append(&mut docs);
}
all_docs.sort_by(|a, b| {
b.updated_at
.partial_cmp(&a.updated_at)
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(all_docs)
}
}