use axum::Json;
use axum::body::Bytes;
use axum::extract::{Path, State};
use axum::http::{HeaderMap, StatusCode, header};
use axum::response::IntoResponse;
use serde_json::json;
use crate::error::ApiError;
use crate::state::{AgentDefinition, AppState, DefFormat, RegisteredAgent};
fn format_from_headers(headers: &HeaderMap) -> Result<DefFormat, ApiError> {
let content_type = headers
.get(header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.unwrap_or("");
if content_type.contains("toml") {
Ok(DefFormat::Toml)
} else if content_type.contains("json") {
Ok(DefFormat::Json)
} else {
Err(ApiError::BadRequest(format!(
"unsupported Content-Type `{content_type}`; send application/toml or application/json"
)))
}
}
pub async fn register(
State(state): State<AppState>,
headers: HeaderMap,
body: Bytes,
) -> Result<impl IntoResponse, ApiError> {
let format = format_from_headers(&headers)?;
let definition = AgentDefinition {
format,
body: body.to_vec(),
};
let built = state
.build_agent(definition.clone())
.await
.map_err(ApiError::BadRequest)?;
let agent_hash = built.agent.def_hash().to_owned();
let name = built.agent.name().map(str::to_owned);
for server in built.servers {
if let Err(error) = server.close().await {
tracing::warn!(%error, "MCP session did not close cleanly after registration");
}
}
let created = state.agent(&agent_hash).is_none();
state.register_agent(RegisteredAgent {
definition,
agent_hash: agent_hash.clone(),
name,
});
Ok((
StatusCode::CREATED,
Json(json!({ "agent": agent_hash, "created": created })),
))
}
pub async fn list(State(state): State<AppState>) -> impl IntoResponse {
let agents: Vec<_> = state
.agent_hashes()
.into_iter()
.map(|hash| {
let name = state.agent(&hash).and_then(|registered| registered.name);
let mut entry = json!({ "agent": hash });
if let Some(name) = name {
entry
.as_object_mut()
.expect("entry is a JSON object")
.insert("name".to_owned(), json!(name));
}
entry
})
.collect();
Json(json!({ "agents": agents }))
}
pub async fn get(
State(state): State<AppState>,
Path(hash): Path<String>,
) -> Result<impl IntoResponse, ApiError> {
let registered = state
.agent(&hash)
.ok_or_else(|| ApiError::UnknownAgent(format!("no agent registered under `{hash}`")))?;
let format = match registered.definition.format {
DefFormat::Toml => "toml",
DefFormat::Json => "json",
};
let body = String::from_utf8_lossy(®istered.definition.body).into_owned();
let mut response = json!({
"agent": registered.agent_hash,
"format": format,
"definition": body,
});
if let Some(name) = registered.name {
response
.as_object_mut()
.expect("response is a JSON object")
.insert("name".to_owned(), json!(name));
}
Ok(Json(response))
}