Skip to main content

flymcprust_core/
server.rs

1//! The MCP server: builder, immutable registry and request dispatch.
2
3use std::collections::BTreeMap;
4use std::convert::Infallible;
5use std::future::Future;
6use std::pin::Pin;
7use std::sync::Arc;
8use std::task::{Context, Poll};
9use std::time::Duration;
10
11use futures_util::FutureExt as _;
12use serde::Deserialize;
13use serde_json::value::RawValue;
14use serde_json::{Map, Value};
15use tokio::sync::Semaphore;
16use tokio_util::sync::CancellationToken;
17
18use crate::auth::AuthContext;
19use crate::completion::{Completer, CompletionRequest};
20use crate::context::{Ctx, RequestMeta, TransportContext, TypeMap};
21use crate::error::McpError;
22use crate::extension::{self, Extension};
23use crate::hints::{self, Once};
24use crate::jsonrpc::{self, Message, Rejection, Request, Response};
25use crate::legacy::{LegacyConfig, LegacySession};
26use crate::limits::Limits;
27use crate::mirror::{self, MirroredParam};
28use crate::mrtr::{Args, MIN_REQUEST_STATE_KEY_BYTES, Round, RoundKey, StateCodec};
29use crate::notify::{Notifier, NotifyChannel};
30use crate::prompt::{GetFn, Prompt, PromptArguments, PromptDef};
31use crate::resource::{ReadFn, Resource, ResourceDef, ResourceKind};
32use crate::spec::{self, CacheScope, RequestId, methods};
33use crate::tool::{CallFn, StateRequirement, Tool, ToolDef, ToolError, ToolOutput};
34use crate::uri_template::{self, UriTemplate, UriVars};
35
36/// The `resultType` of final results.
37const COMPLETE: &str = "complete";
38
39/// Errors detected while building a server.
40///
41/// `Display` gives the problem in one line; [`BuildError::help`] says how to
42/// fix it. `Debug` prints both, so `fn main() -> Result<..>` with
43/// `.build()?` shows the fix too.
44#[derive(thiserror::Error)]
45#[non_exhaustive]
46pub enum BuildError {
47    /// Two tools share a name.
48    #[error("duplicate tool name `{0}`")]
49    DuplicateTool(String),
50    /// A tool name breaks the specification's naming rules.
51    #[error("invalid tool name `{0}`: use 1-128 characters from A-Z, a-z, 0-9, `_`, `-` and `.`")]
52    InvalidToolName(String),
53    /// A handler needs shared state that was not registered.
54    #[error("`{handler}` needs shared state of type `{type_name}`, which is not registered")]
55    MissingState {
56        /// The tool or prompt name, or the resource URI.
57        handler: String,
58        /// The missing type.
59        type_name: &'static str,
60    },
61    /// A resource URI or URI template is invalid.
62    #[error("invalid resource `{uri}`: {reason}")]
63    InvalidResource {
64        /// The URI or URI template.
65        uri: String,
66        /// What is wrong with it.
67        reason: String,
68    },
69    /// Two resources share a URI, or two templates share a URI template.
70    #[error("duplicate resource `{0}`")]
71    DuplicateResource(String),
72    /// Two prompts share a name.
73    #[error("duplicate prompt name `{0}`")]
74    DuplicatePrompt(String),
75    /// A prompt name is empty or contains control characters.
76    #[error("invalid prompt name `{0}`")]
77    InvalidPromptName(String),
78    /// A completion provider is attached to an unknown argument.
79    #[error("`{owner}` has a completion provider for unknown argument `{argument}`")]
80    InvalidCompletion {
81        /// The prompt name or URI template.
82        owner: String,
83        /// The argument or template variable.
84        argument: String,
85    },
86    /// An `x-mcp-header` annotation breaks the specification's constraints.
87    #[error("tool `{tool}` has an invalid `x-mcp-header` annotation: {reason}")]
88    InvalidHeaderAnnotation {
89        /// The tool name.
90        tool: String,
91        /// The violated constraint.
92        reason: String,
93    },
94    /// An extension identifier is malformed or registered twice.
95    #[error("invalid or duplicate extension identifier `{0}`")]
96    InvalidExtension(String),
97    /// A limit is out of range.
98    #[error("invalid limit: {0}")]
99    InvalidLimit(&'static str),
100    /// The `requestState` signing key is shorter than
101    /// [`MIN_REQUEST_STATE_KEY_BYTES`].
102    #[error("the requestState key must be at least {MIN_REQUEST_STATE_KEY_BYTES} bytes")]
103    WeakRequestStateKey,
104    /// No random `requestState` key could be generated.
105    #[error("could not generate a random requestState key: {0}")]
106    RandomUnavailable(String),
107    /// An MCP Apps interface or a tool's `ui` is invalid.
108    #[error("invalid MCP Apps interface `{uri}`: {reason}")]
109    InvalidApp {
110        /// The `ui://` URI (or the tool name).
111        uri: String,
112        /// What is wrong.
113        reason: String,
114    },
115    /// A handler declares a malformed OAuth scope.
116    #[error("`{handler}` declares an invalid scope `{scope}`")]
117    InvalidScope {
118        /// The tool or prompt name, or the resource URI.
119        handler: String,
120        /// The scope.
121        scope: String,
122    },
123    /// A pre-serialized response could not be built.
124    #[error("could not serialize {0}")]
125    Serialize(&'static str, #[source] serde_json::Error),
126}
127
128impl BuildError {
129    /// How to fix the problem.
130    #[must_use]
131    pub fn help(&self) -> String {
132        match self {
133            Self::DuplicateTool(name) => format!(
134                "rename one of the tools with #[tool(name = \"...\")], or check that `{name}` \
135                 is not registered twice (for example with `.tool::<..>()` and `.auto_tools()`)"
136            ),
137            Self::InvalidToolName(_) => "tool names travel in HTTP headers; rename the tool with \
138                 #[tool(name = \"...\")] using only those characters"
139                .to_owned(),
140            Self::MissingState { type_name, .. } => format!(
141                "register a value before `.build()`: `Server::builder(..).state(value)`, \
142                 where `value: {type_name}`"
143            ),
144            Self::InvalidResource { .. } => "fixed URIs need a scheme, such as `config://app` or \
145                 `file:///notes.txt`; templates support `{var}`, `{+var}`, `{/var}` and a final \
146                 `{?query}` (see the `UriTemplate` docs)"
147                .to_owned(),
148            Self::DuplicateResource(_) => {
149                "each URI and each URI template can be registered only once".to_owned()
150            }
151            Self::DuplicatePrompt(_) => {
152                "rename one of the prompts with #[prompt(name = \"...\")]".to_owned()
153            }
154            Self::InvalidPromptName(_) => {
155                "use a non-empty name without control characters".to_owned()
156            }
157            Self::InvalidCompletion { owner, .. } => format!(
158                "#[complete(..)] and `.complete(..)` must name an argument (prompts) or a \
159                 template variable (resources) of `{owner}`"
160            ),
161            Self::InvalidHeaderAnnotation { .. } => "`x-mcp-header` takes an HTTP header name \
162                 (letters, digits and `-`) on a string, integer or boolean property reached \
163                 only through `properties`"
164                .to_owned(),
165            Self::InvalidExtension(_) => "extension identifiers look like \
166                 `com.example/my-extension` and can be registered only once"
167                .to_owned(),
168            Self::InvalidLimit(_) => "limits must be positive; see `max_message_bytes`, \
169                 `max_json_depth`, `tool_timeout` and `max_concurrent_calls` on the builder"
170                .to_owned(),
171            Self::WeakRequestStateKey => format!(
172                "use at least {MIN_REQUEST_STATE_KEY_BYTES} random bytes from your secret store, \
173                 and the same key on every instance"
174            ),
175            Self::RandomUnavailable(_) => "the operating system did not provide randomness; set \
176                 a key explicitly with `.request_state_key(..)`"
177                .to_owned(),
178            Self::InvalidApp { .. } => "MCP Apps interfaces are fixed `ui://` resources marked \
179                 with #[resource(\"ui://..\", app)] (or `ResourceDef::app`), and \
180                 #[tool(ui = \"ui://..\")] must name one of them; CSP origins look like \
181                 `https://api.example.com`"
182                .to_owned(),
183            Self::InvalidScope { .. } => "scopes are non-empty and use visible ASCII except `\"` \
184                 and `\\`, without spaces (for example `files:read`)"
185                .to_owned(),
186            Self::Serialize(..) => "a definition built by hand contains a value JSON cannot \
187                 represent (such as a NaN number); if it came from a macro, please report a bug"
188                .to_owned(),
189        }
190    }
191}
192
193impl std::fmt::Debug for BuildError {
194    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195        write!(f, "{self}\n  = help: {}", self.help())
196    }
197}
198
199/// Builds a [`Server`].
200#[must_use = "call `.build()` to create the server"]
201pub struct ServerBuilder {
202    info: spec::Implementation,
203    instructions: Option<String>,
204    tools: Vec<ToolDef>,
205    resources: Vec<ResourceDef>,
206    prompts: Vec<PromptDef>,
207    state: TypeMap,
208    extensions: Vec<Box<dyn Extension>>,
209    limits: Limits,
210    list_ttl: Duration,
211    discover_ttl: Duration,
212    cache_scope: CacheScope,
213    include_server_info: bool,
214    request_state_key: Option<Vec<u8>>,
215    request_state_ttl: Duration,
216    legacy_clients: bool,
217    legacy_session_ttl: Duration,
218}
219
220impl std::fmt::Debug for ServerBuilder {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        f.debug_struct("ServerBuilder")
223            .field("info", &self.info)
224            .field("tools", &self.tools.len())
225            .field("resources", &self.resources.len())
226            .field("prompts", &self.prompts.len())
227            .field("limits", &self.limits)
228            .finish_non_exhaustive()
229    }
230}
231
232impl ServerBuilder {
233    /// Starts a server with its name and version (reported as `serverInfo`).
234    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
235        Self {
236            info: spec::Implementation::new(name.into(), version.into()),
237            instructions: None,
238            tools: Vec::new(),
239            resources: Vec::new(),
240            prompts: Vec::new(),
241            state: TypeMap::default(),
242            extensions: Vec::new(),
243            limits: Limits::default(),
244            list_ttl: Duration::from_secs(300),
245            discover_ttl: Duration::from_secs(3600),
246            cache_scope: CacheScope::Public,
247            include_server_info: true,
248            request_state_key: None,
249            request_state_ttl: Duration::from_secs(600),
250            legacy_clients: true,
251            legacy_session_ttl: Duration::from_secs(24 * 3600),
252        }
253    }
254
255    /// Sets the human-readable title in `serverInfo`.
256    pub fn title(mut self, title: impl Into<String>) -> Self {
257        self.info.title = Some(title.into());
258        self
259    }
260
261    /// Sets the natural-language guidance returned by `server/discover`.
262    pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
263        self.instructions = Some(instructions.into());
264        self
265    }
266
267    /// Registers a tool declared with `#[tool]` (or implementing [`Tool`]).
268    pub fn tool<T: Tool>(mut self) -> Self {
269        self.tools.push(T::definition());
270        self
271    }
272
273    /// Registers every `#[tool]` linked into the binary (feature
274    /// `auto-register`). Tools can still be added explicitly; duplicates are
275    /// reported by [`build`](Self::build).
276    #[cfg(feature = "auto-register")]
277    pub fn auto_tools(mut self) -> Self {
278        for tool in inventory::iter::<crate::auto::AutoTool> {
279            self.tools.push(tool.definition());
280        }
281        self
282    }
283
284    /// Registers a tool definition built by hand.
285    pub fn tool_def(mut self, def: ToolDef) -> Self {
286        self.tools.push(def);
287        self
288    }
289
290    /// Registers a resource or resource template declared with `#[resource]`
291    /// (or implementing [`Resource`]).
292    pub fn resource<T: Resource>(mut self) -> Self {
293        self.resources.push(T::definition());
294        self
295    }
296
297    /// Registers every `#[resource]` linked into the binary (feature
298    /// `auto-register`).
299    #[cfg(feature = "auto-register")]
300    pub fn auto_resources(mut self) -> Self {
301        for resource in inventory::iter::<crate::auto::AutoResource> {
302            self.resources.push(resource.definition());
303        }
304        self
305    }
306
307    /// Registers a resource definition built by hand.
308    pub fn resource_def(mut self, def: ResourceDef) -> Self {
309        self.resources.push(def);
310        self
311    }
312
313    /// Registers a prompt declared with `#[prompt]` (or implementing
314    /// [`Prompt`]).
315    pub fn prompt<T: Prompt>(mut self) -> Self {
316        self.prompts.push(T::definition());
317        self
318    }
319
320    /// Registers every `#[prompt]` linked into the binary (feature
321    /// `auto-register`).
322    #[cfg(feature = "auto-register")]
323    pub fn auto_prompts(mut self) -> Self {
324        for prompt in inventory::iter::<crate::auto::AutoPrompt> {
325            self.prompts.push(prompt.definition());
326        }
327        self
328    }
329
330    /// Registers a prompt definition built by hand.
331    pub fn prompt_def(mut self, def: PromptDef) -> Self {
332        self.prompts.push(def);
333        self
334    }
335
336    /// Registers shared state, available to handlers through `State<T>`.
337    pub fn state<T: Send + Sync + 'static>(mut self, value: T) -> Self {
338        self.state.insert(Arc::new(value));
339        self
340    }
341
342    /// Registers a protocol extension.
343    pub fn extension(mut self, extension: impl Extension) -> Self {
344        self.extensions.push(Box::new(extension));
345        self
346    }
347
348    /// Replaces all limits at once.
349    pub fn limits(mut self, limits: Limits) -> Self {
350        self.limits = limits;
351        self
352    }
353
354    /// Maximum size of one message in bytes. Default: 4 MiB.
355    pub fn max_message_bytes(mut self, bytes: usize) -> Self {
356        self.limits.max_message_bytes = bytes;
357        self
358    }
359
360    /// Maximum JSON nesting depth. Default: 64 (at most 128).
361    pub fn max_json_depth(mut self, depth: usize) -> Self {
362        self.limits.max_json_depth = depth;
363        self
364    }
365
366    /// Maximum duration of one tool call, resource read or prompt. Default:
367    /// 30 s.
368    pub fn tool_timeout(mut self, timeout: Duration) -> Self {
369        self.limits.tool_timeout = timeout;
370        self
371    }
372
373    /// Maximum number of concurrent handler invocations (tools, resources and
374    /// prompts). Default: 64.
375    pub fn max_concurrent_calls(mut self, calls: usize) -> Self {
376        self.limits.max_concurrent_calls = calls;
377        self
378    }
379
380    /// Freshness hint (`ttlMs`) for `tools/list`, `resources/list`,
381    /// `resources/templates/list` and `prompts/list`. Default: 5 minutes.
382    pub fn list_ttl(mut self, ttl: Duration) -> Self {
383        self.list_ttl = ttl;
384        self
385    }
386
387    /// Freshness hint (`ttlMs`) for `server/discover`. Default: 1 hour.
388    pub fn discover_ttl(mut self, ttl: Duration) -> Self {
389        self.discover_ttl = ttl;
390        self
391    }
392
393    /// `cacheScope` of list and discovery results. Default: `public`. Use
394    /// `private` when results depend on the caller's authorization.
395    pub fn cache_scope(mut self, scope: CacheScope) -> Self {
396        self.cache_scope = scope;
397        self
398    }
399
400    /// Secret key that signs `requestState` (HMAC-SHA256), at least
401    /// [`MIN_REQUEST_STATE_KEY_BYTES`] bytes. Default: a random key per
402    /// process, which is enough for a single instance. Set the same key on
403    /// every instance when requests may be retried on another instance
404    /// (load balancing), and keep it secret.
405    pub fn request_state_key(mut self, key: impl Into<Vec<u8>>) -> Self {
406        self.request_state_key = Some(key.into());
407        self
408    }
409
410    /// How long a client may take to answer an input request (the lifetime
411    /// of `requestState`). Default: 10 minutes.
412    pub fn request_state_ttl(mut self, ttl: Duration) -> Self {
413        self.request_state_ttl = ttl;
414        self
415    }
416
417    /// Whether clients of the older protocol versions (2025-11-25 and
418    /// 2025-06-18, which open with `initialize`) are served. Default: `true`.
419    /// Handlers do not change: the SDK adapts the protocol, including
420    /// `ctx.elicit`, which becomes a request to the client.
421    pub fn legacy_clients(mut self, enabled: bool) -> Self {
422        self.legacy_clients = enabled;
423        self
424    }
425
426    /// Lifetime of the signed `MCP-Session-Id` given to legacy HTTP clients;
427    /// they reconnect with `initialize` after it. Default: 24 hours.
428    pub fn legacy_session_ttl(mut self, ttl: Duration) -> Self {
429        self.legacy_session_ttl = ttl;
430        self
431    }
432
433    /// Whether results carry `io.modelcontextprotocol/serverInfo` in `_meta`
434    /// (recommended by the specification). Default: `true`.
435    pub fn include_server_info(mut self, include: bool) -> Self {
436        self.include_server_info = include;
437        self
438    }
439
440    /// Validates the configuration and builds the server.
441    ///
442    /// # Errors
443    ///
444    /// Returns a [`BuildError`] for duplicate or invalid names and URIs,
445    /// missing state, invalid completion providers, invalid extensions,
446    /// out-of-range limits, or serialization failures of the pre-built
447    /// responses.
448    pub fn build(self) -> Result<Server, BuildError> {
449        if self.limits.max_message_bytes == 0 {
450            return Err(BuildError::InvalidLimit(
451                "max_message_bytes must be positive",
452            ));
453        }
454        if self.limits.max_json_depth == 0 {
455            return Err(BuildError::InvalidLimit("max_json_depth must be positive"));
456        }
457        if self.limits.max_concurrent_calls == 0 {
458            return Err(BuildError::InvalidLimit(
459                "max_concurrent_calls must be positive",
460            ));
461        }
462        if self.limits.tool_timeout.is_zero() {
463            return Err(BuildError::InvalidLimit("tool_timeout must be positive"));
464        }
465
466        let mut tools = self.tools;
467        // Deterministic order, as the specification recommends for caching.
468        tools.sort_by(|a, b| a.tool.name.cmp(&b.tool.name));
469        for pair in tools.windows(2) {
470            if let [a, b] = pair
471                && a.tool.name == b.tool.name
472            {
473                return Err(BuildError::DuplicateTool(a.tool.name.clone()));
474            }
475        }
476        for def in &mut tools {
477            if def.ui.is_some() || def.visibility.is_some() {
478                if def.visibility.as_ref().is_some_and(Vec::is_empty) {
479                    return Err(BuildError::InvalidApp {
480                        uri: def.tool.name.clone(),
481                        reason: "`visibility` must name `model`, `app` or both".to_owned(),
482                    });
483                }
484                crate::apps::tool_meta(
485                    &mut def.tool.meta,
486                    def.ui.as_deref(),
487                    def.visibility.as_deref(),
488                );
489            }
490        }
491        let mut mirrored = Vec::with_capacity(tools.len());
492        for def in &tools {
493            if !is_valid_tool_name(&def.tool.name) {
494                return Err(BuildError::InvalidToolName(def.tool.name.clone()));
495            }
496            check_state(&self.state, &def.required_state, &def.tool.name)?;
497            check_scopes(&def.scopes, &def.tool.name)?;
498            let params = mirror::collect(&def.tool.input_schema.extra).map_err(|reason| {
499                BuildError::InvalidHeaderAnnotation {
500                    tool: def.tool.name.clone(),
501                    reason,
502                }
503            })?;
504            mirrored.push(params);
505        }
506
507        let state_key = match self.request_state_key {
508            Some(key) if key.len() < MIN_REQUEST_STATE_KEY_BYTES => {
509                return Err(BuildError::WeakRequestStateKey);
510            }
511            Some(key) => key,
512            None => {
513                let mut key = vec![0; MIN_REQUEST_STATE_KEY_BYTES];
514                getrandom::fill(&mut key)
515                    .map_err(|e| BuildError::RandomUnavailable(e.to_string()))?;
516                key
517            }
518        };
519        let state_codec = StateCodec::new(state_key, self.request_state_ttl);
520
521        let resources = build_resources(self.resources, &self.state)?;
522        let prompts = build_prompts(self.prompts, &self.state)?;
523
524        // A tool's interface must be one of the registered app resources.
525        for def in &tools {
526            if let Some(uri) = &def.ui {
527                let found = resources
528                    .statics
529                    .iter()
530                    .any(|r| r.uri == *uri && r.app.is_some());
531                if !found {
532                    return Err(BuildError::InvalidApp {
533                        uri: uri.clone(),
534                        reason: format!(
535                            "tool `{}` shows it, but no resource with this URI is marked as an \
536                             app",
537                            def.tool.name
538                        ),
539                    });
540                }
541            }
542        }
543        let uses_apps = tools
544            .iter()
545            .any(|t| t.ui.is_some() || t.visibility.is_some())
546            || resources.statics.iter().any(|r| r.app.is_some());
547
548        let mut extensions = BTreeMap::new();
549        for ext in &self.extensions {
550            let id = ext.id();
551            if !extension::is_valid_id(id) || extensions.contains_key(id) {
552                return Err(BuildError::InvalidExtension(id.to_owned()));
553            }
554            extensions.insert(id.to_owned(), ext.settings());
555        }
556        if uses_apps {
557            // No server settings are defined for MCP Apps.
558            extensions
559                .entry(crate::apps::EXTENSION_ID.to_owned())
560                .or_insert_with(Map::new);
561        }
562
563        let result_meta = self.include_server_info.then(|| spec::ResultMetaObject {
564            server_info: Some(self.info.clone()),
565            extra: Map::new(),
566        });
567
568        let has_completions = resources.templates.iter().any(|t| !t.completers.is_empty())
569            || prompts.registry.iter().any(|p| !p.completers.is_empty());
570        let has_resources = !resources.statics.is_empty() || !resources.templates.is_empty();
571        let has_prompts = !prompts.registry.is_empty();
572        #[allow(deprecated)] // `logging`: deprecated (SEP-2577) but still specified.
573        let capabilities = spec::ServerCapabilities {
574            tools: Some(spec::ServerCapabilitiesTools { list_changed: None }),
575            resources: has_resources.then_some(spec::ServerCapabilitiesResources {
576                list_changed: None,
577                subscribe: None,
578            }),
579            prompts: has_prompts.then_some(spec::ServerCapabilitiesPrompts { list_changed: None }),
580            completions: has_completions.then(Map::new),
581            // Log messages are sent only to clients that ask for them.
582            logging: Some(Map::new()),
583            extensions: (!extensions.is_empty()).then_some(extensions),
584            ..spec::ServerCapabilities::default()
585        };
586
587        let supported_versions = vec![spec::PROTOCOL_VERSION.to_owned()];
588
589        let legacy = LegacyConfig::new(
590            self.legacy_clients,
591            self.legacy_session_ttl,
592            &capabilities,
593            &self.info,
594            self.instructions.as_deref(),
595        );
596
597        let discover = spec::DiscoverResult {
598            meta: result_meta.clone(),
599            cache_scope: self.cache_scope,
600            capabilities,
601            instructions: self.instructions,
602            result_type: COMPLETE.to_owned(),
603            supported_versions: supported_versions.clone(),
604            ttl_ms: millis(self.discover_ttl),
605        };
606        let discover =
607            to_raw(&discover).map_err(|e| BuildError::Serialize("server/discover", e))?;
608
609        let list = spec::ListToolsResult {
610            meta: result_meta.clone(),
611            cache_scope: self.cache_scope,
612            next_cursor: None,
613            result_type: COMPLETE.to_owned(),
614            tools: tools.iter().map(|t| t.tool.clone()).collect(),
615            ttl_ms: millis(self.list_ttl),
616        };
617        let tools_list = to_raw(&list).map_err(|e| BuildError::Serialize("tools/list", e))?;
618        let model_visible = |t: &&ToolDef| {
619            t.visibility
620                .as_ref()
621                .is_none_or(|v| v.contains(&crate::apps::Visibility::Model))
622        };
623        let tools_list_for_models = if tools.iter().all(|t| model_visible(&t)) {
624            None
625        } else {
626            let list = spec::ListToolsResult {
627                tools: tools
628                    .iter()
629                    .filter(model_visible)
630                    .map(|t| t.tool.clone())
631                    .collect(),
632                ..list
633            };
634            Some(to_raw(&list).map_err(|e| BuildError::Serialize("tools/list", e))?)
635        };
636        let list_ttl_ms = millis(self.list_ttl);
637
638        let resources_list = if has_resources {
639            Some(
640                to_raw(&spec::ListResourcesResult {
641                    meta: result_meta.clone(),
642                    cache_scope: self.cache_scope,
643                    next_cursor: None,
644                    resources: resources.listed,
645                    result_type: COMPLETE.to_owned(),
646                    ttl_ms: list_ttl_ms,
647                })
648                .map_err(|e| BuildError::Serialize("resources/list", e))?,
649            )
650        } else {
651            None
652        };
653        let templates_list = if has_resources {
654            Some(
655                to_raw(&spec::ListResourceTemplatesResult {
656                    meta: result_meta.clone(),
657                    cache_scope: self.cache_scope,
658                    next_cursor: None,
659                    resource_templates: resources.listed_templates,
660                    result_type: COMPLETE.to_owned(),
661                    ttl_ms: list_ttl_ms,
662                })
663                .map_err(|e| BuildError::Serialize("resources/templates/list", e))?,
664            )
665        } else {
666            None
667        };
668        let prompts_list = if has_prompts {
669            Some(
670                to_raw(&spec::ListPromptsResult {
671                    meta: result_meta.clone(),
672                    cache_scope: self.cache_scope,
673                    next_cursor: None,
674                    prompts: prompts.listed,
675                    result_type: COMPLETE.to_owned(),
676                    ttl_ms: list_ttl_ms,
677                })
678                .map_err(|e| BuildError::Serialize("prompts/list", e))?,
679            )
680        } else {
681            None
682        };
683
684        let registry = tools
685            .into_iter()
686            .zip(mirrored)
687            .map(|(t, mirrored)| RegisteredTool {
688                name: t.tool.name,
689                call: t.call,
690                mirrored,
691                scopes: t.scopes.into(),
692                app_only: t
693                    .visibility
694                    .as_ref()
695                    .is_some_and(|v| !v.contains(&crate::apps::Visibility::Model)),
696            })
697            .collect::<Box<[RegisteredTool]>>();
698        let has_scopes = registry.iter().any(|t| !t.scopes.is_empty())
699            || resources.statics.iter().any(|r| !r.scopes.is_empty())
700            || resources.templates.iter().any(|t| !t.scopes.is_empty())
701            || prompts.registry.iter().any(|p| !p.scopes.is_empty());
702
703        Ok(Server {
704            inner: Arc::new(Inner {
705                tools: registry,
706                has_scopes,
707                resources: resources.statics,
708                templates: resources.templates,
709                prompts: prompts.registry,
710                has_completions,
711                state_codec,
712                legacy,
713                legacy_hint: Once::new(),
714                state_hint: Once::new(),
715                discover,
716                tools_list,
717                tools_list_for_models,
718                resources_list,
719                templates_list,
720                prompts_list,
721                result_meta,
722                supported_versions,
723                state: Arc::new(self.state),
724                permits: Arc::new(Semaphore::new(self.limits.max_concurrent_calls)),
725                limits: self.limits,
726            }),
727        })
728    }
729}
730
731/// A built MCP server. Cheap to clone; all clones share one registry.
732///
733/// `Server` implements [`tower_service::Service<McpRequest>`], so tower
734/// middleware (timeouts, rate limits, tracing) can wrap it.
735#[derive(Clone)]
736pub struct Server {
737    inner: Arc<Inner>,
738}
739
740struct Inner {
741    /// Sorted by name.
742    tools: Box<[RegisteredTool]>,
743    /// Sorted by URI.
744    resources: Box<[RegisteredResource]>,
745    /// Sorted by name, then URI template; the first match wins.
746    templates: Box<[RegisteredTemplate]>,
747    /// Sorted by name.
748    prompts: Box<[RegisteredPrompt]>,
749    has_completions: bool,
750    /// Whether any handler declares OAuth scopes.
751    has_scopes: bool,
752    state_codec: StateCodec,
753    legacy: LegacyConfig,
754    legacy_hint: Once,
755    state_hint: Once,
756    discover: Box<RawValue>,
757    tools_list: Box<RawValue>,
758    /// `tools/list` without app-only tools, for clients without MCP Apps
759    /// (they would show those tools to the model). `None` if there are none.
760    tools_list_for_models: Option<Box<RawValue>>,
761    resources_list: Option<Box<RawValue>>,
762    templates_list: Option<Box<RawValue>>,
763    prompts_list: Option<Box<RawValue>>,
764    result_meta: Option<spec::ResultMetaObject>,
765    supported_versions: Vec<String>,
766    state: Arc<TypeMap>,
767    permits: Arc<Semaphore>,
768    limits: Limits,
769}
770
771struct RegisteredTool {
772    name: String,
773    call: CallFn,
774    mirrored: Vec<MirroredParam>,
775    scopes: Box<[String]>,
776    /// MCP Apps `visibility` without `model`: only an interface calls it.
777    app_only: bool,
778}
779
780struct RegisteredResource {
781    uri: String,
782    read: ReadFn,
783    ttl_ms: u64,
784    cache_scope: CacheScope,
785    scopes: Box<[String]>,
786    /// `_meta.ui` of an MCP Apps interface, repeated in read results.
787    app: Option<Value>,
788}
789
790struct RegisteredTemplate {
791    template: UriTemplate,
792    read: ReadFn,
793    ttl_ms: u64,
794    cache_scope: CacheScope,
795    completers: Vec<Completer>,
796    scopes: Box<[String]>,
797}
798
799struct RegisteredPrompt {
800    name: String,
801    description: Option<String>,
802    get: GetFn,
803    completers: Vec<Completer>,
804    scopes: Box<[String]>,
805}
806
807struct BuiltResources {
808    statics: Box<[RegisteredResource]>,
809    templates: Box<[RegisteredTemplate]>,
810    listed: Vec<spec::Resource>,
811    listed_templates: Vec<spec::ResourceTemplate>,
812}
813
814struct BuiltPrompts {
815    registry: Box<[RegisteredPrompt]>,
816    listed: Vec<spec::Prompt>,
817}
818
819fn check_state(
820    state: &TypeMap,
821    required: &[StateRequirement],
822    handler: &str,
823) -> Result<(), BuildError> {
824    match required.iter().find(|r| !state.contains(r.type_id)) {
825        Some(missing) => Err(BuildError::MissingState {
826            handler: handler.to_owned(),
827            type_name: missing.type_name,
828        }),
829        None => Ok(()),
830    }
831}
832
833fn check_scopes(scopes: &[String], handler: &str) -> Result<(), BuildError> {
834    match scopes.iter().find(|s| !crate::auth::is_valid_scope(s)) {
835        Some(scope) => Err(BuildError::InvalidScope {
836            handler: handler.to_owned(),
837            scope: scope.clone(),
838        }),
839        None => Ok(()),
840    }
841}
842
843fn check_completers(
844    completers: &[Completer],
845    owner: &str,
846    arguments: &[&str],
847) -> Result<(), BuildError> {
848    match completers
849        .iter()
850        .find(|c| !arguments.contains(&c.argument.as_str()))
851    {
852        Some(unknown) => Err(BuildError::InvalidCompletion {
853            owner: owner.to_owned(),
854            argument: unknown.argument.clone(),
855        }),
856        None => Ok(()),
857    }
858}
859
860fn build_resources(defs: Vec<ResourceDef>, state: &TypeMap) -> Result<BuiltResources, BuildError> {
861    let mut statics = Vec::new();
862    let mut templates = Vec::new();
863    let mut listed = Vec::new();
864    let mut listed_templates = Vec::new();
865    for def in defs {
866        check_state(state, &def.required_state, def.uri())?;
867        check_scopes(&def.scopes, def.uri())?;
868        let ttl_ms = millis(def.ttl);
869        match def.kind {
870            ResourceKind::Static(resource) => {
871                if !uri_template::is_absolute_uri(&resource.uri) {
872                    return Err(BuildError::InvalidResource {
873                        uri: resource.uri,
874                        reason: "expected an absolute URI such as `scheme://path`".to_owned(),
875                    });
876                }
877                check_completers(&def.completers, &resource.uri, &[])?;
878                let app = match &def.app {
879                    None => None,
880                    Some(ui) => {
881                        let invalid = |reason: String| BuildError::InvalidApp {
882                            uri: resource.uri.clone(),
883                            reason,
884                        };
885                        if !resource.uri.starts_with(crate::apps::URI_SCHEME) {
886                            return Err(invalid("the URI must use the `ui://` scheme".to_owned()));
887                        }
888                        ui.validate().map_err(invalid)?;
889                        Some(ui.to_meta())
890                    }
891                };
892                statics.push(RegisteredResource {
893                    uri: resource.uri.clone(),
894                    read: def.read,
895                    ttl_ms,
896                    cache_scope: def.cache_scope,
897                    scopes: def.scopes.into(),
898                    app,
899                });
900                listed.push(resource);
901            }
902            ResourceKind::Template(template) => {
903                if def.app.is_some() {
904                    return Err(BuildError::InvalidApp {
905                        uri: template.uri_template,
906                        reason: "an interface is a fixed `ui://` resource, not a template"
907                            .to_owned(),
908                    });
909                }
910                let parsed = UriTemplate::parse(&template.uri_template).map_err(|e| {
911                    BuildError::InvalidResource {
912                        uri: template.uri_template.clone(),
913                        reason: e.to_string(),
914                    }
915                })?;
916                let variables: Vec<&str> = parsed.variables().collect();
917                check_completers(&def.completers, &template.uri_template, &variables)?;
918                templates.push((
919                    template.name.clone(),
920                    RegisteredTemplate {
921                        template: parsed,
922                        read: def.read,
923                        ttl_ms,
924                        cache_scope: def.cache_scope,
925                        completers: def.completers,
926                        scopes: def.scopes.into(),
927                    },
928                ));
929                listed_templates.push(template);
930            }
931        }
932    }
933    statics.sort_by(|a, b| a.uri.cmp(&b.uri));
934    if let Some(pair) = statics
935        .windows(2)
936        .find(|w| matches!(w, [a, b] if a.uri == b.uri))
937        && let Some(first) = pair.first()
938    {
939        return Err(BuildError::DuplicateResource(first.uri.clone()));
940    }
941    listed.sort_by(|a, b| a.uri.cmp(&b.uri));
942    templates.sort_by(|(na, a), (nb, b)| {
943        na.cmp(nb)
944            .then_with(|| a.template.as_str().cmp(b.template.as_str()))
945    });
946    for (i, (_, t)) in templates.iter().enumerate() {
947        if templates
948            .iter()
949            .skip(i + 1)
950            .any(|(_, other)| other.template.as_str() == t.template.as_str())
951        {
952            return Err(BuildError::DuplicateResource(
953                t.template.as_str().to_owned(),
954            ));
955        }
956    }
957    listed_templates.sort_by(|a, b| {
958        a.name
959            .cmp(&b.name)
960            .then_with(|| a.uri_template.cmp(&b.uri_template))
961    });
962    Ok(BuiltResources {
963        statics: statics.into(),
964        templates: templates.into_iter().map(|(_, t)| t).collect(),
965        listed,
966        listed_templates,
967    })
968}
969
970fn build_prompts(mut defs: Vec<PromptDef>, state: &TypeMap) -> Result<BuiltPrompts, BuildError> {
971    defs.sort_by(|a, b| a.prompt.name.cmp(&b.prompt.name));
972    if let Some(pair) = defs
973        .windows(2)
974        .find(|w| matches!(w, [a, b] if a.prompt.name == b.prompt.name))
975        && let Some(first) = pair.first()
976    {
977        return Err(BuildError::DuplicatePrompt(first.prompt.name.clone()));
978    }
979    let mut registry = Vec::with_capacity(defs.len());
980    let mut listed = Vec::with_capacity(defs.len());
981    for def in defs {
982        let name = &def.prompt.name;
983        if name.is_empty() || name.chars().any(char::is_control) {
984            return Err(BuildError::InvalidPromptName(name.clone()));
985        }
986        check_state(state, &def.required_state, name)?;
987        check_scopes(&def.scopes, name)?;
988        let arguments: Vec<&str> = def
989            .prompt
990            .arguments
991            .iter()
992            .flatten()
993            .map(|a| a.name.as_str())
994            .collect();
995        check_completers(&def.completers, name, &arguments)?;
996        registry.push(RegisteredPrompt {
997            name: name.clone(),
998            description: def.prompt.description.clone(),
999            get: def.get,
1000            completers: def.completers,
1001            scopes: def.scopes.into(),
1002        });
1003        listed.push(def.prompt);
1004    }
1005    Ok(BuiltPrompts {
1006        registry: registry.into(),
1007        listed,
1008    })
1009}
1010
1011impl std::fmt::Debug for Server {
1012    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1013        f.debug_struct("Server")
1014            .field(
1015                "tools",
1016                &self.inner.tools.iter().map(|t| &t.name).collect::<Vec<_>>(),
1017            )
1018            .field(
1019                "resources",
1020                &self
1021                    .inner
1022                    .resources
1023                    .iter()
1024                    .map(|r| &r.uri)
1025                    .collect::<Vec<_>>(),
1026            )
1027            .field(
1028                "prompts",
1029                &self
1030                    .inner
1031                    .prompts
1032                    .iter()
1033                    .map(|p| &p.name)
1034                    .collect::<Vec<_>>(),
1035            )
1036            .field("limits", &self.inner.limits)
1037            .finish_non_exhaustive()
1038    }
1039}
1040
1041/// A parsed message plus transport-provided context.
1042#[derive(Debug)]
1043pub struct McpRequest {
1044    /// The message.
1045    pub message: Message,
1046    /// Values attached by the transport and middleware.
1047    pub transport: TransportContext,
1048    /// Cancelled when the client cancels the request.
1049    pub cancellation: CancellationToken,
1050}
1051
1052impl McpRequest {
1053    /// Wraps a message with an empty transport context.
1054    #[must_use]
1055    pub fn new(message: Message) -> Self {
1056        Self {
1057            message,
1058            transport: TransportContext::default(),
1059            cancellation: CancellationToken::new(),
1060        }
1061    }
1062}
1063
1064impl Server {
1065    /// Starts building a server.
1066    pub fn builder(name: impl Into<String>, version: impl Into<String>) -> ServerBuilder {
1067        ServerBuilder::new(name, version)
1068    }
1069
1070    /// The configured limits.
1071    #[must_use]
1072    pub fn limits(&self) -> &Limits {
1073        &self.inner.limits
1074    }
1075
1076    /// Parameters of `tool` mirrored into `Mcp-Param-*` HTTP headers, or
1077    /// `None` if the tool does not exist.
1078    #[must_use]
1079    pub fn mirrored_params(&self, tool: &str) -> Option<&[MirroredParam]> {
1080        self.find_tool(tool).map(|t| t.mirrored.as_slice())
1081    }
1082
1083    fn find_tool(&self, name: &str) -> Option<&RegisteredTool> {
1084        self.inner
1085            .tools
1086            .binary_search_by(|t| t.name.as_str().cmp(name))
1087            .ok()
1088            .and_then(|i| self.inner.tools.get(i))
1089    }
1090
1091    /// Whether any handler declares OAuth scopes.
1092    #[cfg_attr(not(feature = "http"), allow(dead_code))]
1093    pub(crate) fn has_scopes(&self) -> bool {
1094        self.inner.has_scopes
1095    }
1096
1097    pub(crate) fn legacy(&self) -> &LegacyConfig {
1098        &self.inner.legacy
1099    }
1100
1101    pub(crate) fn state_codec(&self) -> &StateCodec {
1102        &self.inner.state_codec
1103    }
1104
1105    /// The protocol versions this server accepts.
1106    #[must_use]
1107    pub fn supported_versions(&self) -> &[String] {
1108        &self.inner.supported_versions
1109    }
1110
1111    /// Parses raw bytes and handles the message. Returns the response to
1112    /// send, or `None` for notifications, cancelled requests and stray
1113    /// responses.
1114    pub async fn handle_bytes(
1115        &self,
1116        bytes: &[u8],
1117        transport: TransportContext,
1118        cancellation: CancellationToken,
1119    ) -> Option<Response> {
1120        match jsonrpc::parse(bytes, &self.inner.limits) {
1121            Ok(message) => {
1122                self.handle(McpRequest {
1123                    message,
1124                    transport,
1125                    cancellation,
1126                })
1127                .await
1128            }
1129            Err(Rejection::Error { id, error }) => Some(Response::error(id, error)),
1130            Err(Rejection::UnexpectedResponse) => {
1131                tracing::debug!("ignoring a JSON-RPC response sent by the client");
1132                None
1133            }
1134        }
1135    }
1136
1137    /// Handles a parsed message.
1138    pub async fn handle(&self, request: McpRequest) -> Option<Response> {
1139        let McpRequest {
1140            message,
1141            transport,
1142            cancellation,
1143        } = request;
1144        match message {
1145            // Client notifications have no effect on a stateless server;
1146            // cancellation is handled by the transports.
1147            Message::Notification(_) => None,
1148            Message::Request(req) => {
1149                let id = req.id.clone();
1150                // Handler futures are already isolated; this also covers
1151                // synchronous user code (argument parsers, completion
1152                // providers) and any bug in the dispatch path.
1153                let dispatched =
1154                    std::panic::AssertUnwindSafe(self.dispatch(req, transport, cancellation))
1155                        .catch_unwind()
1156                        .await;
1157                match dispatched {
1158                    Ok(Ok(Some(result))) => Some(Response::Result { id, result }),
1159                    Ok(Ok(None)) => None,
1160                    Ok(Err(error)) => Some(Response::error(Some(id), error)),
1161                    Err(_panic) => {
1162                        tracing::error!("request handling panicked");
1163                        Some(Response::error(Some(id), McpError::internal()))
1164                    }
1165                }
1166            }
1167        }
1168    }
1169
1170    async fn dispatch(
1171        &self,
1172        req: Request,
1173        transport: TransportContext,
1174        cancellation: CancellationToken,
1175    ) -> Result<Option<Box<RawValue>>, McpError> {
1176        let params = req.params.as_deref();
1177        match req.method.as_str() {
1178            methods::SERVER_DISCOVER => {
1179                let p: MetaOnly<'_> = parse_params(params)?;
1180                self.validate_meta(p.meta, &transport)?;
1181                Ok(Some(self.inner.discover.clone()))
1182            }
1183            methods::TOOLS_LIST => self.list_tools(params, &transport),
1184            methods::TOOLS_CALL => {
1185                let p: CallParams<'_> = parse_params(params)?;
1186                let meta = self.validate_meta(p.meta, &transport)?;
1187                let auth = transport.get::<AuthContext>();
1188                let key = RoundKey {
1189                    method: methods::TOOLS_CALL,
1190                    target: &p.name,
1191                    arguments: p.arguments.map_or(Args::None, |a| Args::Raw(a.get())),
1192                    principal: auth.as_deref().map(|a| a.digest.as_str()),
1193                };
1194                let round = self.round(&key, p.input_responses, p.request_state.as_deref())?;
1195                let ctx = self.ctx(req.id, meta, &cancellation, transport, &round);
1196                self.call_tool(&p.name, p.arguments, ctx, &cancellation)
1197                    .await
1198                    .or_else(|e| self.input_required(e, &round, &key))
1199            }
1200            methods::RESOURCES_LIST => self.list(
1201                &req.method,
1202                params,
1203                self.inner.resources_list.as_deref(),
1204                &transport,
1205            ),
1206            methods::RESOURCES_TEMPLATES_LIST => self.list(
1207                &req.method,
1208                params,
1209                self.inner.templates_list.as_deref(),
1210                &transport,
1211            ),
1212            methods::RESOURCES_READ if self.inner.resources_list.is_some() => {
1213                let p: ReadParams<'_> = parse_params(params)?;
1214                let meta = self.validate_meta(p.meta, &transport)?;
1215                let auth = transport.get::<AuthContext>();
1216                let key = RoundKey {
1217                    method: methods::RESOURCES_READ,
1218                    target: &p.uri,
1219                    arguments: Args::None,
1220                    principal: auth.as_deref().map(|a| a.digest.as_str()),
1221                };
1222                let round = self.round(&key, p.input_responses, p.request_state.as_deref())?;
1223                let ctx = self.ctx(req.id, meta, &cancellation, transport, &round);
1224                self.read_resource(&p.uri, ctx, &cancellation)
1225                    .await
1226                    .or_else(|e| self.input_required(e, &round, &key))
1227            }
1228            methods::PROMPTS_LIST => self.list(
1229                &req.method,
1230                params,
1231                self.inner.prompts_list.as_deref(),
1232                &transport,
1233            ),
1234            methods::PROMPTS_GET if self.inner.prompts_list.is_some() => {
1235                let p: GetPromptParams<'_> = parse_params(params)?;
1236                let meta = self.validate_meta(p.meta, &transport)?;
1237                let arguments = PromptArguments::new(p.arguments.unwrap_or_default());
1238                let auth = transport.get::<AuthContext>();
1239                let key = RoundKey {
1240                    method: methods::PROMPTS_GET,
1241                    target: &p.name,
1242                    arguments: Args::Map(arguments.as_map()),
1243                    principal: auth.as_deref().map(|a| a.digest.as_str()),
1244                };
1245                let round = self.round(&key, p.input_responses, p.request_state.as_deref())?;
1246                let ctx = self.ctx(req.id, meta, &cancellation, transport, &round);
1247                self.get_prompt(&p.name, &arguments, ctx, &cancellation)
1248                    .await
1249                    .or_else(|e| self.input_required(e, &round, &key))
1250            }
1251            methods::COMPLETION_COMPLETE if self.inner.has_completions => {
1252                let mut p: CompleteParams<'_> = parse_params(params)?;
1253                self.validate_meta(p.meta.take(), &transport)?;
1254                self.complete(p).map(Some)
1255            }
1256            "initialize" if self.inner.legacy.enabled => self
1257                .legacy_initialize(params)
1258                .map(|(result, _)| Some(result)),
1259            "initialize" => {
1260                hints::warn_once(
1261                    &self.inner.legacy_hint,
1262                    format_args!(
1263                        "a client tried to connect with `initialize` (MCP 2025-11-25 or older), \
1264                         but this server only accepts {}",
1265                        self.inner.supported_versions.join(", ")
1266                    ),
1267                    "legacy clients are disabled on this server; enable them with \
1268                     ServerBuilder::legacy_clients(true), or use a client that supports MCP \
1269                     2026-07-28",
1270                );
1271                Err(McpError::method_not_found_explained(format!(
1272                    "This server implements the stateless MCP protocol (versions: {}); \
1273                     `initialize` is not supported",
1274                    self.inner.supported_versions.join(", ")
1275                )))
1276            }
1277            other => Err(McpError::method_not_found(other)),
1278        }
1279    }
1280
1281    /// Answers a list request from its pre-serialized body. `None` means the
1282    /// server does not offer this kind of item.
1283    fn list(
1284        &self,
1285        method: &str,
1286        params: Option<&RawValue>,
1287        body: Option<&RawValue>,
1288        transport: &TransportContext,
1289    ) -> Result<Option<Box<RawValue>>, McpError> {
1290        let Some(body) = body else {
1291            return Err(McpError::method_not_found(method));
1292        };
1293        let p: ListParams<'_> = parse_params(params)?;
1294        self.validate_meta(p.meta, transport)?;
1295        if p.cursor.is_some() {
1296            // Every list fits in one page, so no cursor is valid.
1297            return Err(McpError::invalid_params("Invalid cursor"));
1298        }
1299        Ok(Some(body.to_owned()))
1300    }
1301
1302    /// `tools/list`: clients without MCP Apps do not get app-only tools,
1303    /// which they would otherwise offer to the model.
1304    fn list_tools(
1305        &self,
1306        params: Option<&RawValue>,
1307        transport: &TransportContext,
1308    ) -> Result<Option<Box<RawValue>>, McpError> {
1309        let p: ListParams<'_> = parse_params(params)?;
1310        let meta = self.validate_meta(p.meta, transport)?;
1311        if p.cursor.is_some() {
1312            return Err(McpError::invalid_params("Invalid cursor"));
1313        }
1314        let body = match &self.inner.tools_list_for_models {
1315            Some(for_models) if !crate::apps::supported_by(&meta.client_capabilities) => for_models,
1316            _ => &self.inner.tools_list,
1317        };
1318        Ok(Some(body.to_owned()))
1319    }
1320
1321    #[allow(deprecated)] // `log_level`: deprecated (SEP-2577) but still specified.
1322    fn validate_meta(
1323        &self,
1324        meta: Option<&RawValue>,
1325        transport: &TransportContext,
1326    ) -> Result<RequestMeta, McpError> {
1327        // Legacy clients declared everything at `initialize`.
1328        if let Some(session) = transport.get::<LegacySession>() {
1329            let mut request_meta = session.request_meta();
1330            // Legacy `_meta` may carry a progress token.
1331            request_meta.progress_token = meta
1332                .and_then(|raw| serde_json::from_str::<LegacyMeta>(raw.get()).ok())
1333                .and_then(|m| m.progress_token);
1334            return Ok(request_meta);
1335        }
1336        let missing = || {
1337            McpError::invalid_params(
1338                "Missing `_meta` with `io.modelcontextprotocol/protocolVersion` and \
1339                 `io.modelcontextprotocol/clientCapabilities`",
1340            )
1341        };
1342        let meta: spec::RequestMetaObject = serde_json::from_str(meta.ok_or_else(missing)?.get())
1343            .map_err(|e| {
1344            McpError::invalid_params(format!(
1345                "Invalid `_meta`: {}",
1346                crate::error::sanitize(&e.to_string())
1347            ))
1348        })?;
1349        if !self
1350            .inner
1351            .supported_versions
1352            .contains(&meta.protocol_version)
1353        {
1354            return Err(McpError::unsupported_protocol_version(
1355                &meta.protocol_version,
1356                &self.inner.supported_versions,
1357            ));
1358        }
1359        Ok(RequestMeta {
1360            protocol_version: meta.protocol_version,
1361            client_capabilities: meta.client_capabilities,
1362            client_info: meta.client_info,
1363            progress_token: meta.progress_token,
1364            log_level: meta.log_level,
1365        })
1366    }
1367
1368    fn ctx(
1369        &self,
1370        id: RequestId,
1371        meta: RequestMeta,
1372        cancellation: &CancellationToken,
1373        transport: TransportContext,
1374        round: &Arc<Round>,
1375    ) -> Ctx {
1376        // Only when the client asked for notifications and the transport
1377        // can deliver them.
1378        let notifier = transport
1379            .get::<NotifyChannel>()
1380            .and_then(|channel| {
1381                Notifier::new(&channel, meta.progress_token.clone(), meta.log_level)
1382            })
1383            .map(Arc::new);
1384        Ctx::new(
1385            id,
1386            meta,
1387            cancellation.clone(),
1388            transport,
1389            Arc::clone(&self.inner.state),
1390            Some(Arc::clone(round)),
1391            notifier,
1392        )
1393    }
1394
1395    /// Starts an MRTR round: verifies `requestState` and collects answers.
1396    fn round(
1397        &self,
1398        key: &RoundKey<'_>,
1399        input_responses: Option<Value>,
1400        request_state: Option<&str>,
1401    ) -> Result<Arc<Round>, McpError> {
1402        let has_state = request_state.is_some();
1403        Round::start(&self.inner.state_codec, key, input_responses, request_state)
1404            .map(Arc::new)
1405            .inspect_err(|_| {
1406                if has_state {
1407                    hints::warn_once(
1408                        &self.inner.state_hint,
1409                        format_args!("rejected a requestState that failed verification"),
1410                        "if this server runs as several instances, give all of them the same \
1411                         key with ServerBuilder::request_state_key(..); otherwise the client \
1412                         sent an expired or modified state (or retried another request)",
1413                    );
1414                }
1415            })
1416    }
1417
1418    /// Turns the "input required" signal into an `InputRequiredResult`;
1419    /// other errors pass through.
1420    fn input_required(
1421        &self,
1422        error: HandlerError,
1423        round: &Round,
1424        key: &RoundKey<'_>,
1425    ) -> Result<Option<Box<RawValue>>, McpError> {
1426        match error {
1427            HandlerError::InputRequired => {
1428                let result = round.input_required(
1429                    &self.inner.state_codec,
1430                    key,
1431                    self.inner.result_meta.as_ref(),
1432                )?;
1433                to_raw(&result).map(Some).map_err(|_| McpError::internal())
1434            }
1435            HandlerError::Rpc(error) => Err(error),
1436        }
1437    }
1438
1439    /// Runs a handler future under the concurrency limit, the timeout and
1440    /// panic isolation. `Ok(None)` means the request was cancelled.
1441    async fn run<T>(
1442        &self,
1443        future: Pin<Box<dyn Future<Output = Result<T, ToolError>> + Send>>,
1444        cancellation: &CancellationToken,
1445    ) -> Result<Option<Result<T, ToolError>>, McpError> {
1446        // Bound concurrency; the semaphore is never closed.
1447        let Ok(_permit) = self.inner.permits.acquire().await else {
1448            return Err(McpError::internal());
1449        };
1450        let guarded = std::panic::AssertUnwindSafe(future).catch_unwind();
1451        tokio::select! {
1452            biased;
1453            () = cancellation.cancelled() => Ok(None),
1454            timed = tokio::time::timeout(self.inner.limits.tool_timeout, guarded) => {
1455                Ok(Some(match timed {
1456                    Ok(Ok(result)) => result,
1457                    Ok(Err(_panic)) => Err(ToolError::panicked()),
1458                    Err(_elapsed) => Err(ToolError::timeout()),
1459                }))
1460            }
1461        }
1462    }
1463
1464    fn with_server_info(&self, meta: &mut Option<spec::ResultMetaObject>) {
1465        if let Some(server_meta) = &self.inner.result_meta {
1466            match meta {
1467                Some(meta) => meta.server_info.clone_from(&server_meta.server_info),
1468                None => *meta = Some(server_meta.clone()),
1469            }
1470        }
1471    }
1472
1473    async fn call_tool(
1474        &self,
1475        name: &str,
1476        arguments: Option<&RawValue>,
1477        ctx: Ctx,
1478        cancellation: &CancellationToken,
1479    ) -> Result<Option<Box<RawValue>>, HandlerError> {
1480        let tool = self
1481            .find_tool(name)
1482            .filter(|t| !t.app_only || ctx.client_supports_apps())
1483            .ok_or_else(|| McpError::unknown_tool(name))?;
1484        authorize(&tool.scopes, &ctx)?;
1485
1486        let notifier = ctx.notifier_handle();
1487        let outcome = match (tool.call)(arguments, ctx) {
1488            Err(error) => Err(error),
1489            Ok(future) => match self.run(future, cancellation).await? {
1490                None => return Ok(None),
1491                Some(outcome) => outcome,
1492            },
1493        };
1494        if let Some(notifier) = notifier {
1495            notifier.finish().await;
1496        }
1497
1498        let mut result = match outcome {
1499            Ok(ToolOutput::Complete(result)) => result,
1500            Err(error) if error.is_input_required() => return Err(HandlerError::InputRequired),
1501            Err(error) => match error.into_protocol() {
1502                Ok(protocol_error) => return Err(protocol_error.into()),
1503                Err(error) => error.into_call_result(&tool.name),
1504            },
1505        };
1506        result.result_type = COMPLETE.to_owned();
1507        self.with_server_info(&mut result.meta);
1508        Ok(to_raw(&result).map(Some).map_err(|error| {
1509            tracing::error!(tool = %tool.name, %error, "failed to serialize tool result");
1510            McpError::internal()
1511        })?)
1512    }
1513
1514    /// Finds the handler for a URI: an exact static resource first, then the
1515    /// first matching template.
1516    #[allow(clippy::type_complexity)]
1517    fn find_resource(
1518        &self,
1519        uri: &str,
1520    ) -> Option<(ReadFn, UriVars, u64, CacheScope, &[String], Option<&Value>)> {
1521        if let Some(r) = self
1522            .inner
1523            .resources
1524            .binary_search_by(|r| r.uri.as_str().cmp(uri))
1525            .ok()
1526            .and_then(|i| self.inner.resources.get(i))
1527        {
1528            return Some((
1529                r.read,
1530                UriVars::default(),
1531                r.ttl_ms,
1532                r.cache_scope,
1533                &r.scopes,
1534                r.app.as_ref(),
1535            ));
1536        }
1537        self.inner.templates.iter().find_map(|t| {
1538            t.template
1539                .matches(uri)
1540                .map(|vars| (t.read, vars, t.ttl_ms, t.cache_scope, &*t.scopes, None))
1541        })
1542    }
1543
1544    async fn read_resource(
1545        &self,
1546        uri: &str,
1547        ctx: Ctx,
1548        cancellation: &CancellationToken,
1549    ) -> Result<Option<Box<RawValue>>, HandlerError> {
1550        let (read, vars, ttl_ms, cache_scope, scopes, app) = self
1551            .find_resource(uri)
1552            .ok_or_else(|| McpError::resource_not_found(uri))?;
1553        authorize(scopes, &ctx)?;
1554        let notifier = ctx.notifier_handle();
1555        let outcome = match read(uri, &vars, ctx) {
1556            Err(error) => Err(error),
1557            Ok(future) => match self.run(future, cancellation).await? {
1558                None => return Ok(None),
1559                Some(outcome) => outcome,
1560            },
1561        };
1562        if let Some(notifier) = notifier {
1563            notifier.finish().await;
1564        }
1565        let mut contents =
1566            outcome.map_err(|e| HandlerError::from_tool(e, "resource", Some(uri)))?;
1567        if let Some(ui) = app {
1568            crate::apps::mark_contents(&mut contents, ui);
1569        }
1570        let mut result =
1571            spec::ReadResourceResult::new(cache_scope, contents, COMPLETE.to_owned(), ttl_ms);
1572        self.with_server_info(&mut result.meta);
1573        Ok(to_raw(&result).map(Some).map_err(|error| {
1574            tracing::error!(uri, %error, "failed to serialize resource contents");
1575            McpError::internal()
1576        })?)
1577    }
1578
1579    fn find_prompt(&self, name: &str) -> Option<&RegisteredPrompt> {
1580        self.inner
1581            .prompts
1582            .binary_search_by(|p| p.name.as_str().cmp(name))
1583            .ok()
1584            .and_then(|i| self.inner.prompts.get(i))
1585    }
1586
1587    async fn get_prompt(
1588        &self,
1589        name: &str,
1590        arguments: &PromptArguments,
1591        ctx: Ctx,
1592        cancellation: &CancellationToken,
1593    ) -> Result<Option<Box<RawValue>>, HandlerError> {
1594        let prompt = self
1595            .find_prompt(name)
1596            .ok_or_else(|| McpError::unknown_prompt(name))?;
1597        authorize(&prompt.scopes, &ctx)?;
1598        let notifier = ctx.notifier_handle();
1599        let outcome = match (prompt.get)(arguments, ctx) {
1600            Err(error) => Err(error),
1601            Ok(future) => match self.run(future, cancellation).await? {
1602                None => return Ok(None),
1603                Some(outcome) => outcome,
1604            },
1605        };
1606        if let Some(notifier) = notifier {
1607            notifier.finish().await;
1608        }
1609        let mut result = outcome.map_err(|e| HandlerError::from_tool(e, &prompt.name, None))?;
1610        result.result_type = COMPLETE.to_owned();
1611        if result.description.is_none() {
1612            result.description.clone_from(&prompt.description);
1613        }
1614        self.with_server_info(&mut result.meta);
1615        Ok(to_raw(&result).map(Some).map_err(|error| {
1616            tracing::error!(prompt = %prompt.name, %error, "failed to serialize prompt");
1617            McpError::internal()
1618        })?)
1619    }
1620
1621    fn complete(&self, p: CompleteParams<'_>) -> Result<Box<RawValue>, McpError> {
1622        let completers = match &p.reference {
1623            CompletionRef::Prompt { name } => {
1624                &self
1625                    .find_prompt(name)
1626                    .ok_or_else(|| McpError::unknown_prompt(name))?
1627                    .completers
1628            }
1629            CompletionRef::Resource { uri } => {
1630                &self
1631                    .inner
1632                    .templates
1633                    .iter()
1634                    .find(|t| t.template.as_str() == uri)
1635                    .ok_or_else(|| McpError::resource_not_found(uri))?
1636                    .completers
1637            }
1638        };
1639        let context = p.context.and_then(|c| c.arguments).unwrap_or_default();
1640        let request = CompletionRequest {
1641            argument: &p.argument.name,
1642            value: &p.argument.value,
1643            context: &context,
1644        };
1645        let completion = match completers.iter().find(|c| c.argument == p.argument.name) {
1646            Some(completer) => {
1647                let complete = completer.complete;
1648                std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| complete(&request)))
1649                    .map_err(|_panic| {
1650                        tracing::error!(argument = %p.argument.name, "completion provider panicked");
1651                        McpError::internal()
1652                    })?
1653            }
1654            None => crate::completion::Completion::default(),
1655        };
1656        let mut result = spec::CompleteResult::new(completion.into_spec(), COMPLETE.to_owned());
1657        self.with_server_info(&mut result.meta);
1658        to_raw(&result).map_err(|error| {
1659            tracing::error!(%error, "failed to serialize completion");
1660            McpError::internal()
1661        })
1662    }
1663}
1664
1665impl tower_service::Service<McpRequest> for Server {
1666    type Response = Option<Response>;
1667    type Error = Infallible;
1668    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Infallible>> + Send>>;
1669
1670    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1671        Poll::Ready(Ok(()))
1672    }
1673
1674    fn call(&mut self, request: McpRequest) -> Self::Future {
1675        let server = self.clone();
1676        Box::pin(async move { Ok(server.handle(request).await) })
1677    }
1678}
1679
1680/// The legacy `_meta` fields the adapter uses.
1681#[derive(Deserialize)]
1682struct LegacyMeta {
1683    #[serde(rename = "progressToken", default)]
1684    progress_token: Option<spec::ProgressToken>,
1685}
1686
1687#[derive(Deserialize)]
1688struct MetaOnly<'a> {
1689    #[serde(rename = "_meta", borrow, default)]
1690    meta: Option<&'a RawValue>,
1691}
1692
1693#[derive(Deserialize)]
1694struct ListParams<'a> {
1695    #[serde(rename = "_meta", borrow, default)]
1696    meta: Option<&'a RawValue>,
1697    #[serde(borrow, default)]
1698    cursor: Option<std::borrow::Cow<'a, str>>,
1699}
1700
1701#[derive(Deserialize)]
1702struct CallParams<'a> {
1703    #[serde(rename = "_meta", borrow, default)]
1704    meta: Option<&'a RawValue>,
1705    name: String,
1706    #[serde(borrow, default)]
1707    arguments: Option<&'a RawValue>,
1708    #[serde(rename = "inputResponses", default)]
1709    input_responses: Option<Value>,
1710    #[serde(rename = "requestState", default)]
1711    request_state: Option<String>,
1712}
1713
1714#[derive(Deserialize)]
1715struct ReadParams<'a> {
1716    #[serde(rename = "_meta", borrow, default)]
1717    meta: Option<&'a RawValue>,
1718    #[serde(borrow)]
1719    uri: std::borrow::Cow<'a, str>,
1720    #[serde(rename = "inputResponses", default)]
1721    input_responses: Option<Value>,
1722    #[serde(rename = "requestState", default)]
1723    request_state: Option<String>,
1724}
1725
1726#[derive(Deserialize)]
1727struct GetPromptParams<'a> {
1728    #[serde(rename = "_meta", borrow, default)]
1729    meta: Option<&'a RawValue>,
1730    name: String,
1731    #[serde(default)]
1732    arguments: Option<BTreeMap<String, String>>,
1733    #[serde(rename = "inputResponses", default)]
1734    input_responses: Option<Value>,
1735    #[serde(rename = "requestState", default)]
1736    request_state: Option<String>,
1737}
1738
1739/// The failure of a handler that supports multi round-trip requests.
1740enum HandlerError {
1741    /// The handler asked the client for input.
1742    InputRequired,
1743    /// A JSON-RPC error.
1744    Rpc(McpError),
1745}
1746
1747impl HandlerError {
1748    fn from_tool(error: ToolError, handler: &str, uri: Option<&str>) -> Self {
1749        if error.is_input_required() {
1750            Self::InputRequired
1751        } else {
1752            Self::Rpc(error.into_rpc_error(handler, uri))
1753        }
1754    }
1755}
1756
1757impl From<McpError> for HandlerError {
1758    fn from(error: McpError) -> Self {
1759        Self::Rpc(error)
1760    }
1761}
1762
1763#[derive(Deserialize)]
1764struct CompleteParams<'a> {
1765    #[serde(rename = "_meta", borrow, default)]
1766    meta: Option<&'a RawValue>,
1767    #[serde(rename = "ref")]
1768    reference: CompletionRef,
1769    argument: CompletionArgument,
1770    #[serde(default)]
1771    context: Option<CompletionContext>,
1772}
1773
1774#[derive(Deserialize)]
1775#[serde(tag = "type")]
1776enum CompletionRef {
1777    #[serde(rename = "ref/prompt")]
1778    Prompt { name: String },
1779    #[serde(rename = "ref/resource")]
1780    Resource { uri: String },
1781}
1782
1783#[derive(Deserialize)]
1784struct CompletionArgument {
1785    name: String,
1786    value: String,
1787}
1788
1789#[derive(Deserialize)]
1790struct CompletionContext {
1791    #[serde(default)]
1792    arguments: Option<BTreeMap<String, String>>,
1793}
1794
1795fn parse_params<'a, T: Deserialize<'a>>(params: Option<&'a RawValue>) -> Result<T, McpError> {
1796    let raw = params.map_or("{}", RawValue::get);
1797    serde_json::from_str(raw).map_err(|e| {
1798        McpError::invalid_params(format!(
1799            "Invalid params: {}",
1800            crate::error::sanitize(&e.to_string())
1801        ))
1802    })
1803}
1804
1805fn to_raw<T: serde::Serialize>(value: &T) -> Result<Box<RawValue>, serde_json::Error> {
1806    serde_json::value::to_raw_value(value)
1807}
1808
1809fn millis(d: Duration) -> u64 {
1810    u64::try_from(d.as_millis()).unwrap_or(u64::MAX)
1811}
1812
1813/// Tool names: 1 to 128 characters from `A-Z a-z 0-9 _ - .` (spec SHOULD,
1814/// enforced here so names are always safe as HTTP header values).
1815fn is_valid_tool_name(name: &str) -> bool {
1816    (1..=128).contains(&name.len())
1817        && name
1818            .bytes()
1819            .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-' | b'.'))
1820}
1821
1822/// Checks a handler's scopes against the request's access token. Without
1823/// OAuth (stdio, or HTTP without [`OAuth`](crate::transport::http::OAuth))
1824/// there is no token and nothing to check.
1825fn authorize(scopes: &[String], ctx: &Ctx) -> Result<(), McpError> {
1826    if scopes.is_empty() {
1827        return Ok(());
1828    }
1829    match ctx.transport().get::<AuthContext>() {
1830        Some(auth) => auth.check(scopes),
1831        None => Ok(()),
1832    }
1833}