1use std::net::SocketAddr;
11use std::sync::Arc;
12
13use atd_runtime::dispatch::ServerState;
14use atd_runtime::registry::Registry;
15use atd_runtime::secrets::BearerIdentity;
16use axum::Json;
17use axum::extract::{DefaultBodyLimit, State};
18use axum::http::{HeaderMap, StatusCode};
19use axum::response::IntoResponse;
20use axum::routing::post;
21use axum::{Router, serve};
22use serde_json::{Value, json};
23use tokio::net::TcpListener;
24use tokio::sync::oneshot;
25
26use crate::bearer::{BearerOutcome, resolve_bearer};
27use crate::config::HttpServerConfig;
28use crate::error::HttpServerError;
29use crate::mcp::{
30 JsonRpcRequest, error_response, error_response_with_headers, handle_initialize,
31 handle_initialized_notification, handle_tools_call, handle_tools_list,
32};
33use crate::origin::origin_allowed;
34
35#[derive(Clone)]
42pub(crate) struct HttpAppState {
43 pub state: Arc<ServerState>,
44 pub extra_origins: Arc<Vec<String>>,
45 pub require_bearer: bool,
46 pub server_version: Arc<String>,
47}
48
49pub struct ServerBuilder {
50 registry: Registry,
51 config: HttpServerConfig,
52 tier_policy: atd_runtime::TierPolicy,
53 middleware: Vec<Arc<dyn atd_runtime::Middleware>>,
54}
55
56impl ServerBuilder {
57 pub fn config(mut self, cfg: HttpServerConfig) -> Self {
58 self.config = cfg;
59 self
60 }
61
62 pub fn tier_policy(mut self, policy: atd_runtime::TierPolicy) -> Self {
63 self.tier_policy = policy;
64 self
65 }
66
67 pub fn middleware(mut self, middleware: Vec<Arc<dyn atd_runtime::Middleware>>) -> Self {
68 self.middleware = middleware;
69 self
70 }
71
72 pub fn build(self) -> (Router, Server) {
77 let HttpServerConfig {
78 listen,
79 extra_origins,
80 require_bearer,
81 max_body_bytes,
82 shared,
83 } = self.config;
84
85 let server_version = Arc::new(shared.server_version.clone());
86 let cursor_signing_key = shared.cursor_signing_key;
90
91 let server_state = Arc::new(ServerState {
96 registry: self.registry,
97 config: Arc::try_unwrap(shared).unwrap_or_else(|arc| {
98 atd_runtime::dispatch::SharedServerConfig {
103 cwd: arc.cwd.clone(),
104 max_output_bytes: arc.max_output_bytes,
105 default_call_timeout_ms: arc.default_call_timeout_ms,
106 granted_capabilities: arc.granted_capabilities.clone(),
107 audit_sink: arc.audit_sink.clone(),
108 server_version: arc.server_version.clone(),
109 token_broker: arc.token_broker.clone(),
110 max_ucan_chain_depth: arc.max_ucan_chain_depth,
111 ucan_revocation_store: arc.ucan_revocation_store.clone(),
112 frame_deadline_active_ms: arc.frame_deadline_active_ms,
113 frame_deadline_handshake_ms: arc.frame_deadline_handshake_ms,
114 cursor_signing_key: arc.cursor_signing_key,
115 cursor_ttl_seconds: arc.cursor_ttl_seconds,
116 }
117 }),
118 tier_policy: self.tier_policy,
119 middleware: self.middleware,
120 metrics: Arc::new(atd_runtime::MetricsCounters::default()),
121 cursor_issuer: Arc::new(atd_runtime::cursor::CursorIssuer::new(cursor_signing_key)),
122 });
123
124 let app_state = HttpAppState {
125 state: server_state,
126 extra_origins: Arc::new(extra_origins),
127 require_bearer,
128 server_version,
129 };
130
131 let router = Router::new()
132 .route("/mcp", post(handle_mcp_post))
133 .layer(DefaultBodyLimit::max(max_body_bytes))
136 .with_state(app_state);
137
138 let server = Server {
139 listen,
140 shutdown_tx: None,
141 local_addr: None,
142 };
143 (router, server)
144 }
145}
146
147pub struct Server {
148 listen: SocketAddr,
149 shutdown_tx: Option<oneshot::Sender<()>>,
152 local_addr: Option<SocketAddr>,
153}
154
155impl Server {
156 pub fn builder(registry: Registry) -> ServerBuilder {
158 ServerBuilder {
159 registry,
160 config: HttpServerConfig::default(),
161 tier_policy: atd_runtime::TierPolicy::defaults(),
162 middleware: Vec::new(),
163 }
164 }
165
166 pub fn local_addr(&self) -> Option<SocketAddr> {
171 self.local_addr
172 }
173
174 pub fn shutdown(&mut self) -> bool {
177 match self.shutdown_tx.take() {
178 Some(tx) => tx.send(()).is_ok(),
179 None => false,
180 }
181 }
182
183 pub async fn serve(mut self, router: Router) -> Result<(), HttpServerError> {
186 let listener =
187 TcpListener::bind(self.listen)
188 .await
189 .map_err(|source| HttpServerError::Bind {
190 addr: self.listen,
191 source,
192 })?;
193 let local = listener
194 .local_addr()
195 .map_err(|source| HttpServerError::Bind {
196 addr: self.listen,
197 source,
198 })?;
199 self.local_addr = Some(local);
200
201 let (tx, rx) = oneshot::channel::<()>();
202 self.shutdown_tx = Some(tx);
203
204 serve(listener, router)
205 .with_graceful_shutdown(async move {
206 let _ = rx.await;
207 })
208 .await?;
209 Ok(())
210 }
211
212 pub async fn bind(&mut self) -> Result<TcpListener, HttpServerError> {
216 let listener =
217 TcpListener::bind(self.listen)
218 .await
219 .map_err(|source| HttpServerError::Bind {
220 addr: self.listen,
221 source,
222 })?;
223 let local = listener
224 .local_addr()
225 .map_err(|source| HttpServerError::Bind {
226 addr: self.listen,
227 source,
228 })?;
229 self.local_addr = Some(local);
230 Ok(listener)
231 }
232
233 pub async fn serve_with_listener(
236 mut self,
237 listener: TcpListener,
238 router: Router,
239 ) -> Result<(), HttpServerError> {
240 let (tx, rx) = oneshot::channel::<()>();
241 self.shutdown_tx = Some(tx);
242 serve(listener, router)
243 .with_graceful_shutdown(async move {
244 let _ = rx.await;
245 })
246 .await?;
247 Ok(())
248 }
249}
250
251async fn handle_mcp_post(
255 State(app): State<HttpAppState>,
256 headers: HeaderMap,
257 body: Result<Json<JsonRpcRequest>, axum::extract::rejection::JsonRejection>,
258) -> axum::response::Response {
259 let Json(req) = match body {
264 Ok(j) => j,
265 Err(rej) => {
266 let (status, code, msg) = body_rejection_status(&rej);
269 return error_response(status, None, code, msg);
270 }
271 };
272
273 if !origin_allowed(&headers, &app.extra_origins) {
275 return error_response(StatusCode::FORBIDDEN, req.id, -32001, "origin not allowed");
276 }
277
278 let outcome = resolve_bearer(
285 &headers,
286 app.state.config.token_broker.as_ref(),
287 app.require_bearer,
288 )
289 .await;
290
291 let identity: Option<BearerIdentity> = match outcome {
292 BearerOutcome::Anonymous => None,
293 BearerOutcome::Validated(id) => Some(id),
294 rejection => {
295 let status = rejection.http_status();
296 let message = rejection
297 .rejection_message()
298 .unwrap_or_else(|| "bearer auth failed".into());
299 let mut headers: Vec<(&str, String)> = Vec::new();
300 if let Some(www) = rejection.www_authenticate() {
301 headers.push(("WWW-Authenticate", www.to_string()));
302 }
303 if let Some(retry) = rejection.retry_after() {
304 headers.push(("Retry-After", retry.to_string()));
305 }
306 return error_response_with_headers(status, req.id, -32002, message, &headers);
307 }
308 };
309
310 match req.method.as_str() {
312 "initialize" => handle_initialize(
313 req.id,
314 &app.server_version,
315 app.state.config.token_broker.as_ref(),
316 ),
317 "notifications/initialized" => handle_initialized_notification(req.id),
318 "tools/list" => handle_tools_list(req.id, &app.state),
319 "tools/call" => handle_tools_call(req.id, &app.state, identity.as_ref(), req.params).await,
320 other => error_response(
321 StatusCode::OK,
322 req.id,
323 -32601,
324 format!("method not found: {other}"),
325 ),
326 }
327}
328
329fn body_rejection_status(
331 rej: &axum::extract::rejection::JsonRejection,
332) -> (StatusCode, i32, String) {
333 use axum::extract::rejection::JsonRejection;
334 match rej {
335 JsonRejection::JsonDataError(e) => (StatusCode::BAD_REQUEST, -32600, e.to_string()),
336 JsonRejection::JsonSyntaxError(e) => (StatusCode::BAD_REQUEST, -32700, e.to_string()),
337 JsonRejection::MissingJsonContentType(_) => (
338 StatusCode::UNSUPPORTED_MEDIA_TYPE,
339 -32600,
340 "expected application/json".into(),
341 ),
342 JsonRejection::BytesRejection(_) => (
343 StatusCode::PAYLOAD_TOO_LARGE,
344 -32600,
345 "body too large".into(),
346 ),
347 other => (
348 StatusCode::BAD_REQUEST,
349 -32600,
350 format!("invalid request body: {other}"),
351 ),
352 }
353}
354
355#[allow(dead_code)]
356fn body_too_large_response(id: Option<Value>) -> axum::response::Response {
357 (
358 StatusCode::PAYLOAD_TOO_LARGE,
359 Json(json!({
360 "jsonrpc": "2.0",
361 "id": id,
362 "error": {"code": -32600, "message": "body too large"},
363 })),
364 )
365 .into_response()
366}