nb-mcp-server 0.5.1

MCP server wrapping the nb CLI for LLM-friendly note-taking
use std::path::{Path, PathBuf};

use anyhow::{Context, Result, anyhow};
use tokio::process::Command;

use crate::{Config, nb::NbClient};

pub async fn disable_commit_signing(config: &Config) -> Result<Option<PathBuf>> {
    let nb_client = NbClient::new(config.notebook.as_deref(), config.create_notebook, true)
        .context("create nb client for commit signing update")?;
    let path = nb_client
        .notebook_path(config.notebook.as_deref())
        .await
        .context("fetch notebook path for commit signing update")?;
    disable_signing_for_path(&path).await.map(Some)
}

async fn disable_signing_for_path(path: &Path) -> Result<PathBuf> {
    let root = resolve_git_root(path).await?;
    apply_signing_config(&root).await?;
    Ok(root)
}

async fn resolve_git_root(path: &Path) -> Result<PathBuf> {
    let output = Command::new("git")
        .arg("-C")
        .arg(path)
        .args(["rev-parse", "--show-toplevel"])
        .output()
        .await
        .context("run git rev-parse to resolve notebook repository root")?;
    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let message = select_output(&stderr, &stdout);
        return Err(anyhow!(
            "git rev-parse failed for notebook repository: {}",
            message.trim()
        ));
    }
    let stdout = String::from_utf8_lossy(&output.stdout);
    let root = stdout.trim();
    if root.is_empty() {
        return Err(anyhow!(
            "git rev-parse returned an empty notebook repository path"
        ));
    }
    Ok(PathBuf::from(root))
}

async fn apply_signing_config(path: &Path) -> Result<()> {
    run_git_config(path, "commit.gpgsign", "false").await?;
    run_git_config(path, "tag.gpgsign", "false").await?;
    Ok(())
}

async fn run_git_config(path: &Path, key: &str, value: &str) -> Result<()> {
    let output = Command::new("git")
        .arg("-C")
        .arg(path)
        .arg("config")
        .arg(key)
        .arg(value)
        .output()
        .await
        .with_context(|| format!("run git config {key} for notebook repository"))?;
    if output.status.success() {
        return Ok(());
    }
    let stderr = String::from_utf8_lossy(&output.stderr);
    let stdout = String::from_utf8_lossy(&output.stdout);
    let message = select_output(&stderr, &stdout);
    Err(anyhow!(
        "git config failed for {key} in notebook repository: {}",
        message.trim()
    ))
}

fn select_output<'a>(stderr: &'a str, stdout: &'a str) -> &'a str {
    if stderr.trim().is_empty() {
        stdout
    } else {
        stderr
    }
}