Skip to main content

database_mcp_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`](rmcp::ServerHandler) implementations.
6
7use std::fmt;
8use std::sync::Arc;
9
10use rmcp::RoleServer;
11use rmcp::Service;
12use rmcp::model::{Implementation, ServerCapabilities, ServerInfo};
13use rmcp::service::{DynService, NotificationContext, RequestContext, ServiceExt};
14
15/// Hardcoded product name matching the root binary crate.
16const NAME: &str = "database-mcp";
17
18/// The current version, derived from the workspace `Cargo.toml` at compile time.
19const VERSION: &str = env!("CARGO_PKG_VERSION");
20
21/// Human-readable title for the MCP server.
22const TITLE: &str = "Database MCP Server";
23
24/// Website URL, derived from the workspace `Cargo.toml` at compile time.
25const HOMEPAGE: &str = env!("CARGO_PKG_HOMEPAGE");
26
27/// Cloneable, type-erased MCP server.
28///
29/// Wraps any backend adapter behind an [`Arc`] using rmcp's [`DynService`]
30/// for type erasure. All database backends produce the same concrete
31/// type, eliminating the need for enum dispatch.
32#[derive(Clone)]
33pub struct Server(Arc<dyn DynService<RoleServer>>);
34
35impl fmt::Debug for Server {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        f.debug_struct("Server").finish_non_exhaustive()
38    }
39}
40
41impl Server {
42    /// Creates a new server from any backend adapter.
43    pub fn new(server: impl ServiceExt<RoleServer>) -> Self {
44        Self(Arc::from(server.into_dyn()))
45    }
46}
47
48impl Service<RoleServer> for Server {
49    fn handle_request(
50        &self,
51        request: <RoleServer as rmcp::service::ServiceRole>::PeerReq,
52        context: RequestContext<RoleServer>,
53    ) -> impl Future<Output = Result<<RoleServer as rmcp::service::ServiceRole>::Resp, rmcp::ErrorData>> + Send + '_
54    {
55        DynService::handle_request(self.0.as_ref(), request, context)
56    }
57
58    fn handle_notification(
59        &self,
60        notification: <RoleServer as rmcp::service::ServiceRole>::PeerNot,
61        context: NotificationContext<RoleServer>,
62    ) -> impl Future<Output = Result<(), rmcp::ErrorData>> + Send + '_ {
63        DynService::handle_notification(self.0.as_ref(), notification, context)
64    }
65
66    fn get_info(&self) -> <RoleServer as rmcp::service::ServiceRole>::Info {
67        DynService::get_info(self.0.as_ref())
68    }
69}
70
71/// Returns the shared [`ServerInfo`] for all server implementations.
72///
73/// Builds base [`Implementation`] metadata (name, version, title, URL).
74/// Backend handlers extend this with a backend-specific description
75/// and instructions via the public fields on [`ServerInfo`].
76#[must_use]
77pub fn server_info() -> ServerInfo {
78    let capabilities = ServerCapabilities::builder().enable_tools().build();
79
80    let server_info = Implementation::new(NAME, VERSION)
81        .with_title(TITLE)
82        .with_website_url(HOMEPAGE);
83
84    ServerInfo::new(capabilities).with_server_info(server_info)
85}