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