1use std::sync::mpsc::{self, SyncSender, TrySendError};
33use std::sync::Arc;
34
35use super::{
36 base64_decode, base64_encode, icons_json, json_escape, json_rpc, LogLevel, McpContext, McpServer,
37 PromptMessage, Response, SamplingRequest, SUPPORTED_PROTOCOL_VERSIONS,
38};
39use crate::core::New;
40use crate::header::Header;
41use crate::request::Request;
42use crate::response::STATUS_CODE_REASON_PHRASE;
43
44pub(crate) const MODERN_VERSIONS: [&str; 1] = ["2026-07-28"];
46
47const META_VERSION: &str = "io.modelcontextprotocol/protocolVersion";
49const META_CAPABILITIES: &str = "io.modelcontextprotocol/clientCapabilities";
50const META_LOG_LEVEL: &str = "io.modelcontextprotocol/logLevel";
51
52const HEADER_MISMATCH: i32 = -32020;
53const MISSING_CLIENT_CAPABILITY: i32 = -32021;
54const UNSUPPORTED_PROTOCOL_VERSION: i32 = -32022;
55
56const STATE_TTL_SECS: u64 = 600;
58
59#[derive(Debug, Default)]
62pub(crate) struct ModernRequest {
63 client_capabilities: String,
65 input_responses: Option<String>,
67 request_state: Option<String>,
69 log_level: Option<LogLevel>,
71 tasks_extension: bool,
73}
74
75#[derive(Debug, Clone, Default)]
95pub struct InputRequired {
96 requests: Vec<(String, &'static str, String)>,
98 state: Option<String>,
99}
100
101impl InputRequired {
102 pub fn new() -> Self {
103 Self::default()
104 }
105
106 pub fn elicit(mut self, key: &str, message: &str, requested_schema: &str) -> Self {
110 let params = format!(r#"{{"mode":"form","message":"{}","requestedSchema":{}}}"#, json_escape(message), requested_schema);
111 self.requests.push((key.to_string(), "elicitation/create", params));
112 self
113 }
114
115 pub fn sample(mut self, key: &str, request: &SamplingRequest) -> Self {
118 let messages: Vec<String> = request.messages.iter().map(PromptMessage::to_json).collect();
119 let mut params = format!(r#"{{"messages":[{}],"maxTokens":{}"#, messages.join(","), request.max_tokens);
120 if let Some(system) = &request.system_prompt {
121 params.push_str(&format!(r#","systemPrompt":"{}""#, json_escape(system)));
122 }
123 params.push('}');
124 self.requests.push((key.to_string(), "sampling/createMessage", params));
125 self
126 }
127
128 pub fn list_roots(mut self, key: &str) -> Self {
131 self.requests.push((key.to_string(), "roots/list", "{}".to_string()));
132 self
133 }
134
135 pub fn state(mut self, state: impl Into<String>) -> Self {
138 self.state = Some(state.into());
139 self
140 }
141
142 pub(crate) fn requests_json(&self) -> Vec<(String, String)> {
144 self.requests.iter().map(|(key, method, params)| (key.clone(), format!(r#"{{"method":"{method}","params":{params}}}"#))).collect()
145 }
146
147 pub(crate) fn to_json(&self) -> String {
150 let requests: Vec<String> = self
151 .requests
152 .iter()
153 .map(|(key, method, params)| format!(r#""{}":{{"method":"{method}","params":{params}}}"#, json_escape(key)))
154 .collect();
155 let mut json = format!(r#"{{"inputRequests":{{{}}}"#, requests.join(","));
156 if let Some(state) = &self.state {
157 json.push_str(&format!(r#","requestState":"{}""#, json_escape(state)));
158 }
159 json.push('}');
160 json
161 }
162}
163
164#[derive(Debug, Clone)]
167pub enum PromptReply {
168 Messages(Vec<PromptMessage>),
169 InputRequired(InputRequired),
171}
172
173impl From<Vec<PromptMessage>> for PromptReply {
174 fn from(messages: Vec<PromptMessage>) -> Self {
175 PromptReply::Messages(messages)
176 }
177}
178
179impl McpContext {
180 pub fn input_response(&self, key: &str) -> Option<String> {
185 let responses = self.modern.as_ref()?.input_responses.as_deref()?;
186 member(&members(responses), key).map(str::to_string)
187 }
188
189 pub fn elicitation_content(&self, key: &str) -> Option<String> {
193 let answer = members(&self.input_response(key)?);
194 (member_str(&answer, "action").as_deref() == Some("accept")).then(|| member(&answer, "content").unwrap_or("{}").to_string())
195 }
196
197 pub fn request_state(&self) -> Option<&str> {
200 self.modern.as_ref()?.request_state.as_deref()
201 }
202
203 pub fn client_supports(&self, capability: &str) -> bool {
208 match &self.modern {
209 Some(modern) => member(&members(&modern.client_capabilities), capability).is_some(),
210 None => match capability {
211 "sampling" => self.sampling_supported,
212 "roots" => self.roots_supported,
213 _ => false,
214 },
215 }
216 }
217
218 pub fn is_modern(&self) -> bool {
221 self.modern.is_some()
222 }
223
224 pub fn log(&self, level: LogLevel, data_json: &str) {
230 let (Some(modern), Some(stream)) = (&self.modern, &self.request_stream) else { return };
231 if modern.log_level.is_some_and(|min| level >= min) {
232 let params = format!(r#"{{"level":"{}","data":{data_json}}}"#, level.as_str());
233 let _ = stream.try_send(format!("data: {}\n\n", super::render_notification("notifications/message", Some(¶ms))).into_bytes());
234 }
235 }
236}
237
238fn members(json: &str) -> Vec<(String, String)> {
241 json_rpc::object_members(json).unwrap_or_default()
242}
243
244fn member<'a>(members: &'a [(String, String)], key: &str) -> Option<&'a str> {
245 members.iter().find(|(k, _)| k == key).map(|(_, v)| v.as_str())
246}
247
248fn member_str(members: &[(String, String)], key: &str) -> Option<String> {
250 member(members, key).filter(|v| v.starts_with('"')).and_then(|v| json_rpc::extract_str(&format!(r#"{{"v":{v}}}"#), "v"))
251}
252
253fn header(request: &Request, name: &str) -> Option<String> {
254 request.headers.iter().find(|h| h.name.eq_ignore_ascii_case(name)).map(|h| h.value.to_string())
255}
256
257fn meta_version(body: &str) -> Option<String> {
259 let body = members(body);
260 let params = members(member(&body, "params")?);
261 member_str(&members(member(¶ms, "_meta")?), META_VERSION)
262}
263
264pub(crate) fn is_modern(request: &Request, body: &str) -> bool {
268 let header_version = header(request, "mcp-protocol-version").map(|v| v.trim().to_string());
269 if header_version.as_deref().is_some_and(|v| !SUPPORTED_PROTOCOL_VERSIONS.contains(&v)) {
270 return true;
271 }
272 header_version.is_none() && meta_version(body).is_some()
273}
274
275fn header_value(raw: &str) -> Option<String> {
279 if raw.bytes().any(|b| !(b == b'\t' || (0x20..=0x7e).contains(&b))) {
280 return None;
281 }
282 match raw.strip_prefix("=?base64?").and_then(|rest| rest.strip_suffix("?=")) {
283 Some(encoded) => {
284 let decoded = base64_decode(encoded).filter(|bytes| base64_encode(bytes) == encoded)?;
285 String::from_utf8(decoded).ok()
286 }
287 None => Some(raw.to_string()),
288 }
289}
290
291pub(crate) fn header_params(schema: &str) -> Vec<(Vec<String>, String)> {
295 let mut found = Vec::new();
296 let mut pending: Vec<(Vec<String>, String)> = vec![(Vec::new(), schema.to_string())];
298 while let Some((path, object)) = pending.pop() {
299 if path.len() > 32 {
300 continue;
301 }
302 let Some(properties) = member(&members(&object), "properties").map(members) else { continue };
303 for (key, sub) in properties {
304 let mut at = path.clone();
305 at.push(key);
306 let sub_members = members(&sub);
307 if let Some(name) = member_str(&sub_members, "x-mcp-header") {
308 found.push((at.clone(), name));
309 }
310 pending.push((at, sub));
311 }
312 }
313 found
314}
315
316fn value_at(arguments: &str, path: &[String]) -> Option<String> {
318 let mut value = arguments.to_string();
319 for key in path {
320 value = member(&members(&value), key)?.to_string();
321 }
322 Some(value)
323}
324
325fn header_matches(raw: &str, header: &str) -> bool {
328 if raw.starts_with('"') {
329 return json_rpc::extract_str(&format!(r#"{{"v":{raw}}}"#), "v").as_deref() == Some(header);
330 }
331 if raw == "true" || raw == "false" {
332 return raw == header;
333 }
334 matches!((raw.parse::<f64>(), header.parse::<f64>()), (Ok(a), Ok(b)) if a == b)
335}
336
337fn check_param_headers(request: &Request, schema: &str, arguments: &str) -> Result<(), ModernError> {
340 for (path, name) in header_params(schema) {
341 let header_name = format!("Mcp-Param-{name}");
342 let sent = match header(request, &header_name) {
343 Some(raw) => Some(header_value(&raw).ok_or_else(|| {
344 ModernError::bad_request(HEADER_MISMATCH, format!("Header mismatch: {header_name} isn't a valid header value (or valid =?base64?...?=)"))
345 })?),
346 None => None,
347 };
348 match (value_at(arguments, &path).filter(|v| v != "null"), sent) {
349 (Some(_), None) => {
350 return Err(ModernError::bad_request(HEADER_MISMATCH, format!("Header mismatch: {header_name} is required, the argument {} has a value", path.join("."))))
351 }
352 (Some(value), Some(sent)) if !header_matches(&value, &sent) => {
353 return Err(ModernError::bad_request(HEADER_MISMATCH, format!("Header mismatch: {header_name} {sent:?} doesn't match the argument {}", path.join("."))))
354 }
355 _ => {}
356 }
357 }
358 Ok(())
359}
360
361struct ModernError {
364 code: i32,
365 message: String,
366 data: Option<String>,
367 status: u16,
368}
369
370impl ModernError {
371 fn new(code: i32, message: impl Into<String>) -> Self {
372 ModernError { code, message: message.into(), data: None, status: 200 }
373 }
374
375 fn bad_request(code: i32, message: impl Into<String>) -> Self {
376 ModernError { status: 400, ..Self::new(code, message) }
377 }
378
379 fn data(mut self, data: String) -> Self {
380 self.data = Some(data);
381 self
382 }
383}
384
385impl From<(i32, String)> for ModernError {
386 fn from((code, message): (i32, String)) -> Self {
387 ModernError::new(code, message)
388 }
389}
390
391enum Answer {
393 Complete { result: String, cacheable: bool },
394 InputRequired(String),
395 Task(String),
397}
398
399fn tasks_extension_required() -> ModernError {
401 ModernError::bad_request(MISSING_CLIENT_CAPABILITY, "Missing required client capability: the io.modelcontextprotocol/tasks extension")
402 .data(format!(r#"{{"requiredCapabilities":{{"extensions":{{"{}":{{}}}}}}}}"#, super::tasks::EXTENSION))
403}
404
405pub(crate) struct Listener {
407 id: String,
409 tools: bool,
410 prompts: bool,
411 resources: bool,
412 uris: Vec<String>,
413 task_ids: Vec<String>,
415 tx: SyncSender<Vec<u8>>,
416}
417
418fn with_fields(object: &str, fields: &str) -> String {
421 let inner = object.trim().strip_prefix('{').unwrap_or("}").trim_start();
422 if inner.starts_with('}') {
423 format!("{{{fields}}}")
424 } else {
425 format!("{{{fields},{inner}")
426 }
427}
428
429fn status_response(status: u16, body: String) -> Response {
430 let mut response = super::json_response(&body);
431 if status == 400 {
432 response.status_code = *STATUS_CODE_REASON_PHRASE.n400_bad_request.status_code;
433 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n400_bad_request.reason_phrase.to_string();
434 } else if status == 404 {
435 response.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
436 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
437 }
438 response
439}
440
441fn error_json(id: &str, error: &ModernError) -> String {
442 let data = error.data.as_deref().map(|d| format!(r#","data":{d}"#)).unwrap_or_default();
443 format!(r#"{{"jsonrpc":"2.0","id":{id},"error":{{"code":{},"message":"{}"{data}}}}}"#, error.code, json_escape(&error.message))
444}
445
446pub(crate) fn random_key() -> Vec<u8> {
448 use std::hash::{BuildHasher, Hasher};
449 use std::io::Read;
450 let mut key = vec![0u8; 32];
451 if std::fs::File::open("/dev/urandom").and_then(|mut f| f.read_exact(&mut key)).is_ok() {
452 return key;
453 }
454 let keys = std::collections::hash_map::RandomState::new();
455 let nanos = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_nanos());
456 for (i, chunk) in key.chunks_mut(8).enumerate() {
457 let mut hasher = keys.build_hasher();
458 hasher.write_usize(i);
459 hasher.write_u128(nanos);
460 chunk.copy_from_slice(&hasher.finish().to_le_bytes());
461 }
462 key
463}
464
465fn unix_now() -> u64 {
466 std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_secs())
467}
468
469impl McpServer {
470 pub fn request_state_key(mut self, key: impl AsRef<[u8]>) -> Self {
474 self.state_key = Arc::new(key.as_ref().to_vec());
475 self
476 }
477
478 pub fn cache_hints(mut self, ttl_ms: u64, public: bool) -> Self {
483 self.cache_ttl_ms = ttl_ms;
484 self.cache_scope = if public { "public" } else { "private" };
485 self
486 }
487
488 fn server_info_json(&self) -> String {
489 format!(
490 r#"{{"name":"{}"{}"version":"{}"{}}}"#,
491 json_escape(&self.server_name),
492 self.server_title.as_ref().map(|t| format!(r#","title":"{}","#, json_escape(t))).unwrap_or_else(|| ",".to_string()),
493 json_escape(&self.server_version),
494 icons_json(&self.server_icons),
495 )
496 }
497
498 fn capabilities_json(&self) -> String {
499 let logging = if self.logging_enabled { r#","logging":{}"# } else { "" };
500 let completions = if self.completions.read().unwrap().is_empty() { "" } else { r#","completions":{}"# };
501 format!(
502 r#"{{"tools":{{"listChanged":true}},"resources":{{"subscribe":true,"listChanged":true}},"prompts":{{"listChanged":true}}{logging}{completions},"extensions":{{"{}":{{}}}}}}"#,
503 super::tasks::EXTENSION
504 )
505 }
506
507 fn supported_versions_json() -> String {
510 let all: Vec<String> = MODERN_VERSIONS.iter().chain(SUPPORTED_PROTOCOL_VERSIONS.iter().rev()).map(|v| format!(r#""{v}""#)).collect();
511 format!("[{}]", all.join(","))
512 }
513
514 fn sign_state(&self, method: &str, name: &str, principal: &str, state: &str) -> String {
516 let payload = format!("{}\n{method}\n{name}\n{principal}\n{state}", unix_now() + STATE_TTL_SECS);
517 let mac = crate::core::sha256::hmac_sha256(&self.state_key, payload.as_bytes());
518 format!("{}.{}", base64_encode(payload.as_bytes()), base64_encode(&mac))
519 }
520
521 fn verify_state(&self, token: &str, method: &str, name: &str, principal: &str) -> Option<String> {
524 let (payload, mac) = token.split_once('.')?;
525 let payload = base64_decode(payload)?;
526 if !crate::core::sha256::verify(&self.state_key, &payload, &base64_decode(mac)?) {
527 return None;
528 }
529 let payload = String::from_utf8(payload).ok()?;
530 let mut parts = payload.splitn(5, '\n');
531 let expires: u64 = parts.next()?.parse().ok()?;
532 let fits = parts.next()? == method && parts.next()? == name && parts.next()? == principal;
533 (fits && unix_now() <= expires).then(|| parts.next().unwrap_or("").to_string())
534 }
535
536 pub(crate) fn handle_modern(&self, request: &Request, body: &str, mut ctx: McpContext) -> Response {
538 let body_members = match json_rpc::object_members(body) {
539 Some(members) => members,
540 None => {
541 let error = ModernError::bad_request(json_rpc::INVALID_REQUEST, "The body must be one JSON-RPC request or notification");
542 return status_response(400, error_json("null", &error));
543 }
544 };
545 let id = member(&body_members, "id").map(str::to_string);
546 let id_json = id.clone().unwrap_or_else(|| "null".to_string());
547 let method = match member_str(&body_members, "method") {
548 Some(method) => method,
549 None => return status_response(400, error_json(&id_json, &ModernError::bad_request(json_rpc::INVALID_REQUEST, "Missing method"))),
550 };
551 let params = member(&body_members, "params").unwrap_or("{}").to_string();
552 let modern = match self.validate_modern(request, &method, ¶ms) {
553 Ok(modern) => modern,
554 Err(error) => return status_response(error.status, error_json(&id_json, &error)),
555 };
556 let Some(id) = id else {
558 let mut accepted = super::no_content();
559 accepted.status_code = 202;
560 accepted.reason_phrase = "Accepted".to_string();
561 return accepted;
562 };
563
564 if method == "subscriptions/listen" {
565 return self.listen(&id, ¶ms, modern.tasks_extension);
566 }
567 if method == "tools/call" {
568 if let Err(error) = self.check_tool_headers(request, ¶ms) {
569 return status_response(error.status, error_json(&id, &error));
570 }
571 let tool = member_str(&members(¶ms), "name").unwrap_or_default();
572 if !modern.tasks_extension && self.is_task_required(&tool) {
573 return status_response(400, error_json(&id, &tasks_extension_required()));
574 }
575 }
576
577 let principal = ctx.auth_claims.as_deref().and_then(|claims| json_rpc::extract_str(claims, "sub")).unwrap_or_default();
578 let target = {
579 let params = members(¶ms);
580 member_str(¶ms, "name").or_else(|| member_str(¶ms, "uri")).unwrap_or_default()
581 };
582 let mut modern = modern;
583 if modern.tasks_extension {
584 ctx.tasks = Some(super::tasks::TaskHandle { server: self.clone(), principal: principal.clone() });
585 }
586 if let Some(token) = member_str(&members(¶ms), "requestState") {
587 match self.verify_state(&token, &method, &target, &principal) {
588 Some(state) => modern.request_state = Some(state),
589 None => {
590 let error = ModernError::new(json_rpc::INVALID_PARAMS, "requestState failed verification (altered, expired, or for another request)");
591 return status_response(200, error_json(&id, &error));
592 }
593 }
594 }
595 if let Some(responses) = member(&members(¶ms), "inputResponses") {
596 if !json_rpc::object_members(responses).is_some_and(|answers| answers.iter().all(|(_, v)| v.trim_start().starts_with('{'))) {
597 let error = ModernError::new(json_rpc::INVALID_PARAMS, "inputResponses must be an object of result objects");
598 return status_response(200, error_json(&id, &error));
599 }
600 modern.input_responses = Some(responses.to_string());
601 }
602 let wants_stream = modern.log_level.is_some() || member(&members(¶ms), "_meta").is_some_and(|meta| member(&members(meta), "progressToken").is_some());
603 let accepts_stream = header(request, "accept").is_some_and(|a| a.to_ascii_lowercase().contains("text/event-stream"));
604 ctx.modern = Some(Arc::new(modern));
605 let body = body.to_string();
606
607 if wants_stream && accepts_stream && matches!(method.as_str(), "tools/call" | "prompts/get" | "resources/read") {
608 let (tx, rx) = mpsc::sync_channel::<Vec<u8>>(32);
611 ctx.request_stream = Some(tx.clone());
612 let server = self.clone();
613 std::thread::spawn(move || {
614 let (_, json) = server.answer(&id, &method, &body, ctx, &target, &principal);
615 let _ = tx.send(format!("data: {json}\n\n").into_bytes());
616 });
617 let mut response = Response::new();
618 response.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
619 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
620 response.headers.push(Header { name: Header::_CONTENT_TYPE.into(), value: "text/event-stream".into() });
621 response.headers.push(Header { name: Header::_CACHE_CONTROL.into(), value: "no-cache".into() });
622 response.headers.push(Header { name: "X-Accel-Buffering".into(), value: "no".into() });
623 response.stream_pipe = Some(Box::new(crate::sse::SseReader::new(rx, super::SSE_KEEPALIVE_INTERVAL)));
624 return response;
625 }
626 let (status, json) = self.answer(&id, &method, &body, ctx, &target, &principal);
627 status_response(status, json)
628 }
629
630 fn check_tool_headers(&self, request: &Request, params: &str) -> Result<(), ModernError> {
632 let params = members(params);
633 let Some(name) = member_str(¶ms, "name") else { return Ok(()) };
634 let schema = self.tools.read().unwrap().iter().find(|t| t.name == name).map(|t| t.input_schema.clone());
635 #[cfg(feature = "http2")]
636 let schema = schema.or_else(|| self.async_tools.read().unwrap().iter().find(|t| t.name == name).map(|t| t.input_schema.clone()));
637 match schema {
638 Some(schema) if schema.contains("x-mcp-header") => check_param_headers(request, &schema, member(¶ms, "arguments").unwrap_or("{}")),
639 _ => Ok(()),
640 }
641 }
642
643 fn validate_modern(&self, request: &Request, method: &str, params: &str) -> Result<ModernRequest, ModernError> {
645 let params = members(params);
646 let meta = member(¶ms, "_meta").map(members).ok_or_else(|| {
647 ModernError::bad_request(json_rpc::INVALID_PARAMS, "params._meta is required (protocol version and client capabilities)")
648 })?;
649 let version = member_str(&meta, META_VERSION)
650 .ok_or_else(|| ModernError::bad_request(json_rpc::INVALID_PARAMS, format!("params._meta[\"{META_VERSION}\"] is required")))?;
651 let capabilities = member(&meta, META_CAPABILITIES)
652 .filter(|c| c.trim_start().starts_with('{'))
653 .ok_or_else(|| ModernError::bad_request(json_rpc::INVALID_PARAMS, format!("params._meta[\"{META_CAPABILITIES}\"] is required")))?;
654 match header(request, "mcp-protocol-version").map(|v| v.trim().to_string()) {
655 Some(header_version) if header_version == version => {}
656 Some(header_version) => {
657 return Err(ModernError::bad_request(
658 HEADER_MISMATCH,
659 format!("Header mismatch: MCP-Protocol-Version {header_version:?} doesn't match _meta's {version:?}"),
660 ))
661 }
662 None => return Err(ModernError::bad_request(HEADER_MISMATCH, "The MCP-Protocol-Version header is required")),
663 }
664 if !MODERN_VERSIONS.contains(&version.as_str()) {
665 return Err(ModernError::bad_request(UNSUPPORTED_PROTOCOL_VERSION, "Unsupported protocol version")
666 .data(format!(r#"{{"supported":{},"requested":"{}"}}"#, Self::supported_versions_json(), json_escape(&version))));
667 }
668 match header(request, "mcp-method").and_then(|h| header_value(&h)) {
669 Some(header_method) if header_method == method => {}
670 Some(header_method) => {
671 return Err(ModernError::bad_request(HEADER_MISMATCH, format!("Header mismatch: Mcp-Method {header_method:?} doesn't match the body's {method:?}")))
672 }
673 None => return Err(ModernError::bad_request(HEADER_MISMATCH, "The Mcp-Method header is required (and must be a valid header value)")),
674 }
675 let named = match method {
676 "tools/call" | "prompts/get" => member_str(¶ms, "name"),
677 "resources/read" => member_str(¶ms, "uri"),
678 "tasks/get" | "tasks/update" | "tasks/cancel" => member_str(¶ms, "taskId"),
679 _ => None,
680 };
681 if let Some(body_name) = named {
682 match header(request, "mcp-name").and_then(|h| header_value(&h)) {
683 Some(header_name) if header_name == body_name => {}
684 Some(header_name) => {
685 return Err(ModernError::bad_request(HEADER_MISMATCH, format!("Header mismatch: Mcp-Name {header_name:?} doesn't match the body's {body_name:?}")))
686 }
687 None => return Err(ModernError::bad_request(HEADER_MISMATCH, "The Mcp-Name header is required for this method")),
688 }
689 }
690 let tasks_extension = member(&members(capabilities), "extensions").is_some_and(|ext| member(&members(ext), super::tasks::EXTENSION).is_some());
691 Ok(ModernRequest {
692 client_capabilities: capabilities.to_string(),
693 log_level: member_str(&meta, META_LOG_LEVEL).and_then(|level| LogLevel::parse(&level)),
694 tasks_extension,
695 ..ModernRequest::default()
696 })
697 }
698
699 fn answer(&self, id: &str, method: &str, body: &str, ctx: McpContext, target: &str, principal: &str) -> (u16, String) {
701 let capabilities = ctx.modern.as_ref().map(|m| m.client_capabilities.clone()).unwrap_or_default();
702 let retry = ctx.modern.as_ref().is_some_and(|m| m.input_responses.is_some() || m.request_state.is_some());
703 let server_info = format!(r#""_meta":{{"io.modelcontextprotocol/serverInfo":{}}}"#, self.server_info_json());
704 let answer = self.dispatch_modern(method, body, ctx);
705 let result = match answer {
706 Ok(Answer::Complete { result, cacheable }) => {
707 let mut fields = format!(r#""resultType":"complete",{server_info}"#);
708 if cacheable && !retry {
710 fields.push_str(&format!(r#","ttlMs":{},"cacheScope":"{}""#, self.cache_ttl_ms, self.cache_scope));
711 }
712 Ok(with_fields(&result, &fields))
713 }
714 Ok(Answer::InputRequired(input)) => self.input_required_result(&input, &capabilities, method, target, principal, &server_info),
715 Ok(Answer::Task(task_id)) => match self.create_task_fields(&task_id, principal) {
716 Some(fields) => Ok(format!(r#"{{"resultType":"task",{server_info},{fields}}}"#)),
717 None => Err(ModernError::new(json_rpc::INTERNAL_ERROR, "the task disappeared")),
718 },
719 Err(error) => Err(error),
720 };
721 match result {
722 Ok(result) => (200, format!(r#"{{"jsonrpc":"2.0","id":{id},"result":{result}}}"#)),
723 Err(error) => (error.status, error_json(id, &error)),
724 }
725 }
726
727 fn input_required_result(&self, input: &str, capabilities: &str, method: &str, target: &str, principal: &str, server_info: &str) -> Result<String, ModernError> {
731 let input = members(input);
732 let declared = members(capabilities);
733 let requests = member(&input, "inputRequests").unwrap_or("{}");
734 let mut missing: Vec<&str> = Vec::new();
735 for (_, request) in members(requests) {
736 let needs = match member_str(&members(&request), "method").as_deref() {
737 Some("elicitation/create") => "elicitation",
738 Some("sampling/createMessage") => "sampling",
739 Some("roots/list") => "roots",
740 _ => continue,
741 };
742 if member(&declared, needs).is_none() && !missing.contains(&needs) {
743 missing.push(needs);
744 }
745 }
746 if !missing.is_empty() {
747 let required: Vec<String> = missing.iter().map(|c| format!(r#""{c}":{{}}"#)).collect();
748 return Err(ModernError::bad_request(MISSING_CLIENT_CAPABILITY, format!("This request needs the client capabilities: {}", missing.join(", ")))
749 .data(format!(r#"{{"requiredCapabilities":{{{}}}}}"#, required.join(","))));
750 }
751 let mut fields = format!(r#""resultType":"input_required",{server_info}"#);
752 if requests.trim() != "{}" {
753 fields.push_str(&format!(r#","inputRequests":{requests}"#));
754 }
755 if let Some(state) = member_str(&input, "requestState") {
756 fields.push_str(&format!(r#","requestState":"{}""#, json_escape(&self.sign_state(method, target, principal, &state))));
757 }
758 Ok(format!("{{{fields}}}"))
759 }
760
761 fn dispatch_modern(&self, method: &str, body: &str, ctx: McpContext) -> Result<Answer, ModernError> {
762 let complete = |result: String, cacheable: bool| Answer::Complete { result, cacheable };
763 Ok(match method {
764 "server/discover" => complete(
765 format!(r#"{{"supportedVersions":{},"capabilities":{}}}"#, Self::supported_versions_json(), self.capabilities_json()),
766 true,
767 ),
768 "tools/list" => complete(self.do_tools_list(body, &ctx)?, true),
769 "tools/call" => match self.call_tool(body, ctx)? {
770 Ok(content) if content.kind == "input_required" => Answer::InputRequired(content.text),
771 Ok(content) if content.kind == "task" => Answer::Task(content.text),
772 other => complete(Self::format_tool_result(other), false),
773 },
774 "resources/list" => complete(self.do_resources_list(body, &ctx)?, true),
775 "resources/templates/list" => complete(self.do_resource_templates_list(body, &ctx)?, true),
776 "resources/read" => match self.do_resources_read(body, &ctx) {
777 Ok(result) => complete(result, true),
778 Err((code, message)) if message.starts_with("Resource not found") => {
779 let uri = json_rpc::extract_raw(body, "params").and_then(|p| json_rpc::extract_str(&p, "uri")).unwrap_or_default();
780 return Err(ModernError::new(code, "Resource not found").data(format!(r#"{{"uri":"{}"}}"#, json_escape(&uri))));
781 }
782 Err(error) => return Err(error.into()),
783 },
784 "prompts/list" => complete(self.do_prompts_list(body, &ctx)?, true),
785 "prompts/get" => match self.get_prompt(body, ctx)? {
786 (_, super::PromptReply::InputRequired(input)) => Answer::InputRequired(input.to_json()),
787 (description, super::PromptReply::Messages(messages)) => {
788 let messages: Vec<String> = messages.iter().map(PromptMessage::to_json).collect();
789 complete(format!(r#"{{"description":"{}","messages":[{}]}}"#, json_escape(&description), messages.join(",")), false)
790 }
791 },
792 "completion/complete" => complete(self.do_completion(body)?, false),
793 "tasks/get" | "tasks/update" | "tasks/cancel" => {
794 let principal = ctx.tasks.as_ref().map(|handle| handle.principal.clone()).ok_or_else(tasks_extension_required)?;
795 let params = members(&json_rpc::object_members(body).and_then(|m| m.into_iter().find(|(k, _)| k == "params").map(|(_, v)| v)).unwrap_or_default());
796 let task_id = member_str(¶ms, "taskId").ok_or_else(|| ModernError::new(json_rpc::INVALID_PARAMS, "taskId is required"))?;
797 match method {
798 "tasks/get" => complete(format!("{{{}}}", self.task_get(&task_id, &principal)?), false),
799 "tasks/update" => {
800 self.task_update(&task_id, &principal, member(¶ms, "inputResponses").unwrap_or("{}"))?;
801 complete("{}".to_string(), false)
802 }
803 _ => {
804 self.task_cancel(&task_id, &principal)?;
805 complete("{}".to_string(), false)
806 }
807 }
808 }
809 _ => {
810 let mut error = ModernError::new(json_rpc::METHOD_NOT_FOUND, format!("Method not found: {method}"));
811 error.status = 404;
812 return Err(error);
813 }
814 })
815 }
816
817 pub(crate) fn listen(&self, id: &str, params: &str, tasks_extension: bool) -> Response {
819 let filter = member(&members(params), "notifications").map(members).unwrap_or_default();
820 let flag = |key: &str| member(&filter, key) == Some("true");
821 let uris: Vec<String> = member(&filter, "resourceSubscriptions")
822 .map(|list| json_rpc::split_array_elements(list).iter().filter_map(|u| json_rpc::extract_str(&format!(r#"{{"v":{u}}}"#), "v")).collect())
823 .unwrap_or_default();
824 let (tools, prompts, resources) = (flag("toolsListChanged"), flag("promptsListChanged"), flag("resourcesListChanged"));
825 let task_ids: Vec<String> = member(&filter, "taskIds")
826 .map(|list| json_rpc::split_array_elements(list).iter().filter_map(|t| json_rpc::extract_str(&format!(r#"{{"v":{t}}}"#), "v")).collect())
827 .unwrap_or_default();
828 if !task_ids.is_empty() && !tasks_extension {
829 return status_response(400, error_json(id, &tasks_extension_required()));
830 }
831 let mut honoured: Vec<String> = Vec::new();
832 for (on, key) in [(tools, "toolsListChanged"), (prompts, "promptsListChanged"), (resources, "resourcesListChanged")] {
833 if on {
834 honoured.push(format!(r#""{key}":true"#));
835 }
836 }
837 if !uris.is_empty() {
838 let list: Vec<String> = uris.iter().map(|u| format!(r#""{}""#, json_escape(u))).collect();
839 honoured.push(format!(r#""resourceSubscriptions":[{}]"#, list.join(",")));
840 }
841 if !task_ids.is_empty() {
842 let list: Vec<String> = task_ids.iter().map(|t| format!(r#""{}""#, json_escape(t))).collect();
843 honoured.push(format!(r#""taskIds":[{}]"#, list.join(",")));
844 }
845 let (tx, rx) = mpsc::sync_channel::<Vec<u8>>(super::SSE_CHANNEL_CAPACITY);
846 let ack = format!(
847 r#"{{"jsonrpc":"2.0","method":"notifications/subscriptions/acknowledged","params":{{"_meta":{{"io.modelcontextprotocol/subscriptionId":{id}}},"notifications":{{{}}}}}}}"#,
848 honoured.join(",")
849 );
850 let _ = tx.try_send(format!("data: {ack}\n\n").into_bytes());
851 self.listeners.lock().unwrap().push(Listener { id: id.to_string(), tools, prompts, resources, uris, task_ids, tx });
852 let mut response = Response::new();
853 response.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
854 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
855 response.headers.push(Header { name: Header::_CONTENT_TYPE.into(), value: "text/event-stream".into() });
856 response.headers.push(Header { name: Header::_CACHE_CONTROL.into(), value: "no-cache".into() });
857 response.headers.push(Header { name: "X-Accel-Buffering".into(), value: "no".into() });
858 response.stream_pipe = Some(Box::new(crate::sse::SseReader::new(rx, super::SSE_KEEPALIVE_INTERVAL)));
859 response
860 }
861
862 pub(crate) fn deliver_task_notification(&self, task_id: &str, params_json: &str) {
865 let mut listeners = self.listeners.lock().unwrap();
866 listeners.retain(|listener| {
867 if !listener.task_ids.iter().any(|t| t == task_id) {
868 return true;
869 }
870 let tag = format!(r#""_meta":{{"io.modelcontextprotocol/subscriptionId":{}}}"#, listener.id);
871 let frame = format!("data: {}\n\n", super::render_notification("notifications/tasks", Some(&with_fields(params_json, &tag))));
872 !matches!(listener.tx.try_send(frame.into_bytes()), Err(TrySendError::Disconnected(_)))
873 });
874 }
875
876 pub(crate) fn deliver_to_listeners(&self, method: &str, params_json: Option<&str>, uri: Option<&str>) {
879 let mut listeners = self.listeners.lock().unwrap();
880 if listeners.is_empty() {
881 return;
882 }
883 listeners.retain(|listener| {
884 let wanted = match method {
885 "notifications/tools/list_changed" => listener.tools,
886 "notifications/prompts/list_changed" => listener.prompts,
887 "notifications/resources/list_changed" => listener.resources,
888 "notifications/resources/updated" => uri.is_some_and(|uri| listener.uris.iter().any(|u| u == uri)),
889 _ => false,
890 };
891 if !wanted {
892 return true;
893 }
894 let tag = format!(r#""_meta":{{"io.modelcontextprotocol/subscriptionId":{}}}"#, listener.id);
895 let params = with_fields(params_json.unwrap_or("{}"), &tag);
896 let frame = format!("data: {}\n\n", super::render_notification(method, Some(¶ms)));
897 !matches!(listener.tx.try_send(frame.into_bytes()), Err(TrySendError::Disconnected(_)))
898 });
899 }
900}
901
902#[cfg(test)]
903mod tests;