use crate::compression_manager;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::io::{self, BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[inline]
pub fn fmt_num(n: usize, hex: bool) -> String {
if hex {
format!("{:X}", n)
} else {
n.to_string()
}
}
#[inline]
pub fn fmt_num64(n: u64, hex: bool) -> String {
if hex {
format!("{:X}", n)
} else {
n.to_string()
}
}
pub fn fmt_size(bytes: u64, hex: bool) -> String {
if bytes < 1024 {
if hex {
format!("{}B", fmt_num64(bytes, true))
} else {
format!("{}B", bytes)
}
} else if bytes < 1024 * 1024 {
format!("{:.1}K", bytes as f64 / 1024.0)
} else if bytes < 1024 * 1024 * 1024 {
format!("{:.1}M", bytes as f64 / (1024.0 * 1024.0))
} else {
format!("{:.1}G", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
}
}
#[inline]
pub fn fmt_line(n: usize, hex: bool) -> String {
if hex {
format!("{:>4X}", n)
} else {
format!("{:>4}", n)
}
}
pub mod assistant;
pub mod cache;
pub mod consciousness;
pub mod context_absorber;
mod context_tools;
pub mod dashboard_bridge;
mod enhanced_tool_descriptions;
mod git_memory_integration;
mod helpers;
mod hook_tools;
mod negotiation;
pub mod permissions;
mod proactive_assistant;
mod prompts;
mod prompts_enhanced;
mod resources;
pub mod session;
pub mod smart_background_searcher;
pub mod smart_edit;
mod smart_edit_diff_viewer;
pub mod smart_project_detector;
mod sse;
mod theme_tools;
mod tools;
mod tools_consolidated;
pub mod tools_consolidated_enhanced;
pub mod unified_watcher;
pub mod wave_memory;
use assistant::*;
use cache::*;
use consciousness::*;
use negotiation::*;
use permissions::*;
#[allow(unused_imports)]
use prompts::*;
#[allow(unused_imports)]
use prompts_enhanced::*;
use resources::*;
use session::*;
use tools::*;
fn should_show_startup_messages() -> bool {
use std::env;
if let Ok(val) = env::var("MCP_DEBUG") {
if val == "1" || val.to_lowercase() == "true" {
return true;
}
}
if let Ok(val) = env::var("ST_MCP_VERBOSE") {
if val == "1" || val.to_lowercase() == "true" {
return true;
}
}
false
}
pub struct McpServer {
context: Arc<McpContext>,
consciousness: Arc<tokio::sync::Mutex<ConsciousnessManager>>,
}
#[derive(Clone)]
pub struct McpContext {
pub cache: Arc<AnalysisCache>,
pub config: Arc<McpConfig>,
pub permissions: Arc<tokio::sync::Mutex<PermissionCache>>,
pub sessions: Arc<SessionManager>,
pub assistant: Arc<McpAssistant>,
pub consciousness: Arc<tokio::sync::Mutex<ConsciousnessManager>>,
pub dashboard_bridge: Option<dashboard_bridge::DashboardBridge>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpConfig {
pub cache_enabled: bool,
pub cache_ttl: u64,
pub max_cache_size: usize,
pub allowed_paths: Vec<PathBuf>,
pub blocked_paths: Vec<PathBuf>,
pub use_consolidated_tools: bool,
pub hex_numbers: bool,
}
impl Default for McpConfig {
fn default() -> Self {
Self {
cache_enabled: true,
cache_ttl: 300, max_cache_size: 100 * 1024 * 1024, allowed_paths: vec![],
blocked_paths: vec![
PathBuf::from("/etc"),
PathBuf::from("/sys"),
PathBuf::from("/proc"),
],
use_consolidated_tools: true, hex_numbers: true, }
}
}
#[derive(Debug, Deserialize)]
struct JsonRpcRequest {
#[allow(dead_code)]
jsonrpc: String,
method: String,
params: Option<Value>,
id: Option<Value>,
}
#[derive(Debug, Serialize)]
struct JsonRpcResponse {
jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<JsonRpcError>,
id: Option<Value>,
}
#[derive(Debug, Serialize)]
struct JsonRpcError {
code: i32,
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<Value>,
}
impl McpServer {
pub fn new(config: McpConfig) -> Self {
let consciousness = Arc::new(tokio::sync::Mutex::new(ConsciousnessManager::new_silent()));
let context = Arc::new(McpContext {
cache: Arc::new(AnalysisCache::new(config.cache_ttl)),
config: Arc::new(config),
permissions: Arc::new(tokio::sync::Mutex::new(PermissionCache::new())),
sessions: Arc::new(SessionManager::new()),
assistant: Arc::new(McpAssistant::new()),
consciousness: consciousness.clone(),
dashboard_bridge: None,
});
Self {
context,
consciousness,
}
}
pub async fn run_stdio(&self) -> Result<()> {
let stdin = io::stdin();
let stdout = io::stdout();
let mut reader = BufReader::new(stdin);
let mut stdout = stdout.lock();
{
let mut consciousness = self.consciousness.lock().await;
let _ = consciousness.restore_silent(); }
if should_show_startup_messages() {
eprintln!(
"<!-- Smart Tree MCP server v{} started -->",
env!("CARGO_PKG_VERSION")
);
eprintln!("<!-- Protocol: MCP v1.0 -->");
}
loop {
let mut line = String::new();
match reader.read_line(&mut line) {
Ok(0) => break, Ok(_) => {
let line = line.trim();
if line.is_empty() {
continue;
}
match self.handle_request(line).await {
Ok(response) => {
if !response.is_empty() {
writeln!(stdout, "{}", response)?;
stdout.flush()?;
}
}
Err(e) => {
if should_show_startup_messages() {
eprintln!("Error handling request: {e}");
}
let error_response = json!({
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": e.to_string()
},
"id": null
});
writeln!(stdout, "{}", error_response)?;
stdout.flush()?;
}
}
}
Err(e) => {
if should_show_startup_messages() {
eprintln!("Error reading input: {e}");
}
break;
}
}
}
if should_show_startup_messages() {
eprintln!("Smart Tree MCP server stopped");
}
Ok(())
}
async fn handle_request(&self, request_str: &str) -> Result<String> {
let request: JsonRpcRequest =
serde_json::from_str(request_str).context("Failed to parse JSON-RPC request")?;
if let Some(ref params) = request.params {
compression_manager::check_client_compression_support(params);
}
let is_notification = request.id.is_none();
if is_notification && request.method == "notifications/initialized" {
if should_show_startup_messages() {
eprintln!("Received notification: notifications/initialized");
}
return Ok(String::new()); }
if is_notification && request.method == "logging/setLevel" {
if should_show_startup_messages() {
let level = request
.params
.as_ref()
.and_then(|p| p.get("level"))
.and_then(|v| v.as_str())
.unwrap_or("unspecified");
eprintln!("Received logging/setLevel notification: level={}", level);
}
return Ok(String::new()); }
let result = match request.method.as_str() {
"initialize" => {
if std::env::var("ST_SESSION_AWARE").is_ok() {
handle_session_aware_initialize(request.params, self.context.clone()).await
} else {
handle_initialize(request.params, self.context.clone()).await
}
}
"session/negotiate" => {
handle_negotiate_session(request.params, self.context.clone()).await
}
"tools/list" => {
if self.context.config.use_consolidated_tools {
handle_consolidated_tools_list(request.params, self.context.clone()).await
} else {
handle_tools_list(request.params, self.context.clone()).await
}
}
"tools/call" => {
if self.context.config.use_consolidated_tools {
handle_consolidated_tools_call(
request.params.unwrap_or(json!({})),
self.context.clone(),
)
.await
} else {
handle_tools_call(request.params.unwrap_or(json!({})), self.context.clone())
.await
}
}
"resources/list" => handle_resources_list(request.params, self.context.clone()).await,
"resources/read" => {
handle_resources_read(request.params.unwrap_or(json!({})), self.context.clone())
.await
}
"prompts/list" => {
prompts_enhanced::handle_prompts_list(request.params, self.context.clone()).await
}
"prompts/get" => {
prompts_enhanced::handle_prompts_get(
request.params.unwrap_or(json!({})),
self.context.clone(),
)
.await
}
"notifications/cancelled" => {
if is_notification {
if should_show_startup_messages() {
eprintln!("Received notification: notifications/cancelled");
}
return Ok(String::new());
}
handle_cancelled(request.params, self.context.clone()).await
}
_ => Err(anyhow::anyhow!("Method not found: {}", request.method)),
};
if is_notification {
if result.is_err() && should_show_startup_messages() {
eprintln!(
"Received unknown notification: {} (notifications don't return errors)",
request.method
);
}
return Ok(String::new());
}
let response = match result {
Ok(result) => JsonRpcResponse {
jsonrpc: "2.0".to_string(),
result: Some(result),
error: None,
id: request.id,
},
Err(e) => JsonRpcResponse {
jsonrpc: "2.0".to_string(),
result: None,
error: Some(JsonRpcError {
code: -32603,
message: e.to_string(),
data: None,
}),
id: request.id,
},
};
let mut response_value = serde_json::to_value(&response)?;
compression_manager::smart_compress_mcp_response(&mut response_value)?;
Ok(serde_json::to_string(&response_value)?)
}
}
async fn handle_initialize(params: Option<Value>, _ctx: Arc<McpContext>) -> Result<Value> {
if let Some(params) = params {
compression_manager::check_client_compression_support(¶ms);
}
let update_info = check_for_mcp_updates().await;
let compression_test = compression_manager::create_compression_test();
Ok(json!({
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": false
},
"resources": {
"subscribe": false,
"listChanged": false
},
"prompts": {
"listChanged": false
},
"logging": {}
},
"serverInfo": {
"name": "smart-tree",
"version": env!("CARGO_PKG_VERSION"),
"vendor": "8b-is",
"description": "Smart Tree v5 - NOW WITH COMPRESSION HINTS! 🗜️ Use compress:true for 80% smaller outputs. For massive codebases, use mode:'quantum' for 100x compression!",
"homepage": env!("CARGO_PKG_REPOSITORY"),
"features": [
"quantum-compression",
"mcp-optimization",
"content-search",
"streaming",
"caching",
"emotional-mode",
"auto-compression-hints"
],
"compression_hint": "💡 Always add compress:true to analyze tools for optimal context usage!",
"update_info": update_info,
"compression_test": compression_test
}
}))
}
async fn handle_cancelled(params: Option<Value>, _ctx: Arc<McpContext>) -> Result<Value> {
let request_id = params
.as_ref()
.and_then(|p| p.get("requestId"))
.and_then(|id| id.as_str())
.unwrap_or("unknown");
if should_show_startup_messages() {
eprintln!("[MCP] Request cancelled: {}", request_id);
}
Ok(json!({
"acknowledged": true,
"request_id": request_id,
"message": "Request cancellation acknowledged"
}))
}
async fn handle_consolidated_tools_list(
_params: Option<Value>,
_ctx: Arc<McpContext>,
) -> Result<Value> {
let tools = tools_consolidated_enhanced::get_enhanced_consolidated_tools();
let welcome = tools_consolidated_enhanced::get_welcome_message();
Ok(json!({
"tools": tools,
"_welcome": welcome
}))
}
async fn handle_consolidated_tools_call(params: Value, ctx: Arc<McpContext>) -> Result<Value> {
let tool_name = params["name"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("Missing tool name"))?;
let args = params.get("arguments").cloned();
let result = tools_consolidated_enhanced::dispatch_consolidated_tool(tool_name, args, ctx).await?;
let stringified = serde_json::to_string(&result)?;
if stringified.len() > 50_000 {
return Ok(json!({
"content": [{
"type": "text",
"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())
}]
}));
}
Ok(result)
}
async fn check_for_mcp_updates() -> Value {
let flags = crate::feature_flags::features();
if flags.privacy_mode || flags.disable_external_connections {
return json!(null);
}
if std::env::var("SMART_TREE_NO_UPDATE_CHECK").is_ok() {
return json!(null);
}
let platform = std::env::consts::OS;
let arch = std::env::consts::ARCH;
let current_version = env!("CARGO_PKG_VERSION");
let timeout_duration = tokio::time::Duration::from_secs(2);
let result = tokio::time::timeout(timeout_duration, async {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(2))
.build()
.ok()?;
let api_url = std::env::var("SMART_TREE_FEEDBACK_API")
.unwrap_or_else(|_| "https://f.8b.is".to_string());
let check_url = format!(
"{}/mcp/check?version={}&platform={}&arch={}",
api_url, current_version, platform, arch
);
let response = client.get(&check_url).send().await.ok()?;
if !response.status().is_success() {
return None;
}
response.json::<Value>().await.ok()
})
.await;
match result {
Ok(Some(update_data)) => {
if update_data["update_available"].as_bool().unwrap_or(false) {
json!({
"available": true,
"latest_version": update_data["latest_version"],
"new_features": update_data["new_features"],
"message": update_data["message"]
})
} else {
json!({
"available": false,
"message": "You're running the latest version!"
})
}
}
_ => json!(null), }
}
pub fn is_path_allowed(path: &Path, config: &McpConfig) -> bool {
for blocked in &config.blocked_paths {
if path.starts_with(blocked) {
return false;
}
}
if config.allowed_paths.is_empty() {
return true;
}
for allowed in &config.allowed_paths {
if path.starts_with(allowed) {
return true;
}
}
false
}
pub fn load_config() -> Result<McpConfig> {
let config_path = dirs::home_dir()
.map(|d| d.join(".st_bumpers").join("mcp-config.toml"))
.unwrap_or_else(|| PathBuf::from(".st_bumpers/mcp-config.toml"));
if config_path.exists() {
let config_str =
std::fs::read_to_string(&config_path).context("Failed to read MCP config file")?;
toml::from_str(&config_str).context("Failed to parse MCP config file")
} else {
Ok(McpConfig::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_logging_setlevel_notification() {
let config = McpConfig::default();
let server = McpServer::new(config);
let request = r#"{"jsonrpc":"2.0","method":"logging/setLevel"}"#;
let response = server.handle_request(request).await.unwrap();
assert_eq!(response, "", "Notification should return empty response");
let request_with_level =
r#"{"jsonrpc":"2.0","method":"logging/setLevel","params":{"level":"debug"}}"#;
let response_with_level = server.handle_request(request_with_level).await.unwrap();
assert_eq!(
response_with_level, "",
"Notification with params should return empty response"
);
}
#[tokio::test]
async fn test_notifications_initialized() {
let config = McpConfig::default();
let server = McpServer::new(config);
let request = r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#;
let response = server.handle_request(request).await.unwrap();
assert_eq!(
response, "",
"notifications/initialized should return empty response"
);
}
#[tokio::test]
async fn test_unknown_notification() {
let config = McpConfig::default();
let server = McpServer::new(config);
let request = r#"{"jsonrpc":"2.0","method":"notifications/unknown"}"#;
let response = server.handle_request(request).await.unwrap();
assert_eq!(
response, "",
"Unknown notification should return empty response"
);
}
}