use super::auth::{
accepts_media, attach_http_headers, attach_legacy_deprecation_headers,
lookup_or_create_session, should_stream_post_response, validate_origin,
validate_protocol_header, validate_rc_routing_headers,
};
use super::schema::parse_error_response;
use super::*;
pub(super) async fn http_post_request(
State(state): State<HttpState>,
method: Method,
headers: HeaderMap,
body: Bytes,
) -> Response {
if let Err(response) = validate_origin(&headers) {
return *response;
}
if let Err(response) = validate_protocol_header(&headers) {
return *response;
}
let request = match serde_json::from_slice::<JsonValue>(body.as_ref()) {
Ok(value) => value,
Err(error) => {
return (
StatusCode::BAD_REQUEST,
Json(parse_error_response(&error.to_string())),
)
.into_response()
}
};
if let Err(error_body) = validate_rc_routing_headers(&headers, &request) {
let mut http = Json(error_body).into_response();
attach_http_headers(&mut http, None, MCP_PROTOCOL_VERSION);
return http;
}
let header_session = headers
.get(MCP_SESSION_HEADER)
.and_then(|value| value.to_str().ok())
.map(str::to_string);
let (session_id, session, created) =
match lookup_or_create_session(&state, &request, header_session) {
Ok(value) => value,
Err(response) => return *response,
};
let auth = AuthRequest::from_http(&method, &state.options.path, body.to_vec(), &headers);
let response_protocol = response_protocol_version(&headers, &request);
match state.server.process_message(request, session, auth).await {
ImmediateResult::Accepted => StatusCode::ACCEPTED.into_response(),
ImmediateResult::Response(response) => {
let mut http = if should_stream_post_response(&headers) {
sse_single_response(response).into_response()
} else {
Json(response).into_response()
};
attach_http_headers(
&mut http,
created.then_some(session_id.as_str()),
response_protocol,
);
http
}
ImmediateResult::Stream(job) => {
let stream = spawn_http_stream(state.server.clone(), *job);
let mut http = stream.into_response();
attach_http_headers(
&mut http,
created.then_some(session_id.as_str()),
response_protocol,
);
http
}
}
}
fn response_protocol_version(headers: &HeaderMap, request: &JsonValue) -> &'static str {
if let Some(value) = headers
.get(MCP_PROTOCOL_HEADER)
.and_then(|value| value.to_str().ok())
{
if value == mcp_protocol::DRAFT_PROTOCOL_VERSION {
return mcp_protocol::DRAFT_PROTOCOL_VERSION;
}
}
if headers.contains_key(MCP_METHOD_HEADER) || headers.contains_key(MCP_NAME_HEADER) {
return mcp_protocol::DRAFT_PROTOCOL_VERSION;
}
if request
.pointer("/params/_meta")
.and_then(JsonValue::as_object)
.and_then(|meta| meta.get(mcp_protocol::RC_META_KEY_PROTOCOL_VERSION))
.and_then(JsonValue::as_str)
== Some(mcp_protocol::DRAFT_PROTOCOL_VERSION)
{
return mcp_protocol::DRAFT_PROTOCOL_VERSION;
}
MCP_PROTOCOL_VERSION
}
pub(super) async fn http_get_stream(
State(state): State<HttpState>,
headers: HeaderMap,
) -> Response {
if let Err(response) = validate_origin(&headers) {
return *response;
}
if let Err(response) = validate_protocol_header(&headers) {
return *response;
}
if !accepts_media(&headers, "text/event-stream") {
return StatusCode::NOT_ACCEPTABLE.into_response();
}
let Some(session_id) = headers
.get(MCP_SESSION_HEADER)
.and_then(|value| value.to_str().ok())
else {
return StatusCode::BAD_REQUEST.into_response();
};
let Some(session) = state
.sessions
.lock()
.expect("sessions poisoned")
.get(session_id)
.cloned()
else {
return StatusCode::NOT_FOUND.into_response();
};
let (tx, rx) = unbounded::<JsonValue>();
session.set_stream_tx(Some(tx));
let mut response = sse_response(rx).into_response();
attach_http_headers(&mut response, None, MCP_PROTOCOL_VERSION);
response
}
pub(super) async fn http_delete_session(
State(state): State<HttpState>,
headers: HeaderMap,
) -> Response {
if let Err(response) = validate_origin(&headers) {
return *response;
}
if let Err(response) = validate_protocol_header(&headers) {
return *response;
}
let Some(session_id) = headers
.get(MCP_SESSION_HEADER)
.and_then(|value| value.to_str().ok())
else {
return StatusCode::BAD_REQUEST.into_response();
};
let removed = state
.sessions
.lock()
.expect("sessions poisoned")
.remove(session_id);
let mut response = if removed.is_some() {
StatusCode::NO_CONTENT.into_response()
} else {
StatusCode::NOT_FOUND.into_response()
};
attach_http_headers(&mut response, None, MCP_PROTOCOL_VERSION);
response
}
pub(super) async fn legacy_sse_stream(
State(state): State<HttpState>,
headers: HeaderMap,
) -> Response {
if let Err(response) = validate_origin(&headers) {
return *response;
}
let session_id = Uuid::now_v7().to_string();
let session = SharedSession::new();
let (tx, rx) = unbounded::<JsonValue>();
session.set_stream_tx(Some(tx));
state
.sessions
.lock()
.expect("sessions poisoned")
.insert(session_id.clone(), session);
let endpoint_event = Event::default().event("endpoint").data(format!(
"{}?session_id={session_id}",
state.options.messages_path
));
let stream =
stream::once(async move { Ok::<Event, Infallible>(endpoint_event) }).chain(sse_events(rx));
let mut response = Sse::new(stream)
.keep_alive(KeepAlive::default())
.into_response();
attach_legacy_deprecation_headers(&mut response);
response
}
pub(super) async fn legacy_sse_message(
State(state): State<HttpState>,
Query(query): Query<BTreeMap<String, String>>,
headers: HeaderMap,
body: Bytes,
) -> Response {
if let Err(response) = validate_origin(&headers) {
return *response;
}
let Some(session_id) = query.get("session_id") else {
let mut response = StatusCode::BAD_REQUEST.into_response();
attach_legacy_deprecation_headers(&mut response);
return response;
};
let Some(session) = state
.sessions
.lock()
.expect("sessions poisoned")
.get(session_id)
.cloned()
else {
let mut response = StatusCode::NOT_FOUND.into_response();
attach_legacy_deprecation_headers(&mut response);
return response;
};
let request = match serde_json::from_slice::<JsonValue>(body.as_ref()) {
Ok(value) => value,
Err(error) => {
let mut response = (
StatusCode::BAD_REQUEST,
Json(parse_error_response(&error.to_string())),
)
.into_response();
attach_legacy_deprecation_headers(&mut response);
return response;
}
};
let auth = AuthRequest::from_http(
&Method::POST,
&state.options.messages_path,
body.to_vec(),
&headers,
);
match state
.server
.process_message(request, session.clone(), auth)
.await
{
ImmediateResult::Accepted => {
let mut response = StatusCode::ACCEPTED.into_response();
attach_legacy_deprecation_headers(&mut response);
response
}
ImmediateResult::Response(response) => {
if let Some(tx) = session.stream_tx() {
let _ = tx.unbounded_send(response);
let mut response = StatusCode::ACCEPTED.into_response();
attach_legacy_deprecation_headers(&mut response);
response
} else {
let mut response = StatusCode::GONE.into_response();
attach_legacy_deprecation_headers(&mut response);
response
}
}
ImmediateResult::Stream(job) => {
let Some(tx) = session.stream_tx() else {
let mut response = StatusCode::GONE.into_response();
attach_legacy_deprecation_headers(&mut response);
return response;
};
tokio::spawn(async move {
let notifier = notify_channel(move |message| {
let _ = tx.unbounded_send(message);
});
state.server.execute_streaming_job(*job, notifier).await;
});
let mut response = StatusCode::ACCEPTED.into_response();
attach_legacy_deprecation_headers(&mut response);
response
}
}
}
pub(super) fn sse_single_response(
message: JsonValue,
) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
let prime = Event::default().id(Uuid::now_v7().to_string()).data("");
let message = Event::default()
.id(Uuid::now_v7().to_string())
.event("message")
.data(serde_json::to_string(&message).unwrap_or_else(|_| "{}".to_string()));
Sse::new(stream::iter([
Ok::<Event, Infallible>(prime),
Ok::<Event, Infallible>(message),
]))
.keep_alive(KeepAlive::default())
}
pub(super) fn spawn_http_stream(
server: Arc<McpServer>,
job: StreamJob,
) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
let (tx, rx) = unbounded::<JsonValue>();
tokio::spawn(async move {
let notifier = notify_channel(move |message| {
let _ = tx.unbounded_send(message);
});
server.execute_streaming_job(job, notifier).await;
});
sse_response(rx)
}
pub(super) fn sse_response(
rx: UnboundedReceiver<JsonValue>,
) -> Sse<impl futures::Stream<Item = Result<Event, Infallible>>> {
let prime = Event::default().id(Uuid::now_v7().to_string()).data("");
let stream = stream::once(async move { Ok::<Event, Infallible>(prime) }).chain(sse_events(rx));
Sse::new(stream).keep_alive(KeepAlive::default())
}
pub(super) fn sse_events(
rx: UnboundedReceiver<JsonValue>,
) -> impl futures::Stream<Item = Result<Event, Infallible>> {
rx.map(|message| {
Ok(Event::default()
.id(Uuid::now_v7().to_string())
.event("message")
.data(serde_json::to_string(&message).unwrap_or_else(|_| "{}".to_string())))
})
}
pub(super) fn notify_channel<F>(notify: F) -> Arc<dyn Fn(JsonValue) + Send + Sync>
where
F: Fn(JsonValue) + Send + Sync + 'static,
{
Arc::new(notify)
}