use core::future::Future;
use std::sync::{Arc, Mutex, PoisonError};
use serde_json::Value;
use crate::adapters::primary::transport::http::Refusal;
use crate::adapters::primary::transport::http::documents::{
answer_of, id_as_i64, params_of, refusal_document, refusal_document_from_refusal,
result_document,
};
use crate::adapters::primary::transport::http::{ExchangeTable, HttpExchange};
use crate::adapters::primary::transport::legacy_sse::{LegacyDispatcher, LegacyFrame, LegacyTail};
use crate::application::mcp::request_meta::progress_token;
use crate::application::server::context::{PeerSession, RequestContext};
use crate::application::server::dispatch::Dispatcher;
use crate::application::server::{ListKind, ListSubscription};
use crate::application::session::{DynFrameSink, FrameSink, SinkError};
use crate::domain::feature::resolve;
use crate::domain::ir::IrErrorCode;
use crate::domain::jsonrpc::reply;
use crate::domain::jsonrpc::{Frame, WireId, decode};
use crate::domain::registry::AuthContext;
use crate::domain::session::{CancellationToken, SessionId};
use crate::domain::{
CapabilitySet, McpSessionId, Negotiation, ProtocolVersion, RequestId, TransportKind,
};
#[derive(Debug, Clone)]
struct StreamSink {
tail: LegacyTail,
}
impl FrameSink for StreamSink {
fn send(&self, frame: &str) -> impl Future<Output = Result<(), SinkError>> + Send {
let outcome = serde_json::from_str::<Value>(frame)
.map(|value| {
self.tail.push(LegacyFrame::Message(value));
})
.map_err(|_| SinkError);
core::future::ready(outcome)
}
}
#[derive(Debug)]
pub struct ServerSession {
live: Mutex<Live>,
tail: LegacyTail,
exchange: ExchangeTable,
}
#[derive(Debug)]
struct Live {
peer: PeerSession,
handshaken: bool,
_lists: Option<ListSubscription>,
}
impl ServerSession {
fn live(&self) -> std::sync::MutexGuard<'_, Live> {
self.live.lock().unwrap_or_else(PoisonError::into_inner)
}
}
fn sink_for(tail: &LegacyTail) -> Arc<dyn DynFrameSink> {
Arc::new(StreamSink { tail: tail.clone() })
}
fn exchange_for(table: &ExchangeTable, tail: &LegacyTail) -> Arc<HttpExchange> {
let tail = tail.clone();
Arc::new(HttpExchange::publishing(table.clone(), move |request| {
tail.push(LegacyFrame::Message(request));
}))
}
#[derive(Debug, Clone)]
pub struct ServerDispatcher {
dispatcher: Arc<Dispatcher>,
ceiling: ProtocolVersion,
}
impl ServerDispatcher {
#[must_use]
pub const fn new(dispatcher: Arc<Dispatcher>, ceiling: ProtocolVersion) -> Self {
Self {
dispatcher,
ceiling,
}
}
fn seed(
&self,
id: SessionId,
exchange: Arc<HttpExchange>,
sink: Arc<dyn DynFrameSink>,
) -> PeerSession {
let profile = resolve(Negotiation {
version: self.ceiling,
client_caps: CapabilitySet::empty(),
server_caps: CapabilitySet::all_for(self.ceiling),
transport: TransportKind::LegacySse,
});
PeerSession::new(
self.ceiling,
profile,
id,
TransportKind::LegacySse,
exchange,
sink,
)
}
fn handshake(
&self,
auth: &AuthContext,
id: McpSessionId,
session: &ServerSession,
request: &WireId<'_>,
params: &crate::domain::ir::JsonObject,
) -> Value {
let peer = session.live().peer.clone().with_auth(auth.clone());
let context = RequestContext::for_request_with_state(
&peer,
RequestId::from_wire(Some(request)),
CancellationToken::new(),
None,
Arc::clone(self.dispatcher.state()),
);
match self.dispatcher.handshake(&context, params) {
Ok((result, negotiated)) => {
let sink = sink_for(&session.tail);
let negotiated = negotiated
.without_auth()
.with_sink(Arc::clone(&sink))
.with_exchange(exchange_for(&session.exchange, &session.tail))
.with_session(id.session());
let lists = self.dispatcher.server().handle().subscribe(
&sink,
negotiated.profile(),
&ListKind::ALL,
None,
);
*session.live() = Live {
peer: negotiated,
handshaken: true,
_lists: Some(lists),
};
result_document(Some(request), &result).unwrap_or_else(|refusal| {
refusal_document_from_refusal(Some(request), &refusal)
})
}
Err(error) => refusal_document(Some(request), &error),
}
}
}
impl LegacyDispatcher for ServerDispatcher {
type Session = ServerSession;
fn open(&self, id: SessionId, tail: &LegacyTail) -> ServerSession {
let table = ExchangeTable::readable();
let peer = self.seed(id, exchange_for(&table, tail), sink_for(tail));
ServerSession {
live: Mutex::new(Live {
peer,
handshaken: false,
_lists: None,
}),
tail: tail.clone(),
exchange: table,
}
}
fn dispatch(
&self,
auth: &AuthContext,
id: &McpSessionId,
session: &ServerSession,
frame: &Value,
) -> impl Future<Output = Result<Vec<Value>, Refusal>> + Send {
let served = serde_json::to_vec(frame)
.map_err(|error| malformed(&error))
.and_then(|body| self.serve(auth, *id, session, &body));
core::future::ready(served)
}
}
fn malformed(error: &serde_json::Error) -> Refusal {
Refusal::MalformedBody {
reason: error.to_string(),
}
}
impl ServerDispatcher {
fn serve(
&self,
auth: &AuthContext,
id: McpSessionId,
session: &ServerSession,
body: &[u8],
) -> Result<Vec<Value>, Refusal> {
let decoded = decode(body).map_err(|error| Refusal::MalformedBody {
reason: error.to_string(),
})?;
let Frame::Single(envelope) = decoded else {
return Err(Refusal::MalformedBody {
reason: "a legacy HTTP+SSE message carries one message".to_owned(),
});
};
let request = envelope.id().map(WireId::to_owned);
let Some(method) = envelope.method_str() else {
if let Some(request) = request.as_ref()
&& let Some(numeric) = id_as_i64(request)
{
session.exchange.resolve(numeric, answer_of(body));
}
return Ok(Vec::new());
};
let params = params_of(&envelope)?;
if method == "initialize" {
let Some(request) = request.as_ref() else {
return Ok(Vec::new());
};
return Ok(vec![self.handshake(auth, id, session, request, ¶ms)]);
}
let (peer, handshaken) = {
let live = session.live();
(live.peer.clone().with_auth(auth.clone()), live.handshaken)
};
if !handshaken && method != "ping" {
return Ok(request
.as_ref()
.map(|request| {
reply::error_reply(
request,
IrErrorCode::InvalidRequest,
"the session has not been initialized",
)
})
.into_iter()
.collect());
}
let progress = progress_token(¶ms);
let method = method.to_owned();
let dispatcher = Arc::clone(&self.dispatcher);
let tail = session.tail.clone();
let notification = request.is_none();
tokio::spawn(async move {
let context = RequestContext::for_request_with_state(
&peer,
RequestId::from_wire(request.as_ref()),
CancellationToken::new(),
progress,
Arc::clone(dispatcher.state()),
);
let outcome = dispatcher.dispatch(&context, &method, ¶ms).await;
if notification {
return;
}
let document = match outcome {
Ok(result) => {
result_document(request.as_ref(), &result).unwrap_or_else(|refusal| {
refusal_document_from_refusal(request.as_ref(), &refusal)
})
}
Err(error) => refusal_document(request.as_ref(), &error),
};
tail.push(LegacyFrame::Message(document));
});
Ok(Vec::new())
}
}
#[cfg(test)]
mod tests;