1use crate::{Error, HandlerRegistry, Result};
2use pforge_config::ForgeConfig;
3use std::sync::Arc;
4use tokio::sync::RwLock;
5
6pub struct McpServer {
8 config: ForgeConfig,
9 registry: Arc<RwLock<HandlerRegistry>>,
10}
11
12impl McpServer {
13 pub fn new(config: ForgeConfig) -> Self {
15 Self {
16 config,
17 registry: Arc::new(RwLock::new(HandlerRegistry::new())),
18 }
19 }
20
21 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 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, *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 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 self.register_handlers().await?;
122
123 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 tokio::signal::ctrl_c().await.map_err(Error::Io)?;
131
132 eprintln!("\nShutting down...");
133 Ok(())
134 }
135
136 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 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 }
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 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}