Skip to main content

embacle_server/mcp/
handler.rs

1// ABOUTME: HTTP handler for MCP Streamable HTTP transport at POST /mcp
2// ABOUTME: Accepts JSON-RPC requests and responds via JSON or SSE based on Accept header
3//
4// SPDX-License-Identifier: Apache-2.0
5// Copyright (c) 2026 dravr.ai
6
7use std::convert::Infallible;
8use std::sync::Arc;
9
10use axum::extract::State;
11use axum::http::HeaderMap;
12use axum::response::sse::{Event, Sse};
13use axum::response::{IntoResponse, Response};
14use axum::Json;
15use futures::stream;
16use tracing::{debug, error};
17
18use super::protocol::{JsonRpcRequest, JsonRpcResponse, PARSE_ERROR};
19use super::server::McpServer;
20
21/// Handle an incoming MCP POST request
22///
23/// Parses the body as JSON-RPC, dispatches to the MCP server, and returns
24/// the response as JSON or SSE depending on the Accept header.
25pub async fn handle_mcp_post(
26    State(server): State<Arc<McpServer>>,
27    headers: HeaderMap,
28    body: String,
29) -> Response {
30    let request: JsonRpcRequest = match serde_json::from_str(&body) {
31        Ok(req) => req,
32        Err(e) => {
33            error!(error = %e, "Failed to parse MCP JSON-RPC body");
34            let resp = JsonRpcResponse::error(None, PARSE_ERROR, format!("Parse error: {e}"));
35            return Json(resp).into_response();
36        }
37    };
38
39    debug!(method = %request.method, "Handling MCP request");
40
41    let Some(response) = server.handle_request(request).await else {
42        // Notification — no response needed
43        return axum::http::StatusCode::NO_CONTENT.into_response();
44    };
45
46    let wants_sse = headers
47        .get("accept")
48        .and_then(|v| v.to_str().ok())
49        .is_some_and(|accept| accept.contains("text/event-stream"));
50
51    if wants_sse {
52        respond_sse(&response)
53    } else {
54        Json(response).into_response()
55    }
56}
57
58/// Wrap a JSON-RPC response in a single SSE event
59fn respond_sse(response: &JsonRpcResponse) -> Response {
60    let data = serde_json::to_string(&response).unwrap_or_else(|e| {
61        format!(
62            r#"{{"jsonrpc":"2.0","error":{{"code":-32603,"message":"Serialization failed: {e}"}}}}"#
63        )
64    });
65
66    let event = Event::default().data(data);
67    let event_stream = stream::once(async { Ok::<_, Infallible>(event) });
68
69    Sse::new(event_stream).into_response()
70}