turul_http_mcp_server/
json_rpc_responses.rs

1//! JSON-RPC 2.0 response builders for HTTP transport
2//!
3//! This module provides functions that always return JSON-RPC 2.0 conformant
4//! HTTP responses with proper headers and body content.
5
6use bytes::Bytes;
7use http_body_util::Full;
8use hyper::{Response, StatusCode, header};
9use serde_json::Value;
10use tracing::error;
11
12use turul_mcp_json_rpc_server::{
13    JsonRpcError, JsonRpcResponse, error::JsonRpcErrorObject, types::RequestId,
14};
15
16/// HTTP body type for JSON-RPC responses
17type JsonRpcBody = Full<Bytes>;
18
19/// Build an HTTP response containing a JSON-RPC error object.
20/// Always returns a proper JSON-RPC 2.0 conformant response.
21pub fn jsonrpc_error_response(
22    id: RequestId,
23    code: i64,
24    message: &str,
25    data: Option<Value>,
26) -> Result<Response<JsonRpcBody>, hyper::Error> {
27    let error_obj = JsonRpcErrorObject {
28        code,
29        message: message.to_string(),
30        data,
31    };
32
33    let err = JsonRpcError::new(Some(id), error_obj);
34
35    let body_bytes = serde_json::to_vec(&err).unwrap_or_else(|e| {
36        error!("Failed to serialize JSON-RPC error: {}", e);
37        b"{}".to_vec()
38    });
39
40    Ok(Response::builder()
41        .status(StatusCode::OK)
42        .header(header::CONTENT_TYPE, "application/json")
43        .body(Full::new(Bytes::from(body_bytes)))
44        .unwrap())
45}
46
47/// Build an HTTP response for JSON-RPC notifications (202 Accepted per MCP 2025-06-18).
48pub fn jsonrpc_notification_response() -> Result<Response<JsonRpcBody>, hyper::Error> {
49    Ok(Response::builder()
50        .status(StatusCode::ACCEPTED) // MCP 2025-06-18: 202 Accepted for notifications
51        .header(header::CONTENT_TYPE, "application/json")
52        .body(Full::new(Bytes::new()))
53        .unwrap())
54}
55
56/// Build an HTTP response containing a successful JSON-RPC response.
57pub fn jsonrpc_success_response(
58    id: RequestId,
59    result: Value,
60) -> Result<Response<JsonRpcBody>, hyper::Error> {
61    let response = JsonRpcResponse::success(id, result);
62
63    let body_bytes = serde_json::to_vec(&response).unwrap_or_else(|e| {
64        error!("Failed to serialize JSON-RPC response: {}", e);
65        b"{}".to_vec()
66    });
67
68    Ok(Response::builder()
69        .status(StatusCode::OK)
70        .header(header::CONTENT_TYPE, "application/json")
71        .body(Full::new(Bytes::from(body_bytes)))
72        .unwrap())
73}
74
75/// Build a generic JSON-RPC response with session header support.
76pub fn jsonrpc_response_with_session(
77    response: JsonRpcResponse,
78    session_id: Option<String>,
79) -> Result<Response<JsonRpcBody>, hyper::Error> {
80    let body_bytes = serde_json::to_vec(&response).unwrap_or_else(|e| {
81        error!("Failed to serialize JSON-RPC response: {}", e);
82        b"{}".to_vec()
83    });
84
85    let mut builder = Response::builder()
86        .status(StatusCode::OK)
87        .header(header::CONTENT_TYPE, "application/json");
88
89    if let Some(session_id) = session_id {
90        builder = builder.header("Mcp-Session-Id", session_id);
91    }
92
93    Ok(builder.body(Full::new(Bytes::from(body_bytes))).unwrap())
94}
95
96/// Build HTTP response for method not allowed (405).
97pub fn method_not_allowed_response() -> Response<JsonRpcBody> {
98    Response::builder()
99        .status(StatusCode::METHOD_NOT_ALLOWED)
100        .header("Allow", "GET, POST, DELETE, OPTIONS")
101        .body(Full::new(Bytes::from("Method not allowed")))
102        .unwrap()
103}
104
105/// Build HTTP response for not found (404).
106pub fn not_found_response() -> Response<JsonRpcBody> {
107    Response::builder()
108        .status(StatusCode::NOT_FOUND)
109        .body(Full::new(Bytes::from("Not Found")))
110        .unwrap()
111}
112
113/// Build HTTP response for bad request (400) with JSON-RPC parse error.
114pub fn bad_request_response(message: &str) -> Response<JsonRpcBody> {
115    let error_obj = JsonRpcErrorObject {
116        code: -32700, // Parse error
117        message: message.to_string(),
118        data: None,
119    };
120
121    let err = JsonRpcError::new(None, error_obj);
122
123    let body_bytes = serde_json::to_vec(&err).unwrap_or_else(|_| b"{}".to_vec());
124
125    Response::builder()
126        .status(StatusCode::BAD_REQUEST)
127        .header(header::CONTENT_TYPE, "application/json")
128        .body(Full::new(Bytes::from(body_bytes)))
129        .unwrap()
130}
131
132/// Build HTTP response for OPTIONS preflight requests.
133pub fn options_response() -> Response<JsonRpcBody> {
134    Response::builder()
135        .status(StatusCode::OK)
136        .header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
137        .header(
138            "Access-Control-Allow-Headers",
139            "Content-Type, Accept, MCP-Protocol-Version, Mcp-Session-Id, Last-Event-ID",
140        )
141        .header("Access-Control-Max-Age", "86400")
142        .body(Full::new(Bytes::new()))
143        .unwrap()
144}
145
146/// Build HTTP response for SSE stream (proper headers for text/event-stream).
147pub fn sse_response_headers() -> http::response::Builder {
148    Response::builder()
149        .status(StatusCode::OK)
150        .header(header::CONTENT_TYPE, "text/event-stream")
151        .header(header::CACHE_CONTROL, "no-cache")
152        .header("Connection", "keep-alive")
153        .header("Access-Control-Allow-Origin", "*")
154}