Skip to main content

mcpr_core/proxy/pipeline/
context.rs

1//! Per-request contexts threaded through the pipeline.
2//!
3//! [`RequestContext`] is a one-pass parse of the incoming HTTP request that
4//! every downstream stage reads from instead of re-parsing. Handlers update
5//! a small set of mutable fields (session id after upstream assigns one,
6//! client name/version after session lookup) before handing off to
7//! [`super::emit::emit_request_event`].
8//!
9//! [`ResponseContext`] carries the accumulating response through the
10//! response middleware chain: the raw body, an optional parsed JSON view,
11//! SSE-wrapping state, and JSON-RPC error info. Middleware mutate it; the
12//! final handler builds the `axum::Response` from `resp.body` + `resp.headers`.
13
14use std::time::Instant;
15
16use crate::protocol::session::ClientInfo;
17use crate::protocol::{McpMethod, ParsedBody};
18use axum::http::{HeaderMap, Method};
19use serde_json::Value;
20
21pub struct RequestContext {
22    pub start: Instant,
23
24    // ── HTTP ──
25    pub http_method: Method,
26    pub path: String,
27    pub request_size: usize,
28    pub wants_sse: bool,
29
30    // ── Session (set from header; overwritten when upstream assigns one) ──
31    pub session_id: Option<String>,
32
33    // ── JSON-RPC / MCP (None when the body is not JSON-RPC) ──
34    pub jsonrpc: Option<ParsedBody>,
35    pub mcp_method: Option<McpMethod>,
36    /// String form for event output. Set to the protocol method for MCP POSTs
37    /// and overwritten by specific handlers where appropriate (e.g. "SSE").
38    pub mcp_method_str: Option<String>,
39    /// `tools/call` tool name; `None` for other methods.
40    pub tool: Option<String>,
41    pub is_batch: bool,
42
43    // ── Client info ──
44    /// Parsed from `initialize` params. The Initialize success path stores
45    /// this into the session store.
46    pub client_info_from_init: Option<ClientInfo>,
47    /// Resolved from the session store by the handler before emit.
48    pub client_name: Option<String>,
49    pub client_version: Option<String>,
50
51    /// Transform tags pushed by handlers / middleware. The emit stage joins
52    /// them with `+` to build the `RequestEvent.note` field
53    /// (e.g. `["rewritten", "sse"]` → `"rewritten+sse"`).
54    pub tags: Vec<&'static str>,
55}
56
57/// Response-side state threaded through the response middleware chain.
58///
59/// Middleware mutate `body` and `json` in place. Handlers instantiate after
60/// reading the upstream body and finalize by building an `axum::Response` from
61/// `(status, headers, body)`.
62pub struct ResponseContext {
63    pub status: u16,
64    pub headers: HeaderMap,
65    /// Serialized response body — what gets returned to the client. Mutated
66    /// by the rewrite and SSE-wrap middleware.
67    pub body: Vec<u8>,
68    /// True when the upstream sent SSE-wrapped JSON. `SseUnwrapMiddleware` sets it;
69    /// `SseWrapMiddleware` reads it to decide whether to re-wrap.
70    pub was_sse: bool,
71    /// Parsed JSON view of the body. Populated by `SseUnwrapMiddleware`; mutated by
72    /// later middleware; serialized back into `body` by `SseWrapMiddleware`.
73    pub json: Option<Value>,
74    /// JSON-RPC error extracted from `json` (when present).
75    pub rpc_error: Option<(i64, String)>,
76    pub upstream_us: Option<u64>,
77}
78
79impl ResponseContext {
80    pub fn new(status: u16, headers: HeaderMap, body: Vec<u8>, upstream_us: Option<u64>) -> Self {
81        Self {
82            status,
83            headers,
84            body,
85            was_sse: false,
86            json: None,
87            rpc_error: None,
88            upstream_us,
89        }
90    }
91}