use std::net::SocketAddr;
use tower::layer::util::{Identity, Stack};
pub mod auth;
pub mod discovery;
pub mod error;
pub mod instrumented;
pub mod job;
pub mod mcp;
pub mod state;
pub mod telemetry;
pub mod traits;
pub mod transport;
pub use state::State;
pub use error::{Error, Result};
type LayeredRouter = tonic::transport::server::Router<
Stack<auth::AuthLayer, Stack<crate::telemetry::propagate::ExtractLayer, Identity>>,
>;
pub(crate) type McpSpawner = Box<
dyn FnOnce(
crate::mcp::McpConfig,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = std::result::Result<
(SocketAddr, tokio::task::JoinHandle<()>),
std::io::Error,
>,
> + Send,
>,
> + Send,
>;
pub struct Service {
name: String,
addr: SocketAddr,
router: Option<LayeredRouter>,
auth_layer: Option<auth::AuthLayer>,
mcp: Option<crate::mcp::McpConfig>,
mcp_spawner: Option<McpSpawner>,
}
impl Service {
pub fn new(name: impl Into<String>) -> Self {
let name = name.into();
if let Err(e) = crate::telemetry::init(&name) {
eprintln!("micro: telemetry init failed: {e}");
}
Self {
name,
addr: "0.0.0.0:50051".parse().unwrap(),
router: None,
auth_layer: None,
mcp: None,
mcp_spawner: None,
}
}
pub fn addr(mut self, addr: SocketAddr) -> Self {
self.addr = addr;
self
}
pub fn enable_mcp(mut self) -> Self {
self.mcp = Some(crate::mcp::McpConfig::default());
self
}
pub fn mcp_addr(mut self, addr: SocketAddr) -> Self {
self.mcp = Some(crate::mcp::McpConfig { addr });
self
}
pub fn enable_mcp_with<H, F>(mut self, factory: F) -> Self
where
H: crate::mcp::McpServerHandler + Clone + Send + Sync + 'static,
F: Fn() -> std::result::Result<H, std::io::Error> + Send + Sync + Clone + 'static,
{
if self.mcp.is_none() {
self.mcp = Some(crate::mcp::McpConfig::default());
}
let spawner: McpSpawner = Box::new(move |cfg| {
Box::pin(async move { crate::mcp::spawn_with(cfg, factory).await })
});
self.mcp_spawner = Some(spawner);
self
}
pub fn with_auth<V: auth::TokenVerifier>(mut self, verifier: V) -> Self {
let extractor = auth::default::BearerHeaderExtractor;
self.auth_layer = Some(auth::AuthLayer::new(extractor, verifier));
self
}
pub fn with_auth_layer(mut self, layer: auth::AuthLayer) -> Self {
self.auth_layer = Some(layer);
self
}
pub fn without_auth(mut self) -> Self {
let extractor = auth::default::BearerHeaderExtractor;
let verifier = auth::AnonymousVerifier;
self.auth_layer = Some(auth::AuthLayer::new(extractor, verifier).optional());
self
}
pub fn handler<S>(mut self, svc: S) -> Self
where
S: tower::Service<
http::Request<tonic::body::BoxBody>,
Response = http::Response<tonic::body::BoxBody>,
Error = std::convert::Infallible,
> + tonic::server::NamedService
+ Clone
+ Send
+ 'static,
S::Future: Send + 'static,
{
let auth_layer = self
.auth_layer
.clone()
.unwrap_or_else(default_anonymous_auth);
let router: LayeredRouter = match self.router.take() {
Some(r) => r.add_service(svc),
None => tonic::transport::Server::builder()
.layer(crate::telemetry::propagate::extract_layer())
.layer(auth_layer)
.add_service(svc),
};
self.router = Some(router);
self
}
pub async fn run(self) -> Result<()> {
let Service {
name,
addr,
router,
auth_layer: _,
mcp,
mcp_spawner,
} = self;
let router = router.ok_or_else(|| Error::Config("no handler registered".into()))?;
tracing::info!(service = %name, %addr, "tonin service starting");
let _mcp_handle = if let Some(cfg) = mcp {
let spawn_result = if let Some(spawner) = mcp_spawner {
spawner(cfg).await
} else {
crate::mcp::spawn(cfg).await
};
match spawn_result {
Ok((bound, handle)) => {
tracing::info!(service = %name, mcp_addr = %bound, "mcp listener running");
Some(handle)
}
Err(e) => {
tracing::error!(service = %name, error = %e, "mcp listener failed to bind — continuing without MCP");
None
}
}
} else {
None
};
let serve_result = router.serve(addr).await;
crate::telemetry::shutdown();
serve_result?;
Ok(())
}
}
fn default_anonymous_auth() -> auth::AuthLayer {
auth::AuthLayer::new(
auth::default::BearerHeaderExtractor,
auth::AnonymousVerifier,
)
.optional()
}
pub mod prelude {
pub use super::{Error, Service, State};
pub use crate::auth::{AuthCtx, AuthError, PrincipalKind};
pub use crate::traits::{
Cache, Database, EventBus, MessageId, SecretStore, StartPosition, SubscribeOptions,
};
pub use tonic::{Request, Response, Status};
}