use axum::{
Json,
extract::{Path, State},
response::IntoResponse,
};
use serde_json::json;
use super::{AppState, JsonError, internal_err, not_found};
pub async fn list_servers(State(state): State<AppState>) -> Result<impl IntoResponse, JsonError> {
let config = state.config.read().await;
let mcp_clients = state.mcp_clients.read().await;
let servers: Vec<serde_json::Value> = config
.mcp
.servers
.iter()
.map(|s| {
let conn = mcp_clients.get_connection(&s.name);
let connected = conn.map(|c| c.is_connected()).unwrap_or(false);
let tool_count = conn.map(|c| c.available_tools.len()).unwrap_or(0);
let transport = match &s.spec {
roboticus_core::config::McpServerSpec::Stdio { command, .. } => {
json!({"type": "stdio", "command": command})
}
roboticus_core::config::McpServerSpec::Sse { url } => {
json!({"type": "sse", "url": url})
}
};
json!({
"name": s.name,
"enabled": s.enabled,
"connected": connected,
"tool_count": tool_count,
"transport": transport,
})
})
.collect();
Ok(Json(json!(servers)))
}
pub async fn get_server(
State(state): State<AppState>,
Path(name): Path<String>,
) -> Result<impl IntoResponse, JsonError> {
let config = state.config.read().await;
let server = config
.mcp
.servers
.iter()
.find(|s| s.name == name)
.ok_or_else(|| not_found(format!("MCP server '{name}' not configured")))?;
let mcp_clients = state.mcp_clients.read().await;
let conn = mcp_clients.get_connection(&name);
let connected = conn.map(|c| c.is_connected()).unwrap_or(false);
let tools: Vec<serde_json::Value> = conn
.map(|c| {
c.available_tools
.iter()
.map(|t| {
json!({
"name": t.name,
"description": t.description,
})
})
.collect()
})
.unwrap_or_default();
let transport = match &server.spec {
roboticus_core::config::McpServerSpec::Stdio { command, args, .. } => {
json!({"type": "stdio", "command": command, "args": args})
}
roboticus_core::config::McpServerSpec::Sse { url } => {
json!({"type": "sse", "url": url})
}
};
Ok(Json(json!({
"name": server.name,
"enabled": server.enabled,
"connected": connected,
"transport": transport,
"tools": tools,
})))
}
pub async fn test_server(
State(state): State<AppState>,
Path(name): Path<String>,
) -> Result<impl IntoResponse, JsonError> {
let server = {
let config = state.config.read().await;
config
.mcp
.servers
.iter()
.find(|s| s.name == name)
.ok_or_else(|| not_found(format!("MCP server '{name}' not configured")))?
.clone()
};
let start = std::time::Instant::now();
match roboticus_agent::mcp::client::LiveMcpConnection::connect(&server).await {
Ok(conn) => {
let elapsed = start.elapsed();
Ok(Json(json!({
"name": name,
"success": true,
"server_name": conn.server_name(),
"server_version": conn.server_version(),
"tool_count": conn.tools().len(),
"tools": conn.tools().iter().map(|t| json!({
"name": t.name,
"description": t.description,
})).collect::<Vec<_>>(),
"latency_ms": elapsed.as_millis(),
})))
}
Err(e) => {
let elapsed = start.elapsed();
tracing::warn!(name, error = %e, "MCP server connectivity test failed");
Ok(Json(json!({
"name": name,
"success": false,
"error": internal_err(&e).1,
"latency_ms": elapsed.as_millis(),
})))
}
}
}