1use crate::compression_manager;
7use anyhow::{Context, Result};
8use serde::{Deserialize, Serialize};
9use serde_json::{json, Value};
10use std::io::{self, BufRead, BufReader, Write};
11use std::path::{Path, PathBuf};
12use std::sync::Arc;
13
14#[inline]
26pub fn fmt_num(n: usize, hex: bool) -> String {
27 if hex {
28 format!("{:X}", n)
29 } else {
30 n.to_string()
31 }
32}
33
34#[inline]
36pub fn fmt_num64(n: u64, hex: bool) -> String {
37 if hex {
38 format!("{:X}", n)
39 } else {
40 n.to_string()
41 }
42}
43
44pub fn fmt_size(bytes: u64, hex: bool) -> String {
49 if bytes < 1024 {
50 if hex {
51 format!("{}B", fmt_num64(bytes, true))
52 } else {
53 format!("{}B", bytes)
54 }
55 } else if bytes < 1024 * 1024 {
56 format!("{:.1}K", bytes as f64 / 1024.0)
57 } else if bytes < 1024 * 1024 * 1024 {
58 format!("{:.1}M", bytes as f64 / (1024.0 * 1024.0))
59 } else {
60 format!("{:.1}G", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
61 }
62}
63
64#[inline]
66pub fn fmt_line(n: usize, hex: bool) -> String {
67 if hex {
68 format!("{:>4X}", n)
69 } else {
70 format!("{:>4}", n)
71 }
72}
73
74pub mod assistant;
75pub mod cache;
76pub mod consciousness;
77pub mod context_absorber;
78mod context_tools;
79pub mod dashboard_bridge;
80mod enhanced_tool_descriptions;
81mod git_memory_integration;
82mod helpers;
83mod hook_tools;
84mod negotiation;
85pub mod permissions;
86mod proactive_assistant;
87mod prompts;
88mod prompts_enhanced;
89mod resources;
90pub mod session;
91pub mod smart_background_searcher;
92pub mod smart_edit;
93mod smart_edit_diff_viewer;
94pub mod smart_project_detector;
95mod sse;
96mod theme_tools;
97mod tools;
98mod tools_consolidated;
99pub mod tools_consolidated_enhanced;
100pub mod unified_watcher;
101pub mod wave_memory;
102
103use assistant::*;
104use cache::*;
105use consciousness::*;
106use negotiation::*;
107use permissions::*;
108#[allow(unused_imports)]
109use prompts::*;
110#[allow(unused_imports)]
111use prompts_enhanced::*;
112use resources::*;
113use session::*;
114use tools::*;
115
116fn should_show_startup_messages() -> bool {
127 use std::env;
128
129 if let Ok(val) = env::var("MCP_DEBUG") {
131 if val == "1" || val.to_lowercase() == "true" {
132 return true;
133 }
134 }
135
136 if let Ok(val) = env::var("ST_MCP_VERBOSE") {
137 if val == "1" || val.to_lowercase() == "true" {
138 return true;
139 }
140 }
141
142 false
144}
145
146pub struct McpServer {
148 context: Arc<McpContext>,
149 consciousness: Arc<tokio::sync::Mutex<ConsciousnessManager>>,
150}
151
152#[derive(Clone)]
154pub struct McpContext {
155 pub cache: Arc<AnalysisCache>,
157 pub config: Arc<McpConfig>,
159 pub permissions: Arc<tokio::sync::Mutex<PermissionCache>>,
161 pub sessions: Arc<SessionManager>,
163 pub assistant: Arc<McpAssistant>,
165 pub consciousness: Arc<tokio::sync::Mutex<ConsciousnessManager>>,
167 pub dashboard_bridge: Option<dashboard_bridge::DashboardBridge>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct McpConfig {
174 pub cache_enabled: bool,
176 pub cache_ttl: u64,
178 pub max_cache_size: usize,
180 pub allowed_paths: Vec<PathBuf>,
182 pub blocked_paths: Vec<PathBuf>,
184 pub use_consolidated_tools: bool,
186 pub hex_numbers: bool,
189}
190
191impl Default for McpConfig {
192 fn default() -> Self {
193 Self {
194 cache_enabled: true,
195 cache_ttl: 300, max_cache_size: 100 * 1024 * 1024, allowed_paths: vec![],
198 blocked_paths: vec![
199 PathBuf::from("/etc"),
200 PathBuf::from("/sys"),
201 PathBuf::from("/proc"),
202 ],
203 use_consolidated_tools: true, hex_numbers: true, }
206 }
207}
208
209#[derive(Debug, Deserialize)]
211struct JsonRpcRequest {
212 #[allow(dead_code)]
213 jsonrpc: String,
214 method: String,
215 params: Option<Value>,
216 id: Option<Value>,
217}
218
219#[derive(Debug, Serialize)]
221struct JsonRpcResponse {
222 jsonrpc: String,
223 #[serde(skip_serializing_if = "Option::is_none")]
224 result: Option<Value>,
225 #[serde(skip_serializing_if = "Option::is_none")]
226 error: Option<JsonRpcError>,
227 id: Option<Value>,
228}
229
230#[derive(Debug, Serialize)]
232struct JsonRpcError {
233 code: i32,
234 message: String,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 data: Option<Value>,
237}
238
239impl McpServer {
240 pub fn new(config: McpConfig) -> Self {
242 let consciousness = Arc::new(tokio::sync::Mutex::new(ConsciousnessManager::new_silent()));
244
245 let context = Arc::new(McpContext {
246 cache: Arc::new(AnalysisCache::new(config.cache_ttl)),
247 config: Arc::new(config),
248 permissions: Arc::new(tokio::sync::Mutex::new(PermissionCache::new())),
249 sessions: Arc::new(SessionManager::new()),
250 assistant: Arc::new(McpAssistant::new()),
251 consciousness: consciousness.clone(),
252 dashboard_bridge: None,
253 });
254
255 Self {
256 context,
257 consciousness,
258 }
259 }
260
261 pub async fn run_stdio(&self) -> Result<()> {
263 let stdin = io::stdin();
264 let stdout = io::stdout();
265 let mut reader = BufReader::new(stdin);
266 let mut stdout = stdout.lock();
267
268 {
270 let mut consciousness = self.consciousness.lock().await;
271 let _ = consciousness.restore_silent(); }
273
274 if should_show_startup_messages() {
278 eprintln!(
279 "<!-- Smart Tree MCP server v{} started -->",
280 env!("CARGO_PKG_VERSION")
281 );
282 eprintln!("<!-- Protocol: MCP v1.0 -->");
283 }
284
285 loop {
286 let mut line = String::new();
287 match reader.read_line(&mut line) {
288 Ok(0) => break, Ok(_) => {
290 let line = line.trim();
291 if line.is_empty() {
292 continue;
293 }
294
295 match self.handle_request(line).await {
296 Ok(response) => {
297 if !response.is_empty() {
299 writeln!(stdout, "{}", response)?;
300 stdout.flush()?;
301 }
302 }
303 Err(e) => {
304 if should_show_startup_messages() {
305 eprintln!("Error handling request: {e}");
306 }
307 let error_response = json!({
308 "jsonrpc": "2.0",
309 "error": {
310 "code": -32603,
311 "message": e.to_string()
312 },
313 "id": null
314 });
315 writeln!(stdout, "{}", error_response)?;
316 stdout.flush()?;
317 }
318 }
319 }
320 Err(e) => {
321 if should_show_startup_messages() {
322 eprintln!("Error reading input: {e}");
323 }
324 break;
325 }
326 }
327 }
328
329 if should_show_startup_messages() {
330 eprintln!("Smart Tree MCP server stopped");
331 }
332 Ok(())
333 }
334
335 async fn handle_request(&self, request_str: &str) -> Result<String> {
337 let request: JsonRpcRequest =
339 serde_json::from_str(request_str).context("Failed to parse JSON-RPC request")?;
340
341 if let Some(ref params) = request.params {
343 compression_manager::check_client_compression_support(params);
344 }
345
346 let is_notification = request.id.is_none();
348
349 if is_notification && request.method == "notifications/initialized" {
351 if should_show_startup_messages() {
353 eprintln!("Received notification: notifications/initialized");
354 }
355 return Ok(String::new()); }
357
358 if is_notification && request.method == "logging/setLevel" {
360 if should_show_startup_messages() {
362 let level = request
363 .params
364 .as_ref()
365 .and_then(|p| p.get("level"))
366 .and_then(|v| v.as_str())
367 .unwrap_or("unspecified");
368 eprintln!("Received logging/setLevel notification: level={}", level);
369 }
370 return Ok(String::new()); }
372
373 let result = match request.method.as_str() {
375 "initialize" => {
376 if std::env::var("ST_SESSION_AWARE").is_ok() {
378 handle_session_aware_initialize(request.params, self.context.clone()).await
379 } else {
380 handle_initialize(request.params, self.context.clone()).await
381 }
382 }
383 "session/negotiate" => {
384 handle_negotiate_session(request.params, self.context.clone()).await
385 }
386 "tools/list" => {
387 if self.context.config.use_consolidated_tools {
388 handle_consolidated_tools_list(request.params, self.context.clone()).await
389 } else {
390 handle_tools_list(request.params, self.context.clone()).await
391 }
392 }
393 "tools/call" => {
394 if self.context.config.use_consolidated_tools {
395 handle_consolidated_tools_call(
396 request.params.unwrap_or(json!({})),
397 self.context.clone(),
398 )
399 .await
400 } else {
401 handle_tools_call(request.params.unwrap_or(json!({})), self.context.clone())
402 .await
403 }
404 }
405 "resources/list" => handle_resources_list(request.params, self.context.clone()).await,
406 "resources/read" => {
407 handle_resources_read(request.params.unwrap_or(json!({})), self.context.clone())
408 .await
409 }
410 "prompts/list" => {
411 prompts_enhanced::handle_prompts_list(request.params, self.context.clone()).await
413 }
414 "prompts/get" => {
415 prompts_enhanced::handle_prompts_get(
416 request.params.unwrap_or(json!({})),
417 self.context.clone(),
418 )
419 .await
420 }
421 "notifications/cancelled" => {
422 if is_notification {
424 if should_show_startup_messages() {
425 eprintln!("Received notification: notifications/cancelled");
426 }
427 return Ok(String::new());
428 }
429 handle_cancelled(request.params, self.context.clone()).await
430 }
431 _ => Err(anyhow::anyhow!("Method not found: {}", request.method)),
432 };
433
434 if is_notification {
436 if result.is_err() && should_show_startup_messages() {
438 eprintln!(
439 "Received unknown notification: {} (notifications don't return errors)",
440 request.method
441 );
442 }
443 return Ok(String::new());
444 }
445
446 let response = match result {
448 Ok(result) => JsonRpcResponse {
449 jsonrpc: "2.0".to_string(),
450 result: Some(result),
451 error: None,
452 id: request.id,
453 },
454 Err(e) => JsonRpcResponse {
455 jsonrpc: "2.0".to_string(),
456 result: None,
457 error: Some(JsonRpcError {
458 code: -32603,
459 message: e.to_string(),
460 data: None,
461 }),
462 id: request.id,
463 },
464 };
465
466 let mut response_value = serde_json::to_value(&response)?;
468 compression_manager::smart_compress_mcp_response(&mut response_value)?;
469
470 Ok(serde_json::to_string(&response_value)?)
471 }
472}
473
474async fn handle_initialize(params: Option<Value>, _ctx: Arc<McpContext>) -> Result<Value> {
477 if let Some(params) = params {
479 compression_manager::check_client_compression_support(¶ms);
480 }
481
482 let update_info = check_for_mcp_updates().await;
484
485 let compression_test = compression_manager::create_compression_test();
487
488 Ok(json!({
489 "protocolVersion": "2025-06-18",
490 "capabilities": {
491 "tools": {
492 "listChanged": false
493 },
494 "resources": {
495 "subscribe": false,
496 "listChanged": false
497 },
498 "prompts": {
499 "listChanged": false
500 },
501 "logging": {}
502 },
503 "serverInfo": {
504 "name": "smart-tree",
505 "version": env!("CARGO_PKG_VERSION"),
506 "vendor": "8b-is",
507 "description": "Smart Tree v5 - NOW WITH COMPRESSION HINTS! 🗜️ Use compress:true for 80% smaller outputs. For massive codebases, use mode:'quantum' for 100x compression!",
508 "homepage": env!("CARGO_PKG_REPOSITORY"),
509 "features": [
510 "quantum-compression",
511 "mcp-optimization",
512 "content-search",
513 "streaming",
514 "caching",
515 "emotional-mode",
516 "auto-compression-hints"
517 ],
518 "compression_hint": "💡 Always add compress:true to analyze tools for optimal context usage!",
519 "update_info": update_info,
520 "compression_test": compression_test
521 }
522 }))
523}
524
525async fn handle_cancelled(params: Option<Value>, _ctx: Arc<McpContext>) -> Result<Value> {
530 let request_id = params
532 .as_ref()
533 .and_then(|p| p.get("requestId"))
534 .and_then(|id| id.as_str())
535 .unwrap_or("unknown");
536
537 if should_show_startup_messages() {
539 eprintln!("[MCP] Request cancelled: {}", request_id);
540 }
541
542 Ok(json!({
544 "acknowledged": true,
545 "request_id": request_id,
546 "message": "Request cancellation acknowledged"
547 }))
548}
549
550async fn handle_consolidated_tools_list(
552 _params: Option<Value>,
553 _ctx: Arc<McpContext>,
554) -> Result<Value> {
555 let tools = tools_consolidated_enhanced::get_enhanced_consolidated_tools();
557
558 let welcome = tools_consolidated_enhanced::get_welcome_message();
560
561 Ok(json!({
562 "tools": tools,
563 "_welcome": welcome
564 }))
565}
566
567async fn handle_consolidated_tools_call(params: Value, ctx: Arc<McpContext>) -> Result<Value> {
569 let tool_name = params["name"]
570 .as_str()
571 .ok_or_else(|| anyhow::anyhow!("Missing tool name"))?;
572 let args = params.get("arguments").cloned();
573
574 let result = tools_consolidated_enhanced::dispatch_consolidated_tool(tool_name, args, ctx).await?;
576
577 let stringified = serde_json::to_string(&result)?;
579 if stringified.len() > 50_000 {
580 return Ok(json!({
581 "content": [{
582 "type": "text",
583 "text": format!("⚠️ ERROR: Tool '{}' response was too large to return ({} bytes, max 50,000). The operation succeeded, but returning the data would overwhelm your context window.\n\nPlease use the 'limit' and 'offset' parameters to paginate through the results, or narrow the search parameters.", tool_name, stringified.len())
584 }]
585 }));
586 }
587
588 Ok(result)
589}
590
591async fn check_for_mcp_updates() -> Value {
593 let flags = crate::feature_flags::features();
595 if flags.privacy_mode || flags.disable_external_connections {
596 return json!(null);
597 }
598
599 if std::env::var("SMART_TREE_NO_UPDATE_CHECK").is_ok() {
601 return json!(null);
602 }
603
604 let platform = std::env::consts::OS;
606 let arch = std::env::consts::ARCH;
607 let current_version = env!("CARGO_PKG_VERSION");
608
609 let timeout_duration = tokio::time::Duration::from_secs(2);
611
612 let result = tokio::time::timeout(timeout_duration, async {
613 let client = reqwest::Client::builder()
615 .timeout(std::time::Duration::from_secs(2))
616 .build()
617 .ok()?;
618
619 let api_url = std::env::var("SMART_TREE_FEEDBACK_API")
620 .unwrap_or_else(|_| "https://f.8b.is".to_string());
621
622 let check_url = format!(
624 "{}/mcp/check?version={}&platform={}&arch={}",
625 api_url, current_version, platform, arch
626 );
627
628 let response = client.get(&check_url).send().await.ok()?;
629
630 if !response.status().is_success() {
631 return None;
632 }
633
634 response.json::<Value>().await.ok()
635 })
636 .await;
637
638 match result {
639 Ok(Some(update_data)) => {
640 if update_data["update_available"].as_bool().unwrap_or(false) {
642 json!({
643 "available": true,
644 "latest_version": update_data["latest_version"],
645 "new_features": update_data["new_features"],
646 "message": update_data["message"]
647 })
648 } else {
649 json!({
650 "available": false,
651 "message": "You're running the latest version!"
652 })
653 }
654 }
655 _ => json!(null), }
657}
658
659pub fn is_path_allowed(path: &Path, config: &McpConfig) -> bool {
661 for blocked in &config.blocked_paths {
663 if path.starts_with(blocked) {
664 return false;
665 }
666 }
667
668 if config.allowed_paths.is_empty() {
670 return true;
671 }
672
673 for allowed in &config.allowed_paths {
675 if path.starts_with(allowed) {
676 return true;
677 }
678 }
679
680 false
681}
682
683pub fn load_config() -> Result<McpConfig> {
685 let config_path = dirs::home_dir()
686 .map(|d| d.join(".st_bumpers").join("mcp-config.toml"))
687 .unwrap_or_else(|| PathBuf::from(".st_bumpers/mcp-config.toml"));
688
689 if config_path.exists() {
690 let config_str =
691 std::fs::read_to_string(&config_path).context("Failed to read MCP config file")?;
692 toml::from_str(&config_str).context("Failed to parse MCP config file")
693 } else {
694 Ok(McpConfig::default())
695 }
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701
702 #[tokio::test]
703 async fn test_logging_setlevel_notification() {
704 let config = McpConfig::default();
705 let server = McpServer::new(config);
706
707 let request = r#"{"jsonrpc":"2.0","method":"logging/setLevel"}"#;
709 let response = server.handle_request(request).await.unwrap();
710 assert_eq!(response, "", "Notification should return empty response");
711
712 let request_with_level =
714 r#"{"jsonrpc":"2.0","method":"logging/setLevel","params":{"level":"debug"}}"#;
715 let response_with_level = server.handle_request(request_with_level).await.unwrap();
716 assert_eq!(
717 response_with_level, "",
718 "Notification with params should return empty response"
719 );
720 }
721
722 #[tokio::test]
723 async fn test_notifications_initialized() {
724 let config = McpConfig::default();
725 let server = McpServer::new(config);
726
727 let request = r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#;
729 let response = server.handle_request(request).await.unwrap();
730 assert_eq!(
731 response, "",
732 "notifications/initialized should return empty response"
733 );
734 }
735
736 #[tokio::test]
737 async fn test_unknown_notification() {
738 let config = McpConfig::default();
739 let server = McpServer::new(config);
740
741 let request = r#"{"jsonrpc":"2.0","method":"notifications/unknown"}"#;
743 let response = server.handle_request(request).await.unwrap();
744 assert_eq!(
745 response, "",
746 "Unknown notification should return empty response"
747 );
748 }
749}