objectiveai_mcp_filesystem/
run.rs1use envconfig::Envconfig;
8use rmcp::transport::streamable_http_server::{
9 StreamableHttpServerConfig, StreamableHttpService,
10 session::local::LocalSessionManager,
11};
12use tokio_util::sync::CancellationToken;
13
14use crate::tools::FilesystemMcp;
15
16#[derive(Envconfig)]
17struct EnvConfigBuilder {
18 #[envconfig(from = "ADDRESS")]
19 address: Option<String>,
20 #[envconfig(from = "PORT")]
21 port: Option<u16>,
22 #[envconfig(from = "SUPPRESS_OUTPUT")]
23 suppress_output: Option<String>,
24}
25
26impl EnvConfigBuilder {
27 fn build(self) -> ConfigBuilder {
28 ConfigBuilder {
29 address: self.address,
30 port: self.port,
31 suppress_output: self.suppress_output.map(|v| {
32 matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")
33 }),
34 }
35 }
36}
37
38#[derive(Default)]
39pub struct ConfigBuilder {
40 pub address: Option<String>,
41 pub port: Option<u16>,
42 pub suppress_output: Option<bool>,
43}
44
45impl Envconfig for ConfigBuilder {
46 #[allow(deprecated)]
47 fn init() -> Result<Self, envconfig::Error> {
48 EnvConfigBuilder::init().map(|e| e.build())
49 }
50
51 fn init_from_env() -> Result<Self, envconfig::Error> {
52 EnvConfigBuilder::init_from_env().map(|e| e.build())
53 }
54
55 fn init_from_hashmap(
56 hashmap: &std::collections::HashMap<String, String>,
57 ) -> Result<Self, envconfig::Error> {
58 EnvConfigBuilder::init_from_hashmap(hashmap).map(|e| e.build())
59 }
60}
61
62impl ConfigBuilder {
63 pub fn build(self) -> Config {
64 Config {
65 address: self.address.unwrap_or_else(|| "0.0.0.0".to_string()),
66 port: self.port.unwrap_or(3000),
67 suppress_output: self.suppress_output.unwrap_or(false),
68 }
69 }
70}
71
72pub struct Config {
73 pub address: String,
74 pub port: u16,
75 pub suppress_output: bool,
76}
77
78pub async fn setup(config: Config) -> std::io::Result<(tokio::net::TcpListener, axum::Router)> {
79 let Config {
80 address,
81 port,
82 suppress_output: _,
83 } = config;
84
85 let server = FilesystemMcp::new();
86 server.init().await;
87
88 let ct = CancellationToken::new();
89
90 let service: StreamableHttpService<FilesystemMcp, LocalSessionManager> =
91 StreamableHttpService::new(
92 move || Ok(server.clone()),
93 Default::default(),
94 StreamableHttpServerConfig {
95 stateful_mode: true,
96 sse_keep_alive: None,
97 cancellation_token: ct.child_token(),
98 ..Default::default()
99 },
100 );
101
102 let router = axum::Router::new().fallback_service(service);
107 let listener = tokio::net::TcpListener::bind(format!("{address}:{port}")).await?;
108
109 Ok((listener, router))
110}
111
112pub async fn serve(listener: tokio::net::TcpListener, app: axum::Router) -> std::io::Result<()> {
113 axum::serve(listener, app).await
114}
115
116pub async fn run(config: Config) -> std::io::Result<()> {
117 let suppress_output = config.suppress_output;
118 let (listener, app) = setup(config).await?;
119 if !suppress_output {
120 let addr = listener.local_addr()?;
121 eprintln!("listening on {addr}");
122 }
123 serve(listener, app).await
124}