use anyhow::{Context, Result};
use clap::Args;
use std::fs;
use std::path::PathBuf;
use assay_core::mcp::signing::{extract_signature, sign_tool};
use assay_core::mcp::trust_policy::load_private_key_pem;
#[derive(Args, Debug)]
pub struct SignArgs {
pub tool: PathBuf,
#[arg(long, short)]
pub key: PathBuf,
#[arg(long, short)]
pub out: Option<PathBuf>,
#[arg(long, conflicts_with = "out")]
pub in_place: bool,
#[arg(long)]
pub embed_pubkey: bool,
}
pub fn cmd_sign(args: SignArgs) -> i32 {
match run_sign(args) {
Ok(()) => 0,
Err(e) => {
eprintln!("error: {e:#}");
1
}
}
}
fn run_sign(args: SignArgs) -> Result<()> {
let output_path = if args.in_place {
args.tool.clone()
} else if let Some(out) = args.out {
out
} else {
anyhow::bail!("must specify --out <PATH> or --in-place");
};
let signing_key = load_private_key_pem(&args.key)?;
let tool_json = fs::read_to_string(&args.tool)
.with_context(|| format!("failed to read tool file: {}", args.tool.display()))?;
let tool: serde_json::Value = serde_json::from_str(&tool_json)
.with_context(|| format!("failed to parse tool JSON: {}", args.tool.display()))?;
let signed = sign_tool(&tool, &signing_key, args.embed_pubkey)?;
let sig = extract_signature(&signed).expect("just signed");
let mut output_json = serde_json::to_string_pretty(&signed)?;
output_json.push('\n');
fs::write(&output_path, output_json)
.with_context(|| format!("failed to write output: {}", output_path.display()))?;
println!("Signed tool definition:");
println!(" Input: {}", args.tool.display());
println!(" Output: {}", output_path.display());
println!();
println!("Signature:");
println!(" key_id: {}", sig.key_id);
println!(" payload_digest: {}", sig.payload_digest);
println!(" signed_at: {}", sig.signed_at);
if sig.public_key.is_some() {
println!(" public_key: (embedded)");
}
Ok(())
}