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 list_ttl_ms = millis(self.list_ttl);
619
620        let resources_list = if has_resources {
621            Some(
622                to_raw(&spec::ListResourcesResult {
623                    meta: result_meta.clone(),
624                    cache_scope: self.cache_scope,
625                    next_cursor: None,
626                    resources: resources.listed,
627                    result_type: COMPLETE.to_owned(),
628                    ttl_ms: list_ttl_ms,
629                })
630                .map_err(|e| BuildError::Serialize("resources/list", e))?,
631            )
632        } else {
633            None
634        };
635        let templates_list = if has_resources {
636            Some(
637                to_raw(&spec::ListResourceTemplatesResult {
638                    meta: result_meta.clone(),
639                    cache_scope: self.cache_scope,
640                    next_cursor: None,
641                    resource_templates: resources.listed_templates,
642                    result_type: COMPLETE.to_owned(),
643                    ttl_ms: list_ttl_ms,
644                })
645                .map_err(|e| BuildError::Serialize("resources/templates/list", e))?,
646            )
647        } else {
648            None
649        };
650        let prompts_list = if has_prompts {
651            Some(
652                to_raw(&spec::ListPromptsResult {
653                    meta: result_meta.clone(),
654                    cache_scope: self.cache_scope,
655                    next_cursor: None,
656                    prompts: prompts.listed,
657                    result_type: COMPLETE.to_owned(),
658                    ttl_ms: list_ttl_ms,
659                })
660                .map_err(|e| BuildError::Serialize("prompts/list", e))?,
661            )
662        } else {
663            None
664        };
665
666        let registry = tools
667            .into_iter()
668            .zip(mirrored)
669            .map(|(t, mirrored)| RegisteredTool {
670                name: t.tool.name,
671                call: t.call,
672                mirrored,
673                scopes: t.scopes.into(),
674            })
675            .collect::<Box<[RegisteredTool]>>();
676        let has_scopes = registry.iter().any(|t| !t.scopes.is_empty())
677            || resources.statics.iter().any(|r| !r.scopes.is_empty())
678            || resources.templates.iter().any(|t| !t.scopes.is_empty())
679            || prompts.registry.iter().any(|p| !p.scopes.is_empty());
680
681        Ok(Server {
682            inner: Arc::new(Inner {
683                tools: registry,
684                has_scopes,
685                resources: resources.statics,
686                templates: resources.templates,
687                prompts: prompts.registry,
688                has_completions,
689                state_codec,
690                legacy,
691                legacy_hint: Once::new(),
692                state_hint: Once::new(),
693                discover,
694                tools_list,
695                resources_list,
696                templates_list,
697                prompts_list,
698                result_meta,
699                supported_versions,
700                state: Arc::new(self.state),
701                permits: Arc::new(Semaphore::new(self.limits.max_concurrent_calls)),
702                limits: self.limits,
703            }),
704        })
705    }
706}
707
708/// A built MCP server. Cheap to clone; all clones share one registry.
709///
710/// `Server` implements [`tower_service::Service<McpRequest>`], so tower
711/// middleware (timeouts, rate limits, tracing) can wrap it.
712#[derive(Clone)]
713pub struct Server {
714    inner: Arc<Inner>,
715}
716
717struct Inner {
718    /// Sorted by name.
719    tools: Box<[RegisteredTool]>,
720    /// Sorted by URI.
721    resources: Box<[RegisteredResource]>,
722    /// Sorted by name, then URI template; the first match wins.
723    templates: Box<[RegisteredTemplate]>,
724    /// Sorted by name.
725    prompts: Box<[RegisteredPrompt]>,
726    has_completions: bool,
727    /// Whether any handler declares OAuth scopes.
728    has_scopes: bool,
729    state_codec: StateCodec,
730    legacy: LegacyConfig,
731    legacy_hint: Once,
732    state_hint: Once,
733    discover: Box<RawValue>,
734    tools_list: Box<RawValue>,
735    resources_list: Option<Box<RawValue>>,
736    templates_list: Option<Box<RawValue>>,
737    prompts_list: Option<Box<RawValue>>,
738    result_meta: Option<spec::ResultMetaObject>,
739    supported_versions: Vec<String>,
740    state: Arc<TypeMap>,
741    permits: Arc<Semaphore>,
742    limits: Limits,
743}
744
745struct RegisteredTool {
746    name: String,
747    call: CallFn,
748    mirrored: Vec<MirroredParam>,
749    scopes: Box<[String]>,
750}
751
752struct RegisteredResource {
753    uri: String,
754    read: ReadFn,
755    ttl_ms: u64,
756    cache_scope: CacheScope,
757    scopes: Box<[String]>,
758    /// `_meta.ui` of an MCP Apps interface, repeated in read results.
759    app: Option<Value>,
760}
761
762struct RegisteredTemplate {
763    template: UriTemplate,
764    read: ReadFn,
765    ttl_ms: u64,
766    cache_scope: CacheScope,
767    completers: Vec<Completer>,
768    scopes: Box<[String]>,
769}
770
771struct RegisteredPrompt {
772    name: String,
773    description: Option<String>,
774    get: GetFn,
775    completers: Vec<Completer>,
776    scopes: Box<[String]>,
777}
778
779struct BuiltResources {
780    statics: Box<[RegisteredResource]>,
781    templates: Box<[RegisteredTemplate]>,
782    listed: Vec<spec::Resource>,
783    listed_templates: Vec<spec::ResourceTemplate>,
784}
785
786struct BuiltPrompts {
787    registry: Box<[RegisteredPrompt]>,
788    listed: Vec<spec::Prompt>,
789}
790
791fn check_state(
792    state: &TypeMap,
793    required: &[StateRequirement],
794    handler: &str,
795) -> Result<(), BuildError> {
796    match required.iter().find(|r| !state.contains(r.type_id)) {
797        Some(missing) => Err(BuildError::MissingState {
798            handler: handler.to_owned(),
799            type_name: missing.type_name,
800        }),
801        None => Ok(()),
802    }
803}
804
805fn check_scopes(scopes: &[String], handler: &str) -> Result<(), BuildError> {
806    match scopes.iter().find(|s| !crate::auth::is_valid_scope(s)) {
807        Some(scope) => Err(BuildError::InvalidScope {
808            handler: handler.to_owned(),
809            scope: scope.clone(),
810        }),
811        None => Ok(()),
812    }
813}
814
815fn check_completers(
816    completers: &[Completer],
817    owner: &str,
818    arguments: &[&str],
819) -> Result<(), BuildError> {
820    match completers
821        .iter()
822        .find(|c| !arguments.contains(&c.argument.as_str()))
823    {
824        Some(unknown) => Err(BuildError::InvalidCompletion {
825            owner: owner.to_owned(),
826            argument: unknown.argument.clone(),
827        }),
828        None => Ok(()),
829    }
830}
831
832fn build_resources(defs: Vec<ResourceDef>, state: &TypeMap) -> Result<BuiltResources, BuildError> {
833    let mut statics = Vec::new();
834    let mut templates = Vec::new();
835    let mut listed = Vec::new();
836    let mut listed_templates = Vec::new();
837    for def in defs {
838        check_state(state, &def.required_state, def.uri())?;
839        check_scopes(&def.scopes, def.uri())?;
840        let ttl_ms = millis(def.ttl);
841        match def.kind {
842            ResourceKind::Static(resource) => {
843                if !uri_template::is_absolute_uri(&resource.uri) {
844                    return Err(BuildError::InvalidResource {
845                        uri: resource.uri,
846                        reason: "expected an absolute URI such as `scheme://path`".to_owned(),
847                    });
848                }
849                check_completers(&def.completers, &resource.uri, &[])?;
850                let app = match &def.app {
851                    None => None,
852                    Some(ui) => {
853                        let invalid = |reason: String| BuildError::InvalidApp {
854                            uri: resource.uri.clone(),
855                            reason,
856                        };
857                        if !resource.uri.starts_with(crate::apps::URI_SCHEME) {
858                            return Err(invalid("the URI must use the `ui://` scheme".to_owned()));
859                        }
860                        ui.validate().map_err(invalid)?;
861                        Some(ui.to_meta())
862                    }
863                };
864                statics.push(RegisteredResource {
865                    uri: resource.uri.clone(),
866                    read: def.read,
867                    ttl_ms,
868                    cache_scope: def.cache_scope,
869                    scopes: def.scopes.into(),
870                    app,
871                });
872                listed.push(resource);
873            }
874            ResourceKind::Template(template) => {
875                if def.app.is_some() {
876                    return Err(BuildError::InvalidApp {
877                        uri: template.uri_template,
878                        reason: "an interface is a fixed `ui://` resource, not a template"
879                            .to_owned(),
880                    });
881                }
882                let parsed = UriTemplate::parse(&template.uri_template).map_err(|e| {
883                    BuildError::InvalidResource {
884                        uri: template.uri_template.clone(),
885                        reason: e.to_string(),
886                    }
887                })?;
888                let variables: Vec<&str> = parsed.variables().collect();
889                check_completers(&def.completers, &template.uri_template, &variables)?;
890                templates.push((
891                    template.name.clone(),
892                    RegisteredTemplate {
893                        template: parsed,
894                        read: def.read,
895                        ttl_ms,
896                        cache_scope: def.cache_scope,
897                        completers: def.completers,
898                        scopes: def.scopes.into(),
899                    },
900                ));
901                listed_templates.push(template);
902            }
903        }
904    }
905    statics.sort_by(|a, b| a.uri.cmp(&b.uri));
906    if let Some(pair) = statics
907        .windows(2)
908        .find(|w| matches!(w, [a, b] if a.uri == b.uri))
909        && let Some(first) = pair.first()
910    {
911        return Err(BuildError::DuplicateResource(first.uri.clone()));
912    }
913    listed.sort_by(|a, b| a.uri.cmp(&b.uri));
914    templates.sort_by(|(na, a), (nb, b)| {
915        na.cmp(nb)
916            .then_with(|| a.template.as_str().cmp(b.template.as_str()))
917    });
918    for (i, (_, t)) in templates.iter().enumerate() {
919        if templates
920            .iter()
921            .skip(i + 1)
922            .any(|(_, other)| other.template.as_str() == t.template.as_str())
923        {
924            return Err(BuildError::DuplicateResource(
925                t.template.as_str().to_owned(),
926            ));
927        }
928    }
929    listed_templates.sort_by(|a, b| {
930        a.name
931            .cmp(&b.name)
932            .then_with(|| a.uri_template.cmp(&b.uri_template))
933    });
934    Ok(BuiltResources {
935        statics: statics.into(),
936        templates: templates.into_iter().map(|(_, t)| t).collect(),
937        listed,
938        listed_templates,
939    })
940}
941
942fn build_prompts(mut defs: Vec<PromptDef>, state: &TypeMap) -> Result<BuiltPrompts, BuildError> {
943    defs.sort_by(|a, b| a.prompt.name.cmp(&b.prompt.name));
944    if let Some(pair) = defs
945        .windows(2)
946        .find(|w| matches!(w, [a, b] if a.prompt.name == b.prompt.name))
947        && let Some(first) = pair.first()
948    {
949        return Err(BuildError::DuplicatePrompt(first.prompt.name.clone()));
950    }
951    let mut registry = Vec::with_capacity(defs.len());
952    let mut listed = Vec::with_capacity(defs.len());
953    for def in defs {
954        let name = &def.prompt.name;
955        if name.is_empty() || name.chars().any(char::is_control) {
956            return Err(BuildError::InvalidPromptName(name.clone()));
957        }
958        check_state(state, &def.required_state, name)?;
959        check_scopes(&def.scopes, name)?;
960        let arguments: Vec<&str> = def
961            .prompt
962            .arguments
963            .iter()
964            .flatten()
965            .map(|a| a.name.as_str())
966            .collect();
967        check_completers(&def.completers, name, &arguments)?;
968        registry.push(RegisteredPrompt {
969            name: name.clone(),
970            description: def.prompt.description.clone(),
971            get: def.get,
972            completers: def.completers,
973            scopes: def.scopes.into(),
974        });
975        listed.push(def.prompt);
976    }
977    Ok(BuiltPrompts {
978        registry: registry.into(),
979        listed,
980    })
981}
982
983impl std::fmt::Debug for Server {
984    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
985        f.debug_struct("Server")
986            .field(
987                "tools",
988                &self.inner.tools.iter().map(|t| &t.name).collect::<Vec<_>>(),
989            )
990            .field(
991                "resources",
992                &self
993                    .inner
994                    .resources
995                    .iter()
996                    .map(|r| &r.uri)
997                    .collect::<Vec<_>>(),
998            )
999            .field(
1000                "prompts",
1001                &self
1002                    .inner
1003                    .prompts
1004                    .iter()
1005                    .map(|p| &p.name)
1006                    .collect::<Vec<_>>(),
1007            )
1008            .field("limits", &self.inner.limits)
1009            .finish_non_exhaustive()
1010    }
1011}
1012
1013/// A parsed message plus transport-provided context.
1014#[derive(Debug)]
1015pub struct McpRequest {
1016    /// The message.
1017    pub message: Message,
1018    /// Values attached by the transport and middleware.
1019    pub transport: TransportContext,
1020    /// Cancelled when the client cancels the request.
1021    pub cancellation: CancellationToken,
1022}
1023
1024impl McpRequest {
1025    /// Wraps a message with an empty transport context.
1026    #[must_use]
1027    pub fn new(message: Message) -> Self {
1028        Self {
1029            message,
1030            transport: TransportContext::default(),
1031            cancellation: CancellationToken::new(),
1032        }
1033    }
1034}
1035
1036impl Server {
1037    /// Starts building a server.
1038    pub fn builder(name: impl Into<String>, version: impl Into<String>) -> ServerBuilder {
1039        ServerBuilder::new(name, version)
1040    }
1041
1042    /// The configured limits.
1043    #[must_use]
1044    pub fn limits(&self) -> &Limits {
1045        &self.inner.limits
1046    }
1047
1048    /// Parameters of `tool` mirrored into `Mcp-Param-*` HTTP headers, or
1049    /// `None` if the tool does not exist.
1050    #[must_use]
1051    pub fn mirrored_params(&self, tool: &str) -> Option<&[MirroredParam]> {
1052        self.find_tool(tool).map(|t| t.mirrored.as_slice())
1053    }
1054
1055    fn find_tool(&self, name: &str) -> Option<&RegisteredTool> {
1056        self.inner
1057            .tools
1058            .binary_search_by(|t| t.name.as_str().cmp(name))
1059            .ok()
1060            .and_then(|i| self.inner.tools.get(i))
1061    }
1062
1063    /// Whether any handler declares OAuth scopes.
1064    #[cfg_attr(not(feature = "http"), allow(dead_code))]
1065    pub(crate) fn has_scopes(&self) -> bool {
1066        self.inner.has_scopes
1067    }
1068
1069    pub(crate) fn legacy(&self) -> &LegacyConfig {
1070        &self.inner.legacy
1071    }
1072
1073    pub(crate) fn state_codec(&self) -> &StateCodec {
1074        &self.inner.state_codec
1075    }
1076
1077    /// The protocol versions this server accepts.
1078    #[must_use]
1079    pub fn supported_versions(&self) -> &[String] {
1080        &self.inner.supported_versions
1081    }
1082
1083    /// Parses raw bytes and handles the message. Returns the response to
1084    /// send, or `None` for notifications, cancelled requests and stray
1085    /// responses.
1086    pub async fn handle_bytes(
1087        &self,
1088        bytes: &[u8],
1089        transport: TransportContext,
1090        cancellation: CancellationToken,
1091    ) -> Option<Response> {
1092        match jsonrpc::parse(bytes, &self.inner.limits) {
1093            Ok(message) => {
1094                self.handle(McpRequest {
1095                    message,
1096                    transport,
1097                    cancellation,
1098                })
1099                .await
1100            }
1101            Err(Rejection::Error { id, error }) => Some(Response::error(id, error)),
1102            Err(Rejection::UnexpectedResponse) => {
1103                tracing::debug!("ignoring a JSON-RPC response sent by the client");
1104                None
1105            }
1106        }
1107    }
1108
1109    /// Handles a parsed message.
1110    pub async fn handle(&self, request: McpRequest) -> Option<Response> {
1111        let McpRequest {
1112            message,
1113            transport,
1114            cancellation,
1115        } = request;
1116        match message {
1117            // Client notifications have no effect on a stateless server;
1118            // cancellation is handled by the transports.
1119            Message::Notification(_) => None,
1120            Message::Request(req) => {
1121                let id = req.id.clone();
1122                // Handler futures are already isolated; this also covers
1123                // synchronous user code (argument parsers, completion
1124                // providers) and any bug in the dispatch path.
1125                let dispatched =
1126                    std::panic::AssertUnwindSafe(self.dispatch(req, transport, cancellation))
1127                        .catch_unwind()
1128                        .await;
1129                match dispatched {
1130                    Ok(Ok(Some(result))) => Some(Response::Result { id, result }),
1131                    Ok(Ok(None)) => None,
1132                    Ok(Err(error)) => Some(Response::error(Some(id), error)),
1133                    Err(_panic) => {
1134                        tracing::error!("request handling panicked");
1135                        Some(Response::error(Some(id), McpError::internal()))
1136                    }
1137                }
1138            }
1139        }
1140    }
1141
1142    async fn dispatch(
1143        &self,
1144        req: Request,
1145        transport: TransportContext,
1146        cancellation: CancellationToken,
1147    ) -> Result<Option<Box<RawValue>>, McpError> {
1148        let params = req.params.as_deref();
1149        match req.method.as_str() {
1150            methods::SERVER_DISCOVER => {
1151                let p: MetaOnly<'_> = parse_params(params)?;
1152                self.validate_meta(p.meta, &transport)?;
1153                Ok(Some(self.inner.discover.clone()))
1154            }
1155            // Every client gets every tool, including MCP Apps app-only
1156            // tools: hosts hide those from the model, and some hosts that
1157            // render interfaces do not declare the extension (see the
1158            // decision log).
1159            methods::TOOLS_LIST => self.list(
1160                &req.method,
1161                params,
1162                Some(&*self.inner.tools_list),
1163                &transport,
1164            ),
1165            methods::TOOLS_CALL => {
1166                let p: CallParams<'_> = parse_params(params)?;
1167                let meta = self.validate_meta(p.meta, &transport)?;
1168                let auth = transport.get::<AuthContext>();
1169                let key = RoundKey {
1170                    method: methods::TOOLS_CALL,
1171                    target: &p.name,
1172                    arguments: p.arguments.map_or(Args::None, |a| Args::Raw(a.get())),
1173                    principal: auth.as_deref().map(|a| a.digest.as_str()),
1174                };
1175                let round = self.round(&key, p.input_responses, p.request_state.as_deref())?;
1176                let ctx = self.ctx(req.id, meta, &cancellation, transport, &round);
1177                self.call_tool(&p.name, p.arguments, ctx, &cancellation)
1178                    .await
1179                    .or_else(|e| self.input_required(e, &round, &key))
1180            }
1181            methods::RESOURCES_LIST => self.list(
1182                &req.method,
1183                params,
1184                self.inner.resources_list.as_deref(),
1185                &transport,
1186            ),
1187            methods::RESOURCES_TEMPLATES_LIST => self.list(
1188                &req.method,
1189                params,
1190                self.inner.templates_list.as_deref(),
1191                &transport,
1192            ),
1193            methods::RESOURCES_READ if self.inner.resources_list.is_some() => {
1194                let p: ReadParams<'_> = parse_params(params)?;
1195                let meta = self.validate_meta(p.meta, &transport)?;
1196                let auth = transport.get::<AuthContext>();
1197                let key = RoundKey {
1198                    method: methods::RESOURCES_READ,
1199                    target: &p.uri,
1200                    arguments: Args::None,
1201                    principal: auth.as_deref().map(|a| a.digest.as_str()),
1202                };
1203                let round = self.round(&key, p.input_responses, p.request_state.as_deref())?;
1204                let ctx = self.ctx(req.id, meta, &cancellation, transport, &round);
1205                self.read_resource(&p.uri, ctx, &cancellation)
1206                    .await
1207                    .or_else(|e| self.input_required(e, &round, &key))
1208            }
1209            methods::PROMPTS_LIST => self.list(
1210                &req.method,
1211                params,
1212                self.inner.prompts_list.as_deref(),
1213                &transport,
1214            ),
1215            methods::PROMPTS_GET if self.inner.prompts_list.is_some() => {
1216                let p: GetPromptParams<'_> = parse_params(params)?;
1217                let meta = self.validate_meta(p.meta, &transport)?;
1218                let arguments = PromptArguments::new(p.arguments.unwrap_or_default());
1219                let auth = transport.get::<AuthContext>();
1220                let key = RoundKey {
1221                    method: methods::PROMPTS_GET,
1222                    target: &p.name,
1223                    arguments: Args::Map(arguments.as_map()),
1224                    principal: auth.as_deref().map(|a| a.digest.as_str()),
1225                };
1226                let round = self.round(&key, p.input_responses, p.request_state.as_deref())?;
1227                let ctx = self.ctx(req.id, meta, &cancellation, transport, &round);
1228                self.get_prompt(&p.name, &arguments, ctx, &cancellation)
1229                    .await
1230                    .or_else(|e| self.input_required(e, &round, &key))
1231            }
1232            methods::COMPLETION_COMPLETE if self.inner.has_completions => {
1233                let mut p: CompleteParams<'_> = parse_params(params)?;
1234                self.validate_meta(p.meta.take(), &transport)?;
1235                self.complete(p).map(Some)
1236            }
1237            "initialize" if self.inner.legacy.enabled => self
1238                .legacy_initialize(params)
1239                .map(|(result, _)| Some(result)),
1240            "initialize" => {
1241                hints::warn_once(
1242                    &self.inner.legacy_hint,
1243                    format_args!(
1244                        "a client tried to connect with `initialize` (MCP 2025-11-25 or older), \
1245                         but this server only accepts {}",
1246                        self.inner.supported_versions.join(", ")
1247                    ),
1248                    "legacy clients are disabled on this server; enable them with \
1249                     ServerBuilder::legacy_clients(true), or use a client that supports MCP \
1250                     2026-07-28",
1251                );
1252                Err(McpError::method_not_found_explained(format!(
1253                    "This server implements the stateless MCP protocol (versions: {}); \
1254                     `initialize` is not supported",
1255                    self.inner.supported_versions.join(", ")
1256                )))
1257            }
1258            other => Err(McpError::method_not_found(other)),
1259        }
1260    }
1261
1262    /// Answers a list request from its pre-serialized body. `None` means the
1263    /// server does not offer this kind of item.
1264    fn list(
1265        &self,
1266        method: &str,
1267        params: Option<&RawValue>,
1268        body: Option<&RawValue>,
1269        transport: &TransportContext,
1270    ) -> Result<Option<Box<RawValue>>, McpError> {
1271        let Some(body) = body else {
1272            return Err(McpError::method_not_found(method));
1273        };
1274        let p: ListParams<'_> = parse_params(params)?;
1275        self.validate_meta(p.meta, transport)?;
1276        if p.cursor.is_some() {
1277            // Every list fits in one page, so no cursor is valid.
1278            return Err(McpError::invalid_params("Invalid cursor"));
1279        }
1280        Ok(Some(body.to_owned()))
1281    }
1282
1283    #[allow(deprecated)] // `log_level`: deprecated (SEP-2577) but still specified.
1284    fn validate_meta(
1285        &self,
1286        meta: Option<&RawValue>,
1287        transport: &TransportContext,
1288    ) -> Result<RequestMeta, McpError> {
1289        // Legacy clients declared everything at `initialize`.
1290        if let Some(session) = transport.get::<LegacySession>() {
1291            let mut request_meta = session.request_meta();
1292            // Legacy `_meta` may carry a progress token.
1293            request_meta.progress_token = meta
1294                .and_then(|raw| serde_json::from_str::<LegacyMeta>(raw.get()).ok())
1295                .and_then(|m| m.progress_token);
1296            return Ok(request_meta);
1297        }
1298        let missing = || {
1299            McpError::invalid_params(
1300                "Missing `_meta` with `io.modelcontextprotocol/protocolVersion` and \
1301                 `io.modelcontextprotocol/clientCapabilities`",
1302            )
1303        };
1304        let meta: spec::RequestMetaObject = serde_json::from_str(meta.ok_or_else(missing)?.get())
1305            .map_err(|e| {
1306            McpError::invalid_params(format!(
1307                "Invalid `_meta`: {}",
1308                crate::error::sanitize(&e.to_string())
1309            ))
1310        })?;
1311        if !self
1312            .inner
1313            .supported_versions
1314            .contains(&meta.protocol_version)
1315        {
1316            return Err(McpError::unsupported_protocol_version(
1317                &meta.protocol_version,
1318                &self.inner.supported_versions,
1319            ));
1320        }
1321        Ok(RequestMeta {
1322            protocol_version: meta.protocol_version,
1323            client_capabilities: meta.client_capabilities,
1324            client_info: meta.client_info,
1325            progress_token: meta.progress_token,
1326            log_level: meta.log_level,
1327        })
1328    }
1329
1330    fn ctx(
1331        &self,
1332        id: RequestId,
1333        meta: RequestMeta,
1334        cancellation: &CancellationToken,
1335        transport: TransportContext,
1336        round: &Arc<Round>,
1337    ) -> Ctx {
1338        // Only when the client asked for notifications and the transport
1339        // can deliver them.
1340        let notifier = transport
1341            .get::<NotifyChannel>()
1342            .and_then(|channel| {
1343                Notifier::new(&channel, meta.progress_token.clone(), meta.log_level)
1344            })
1345            .map(Arc::new);
1346        Ctx::new(
1347            id,
1348            meta,
1349            cancellation.clone(),
1350            transport,
1351            Arc::clone(&self.inner.state),
1352            Some(Arc::clone(round)),
1353            notifier,
1354        )
1355    }
1356
1357    /// Starts an MRTR round: verifies `requestState` and collects answers.
1358    fn round(
1359        &self,
1360        key: &RoundKey<'_>,
1361        input_responses: Option<Value>,
1362        request_state: Option<&str>,
1363    ) -> Result<Arc<Round>, McpError> {
1364        let has_state = request_state.is_some();
1365        Round::start(&self.inner.state_codec, key, input_responses, request_state)
1366            .map(Arc::new)
1367            .inspect_err(|_| {
1368                if has_state {
1369                    hints::warn_once(
1370                        &self.inner.state_hint,
1371                        format_args!("rejected a requestState that failed verification"),
1372                        "if this server runs as several instances, give all of them the same \
1373                         key with ServerBuilder::request_state_key(..); otherwise the client \
1374                         sent an expired or modified state (or retried another request)",
1375                    );
1376                }
1377            })
1378    }
1379
1380    /// Turns the "input required" signal into an `InputRequiredResult`;
1381    /// other errors pass through.
1382    fn input_required(
1383        &self,
1384        error: HandlerError,
1385        round: &Round,
1386        key: &RoundKey<'_>,
1387    ) -> Result<Option<Box<RawValue>>, McpError> {
1388        match error {
1389            HandlerError::InputRequired => {
1390                let result = round.input_required(
1391                    &self.inner.state_codec,
1392                    key,
1393                    self.inner.result_meta.as_ref(),
1394                )?;
1395                to_raw(&result).map(Some).map_err(|_| McpError::internal())
1396            }
1397            HandlerError::Rpc(error) => Err(error),
1398        }
1399    }
1400
1401    /// Runs a handler future under the concurrency limit, the timeout and
1402    /// panic isolation. `Ok(None)` means the request was cancelled.
1403    async fn run<T>(
1404        &self,
1405        future: Pin<Box<dyn Future<Output = Result<T, ToolError>> + Send>>,
1406        cancellation: &CancellationToken,
1407    ) -> Result<Option<Result<T, ToolError>>, McpError> {
1408        // Bound concurrency; the semaphore is never closed.
1409        let Ok(_permit) = self.inner.permits.acquire().await else {
1410            return Err(McpError::internal());
1411        };
1412        let guarded = std::panic::AssertUnwindSafe(future).catch_unwind();
1413        tokio::select! {
1414            biased;
1415            () = cancellation.cancelled() => Ok(None),
1416            timed = tokio::time::timeout(self.inner.limits.tool_timeout, guarded) => {
1417                Ok(Some(match timed {
1418                    Ok(Ok(result)) => result,
1419                    Ok(Err(_panic)) => Err(ToolError::panicked()),
1420                    Err(_elapsed) => Err(ToolError::timeout()),
1421                }))
1422            }
1423        }
1424    }
1425
1426    fn with_server_info(&self, meta: &mut Option<spec::ResultMetaObject>) {
1427        if let Some(server_meta) = &self.inner.result_meta {
1428            match meta {
1429                Some(meta) => meta.server_info.clone_from(&server_meta.server_info),
1430                None => *meta = Some(server_meta.clone()),
1431            }
1432        }
1433    }
1434
1435    async fn call_tool(
1436        &self,
1437        name: &str,
1438        arguments: Option<&RawValue>,
1439        ctx: Ctx,
1440        cancellation: &CancellationToken,
1441    ) -> Result<Option<Box<RawValue>>, HandlerError> {
1442        let tool = self
1443            .find_tool(name)
1444            .ok_or_else(|| McpError::unknown_tool(name))?;
1445        authorize(&tool.scopes, &ctx)?;
1446
1447        let notifier = ctx.notifier_handle();
1448        let outcome = match (tool.call)(arguments, ctx) {
1449            Err(error) => Err(error),
1450            Ok(future) => match self.run(future, cancellation).await? {
1451                None => return Ok(None),
1452                Some(outcome) => outcome,
1453            },
1454        };
1455        if let Some(notifier) = notifier {
1456            notifier.finish().await;
1457        }
1458
1459        let mut result = match outcome {
1460            Ok(ToolOutput::Complete(result)) => result,
1461            Err(error) if error.is_input_required() => return Err(HandlerError::InputRequired),
1462            Err(error) => match error.into_protocol() {
1463                Ok(protocol_error) => return Err(protocol_error.into()),
1464                Err(error) => error.into_call_result(&tool.name),
1465            },
1466        };
1467        result.result_type = COMPLETE.to_owned();
1468        self.with_server_info(&mut result.meta);
1469        Ok(to_raw(&result).map(Some).map_err(|error| {
1470            tracing::error!(tool = %tool.name, %error, "failed to serialize tool result");
1471            McpError::internal()
1472        })?)
1473    }
1474
1475    /// Finds the handler for a URI: an exact static resource first, then the
1476    /// first matching template.
1477    #[allow(clippy::type_complexity)]
1478    fn find_resource(
1479        &self,
1480        uri: &str,
1481    ) -> Option<(ReadFn, UriVars, u64, CacheScope, &[String], Option<&Value>)> {
1482        if let Some(r) = self
1483            .inner
1484            .resources
1485            .binary_search_by(|r| r.uri.as_str().cmp(uri))
1486            .ok()
1487            .and_then(|i| self.inner.resources.get(i))
1488        {
1489            return Some((
1490                r.read,
1491                UriVars::default(),
1492                r.ttl_ms,
1493                r.cache_scope,
1494                &r.scopes,
1495                r.app.as_ref(),
1496            ));
1497        }
1498        self.inner.templates.iter().find_map(|t| {
1499            t.template
1500                .matches(uri)
1501                .map(|vars| (t.read, vars, t.ttl_ms, t.cache_scope, &*t.scopes, None))
1502        })
1503    }
1504
1505    async fn read_resource(
1506        &self,
1507        uri: &str,
1508        ctx: Ctx,
1509        cancellation: &CancellationToken,
1510    ) -> Result<Option<Box<RawValue>>, HandlerError> {
1511        let (read, vars, ttl_ms, cache_scope, scopes, app) = self
1512            .find_resource(uri)
1513            .ok_or_else(|| McpError::resource_not_found(uri))?;
1514        authorize(scopes, &ctx)?;
1515        let notifier = ctx.notifier_handle();
1516        let outcome = match read(uri, &vars, ctx) {
1517            Err(error) => Err(error),
1518            Ok(future) => match self.run(future, cancellation).await? {
1519                None => return Ok(None),
1520                Some(outcome) => outcome,
1521            },
1522        };
1523        if let Some(notifier) = notifier {
1524            notifier.finish().await;
1525        }
1526        let mut contents =
1527            outcome.map_err(|e| HandlerError::from_tool(e, "resource", Some(uri)))?;
1528        if let Some(ui) = app {
1529            crate::apps::mark_contents(&mut contents, ui);
1530        }
1531        let mut result =
1532            spec::ReadResourceResult::new(cache_scope, contents, COMPLETE.to_owned(), ttl_ms);
1533        self.with_server_info(&mut result.meta);
1534        Ok(to_raw(&result).map(Some).map_err(|error| {
1535            tracing::error!(uri, %error, "failed to serialize resource contents");
1536            McpError::internal()
1537        })?)
1538    }
1539
1540    fn find_prompt(&self, name: &str) -> Option<&RegisteredPrompt> {
1541        self.inner
1542            .prompts
1543            .binary_search_by(|p| p.name.as_str().cmp(name))
1544            .ok()
1545            .and_then(|i| self.inner.prompts.get(i))
1546    }
1547
1548    async fn get_prompt(
1549        &self,
1550        name: &str,
1551        arguments: &PromptArguments,
1552        ctx: Ctx,
1553        cancellation: &CancellationToken,
1554    ) -> Result<Option<Box<RawValue>>, HandlerError> {
1555        let prompt = self
1556            .find_prompt(name)
1557            .ok_or_else(|| McpError::unknown_prompt(name))?;
1558        authorize(&prompt.scopes, &ctx)?;
1559        let notifier = ctx.notifier_handle();
1560        let outcome = match (prompt.get)(arguments, ctx) {
1561            Err(error) => Err(error),
1562            Ok(future) => match self.run(future, cancellation).await? {
1563                None => return Ok(None),
1564                Some(outcome) => outcome,
1565            },
1566        };
1567        if let Some(notifier) = notifier {
1568            notifier.finish().await;
1569        }
1570        let mut result = outcome.map_err(|e| HandlerError::from_tool(e, &prompt.name, None))?;
1571        result.result_type = COMPLETE.to_owned();
1572        if result.description.is_none() {
1573            result.description.clone_from(&prompt.description);
1574        }
1575        self.with_server_info(&mut result.meta);
1576        Ok(to_raw(&result).map(Some).map_err(|error| {
1577            tracing::error!(prompt = %prompt.name, %error, "failed to serialize prompt");
1578            McpError::internal()
1579        })?)
1580    }
1581
1582    fn complete(&self, p: CompleteParams<'_>) -> Result<Box<RawValue>, McpError> {
1583        let completers = match &p.reference {
1584            CompletionRef::Prompt { name } => {
1585                &self
1586                    .find_prompt(name)
1587                    .ok_or_else(|| McpError::unknown_prompt(name))?
1588                    .completers
1589            }
1590            CompletionRef::Resource { uri } => {
1591                &self
1592                    .inner
1593                    .templates
1594                    .iter()
1595                    .find(|t| t.template.as_str() == uri)
1596                    .ok_or_else(|| McpError::resource_not_found(uri))?
1597                    .completers
1598            }
1599        };
1600        let context = p.context.and_then(|c| c.arguments).unwrap_or_default();
1601        let request = CompletionRequest {
1602            argument: &p.argument.name,
1603            value: &p.argument.value,
1604            context: &context,
1605        };
1606        let completion = match completers.iter().find(|c| c.argument == p.argument.name) {
1607            Some(completer) => {
1608                let complete = completer.complete;
1609                std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| complete(&request)))
1610                    .map_err(|_panic| {
1611                        tracing::error!(argument = %p.argument.name, "completion provider panicked");
1612                        McpError::internal()
1613                    })?
1614            }
1615            None => crate::completion::Completion::default(),
1616        };
1617        let mut result = spec::CompleteResult::new(completion.into_spec(), COMPLETE.to_owned());
1618        self.with_server_info(&mut result.meta);
1619        to_raw(&result).map_err(|error| {
1620            tracing::error!(%error, "failed to serialize completion");
1621            McpError::internal()
1622        })
1623    }
1624}
1625
1626impl tower_service::Service<McpRequest> for Server {
1627    type Response = Option<Response>;
1628    type Error = Infallible;
1629    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Infallible>> + Send>>;
1630
1631    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1632        Poll::Ready(Ok(()))
1633    }
1634
1635    fn call(&mut self, request: McpRequest) -> Self::Future {
1636        let server = self.clone();
1637        Box::pin(async move { Ok(server.handle(request).await) })
1638    }
1639}
1640
1641/// The legacy `_meta` fields the adapter uses.
1642#[derive(Deserialize)]
1643struct LegacyMeta {
1644    #[serde(rename = "progressToken", default)]
1645    progress_token: Option<spec::ProgressToken>,
1646}
1647
1648#[derive(Deserialize)]
1649struct MetaOnly<'a> {
1650    #[serde(rename = "_meta", borrow, default)]
1651    meta: Option<&'a RawValue>,
1652}
1653
1654#[derive(Deserialize)]
1655struct ListParams<'a> {
1656    #[serde(rename = "_meta", borrow, default)]
1657    meta: Option<&'a RawValue>,
1658    #[serde(borrow, default)]
1659    cursor: Option<std::borrow::Cow<'a, str>>,
1660}
1661
1662#[derive(Deserialize)]
1663struct CallParams<'a> {
1664    #[serde(rename = "_meta", borrow, default)]
1665    meta: Option<&'a RawValue>,
1666    name: String,
1667    #[serde(borrow, default)]
1668    arguments: Option<&'a RawValue>,
1669    #[serde(rename = "inputResponses", default)]
1670    input_responses: Option<Value>,
1671    #[serde(rename = "requestState", default)]
1672    request_state: Option<String>,
1673}
1674
1675#[derive(Deserialize)]
1676struct ReadParams<'a> {
1677    #[serde(rename = "_meta", borrow, default)]
1678    meta: Option<&'a RawValue>,
1679    #[serde(borrow)]
1680    uri: std::borrow::Cow<'a, str>,
1681    #[serde(rename = "inputResponses", default)]
1682    input_responses: Option<Value>,
1683    #[serde(rename = "requestState", default)]
1684    request_state: Option<String>,
1685}
1686
1687#[derive(Deserialize)]
1688struct GetPromptParams<'a> {
1689    #[serde(rename = "_meta", borrow, default)]
1690    meta: Option<&'a RawValue>,
1691    name: String,
1692    #[serde(default)]
1693    arguments: Option<BTreeMap<String, String>>,
1694    #[serde(rename = "inputResponses", default)]
1695    input_responses: Option<Value>,
1696    #[serde(rename = "requestState", default)]
1697    request_state: Option<String>,
1698}
1699
1700/// The failure of a handler that supports multi round-trip requests.
1701enum HandlerError {
1702    /// The handler asked the client for input.
1703    InputRequired,
1704    /// A JSON-RPC error.
1705    Rpc(McpError),
1706}
1707
1708impl HandlerError {
1709    fn from_tool(error: ToolError, handler: &str, uri: Option<&str>) -> Self {
1710        if error.is_input_required() {
1711            Self::InputRequired
1712        } else {
1713            Self::Rpc(error.into_rpc_error(handler, uri))
1714        }
1715    }
1716}
1717
1718impl From<McpError> for HandlerError {
1719    fn from(error: McpError) -> Self {
1720        Self::Rpc(error)
1721    }
1722}
1723
1724#[derive(Deserialize)]
1725struct CompleteParams<'a> {
1726    #[serde(rename = "_meta", borrow, default)]
1727    meta: Option<&'a RawValue>,
1728    #[serde(rename = "ref")]
1729    reference: CompletionRef,
1730    argument: CompletionArgument,
1731    #[serde(default)]
1732    context: Option<CompletionContext>,
1733}
1734
1735#[derive(Deserialize)]
1736#[serde(tag = "type")]
1737enum CompletionRef {
1738    #[serde(rename = "ref/prompt")]
1739    Prompt { name: String },
1740    #[serde(rename = "ref/resource")]
1741    Resource { uri: String },
1742}
1743
1744#[derive(Deserialize)]
1745struct CompletionArgument {
1746    name: String,
1747    value: String,
1748}
1749
1750#[derive(Deserialize)]
1751struct CompletionContext {
1752    #[serde(default)]
1753    arguments: Option<BTreeMap<String, String>>,
1754}
1755
1756fn parse_params<'a, T: Deserialize<'a>>(params: Option<&'a RawValue>) -> Result<T, McpError> {
1757    let raw = params.map_or("{}", RawValue::get);
1758    serde_json::from_str(raw).map_err(|e| {
1759        McpError::invalid_params(format!(
1760            "Invalid params: {}",
1761            crate::error::sanitize(&e.to_string())
1762        ))
1763    })
1764}
1765
1766fn to_raw<T: serde::Serialize>(value: &T) -> Result<Box<RawValue>, serde_json::Error> {
1767    serde_json::value::to_raw_value(value)
1768}
1769
1770fn millis(d: Duration) -> u64 {
1771    u64::try_from(d.as_millis()).unwrap_or(u64::MAX)
1772}
1773
1774/// Tool names: 1 to 128 characters from `A-Z a-z 0-9 _ - .` (spec SHOULD,
1775/// enforced here so names are always safe as HTTP header values).
1776fn is_valid_tool_name(name: &str) -> bool {
1777    (1..=128).contains(&name.len())
1778        && name
1779            .bytes()
1780            .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-' | b'.'))
1781}
1782
1783/// Checks a handler's scopes against the request's access token. Without
1784/// OAuth (stdio, or HTTP without [`OAuth`](crate::transport::http::OAuth))
1785/// there is no token and nothing to check.
1786fn authorize(scopes: &[String], ctx: &Ctx) -> Result<(), McpError> {
1787    if scopes.is_empty() {
1788        return Ok(());
1789    }
1790    match ctx.transport().get::<AuthContext>() {
1791        Some(auth) => auth.check(scopes),
1792        None => Ok(()),
1793    }
1794}