use std::io::Write;
use anyhow::Result;
use rmcp::{
service::{RunningService, ServiceExt},
RoleServer,
};
use tracing_subscriber::EnvFilter;
use super::OmniDevServer;
pub fn try_init_tracing() -> Result<()> {
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_ansi(false)
.with_env_filter(
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn")),
)
.try_init()
.map_err(|e| anyhow::anyhow!("tracing subscriber already set: {e}"))?;
Ok(())
}
pub async fn serve_with<T, E, A>(transport: T) -> Result<()>
where
T: rmcp::transport::IntoTransport<RoleServer, E, A>,
E: std::error::Error + Send + Sync + 'static,
{
let service: RunningService<RoleServer, OmniDevServer> =
OmniDevServer::new().serve(transport).await?;
service.waiting().await?;
Ok(())
}
pub fn feature_flags() -> &'static str {
"mcp"
}
pub fn log_startup_event() {
let version = env!("CARGO_PKG_VERSION");
let features = feature_flags();
tracing::info!(version, features, "starting omni-dev MCP server");
}
pub fn write_error_chain<W: Write>(writer: &mut W, err: &anyhow::Error) -> std::io::Result<()> {
writeln!(writer, "Error: {err}")?;
let mut source = err.source();
while let Some(inner) = source {
writeln!(writer, " Caused by: {inner}")?;
source = inner.source();
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use anyhow::{anyhow, Context};
#[test]
fn write_error_chain_single_error() {
let err = anyhow!("only failure");
let mut buf = Vec::new();
write_error_chain(&mut buf, &err).unwrap();
let out = String::from_utf8(buf).unwrap();
assert_eq!(out, "Error: only failure\n");
}
#[test]
fn write_error_chain_preserves_chain() {
let result: Result<(), anyhow::Error> =
Err(anyhow!("root")).context("middle").context("outermost");
let err = result.expect_err("constructed Err");
let mut buf = Vec::new();
write_error_chain(&mut buf, &err).unwrap();
let out = String::from_utf8(buf).unwrap();
assert!(out.starts_with("Error: outermost\n"), "got: {out:?}");
assert!(out.contains(" Caused by: middle\n"));
assert!(out.contains(" Caused by: root\n"));
}
#[tokio::test]
async fn serve_with_handles_peer_disconnect() {
let (server_transport, client_transport) = tokio::io::duplex(4096);
let server_handle = tokio::spawn(async move { serve_with(server_transport).await });
drop(client_transport);
let result = server_handle.await.unwrap();
let _ = result;
}
#[test]
fn feature_flags_includes_mcp() {
let flags = feature_flags();
assert!(
flags.contains("mcp"),
"expected feature flags to include mcp, got {flags:?}"
);
}
#[test]
fn log_startup_event_does_not_panic() {
log_startup_event();
}
#[test]
fn try_init_tracing_is_idempotent_or_errors() {
let _ = try_init_tracing();
let second = try_init_tracing();
assert!(second.is_err(), "second init should report already-set");
}
}