use serde_json::Value;
use crate::client::ConfluenceClient;
use crate::mcp::{CallToolResult, ToolDefinition};
use crate::types::{ConfluencePage, ConfluencePageV1};
use super::schema;
pub fn definitions() -> Vec<ToolDefinition> {
vec![ToolDefinition {
name: "get_page_storage_format".to_string(),
description: "Get the raw Confluence storage format (XML/HTML) of a page. Essential for making precise edits to complex pages with tables, macros, etc.".to_string(),
input_schema: schema(&[
("pageId", "string", true, "The page ID to get storage format for"),
("section", "string", false, "Optional: Extract only a section containing this text (useful for large pages)"),
]),
}]
}
pub async fn get_page_storage_format(client: &ConfluenceClient, args: &Value) -> CallToolResult {
let page_id = match args.get("pageId").and_then(|v| v.as_str()) {
Some(id) => id,
None => return CallToolResult::text("Error: pageId is required."),
};
let section = args.get("section").and_then(|v| v.as_str());
let fetch_result: Result<(String, String, i64), String> = async {
if client.config().is_cloud {
let page: ConfluencePage = client
.get(&format!("/pages/{page_id}?body-format=storage"))
.await?;
let body = page
.body
.and_then(|b| b.storage)
.map_or(String::new(), |s| s.value);
let ver = page.version.map_or(1, |v| v.number);
Ok((page.title, body, ver))
} else {
let page: ConfluencePageV1 = client
.get(&format!(
"/content/{page_id}?expand=body.storage,version"
))
.await?;
let body = page
.body
.and_then(|b| b.storage)
.map_or(String::new(), |s| s.value);
let ver = page.version.map_or(1, |v| v.number);
Ok((page.title, body, ver))
}
}
.await;
match fetch_result {
Ok((title, body, version)) => {
if body.is_empty() {
return CallToolResult::text(format!("Page \"{title}\" has no content."));
}
let output_content = if let Some(sec) = section {
let lower_body = body.to_lowercase();
let lower_sec = sec.to_lowercase();
if let Some(idx) = lower_body.find(&lower_sec) {
let before = &body[..idx];
let after = &body[idx..];
let tr_start = before.rfind("<tr").unwrap_or(0);
let table_start = before.rfind("<table").unwrap_or(0);
let start = tr_start.max(table_start).max(idx.saturating_sub(2000));
let tr_end = after.find("</tr>").map(|p| idx + p + 5);
let end = tr_end.unwrap_or_else(|| (idx + 5000).min(body.len()));
body[start..end].to_string()
} else {
body.clone()
}
} else {
body.clone()
};
let section_note = section
.map(|s| format!("| **Extracted Section** | Around \"{s}\" |\n"))
.unwrap_or_default();
CallToolResult::text(format!(
"# Storage Format: {title}\n\n| Property | Value |\n|----------|-------|\n| **Page ID** | {page_id} |\n| **Current Version** | {version} |\n| **Content Length** | {} characters |\n{section_note}\n## Raw Storage Format\n\n```xml\n{output_content}\n```\n\n---\n💡 Use `update_page` with this storage format to make edits. Remember to increment the version.",
body.len(),
))
}
Err(e) => CallToolResult::text(format!("Error getting storage format: {e}")),
}
}