httpmcp_rust/
server.rs

1use crate::auth::OAuthConfig;
2use crate::handler_types::{RegisteredPrompt, RegisteredResource, RegisteredTool};
3use crate::jsonrpc::JsonRpcResponse;
4use crate::metadata::{PromptMeta, ResourceMeta, ToolMeta};
5use crate::protocol::{Implementation, ServerCapabilities};
6use crate::transport::create_app;
7use actix_web::{middleware::Logger, App, HttpServer};
8use std::collections::HashMap;
9use std::sync::Arc;
10use tokio::sync::broadcast;
11
12/// Main HTTP MCP Server
13pub struct HttpMcpServer {
14    pub(crate) server_info: Implementation,
15    pub(crate) capabilities: ServerCapabilities,
16    pub(crate) tools: HashMap<String, RegisteredTool>,
17    pub(crate) resources: HashMap<String, RegisteredResource>,
18    pub(crate) prompts: HashMap<String, RegisteredPrompt>,
19    pub(crate) oauth_config: Option<OAuthConfig>,
20    pub(crate) enable_cors: bool,
21    pub(crate) response_tx: broadcast::Sender<JsonRpcResponse>,
22}
23
24impl HttpMcpServer {
25    /// Create a new server builder
26    pub fn builder() -> HttpMcpServerBuilder {
27        HttpMcpServerBuilder::new()
28    }
29
30    /// Run the server on the specified address
31    pub async fn run(self, addr: impl Into<String>) -> std::io::Result<()> {
32        let addr = addr.into();
33        let server = Arc::new(self);
34
35        tracing::info!("Starting MCP server on {}", addr);
36
37        HttpServer::new(move || {
38            App::new()
39                .wrap(Logger::default())
40                .configure(|cfg| create_app(cfg, server.clone()))
41        })
42        .bind(&addr)?
43        .run()
44        .await
45    }
46}
47
48/// Builder for HttpMcpServer
49pub struct HttpMcpServerBuilder {
50    name: String,
51    version: String,
52    tools: HashMap<String, RegisteredTool>,
53    resources: HashMap<String, RegisteredResource>,
54    prompts: HashMap<String, RegisteredPrompt>,
55    oauth_config: Option<OAuthConfig>,
56    enable_cors: bool,
57}
58
59impl HttpMcpServerBuilder {
60    pub fn new() -> Self {
61        Self {
62            name: "httpmcp-server".to_string(),
63            version: "1.0.0".to_string(),
64            tools: HashMap::new(),
65            resources: HashMap::new(),
66            prompts: HashMap::new(),
67            oauth_config: None,
68            enable_cors: true,
69        }
70    }
71
72    /// Set server name
73    pub fn name(mut self, name: impl Into<String>) -> Self {
74        self.name = name.into();
75        self
76    }
77
78    /// Set server version
79    pub fn version(mut self, version: impl Into<String>) -> Self {
80        self.version = version.into();
81        self
82    }
83
84    /// Register a tool with handler
85    pub fn tool<F, Fut>(mut self, name: impl Into<String>, meta: ToolMeta, handler: F) -> Self
86    where
87        F: Fn(HashMap<String, serde_json::Value>, crate::context::RequestContext) -> Fut
88            + Send
89            + Sync
90            + 'static,
91        Fut: std::future::Future<Output = crate::error::Result<serde_json::Value>> + Send + 'static,
92    {
93        let name_str = name.into();
94        let tool = RegisteredTool {
95            meta: meta.to_tool(name_str.clone()),
96            handler: Box::new(move |args, ctx| Box::pin(handler(args, ctx))),
97        };
98        self.tools.insert(name_str, tool);
99        self
100    }
101
102    /// Register a resource with list and read handlers
103    pub fn resource<FL, FR, FutL, FutR>(
104        mut self,
105        uri: impl Into<String>,
106        meta: ResourceMeta,
107        list_handler: FL,
108        read_handler: FR,
109    ) -> Self
110    where
111        FL: Fn(Option<String>, crate::context::RequestContext) -> FutL + Send + Sync + 'static,
112        FutL: std::future::Future<
113                Output = crate::error::Result<(Vec<crate::protocol::Resource>, Option<String>)>,
114            > + Send
115            + 'static,
116        FR: Fn(String, crate::context::RequestContext) -> FutR + Send + Sync + 'static,
117        FutR: std::future::Future<
118                Output = crate::error::Result<Vec<crate::protocol::ResourceContents>>,
119            > + Send
120            + 'static,
121    {
122        let uri_str = uri.into();
123        let resource = RegisteredResource {
124            meta: meta.to_resource(uri_str.clone()),
125            list_handler: Box::new(move |cursor, ctx| Box::pin(list_handler(cursor, ctx))),
126            read_handler: Box::new(move |uri, ctx| Box::pin(read_handler(uri, ctx))),
127        };
128        self.resources.insert(uri_str, resource);
129        self
130    }
131
132    /// Register a prompt with handler
133    pub fn prompt<F, Fut>(mut self, name: impl Into<String>, meta: PromptMeta, handler: F) -> Self
134    where
135        F: Fn(String, Option<HashMap<String, String>>, crate::context::RequestContext) -> Fut
136            + Send
137            + Sync
138            + 'static,
139        Fut: std::future::Future<
140                Output = crate::error::Result<(
141                    Option<String>,
142                    Vec<crate::protocol::PromptMessage>,
143                )>,
144            > + Send
145            + 'static,
146    {
147        let name_str = name.into();
148        let prompt = RegisteredPrompt {
149            meta: meta.to_prompt(name_str.clone()),
150            handler: Box::new(move |name, args, ctx| Box::pin(handler(name, args, ctx))),
151        };
152        self.prompts.insert(name_str, prompt);
153        self
154    }
155
156    /// Configure OAuth 2.0
157    pub fn with_oauth(
158        mut self,
159        client_id: impl Into<String>,
160        client_secret: impl Into<String>,
161        _token_url: impl Into<String>,
162        _auth_url: impl Into<String>,
163    ) -> Self {
164        self.oauth_config = Some(OAuthConfig {
165            client_id: client_id.into(),
166            client_secret: client_secret.into(),
167        });
168        self
169    }
170
171    /// Enable or disable CORS
172    pub fn enable_cors(mut self, enable: bool) -> Self {
173        self.enable_cors = enable;
174        self
175    }
176
177    /// Build the server
178    pub fn build(self) -> crate::error::Result<HttpMcpServer> {
179        let capabilities = ServerCapabilities {
180            logging: Some(Default::default()),
181            prompts: if self.prompts.is_empty() {
182                None
183            } else {
184                Some(Default::default())
185            },
186            resources: if self.resources.is_empty() {
187                None
188            } else {
189                Some(Default::default())
190            },
191            tools: if self.tools.is_empty() {
192                None
193            } else {
194                Some(Default::default())
195            },
196        };
197
198        // Create broadcast channel for SSE responses
199        let (response_tx, _) = broadcast::channel(100);
200
201        Ok(HttpMcpServer {
202            server_info: Implementation {
203                name: self.name,
204                version: self.version,
205            },
206            capabilities,
207            tools: self.tools,
208            resources: self.resources,
209            prompts: self.prompts,
210            oauth_config: self.oauth_config,
211            enable_cors: self.enable_cors,
212            response_tx,
213        })
214    }
215}
216
217impl Default for HttpMcpServerBuilder {
218    fn default() -> Self {
219        Self::new()
220    }
221}