httpmcp_rust/
server.rs

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