pforge_runtime/
server.rs

1use crate::{Error, HandlerRegistry, Result};
2use pforge_config::ForgeConfig;
3use std::sync::Arc;
4use tokio::sync::RwLock;
5
6/// MCP Server implementation
7pub struct McpServer {
8    config: ForgeConfig,
9    registry: Arc<RwLock<HandlerRegistry>>,
10}
11
12impl McpServer {
13    /// Create a new MCP server from configuration
14    pub fn new(config: ForgeConfig) -> Self {
15        Self {
16            config,
17            registry: Arc::new(RwLock::new(HandlerRegistry::new())),
18        }
19    }
20
21    /// Register all handlers from configuration
22    pub async fn register_handlers(&self) -> Result<()> {
23        let mut registry = self.registry.write().await;
24
25        for tool in &self.config.tools {
26            match tool {
27                pforge_config::ToolDef::Native { name, .. } => {
28                    // Native handlers will be registered by generated code
29                    eprintln!(
30                        "Note: Native handler '{}' requires handler implementation",
31                        name
32                    );
33                }
34                pforge_config::ToolDef::Cli {
35                    name,
36                    command,
37                    args,
38                    cwd,
39                    env,
40                    stream,
41                    ..
42                } => {
43                    use crate::handlers::cli::CliHandler;
44                    let handler = CliHandler::new(
45                        command.clone(),
46                        args.clone(),
47                        cwd.clone(),
48                        env.clone(),
49                        None, // timeout from tool config
50                        *stream,
51                    );
52                    registry.register(name, handler);
53                    eprintln!("Registered CLI handler: {}", name);
54                }
55                pforge_config::ToolDef::Http {
56                    name,
57                    endpoint,
58                    method,
59                    headers,
60                    auth,
61                    ..
62                } => {
63                    use crate::handlers::http::{
64                        AuthConfig as HttpAuthConfig, HttpHandler, HttpMethod as HandlerHttpMethod,
65                    };
66
67                    let handler_method = match method {
68                        pforge_config::HttpMethod::Get => HandlerHttpMethod::Get,
69                        pforge_config::HttpMethod::Post => HandlerHttpMethod::Post,
70                        pforge_config::HttpMethod::Put => HandlerHttpMethod::Put,
71                        pforge_config::HttpMethod::Delete => HandlerHttpMethod::Delete,
72                        pforge_config::HttpMethod::Patch => HandlerHttpMethod::Patch,
73                    };
74
75                    let handler_auth = auth.as_ref().map(|a| match a {
76                        pforge_config::AuthConfig::Bearer { token } => HttpAuthConfig::Bearer {
77                            token: token.clone(),
78                        },
79                        pforge_config::AuthConfig::Basic { username, password } => {
80                            HttpAuthConfig::Basic {
81                                username: username.clone(),
82                                password: password.clone(),
83                            }
84                        }
85                        pforge_config::AuthConfig::ApiKey { key, header } => {
86                            HttpAuthConfig::ApiKey {
87                                key: key.clone(),
88                                header: header.clone(),
89                            }
90                        }
91                    });
92
93                    let handler = HttpHandler::new(
94                        endpoint.clone(),
95                        handler_method,
96                        headers.clone(),
97                        handler_auth,
98                    );
99                    registry.register(name, handler);
100                    eprintln!("Registered HTTP handler: {}", name);
101                }
102                pforge_config::ToolDef::Pipeline { name, .. } => {
103                    eprintln!("Note: Pipeline handler '{}' pending implementation", name);
104                }
105            }
106        }
107
108        Ok(())
109    }
110
111    /// Run the MCP server
112    pub async fn run(&self) -> Result<()> {
113        eprintln!(
114            "Starting MCP server: {} v{}",
115            self.config.forge.name, self.config.forge.version
116        );
117        eprintln!("Transport: {:?}", self.config.forge.transport);
118        eprintln!("Tools registered: {}", self.config.tools.len());
119
120        // Register handlers
121        self.register_handlers().await?;
122
123        // TODO: Implement actual MCP protocol loop
124        // For now, just keep the server alive
125        eprintln!("\n⚠ MCP protocol loop not yet implemented");
126        eprintln!("Server configuration loaded and handlers registered successfully");
127        eprintln!("Press Ctrl+C to exit");
128
129        // Wait indefinitely (will be replaced with actual MCP loop)
130        tokio::signal::ctrl_c().await.map_err(Error::Io)?;
131
132        eprintln!("\nShutting down...");
133        Ok(())
134    }
135
136    /// Get the handler registry (for testing)
137    pub fn registry(&self) -> Arc<RwLock<HandlerRegistry>> {
138        self.registry.clone()
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use pforge_config::{ForgeMetadata, ParamSchema, ToolDef, TransportType};
146
147    fn create_test_config() -> ForgeConfig {
148        ForgeConfig {
149            forge: ForgeMetadata {
150                name: "test-server".to_string(),
151                version: "0.1.0".to_string(),
152                transport: TransportType::Stdio,
153                optimization: pforge_config::OptimizationLevel::Debug,
154            },
155            tools: vec![],
156            resources: vec![],
157            prompts: vec![],
158            state: None,
159        }
160    }
161
162    #[test]
163    fn test_server_new() {
164        let config = create_test_config();
165        let server = McpServer::new(config);
166
167        assert_eq!(server.config.forge.name, "test-server");
168        assert_eq!(server.config.forge.version, "0.1.0");
169    }
170
171    #[tokio::test]
172    async fn test_register_handlers_cli() {
173        let mut config = create_test_config();
174        config.tools.push(ToolDef::Cli {
175            name: "test_cli".to_string(),
176            description: "Test CLI handler".to_string(),
177            command: "echo".to_string(),
178            args: vec!["hello".to_string()],
179            cwd: None,
180            env: std::collections::HashMap::new(),
181            stream: false,
182        });
183
184        let server = McpServer::new(config);
185        let result = server.register_handlers().await;
186
187        assert!(result.is_ok());
188    }
189
190    #[tokio::test]
191    async fn test_register_handlers_http() {
192        let mut config = create_test_config();
193        config.tools.push(ToolDef::Http {
194            name: "test_http".to_string(),
195            description: "Test HTTP handler".to_string(),
196            endpoint: "https://api.example.com".to_string(),
197            method: pforge_config::HttpMethod::Get,
198            headers: std::collections::HashMap::new(),
199            auth: None,
200        });
201
202        let server = McpServer::new(config);
203        let result = server.register_handlers().await;
204
205        assert!(result.is_ok());
206    }
207
208    #[tokio::test]
209    async fn test_register_handlers_native() {
210        let mut config = create_test_config();
211        config.tools.push(ToolDef::Native {
212            name: "test_native".to_string(),
213            description: "Test native handler".to_string(),
214            handler: pforge_config::HandlerRef {
215                path: "handlers::test::TestHandler".to_string(),
216                inline: None,
217            },
218            params: ParamSchema {
219                fields: std::collections::HashMap::new(),
220            },
221            timeout_ms: Some(5000),
222        });
223
224        let server = McpServer::new(config);
225        let result = server.register_handlers().await;
226
227        // Should succeed (native handlers registered by generated code)
228        assert!(result.is_ok());
229    }
230
231    #[tokio::test]
232    async fn test_registry_access() {
233        let config = create_test_config();
234        let server = McpServer::new(config);
235
236        let registry = server.registry();
237        let _lock = registry.read().await;
238
239        // Registry is accessible (test passes if no panic)
240    }
241
242    #[tokio::test]
243    async fn test_register_handlers_pipeline() {
244        let mut config = create_test_config();
245        config.tools.push(ToolDef::Pipeline {
246            name: "test_pipeline".to_string(),
247            description: "Test pipeline handler".to_string(),
248            steps: vec![],
249        });
250
251        let server = McpServer::new(config);
252        let result = server.register_handlers().await;
253
254        // Should succeed (pipeline handler pending)
255        assert!(result.is_ok());
256    }
257
258    #[tokio::test]
259    async fn test_server_with_multiple_tools() {
260        let mut config = create_test_config();
261
262        config.tools.push(ToolDef::Cli {
263            name: "cli1".to_string(),
264            description: "CLI 1".to_string(),
265            command: "echo".to_string(),
266            args: vec![],
267            cwd: None,
268            env: std::collections::HashMap::new(),
269            stream: false,
270        });
271
272        config.tools.push(ToolDef::Http {
273            name: "http1".to_string(),
274            description: "HTTP 1".to_string(),
275            endpoint: "https://example.com".to_string(),
276            method: pforge_config::HttpMethod::Get,
277            headers: std::collections::HashMap::new(),
278            auth: None,
279        });
280
281        let server = McpServer::new(config);
282        assert_eq!(server.config.tools.len(), 2);
283
284        let result = server.register_handlers().await;
285        assert!(result.is_ok());
286    }
287}