1use spin_sdk::http::{Method, Request, Response};
2
3use crate::{server::McpServer, tool::Tool, types::JsonRpcRequest};
4
5pub fn create_handler<T: Tool + 'static>(
11 tool: T,
12) -> impl Fn(Request) -> Result<Response, String> + Clone {
13 move |req: Request| -> Result<Response, String> {
14 let server = McpServer::new(tool.clone());
15 handle_mcp_request(server, req)
16 }
17}
18
19fn handle_mcp_request<T: Tool>(server: McpServer<T>, req: Request) -> Result<Response, String> {
20 if *req.method() == Method::Options {
22 return Ok(Response::builder()
23 .status(200)
24 .header("Access-Control-Allow-Origin", "*")
25 .header("Access-Control-Allow-Methods", "POST, OPTIONS")
26 .header("Access-Control-Allow-Headers", "Content-Type")
27 .header("Content-Type", "application/json")
28 .body("")
29 .build());
30 }
31
32 if *req.method() != Method::Post {
34 return Ok(Response::builder()
35 .status(405)
36 .header("Access-Control-Allow-Origin", "*")
37 .header("Access-Control-Allow-Methods", "POST, OPTIONS")
38 .header("Access-Control-Allow-Headers", "Content-Type")
39 .header("Content-Type", "application/json")
40 .body("Method not allowed")
41 .build());
42 }
43
44 match read_request_body(&req) {
46 Ok(body_str) => match serde_json::from_str::<JsonRpcRequest>(&body_str) {
47 Ok(json_req) => {
48 let response_data = server.handle_request(json_req);
49 let response_json = serde_json::to_string(&response_data)
50 .map_err(|e| format!("Failed to serialize response: {e}"))?;
51
52 Ok(Response::builder()
53 .status(200)
54 .header("Access-Control-Allow-Origin", "*")
55 .header("Access-Control-Allow-Methods", "POST, OPTIONS")
56 .header("Access-Control-Allow-Headers", "Content-Type")
57 .header("Content-Type", "application/json")
58 .body(response_json)
59 .build())
60 }
61 Err(e) => {
62 eprintln!("Failed to parse JSON-RPC request: {e}");
63 let error_response =
64 crate::types::JsonRpcResponse::error(None, -32700, "Parse error");
65
66 let response_json = serde_json::to_string(&error_response)
67 .map_err(|e| format!("Failed to serialize error response: {e}"))?;
68 Ok(Response::builder()
69 .status(400)
70 .header("Access-Control-Allow-Origin", "*")
71 .header("Access-Control-Allow-Methods", "POST, OPTIONS")
72 .header("Access-Control-Allow-Headers", "Content-Type")
73 .header("Content-Type", "application/json")
74 .body(response_json)
75 .build())
76 }
77 },
78 Err(e) => {
79 eprintln!("Failed to read request body: {e}");
80 let error_response =
81 crate::types::JsonRpcResponse::error(None, -32700, "Failed to read request body");
82
83 let response_json = serde_json::to_string(&error_response)
84 .map_err(|e| format!("Failed to serialize error response: {e}"))?;
85 Ok(Response::builder()
86 .status(400)
87 .header("Access-Control-Allow-Origin", "*")
88 .header("Access-Control-Allow-Methods", "POST, OPTIONS")
89 .header("Access-Control-Allow-Headers", "Content-Type")
90 .header("Content-Type", "application/json")
91 .body(response_json)
92 .build())
93 }
94 }
95}
96
97fn read_request_body(req: &Request) -> Result<String, String> {
98 String::from_utf8(req.body().to_vec())
99 .map_err(|e| format!("Failed to parse request body as UTF-8: {e}"))
100}
101
102#[macro_export]
130macro_rules! ftl_mcp_server {
131 ($tool:expr) => {
132 #[spin_sdk::http_component]
133 fn handle_mcp_request(req: spin_sdk::http::Request) -> spin_sdk::http::Response {
134 let server = $crate::McpServer::new($tool);
135
136 let create_error_response = |status: u16, message: &str| {
138 spin_sdk::http::Response::builder()
139 .status(status)
140 .header("Access-Control-Allow-Origin", "*")
141 .header("Access-Control-Allow-Methods", "POST, OPTIONS")
142 .header("Access-Control-Allow-Headers", "Content-Type")
143 .header("Content-Type", "application/json")
144 .body(message)
145 .build()
146 };
147
148 match req.method() {
149 &spin_sdk::http::Method::Options => spin_sdk::http::Response::builder()
150 .status(200)
151 .header("Access-Control-Allow-Origin", "*")
152 .header("Access-Control-Allow-Methods", "POST, OPTIONS")
153 .header("Access-Control-Allow-Headers", "Content-Type")
154 .header("Content-Type", "application/json")
155 .body("")
156 .build(),
157 &spin_sdk::http::Method::Post => {
158 let body_str = match String::from_utf8(req.body().to_vec()) {
159 Ok(s) => s,
160 Err(_) => {
161 return create_error_response(400, "Invalid UTF-8 in request body");
162 }
163 };
164
165 match serde_json::from_str::<$crate::JsonRpcRequest>(&body_str) {
166 Ok(json_req) => {
167 let response_data = server.handle_request(json_req);
168 match serde_json::to_string(&response_data) {
169 Ok(response_json) => spin_sdk::http::Response::builder()
170 .status(200)
171 .header("Access-Control-Allow-Origin", "*")
172 .header("Access-Control-Allow-Methods", "POST, OPTIONS")
173 .header("Access-Control-Allow-Headers", "Content-Type")
174 .header("Content-Type", "application/json")
175 .body(response_json)
176 .build(),
177 Err(_) => {
178 create_error_response(500, "Failed to serialize response")
179 }
180 }
181 }
182 Err(_) => {
183 let error_response =
184 $crate::JsonRpcResponse::error(None, -32700, "Parse error");
185 match serde_json::to_string(&error_response) {
186 Ok(response_json) => spin_sdk::http::Response::builder()
187 .status(400)
188 .header("Access-Control-Allow-Origin", "*")
189 .header("Access-Control-Allow-Methods", "POST, OPTIONS")
190 .header("Access-Control-Allow-Headers", "Content-Type")
191 .header("Content-Type", "application/json")
192 .body(response_json)
193 .build(),
194 Err(_) => {
195 create_error_response(500, "Failed to serialize error response")
196 }
197 }
198 }
199 }
200 }
201 _ => create_error_response(405, "Method not allowed"),
202 }
203 }
204 };
205}