1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! Constants for HTTP headers, content types and transport byte ceilings used
//! in MCP.
//!
//! This module is deliberately UNGATED (`src/shared/mod.rs` declares it with no
//! `cfg`), which is why the shared SSE byte ceiling lives here rather than in
//! `crate::shared::http`: that module is gated on `feature = "http"`, and
//! `feature = "sse"` does not enable it, yet `sse_optimized` needs the same
//! number. One ceiling for every SSE read in the crate.
//!
//! # Severance is PER-CONST, never per-module (SMPL-01)
//!
//! Exactly one constant here is v1-only and carries
//! `#[cfg(feature = "v1-compat")]`: `LAST_EVENT_ID`. Gating the MODULE
//! instead would take `DEFAULT_HTTP_SSE_BUFFERED_BYTES`, [`MCP_METHOD`] and
//! [`MCP_NAME`] with it, and the last two are v2-REQUIRED (VERS-05).
//!
//! `LAST_EVENT_ID` is a code span rather than an intra-doc link ON PURPOSE, here
//! and in `MCP_SESSION_ID`'s doc below: both docs are UNGATED while the constant
//! is not, so a link resolves to nothing under
//! `cargo doc --no-default-features --features full-v2` and rustdoc emits a
//! `broken_intra_doc_links` warning — leaving the crate's docs subtly wrong in
//! exactly the configuration Phase 117 created. Do not "fix" either back into a
//! link.
//!
//! [`MCP_SESSION_ID`] is deliberately UNGATED, and that is a MEASURED decision
//! rather than an oversight — see its own doc for the trace.
// Header Names
/// MCP session ID header name.
///
/// # Deliberately UNGATED, measured (plan 117-14, assumption A4)
///
/// A4 claimed this constant's server-side readers were v1-reachable only, and
/// that it could therefore be gated behind `v1-compat` alongside
/// `LAST_EVENT_ID` (a span, not a link: `MCP_SESSION_ID` is UNGATED and
/// `LAST_EVENT_ID` is not, so a link here breaks under
/// `cargo doc --no-default-features --features full-v2`). The claim was traced
/// and is FALSE:
///
/// * `extract_session_and_protocol_headers` reads it on EVERY POST — it is
/// shared by the fast path and the middleware path, and it is the same read
/// that yields `MCP-Protocol-Version`, which v2 needs. A v2 POST goes
/// through it.
/// * `build_middleware_context` reads it off the middleware-adapted request on
/// the middleware POST path, which serves v2 traffic (`http_middleware` is a
/// SHARED, ungated config field).
/// * The v2 test surface reads it precisely to assert its ABSENCE — a v2
/// server must not emit it and a v2 client must not send it. Gating it would
/// delete the vocabulary the v2 side needs to state that property.
///
/// The name of a header a build refuses to honour is not v1 machinery. What is
/// severed is the STORING and SENDING of a session id (the client's capture,
/// accessors and DELETE teardown; the server's session map), not the string.
pub const MCP_SESSION_ID: &str = "mcp-session-id";
/// MCP protocol version header name
pub const MCP_PROTOCOL_VERSION: &str = "mcp-protocol-version";
/// MCP method header name (VERS-05, v2 `2026-07-28`).
///
/// On the v2 HTTP path this header MUST be present and MUST equal the
/// JSON-RPC body's `method` (cross-checked fail-closed; a header/body desync
/// is a smuggling signal — D-06). HTTP header names are case-insensitive; the
/// lowercase form matches the existing `mcp-*` family.
pub const MCP_METHOD: &str = "mcp-method";
/// MCP name header name (VERS-05, v2 `2026-07-28`).
///
/// On the v2 HTTP path this header MUST be present; for name-bearing methods
/// (`tools/call`, `prompts/get`, `resources/read`) its value is cross-checked
/// against the request's logical name (`params.name`) and a mismatch is
/// rejected fail-closed (same class as D-06).
pub const MCP_NAME: &str = "mcp-name";
/// SSE Last-Event-ID header name for resumption.
///
/// v1-ONLY (`feature = "v1-compat"`). MCP `2026-07-28` removed SSE
/// resumability entirely: there is no event store, no replay cursor, and
/// therefore no header to name. On a `full-v2` build this constant does not
/// exist, which is what makes "the severed client never writes an
/// attacker-influenced replay cursor onto the wire" (T-117-53) a property of
/// the compiled crate rather than an ordering someone has to preserve.
///
/// # Gated together with its readers
///
/// The const and every reader of it carry the SAME `#[cfg]`, applied in one
/// edit: gating either alone is a compile break. Its readers are
/// `crate::server::streamable_http_server::v1::replay_sse_events_from_header`
/// (inside the `v1-compat` half of the paired module, so gated by the module —
/// plan 117-12) and the client's own resumption-header writer
/// `StreamableHttpTransport::apply_resumption_header` (plan 117-14).
///
/// Per-CONST gating only. Do NOT gate this module — see the module doc.
pub const LAST_EVENT_ID: &str = "Last-Event-ID";
/// HTTP Accept header name
pub const ACCEPT: &str = "Accept";
/// HTTP Content-Type header name
pub const CONTENT_TYPE: &str = "Content-Type";
// Content Types
/// JSON content type value
pub const APPLICATION_JSON: &str = "application/json";
/// Server-Sent Events content type value
pub const TEXT_EVENT_STREAM: &str = "text/event-stream";
/// Accept header value for streamable HTTP (both JSON and SSE)
pub const ACCEPT_STREAMABLE: &str = "application/json, text/event-stream";
// Transport byte ceilings
/// Default ceiling on the SSE bytes an SSE reader may hold IN FLIGHT, in bytes
/// (16 MiB).
///
/// # Its two readers
///
/// One number for every SSE read in the crate (plan 113.1-03, D-03):
///
/// - `HttpTransport::connect_sse`'s reader task, which retains across chunks;
/// - `OptimizedSseTransport::connect_sse`, whose whole-response read is bounded
/// by a running total checked against this value before each append.
///
/// Re-exported as `crate::shared::http::DEFAULT_HTTP_SSE_BUFFERED_BYTES`, which
/// remains the documented public path. The definition lives here because this
/// module is ungated while `crate::shared::http` is behind `feature = "http"`,
/// which `feature = "sse"` does not enable.
///
/// # What breaks at this boundary
///
/// A single JSON-RPC payload whose in-flight bytes exceed the configured ceiling
/// is DISCARDED and ENDS the reader task. That is a real behaviour change: before
/// Phase 113-17 that transport's parser bounded only an unterminated line, so an
/// arbitrarily large `data:` payload accumulated without limit and was delivered
/// (T-113-85).
///
/// # Why it is configurable rather than fixed
///
/// A fixed ceiling is not defensible for a transport that carries arbitrary
/// JSON-RPC results. MCP `image`/`audio` content is unconstrained base64, and
/// base64 expands by ~4/3: a 12 MiB binary is ALREADY 16 MiB once encoded,
/// BEFORE the JSON envelope, the `data: ` prefix and the MIME type — so it does
/// NOT fit under this default. Large text, resources and `structuredContent` can
/// legitimately exceed it too. Any claim that media is "unaffected" by a 16 MiB
/// ceiling is arithmetically false.
///
/// [`crate::shared::http::HttpTransport::with_sse_buffered_bytes`] is the escape
/// hatch for that reader: raise the ceiling for a deployment whose payloads are
/// legitimately larger. `OptimizedSseTransport` has no such setter — it is
/// deprecated toward `StreamableHttpTransport`, which carries its own
/// configurable cap.
pub const DEFAULT_HTTP_SSE_BUFFERED_BYTES: usize = 16 * 1024 * 1024;