mcp-confluence 1.0.0

MCP server for Confluence integration - create, update, search, and manage Confluence pages
use serde_json::Value;

use crate::client::ConfluenceClient;
use crate::mcp::{CallToolResult, ToolDefinition};
use crate::types::LabelListResponse;

use super::{schema, schema_with_array};

pub fn definitions() -> Vec<ToolDefinition> {
    vec![
        ToolDefinition {
            name: "add_labels".to_string(),
            description: "Add labels to a Confluence page".to_string(),
            input_schema: schema_with_array(
                &[("pageId", "string", true, "The ID of the page")],
                &[("labels", "string", true, "Array of labels to add")],
            ),
        },
        ToolDefinition {
            name: "get_labels".to_string(),
            description: "Get labels from a Confluence page".to_string(),
            input_schema: schema(&[("pageId", "string", true, "The ID of the page")]),
        },
    ]
}

pub async fn add_labels(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 labels: Vec<String> = match args.get("labels").and_then(|v| v.as_array()) {
        Some(arr) => arr.iter().filter_map(|v| v.as_str().map(String::from)).collect(),
        None => return CallToolResult::text("Error: labels is required."),
    };

    let label_data: Vec<Value> = labels
        .iter()
        .map(|name| serde_json::json!({ "prefix": "global", "name": name }))
        .collect();
    let body = Value::Array(label_data);

    let endpoint = if client.config().is_cloud {
        format!("/pages/{page_id}/labels")
    } else {
        format!("/content/{page_id}/label")
    };

    match client.post::<Value>(&endpoint, &body).await {
        Ok(_) => CallToolResult::text(format!(
            "✅ Labels added to page {page_id}: {}",
            labels.join(", ")
        )),
        Err(e) => CallToolResult::text(format!("Error adding labels: {e}")),
    }
}

pub async fn get_labels(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 endpoint = if client.config().is_cloud {
        format!("/pages/{page_id}/labels")
    } else {
        format!("/content/{page_id}/label")
    };

    match client.get::<LabelListResponse>(&endpoint).await {
        Ok(result) => {
            if result.results.is_empty() {
                return CallToolResult::text("No labels found on this page.");
            }
            let label_list: Vec<&str> = result.results.iter().map(|l| l.name.as_str()).collect();
            CallToolResult::text(format!(
                "Labels on page {page_id}: {}",
                label_list.join(", ")
            ))
        }
        Err(e) => CallToolResult::text(format!("Error getting labels: {e}")),
    }
}