soma_mcp_server/trace.rs
1//! Reusable trace metadata extraction, integrating `rmcp-traces` for inbound
2//! RMCP request `_meta`.
3//!
4//! This module owns the generic, protocol-level extraction step only. Turning
5//! the recovered fields into a product-specific trace-context type (with its
6//! own field names, propagation policy, and downstream plumbing) is a product
7//! adapter concern and stays out of this crate.
8
9use rmcp::model::Meta;
10
11pub use rmcp_traces::{TraceSummary, TraceTrust};
12
13/// Build a bounded [`TraceSummary`] from inbound RMCP request `_meta`.
14///
15/// This is a thin wrapper so MCP server implementations get discoverable,
16/// documented trace extraction from `soma-mcp-server` without adding a direct
17/// `rmcp-traces` dependency themselves.
18pub fn trace_summary_from_meta(meta: &Meta, trust: TraceTrust) -> TraceSummary {
19 TraceSummary::from_meta(meta, trust)
20}
21
22/// Raw W3C `traceparent`/`tracestate` header values recovered from `_meta`.
23#[derive(Clone, Debug, Default, PartialEq, Eq)]
24pub struct RawTraceFields {
25 pub traceparent: Option<String>,
26 pub tracestate: Option<String>,
27}
28
29/// One bounded parse of request `_meta`, keeping the validated summary and raw
30/// fields tied to the same input.
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct TraceMetaExtraction {
33 pub summary: TraceSummary,
34 pub raw_fields: Option<RawTraceFields>,
35}
36
37/// Parse request `_meta` once and derive both safe summary and gated raw fields.
38pub fn extract_trace_meta(meta: &Meta, trust: TraceTrust) -> TraceMetaExtraction {
39 let summary = trace_summary_from_meta(meta, trust);
40 let raw_fields = raw_trace_fields_from_summary(meta, &summary);
41 TraceMetaExtraction {
42 summary,
43 raw_fields,
44 }
45}
46
47/// Recover raw `traceparent`/`tracestate` values from `_meta`, gated on the
48/// bounded [`TraceSummary`] confirming a validated trace id. Returns `None`
49/// when no valid trace id was extracted (absent, malformed, or over budget).
50pub fn raw_trace_fields_from_meta(meta: &Meta, trust: TraceTrust) -> Option<RawTraceFields> {
51 extract_trace_meta(meta, trust).raw_fields
52}
53
54fn raw_trace_fields_from_summary(meta: &Meta, summary: &TraceSummary) -> Option<RawTraceFields> {
55 summary.trace_id_prefix()?;
56 Some(RawTraceFields {
57 traceparent: meta.get_traceparent().map(ToOwned::to_owned),
58 tracestate: summary
59 .has_tracestate()
60 .then(|| meta.get_tracestate().map(ToOwned::to_owned))
61 .flatten(),
62 })
63}
64
65#[cfg(test)]
66#[path = "trace_tests.rs"]
67mod tests;