Skip to main content

dbmcp_server/
server.rs

1//! Shared MCP server wrapper and metadata.
2//!
3//! Provides the cloneable, type-erased [`Server`] consumed by the
4//! stdio and HTTP transports, plus [`server_info`] used by per-backend
5//! [`ServerHandler`] implementations.
6
7use std::fmt;
8use std::pin::Pin;
9use std::sync::Arc;
10
11use rmcp::model::{
12    CallToolRequestParams, CallToolResponse, Implementation, ListToolsResult, PaginatedRequestParams,
13    ServerCapabilities, ServerConfig, Tool,
14};
15use rmcp::service::RequestContext;
16use rmcp::{ErrorData, RoleServer, ServerHandler};
17
18/// Hardcoded product name matching the root binary crate.
19const NAME: &str = "dbmcp";
20
21/// The current version, derived from the workspace `Cargo.toml` at compile time.
22const VERSION: &str = env!("CARGO_PKG_VERSION");
23
24/// Human-readable title for the MCP server.
25const TITLE: &str = "Database MCP Server";
26
27/// Website URL, derived from the workspace `Cargo.toml` at compile time.
28const HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE");
29
30/// Cloneable, type-erased MCP server.
31///
32/// Wraps any backend adapter behind an [`Arc`] using the private
33/// [`DynHandler`] trait for type erasure. All database backends produce the same concrete
34/// type, eliminating the need for enum dispatch. The same `Arc` is held
35/// a second time as a [`Shutdown`] view so transports can drain pools
36/// via [`Server::shutdown`].
37#[derive(Clone)]
38pub struct Server {
39    handler: Arc<dyn DynHandler>,
40    shutdown: Arc<dyn Shutdown>,
41}
42
43/// Boxed `Send` future returned by [`DynHandler`] request methods.
44type HandlerFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T, ErrorData>> + Send + 'a>>;
45
46/// Object-safe view of the [`ServerHandler`] methods backends override.
47///
48/// [`ServerHandler`] returns `impl Future`, so it cannot be a trait object.
49/// Every method not listed here keeps rmcp's default on [`Server`], which
50/// matches what each backend inherits today.
51trait DynHandler: Send + Sync {
52    fn get_info(&self) -> ServerConfig;
53
54    fn call_tool(
55        &self,
56        request: CallToolRequestParams,
57        context: RequestContext<RoleServer>,
58    ) -> HandlerFuture<'_, CallToolResponse>;
59
60    fn list_tools(
61        &self,
62        request: Option<PaginatedRequestParams>,
63        context: RequestContext<RoleServer>,
64    ) -> HandlerFuture<'_, ListToolsResult>;
65
66    fn get_tool(&self, name: &str) -> Option<Tool>;
67}
68
69impl<H: ServerHandler> DynHandler for H {
70    fn get_info(&self) -> ServerConfig {
71        ServerHandler::get_info(self)
72    }
73
74    fn call_tool(
75        &self,
76        request: CallToolRequestParams,
77        context: RequestContext<RoleServer>,
78    ) -> HandlerFuture<'_, CallToolResponse> {
79        Box::pin(ServerHandler::call_tool(self, request, context))
80    }
81
82    fn list_tools(
83        &self,
84        request: Option<PaginatedRequestParams>,
85        context: RequestContext<RoleServer>,
86    ) -> HandlerFuture<'_, ListToolsResult> {
87        Box::pin(ServerHandler::list_tools(self, request, context))
88    }
89
90    fn get_tool(&self, name: &str) -> Option<Tool> {
91        ServerHandler::get_tool(self, name)
92    }
93}
94
95/// Boxed `Send` future returned by [`Shutdown::shutdown`].
96pub type ShutdownFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
97
98/// Closes every live connection pool the server owns.
99///
100/// Implemented by each backend handler so transports can drain pools
101/// gracefully on shutdown, separate from the type-erased [`Server`].
102pub trait Shutdown: Send + Sync {
103    /// Awaits a clean close of all pools. Idempotent.
104    fn shutdown(&self) -> ShutdownFuture<'_>;
105}
106
107impl fmt::Debug for Server {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        f.debug_struct("Server").finish_non_exhaustive()
110    }
111}
112
113impl Server {
114    /// Creates a new server from any backend adapter.
115    ///
116    /// One [`Arc`] is shared as two trait-object views — [`DynHandler`]
117    /// for request handling and [`Shutdown`] for pool draining — so the
118    /// adapter itself is never cloned.
119    pub fn new<S>(handler: S) -> Self
120    where
121        S: ServerHandler + Shutdown,
122    {
123        let handler = Arc::new(handler);
124        Self {
125            handler: handler.clone(),
126            shutdown: handler,
127        }
128    }
129
130    /// Closes every live connection pool. Idempotent.
131    pub async fn shutdown(&self) {
132        self.shutdown.shutdown().await;
133    }
134}
135
136impl ServerHandler for Server {
137    fn get_info(&self) -> ServerConfig {
138        self.handler.get_info()
139    }
140
141    async fn call_tool(
142        &self,
143        request: CallToolRequestParams,
144        context: RequestContext<RoleServer>,
145    ) -> Result<CallToolResponse, ErrorData> {
146        self.handler.call_tool(request, context).await
147    }
148
149    async fn list_tools(
150        &self,
151        request: Option<PaginatedRequestParams>,
152        context: RequestContext<RoleServer>,
153    ) -> Result<ListToolsResult, ErrorData> {
154        self.handler.list_tools(request, context).await
155    }
156
157    fn get_tool(&self, name: &str) -> Option<Tool> {
158        self.handler.get_tool(name)
159    }
160}
161
162/// Returns the shared [`ServerConfig`] for all server implementations.
163///
164/// Builds base [`Implementation`] metadata (name, version, title, URL).
165/// Backend handlers extend this with a backend-specific description
166/// and instructions via the public fields on [`ServerConfig`].
167#[must_use]
168pub fn server_info() -> ServerConfig {
169    let capabilities = ServerCapabilities::builder().enable_tools().build();
170
171    let server_info = Implementation::new(NAME, VERSION)
172        .with_title(TITLE)
173        .with_website_url(HOMEPAGE);
174
175    ServerConfig::new(capabilities).with_server_info(server_info)
176}