mod backend;
mod config;
mod events;
mod middleware;
mod types;
pub use backend::{
CloudWatchBackend, CloudWatchConfig, CompositeBackend, ConsoleBackend, NullBackend,
ObservabilityBackend,
};
pub use config::{
ConfigError, ConsoleConfig, FieldsConfig, MetricsConfig, ObservabilityConfig, TracingConfig,
};
pub use events::{
McpMetric, McpRequestEvent, McpResponseEvent, MetricUnit, RequestStart, StandardMetrics,
};
pub use middleware::McpObservabilityMiddleware;
pub use types::{hash_value, McpOperationDetails, RequestMetadata, TraceContext};
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[tokio::test]
async fn test_end_to_end_observability() {
let config = ObservabilityConfig::development();
let backend = Arc::new(ConsoleBackend::new(config.console.pretty));
let middleware = McpObservabilityMiddleware::new("test-server", config, backend);
assert!(format!("{:?}", middleware).contains("test-server"));
}
#[test]
fn test_trace_context_propagation() {
let root = TraceContext::new_root();
assert_eq!(root.depth, 0);
assert!(root.parent_span_id.is_none());
let child1 = root.child();
assert_eq!(child1.depth, 1);
assert_eq!(child1.trace_id, root.trace_id);
assert_eq!(child1.parent_span_id, Some(root.span_id.clone()));
let child2 = child1.child();
assert_eq!(child2.depth, 2);
assert_eq!(child2.trace_id, root.trace_id);
assert_eq!(child2.parent_span_id, Some(child1.span_id.clone()));
}
#[test]
fn test_config_loading_from_toml() {
let toml = r#"
[observability]
enabled = true
backend = "cloudwatch"
sample_rate = 0.5
[observability.fields]
capture_tool_name = true
capture_user_id = true
[observability.cloudwatch]
namespace = "TestApp/MCP"
"#;
let config = ObservabilityConfig::from_toml(toml).unwrap();
assert!(config.enabled);
assert_eq!(config.backend, "cloudwatch");
assert!((config.sample_rate - 0.5).abs() < f64::EPSILON);
assert!(config.fields.capture_tool_name);
assert_eq!(config.cloudwatch.namespace, "TestApp/MCP");
}
#[test]
fn test_operation_details_tool_call() {
let details = McpOperationDetails::tool_call("get_weather");
assert_eq!(details.method, "tools/call");
assert_eq!(details.tool_name, Some("get_weather".to_string()));
assert_eq!(details.operation_name(), Some("get_weather"));
}
#[test]
fn test_request_metadata_builder() {
let metadata = RequestMetadata::default()
.with_client_type("claude-desktop")
.with_client_version("1.2.3")
.with_session_id("session-123");
assert_eq!(metadata.client_type, Some("claude-desktop".to_string()));
assert_eq!(metadata.client_version, Some("1.2.3".to_string()));
assert_eq!(metadata.session_id, Some("session-123".to_string()));
}
#[tokio::test]
async fn test_composite_backend() {
let null1 = Arc::new(NullBackend);
let null2 = Arc::new(NullBackend);
let composite = CompositeBackend::new(vec![null1, null2]);
composite.flush().await;
assert_eq!(composite.len(), 2);
}
}