use std::net::SocketAddr;
use crate::cli::admin_auth::{admin_get, parse_error_message};
pub fn run_list(
config: &bitrouter_config::BitrouterConfig,
addr: SocketAddr,
) -> Result<(), Box<dyn std::error::Error>> {
let resp = admin_get(config, addr, "/admin/tools")?;
if !resp.status().is_success() {
let msg = parse_error_message(resp)?;
return Err(format!("failed to list tools: {msg}").into());
}
let body: serde_json::Value = resp.json()?;
let tools = body["tools"].as_array();
match tools {
Some(tools) if !tools.is_empty() => {
for tool in tools {
let name = tool["name"].as_str().unwrap_or("?");
let server = tool["provider"].as_str().unwrap_or("?");
let desc = tool["description"].as_str().unwrap_or("");
if desc.is_empty() {
println!(" {name} [{server}]");
} else {
println!(" {name} [{server}] — {desc}");
}
}
}
_ => {
println!(" (no tools available)");
}
}
Ok(())
}
pub fn run_status(
config: &bitrouter_config::BitrouterConfig,
addr: SocketAddr,
) -> Result<(), Box<dyn std::error::Error>> {
let resp = admin_get(config, addr, "/admin/tools/upstreams")?;
if !resp.status().is_success() {
let msg = parse_error_message(resp)?;
return Err(format!("failed to get tool status: {msg}").into());
}
let body: serde_json::Value = resp.json()?;
let servers = body["servers"].as_array();
match servers {
Some(servers) if !servers.is_empty() => {
for server in servers {
let name = server["name"].as_str().unwrap_or("?");
let tool_count = server["tool_count"].as_u64().unwrap_or(0);
let has_filter = server["filter"].is_object();
let filter_info = if has_filter { " (filtered)" } else { "" };
println!(" {name} {tool_count} tools{filter_info}");
}
}
_ => {
println!(" (no MCP servers configured)");
}
}
Ok(())
}
#[cfg(feature = "mcp")]
pub async fn run_discover(
config: &bitrouter_config::BitrouterConfig,
provider_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
use bitrouter_providers::mcp::client::config::{McpServerConfig, McpServerTransport};
use bitrouter_providers::mcp::client::upstream::UpstreamConnection;
let provider = config
.providers
.get(provider_name)
.ok_or_else(|| format!("provider '{provider_name}' not found in config"))?;
let url = provider
.api_base
.as_deref()
.ok_or_else(|| format!("provider '{provider_name}' has no api_base configured"))?;
let mut headers = provider.default_headers.clone().unwrap_or_default();
if let Some(ref key) = provider.api_key {
headers
.entry("Authorization".to_owned())
.or_insert_with(|| format!("Bearer {key}"));
}
let mcp_config = McpServerConfig {
name: provider_name.to_owned(),
transport: McpServerTransport::Http {
url: url.to_owned(),
headers,
},
};
eprintln!("Connecting to {provider_name} ({url})...");
let conn = UpstreamConnection::connect(mcp_config, None)
.await
.map_err(|e| format!("failed to connect to '{provider_name}': {e}"))?;
let tools = conn.raw_tools().await;
if tools.is_empty() {
eprintln!("No tools discovered from '{provider_name}'.");
return Ok(());
}
eprintln!("Discovered {} tools from '{provider_name}'.", tools.len());
eprintln!();
println!("# Discovered from provider \"{provider_name}\"");
println!("# Paste under the `tools:` section of your bitrouter.yaml");
println!();
println!("tools:");
for tool in &tools {
println!(" {}:", tool.name);
println!(" endpoints:");
println!(" - provider: {provider_name}");
println!(" tool_id: {}", tool.name);
if let Some(ref desc) = tool.description {
let escaped = desc.replace('"', "\\\"");
println!(" description: \"{escaped}\"");
}
if tool.input_schema.is_object() && tool.input_schema.get("properties").is_some() {
println!(
" input_schema: {}",
serde_json::to_string(&tool.input_schema).unwrap_or_default()
);
}
}
Ok(())
}
#[cfg(not(feature = "mcp"))]
pub async fn run_discover(
_config: &bitrouter_config::BitrouterConfig,
_provider_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
Err("MCP feature is not enabled — cannot discover tools".into())
}