systemprompt_api/services/middleware/context/middleware/
flavours.rs1use std::sync::Arc;
14
15use axum::extract::Request;
16use axum::middleware::Next;
17use axum::response::Response;
18use systemprompt_identifiers::{AgentName, ContextId};
19use systemprompt_models::execution::context::RequestContext;
20use systemprompt_security::HeaderExtractor;
21use tracing::Instrument;
22
23use super::super::extractors::ContextExtractor;
24use super::error::log_error_response;
25use super::support::{DynExtractor, create_request_span, session_context_required_error};
26
27#[derive(Clone, Copy, Debug, Default)]
34pub struct PublicContextMiddleware;
35
36impl PublicContextMiddleware {
37 #[must_use]
38 pub const fn new() -> Self {
39 Self
40 }
41
42 pub async fn handle(&self, mut request: Request, next: Next) -> Response {
43 let Some(mut req_ctx) = request.extensions().get::<RequestContext>().cloned() else {
44 let trace_id = HeaderExtractor::extract_trace_id(request.headers());
45 let path = request.uri().path().to_owned();
46 let method = request.method().to_string();
47 return session_context_required_error(&trace_id, &path, &method);
48 };
49
50 let headers = request.headers();
51 if let Some(context_id) = headers.get("x-context-id")
52 && let Ok(id) = context_id.to_str()
53 {
54 match ContextId::try_new(id.to_owned()) {
55 Ok(parsed) => req_ctx.execution.context_id = parsed,
56 Err(e) => {
57 tracing::warn!(error = %e, "ignoring malformed x-context-id header");
58 },
59 }
60 }
61
62 if let Some(agent_name) = headers.get("x-agent-name")
63 && let Ok(name) = agent_name.to_str()
64 {
65 match AgentName::try_new(name.to_owned()) {
66 Ok(parsed) => req_ctx.execution.agent_name = parsed,
67 Err(e) => {
68 tracing::warn!(error = %e, "ignoring malformed x-agent-name header");
69 },
70 }
71 }
72
73 let span = create_request_span(&req_ctx);
74 request.extensions_mut().insert(req_ctx);
75 next.run(request).instrument(span).await
76 }
77}
78
79#[derive(Clone)]
80pub struct UserOnlyContextMiddleware {
81 extractor: DynExtractor,
82}
83
84impl std::fmt::Debug for UserOnlyContextMiddleware {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 f.debug_struct("UserOnlyContextMiddleware").finish()
87 }
88}
89
90impl UserOnlyContextMiddleware {
91 pub fn new<E>(extractor: E) -> Self
92 where
93 E: ContextExtractor + Send + Sync + 'static,
94 {
95 Self {
96 extractor: Arc::new(extractor),
97 }
98 }
99
100 pub async fn handle(&self, mut request: Request, next: Next) -> Response {
101 let trace_id = HeaderExtractor::extract_trace_id(request.headers());
102 let path = request.uri().path().to_owned();
103 let method = request.method().to_string();
104
105 match self.extractor.extract_from_headers(request.headers()).await {
106 Ok(context) => {
107 let span = create_request_span(&context);
108 request.extensions_mut().insert(context);
109 next.run(request).instrument(span).await
110 },
111 Err(e) => log_error_response(&e, &trace_id, &path, &method),
112 }
113 }
114}
115
116#[derive(Clone)]
122pub struct A2AContextMiddleware {
123 extractor: DynExtractor,
124}
125
126impl std::fmt::Debug for A2AContextMiddleware {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 f.debug_struct("A2AContextMiddleware").finish()
129 }
130}
131
132impl A2AContextMiddleware {
133 pub fn new<E>(extractor: E) -> Self
134 where
135 E: ContextExtractor + Send + Sync + 'static,
136 {
137 Self {
138 extractor: Arc::new(extractor),
139 }
140 }
141
142 pub async fn handle(&self, request: Request, next: Next) -> Response {
143 let trace_id = HeaderExtractor::extract_trace_id(request.headers());
144 let path = request.uri().path().to_owned();
145 let method = request.method().to_string();
146
147 match self.extractor.extract_from_request(request).await {
148 Ok((context, reconstructed_request)) => {
149 let span = create_request_span(&context);
150 let mut req = reconstructed_request;
151 req.extensions_mut().insert(context);
152 next.run(req).instrument(span).await
153 },
154 Err(e) => log_error_response(&e, &trace_id, &path, &method),
155 }
156 }
157}
158
159#[derive(Clone)]
171pub struct McpContextMiddleware {
172 extractor: DynExtractor,
173}
174
175impl std::fmt::Debug for McpContextMiddleware {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 f.debug_struct("McpContextMiddleware").finish()
178 }
179}
180
181impl McpContextMiddleware {
182 pub fn new<E>(extractor: E) -> Self
183 where
184 E: ContextExtractor + Send + Sync + 'static,
185 {
186 Self {
187 extractor: Arc::new(extractor),
188 }
189 }
190
191 pub async fn handle(&self, request: Request, next: Next) -> Response {
192 let trace_id = HeaderExtractor::extract_trace_id(request.headers());
193 let path = request.uri().path().to_owned();
194 let method = request.method().to_string();
195
196 match self.extractor.extract_from_headers(request.headers()).await {
197 Ok(context) => {
198 let span = create_request_span(&context);
199 let mut req = request;
200 req.extensions_mut().insert(context);
201 next.run(req).instrument(span).await
202 },
203 Err(e) => {
204 if let Some(ctx) = request.extensions().get::<RequestContext>().cloned() {
205 tracing::debug!(
206 error = %e,
207 trace_id = %trace_id,
208 "MCP header extraction failed, using session context"
209 );
210 let span = create_request_span(&ctx);
211 next.run(request).instrument(span).await
212 } else {
213 session_context_required_error(&trace_id, &path, &method)
214 }
215 },
216 }
217 }
218}