diff --git a/Cargo.lock b/Cargo.lock index 0a20190..b2591b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -473,6 +473,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.172" @@ -501,6 +507,15 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "mcp-discovery" version = "0.1.3" @@ -520,6 +535,8 @@ dependencies = [ "tempfile", "thiserror", "tokio", + "tracing", + "tracing-subscriber", "unicode-width", ] @@ -549,6 +566,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-modular" version = "0.6.1" @@ -579,6 +606,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.3" @@ -706,8 +739,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -718,9 +760,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -856,6 +904,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.5" @@ -949,6 +1006,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tokio" version = "1.44.2" @@ -978,6 +1045,67 @@ dependencies = [ "syn", ] +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "typenum" version = "1.18.0" @@ -1014,6 +1142,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" @@ -1044,6 +1178,28 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 5501451..dbafb8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,13 @@ thiserror = { version = "2.0" } handlebars = "6.3" html-escape = "0.2" regex = "1.1" -path-clean = "1.0.1" - +path-clean = "1.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = [ + "env-filter", + "std", + "fmt", +] } [dev-dependencies] tempfile = "3" diff --git a/src/cli.rs b/src/cli.rs index 83c2248..fd14aad 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,5 @@ use std::{ + fmt::Display, io::{self, ErrorKind}, path::PathBuf, }; @@ -15,6 +16,28 @@ pub enum Template { Txt, } +#[derive(Debug, Clone, ValueEnum, PartialEq)] +#[allow(non_camel_case_types)] +pub enum LogLevel { + error, + warn, + info, + debug, + trace, +} + +impl Display for LogLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LogLevel::error => write!(f, "error"), + LogLevel::warn => write!(f, "warn"), + LogLevel::info => write!(f, "info"), + LogLevel::debug => write!(f, "debug"), + LogLevel::trace => write!(f, "trace"), + } + } +} + #[derive(Subcommand, Debug)] pub enum DiscoveryCommand { /// Displays MCP server capability details in the terminal. @@ -47,6 +70,9 @@ pub struct WriteOptions { )] pub template_string: Option, + /// Specifies the logging level for the application (default: info) + #[arg(long, short)] + pub log_level: Option, /// Command and arguments to launch the MCP server. #[arg( value_name = "MCP Launch Command", @@ -98,6 +124,10 @@ conflicts_with_all = ["template", "template_string"])] )] pub template_string: Option, + /// Specifies the logging level for the application (default: info) + #[arg(long, short)] + pub log_level: Option, + /// Command and arguments to launch the MCP server. #[arg( value_name = "MCP Launch Command", @@ -127,6 +157,14 @@ impl DiscoveryCommand { DiscoveryCommand::Print(print_args) => &print_args.mcp_server_cmd, } } + + pub fn log_level(&self) -> &Option { + match self { + DiscoveryCommand::Create(create_options) => &create_options.log_level, + DiscoveryCommand::Update(update_options) => &update_options.log_level, + DiscoveryCommand::Print(print_args) => &print_args.log_level, + } + } } #[derive(Parser, Debug)] @@ -156,6 +194,10 @@ pub struct CommandArguments { )] pub template_string: Option, + /// Specifies the logging level for the application (default: info) + #[arg(long, short)] + pub log_level: Option, + /// Command and arguments to launch the MCP server. #[arg( value_name = "MCP Launch Command", diff --git a/src/lib.rs b/src/lib.rs index 88bb8ba..95f7ff6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ pub mod std_output; pub mod templates; pub mod utils; -pub use cli::{CommandArguments, DiscoveryCommand, PrintOptions, Template, WriteOptions}; +pub use cli::{CommandArguments, DiscoveryCommand, LogLevel, PrintOptions, Template, WriteOptions}; use colored::Colorize; use error::{DiscoveryError, DiscoveryResult}; use render_template::{detect_render_markers, render_template}; @@ -39,6 +39,16 @@ pub struct McpCapabilities { pub experimental: bool, } +impl Display for McpCapabilities { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "tools:{}, prompts:{}, resources:{}, logging:{}, experimental:{}", + self.tools, self.prompts, self.resources, self.logging, self.experimental + ) + } +} + #[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)] pub enum ParamTypes { Primitive(String), @@ -177,6 +187,8 @@ impl McpDiscovery { } pub async fn create_document(&self, create_options: &WriteOptions) -> DiscoveryResult<()> { + tracing::trace!("Creating '{}' ", create_options.filename.to_string_lossy()); + let server_info = self .server_info .as_ref() @@ -188,10 +200,25 @@ impl McpDiscovery { tokio::fs::write(&create_options.filename, content).await?; + tracing::info!( + "File '{}' was created successfully.", + create_options.filename.to_string_lossy(), + ); + tracing::info!( + "Full path: {}", + create_options + .filename + .canonicalize() + .map(|f| f.to_string_lossy().into_owned()) + .unwrap_or_else(|_| create_options.filename.to_string_lossy().into_owned()) + ); + Ok(()) } pub async fn update_document(&self, update_options: &WriteOptions) -> DiscoveryResult<()> { + tracing::trace!("Updating '{}' ", update_options.filename.to_string_lossy()); + let server_info = self .server_info .as_ref() @@ -222,7 +249,10 @@ impl McpDiscovery { let updated_content = content_lines.join(&template_markers.line_ending); std::fs::write(&update_options.filename, updated_content)?; - + tracing::info!( + "File '{}' was updated successfully.", + update_options.filename.to_string_lossy() + ); Ok(()) } @@ -370,6 +400,8 @@ impl McpDiscovery { return Ok(None); } + tracing::trace!("retrieving tools..."); + let tools_result = client .list_tools(Some(ListToolsRequestParams::default())) .await? @@ -399,6 +431,7 @@ impl McpDiscovery { if !client.server_has_prompts().unwrap_or(false) { return Ok(None); } + tracing::trace!("retrieving prompts..."); let prompts: Vec = client .list_prompts(Some(ListPromptsRequestParams::default())) @@ -416,6 +449,8 @@ impl McpDiscovery { return Ok(None); } + tracing::trace!("retrieving resources..."); + let resources: Vec = client .list_resources(Some(ListResourcesRequestParams::default())) .await? @@ -432,13 +467,15 @@ impl McpDiscovery { return Ok(None); } + tracing::trace!("retrieving resource templates..."); + let result = client .list_resource_templates(Some(ListResourceTemplatesRequestParams::default())) .await; match result { Ok(data) => Ok(Some(data.resource_templates)), Err(err) => { - eprintln!("Unable to retrieve resource templates : {}", err); + tracing::trace!("Unable to retrieve resource templates : {}", err); Ok(None) } } @@ -451,6 +488,12 @@ impl McpDiscovery { .server_version() .ok_or(DiscoveryError::ServerNotInitialized)?; + tracing::trace!( + "Server: {} v{}", + server_version.name, + server_version.version + ); + let capabilities: McpCapabilities = McpCapabilities { tools: client .server_has_tools() @@ -469,6 +512,8 @@ impl McpDiscovery { .ok_or(DiscoveryError::ServerNotInitialized)?, }; + tracing::trace!("Capabilities: {}", capabilities); + let tools = self.tools(Arc::clone(&client)).await?; let prompts = self.get_prompts(Arc::clone(&client)).await?; let resources = self.get_resources(Arc::clone(&client)).await?; @@ -499,8 +544,20 @@ impl McpDiscovery { protocol_version: JSONRPC_VERSION.into(), }; + tracing::trace!( + "Client : {} v{}", + client_details.client_info.name, + client_details.client_info.version + ); + let (mcp_command, mcp_args) = self.options.mcp_launch_command().split_at(1); + tracing::trace!( + "launching command : {} {}", + mcp_command.first().map(String::as_ref).unwrap_or(""), + mcp_args.join(" ") + ); + let transport = StdioTransport::create_with_server_launch( mcp_command.first().unwrap(), mcp_args.into(), @@ -512,8 +569,12 @@ impl McpDiscovery { let client = client_runtime::create_client(client_details, transport, handler); + tracing::trace!("Launching MCP server ..."); + client.clone().start().await?; + tracing::trace!("MCP server started successfully."); + Ok(client) } } diff --git a/src/main.rs b/src/main.rs index 86e649c..68066c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use clap::Parser; use colored::Colorize; - -use mcp_discovery::{CommandArguments, DiscoveryCommand, McpDiscovery, PrintOptions}; +use mcp_discovery::{CommandArguments, DiscoveryCommand, LogLevel, McpDiscovery, PrintOptions}; +use tracing_subscriber::{self, EnvFilter}; #[tokio::main] async fn main() { @@ -14,8 +14,22 @@ async fn main() { template: args.template, template_file: args.template_file, template_string: args.template_string, + log_level: args.log_level, })); + let filter = format!( + "{}={}", + env!("CARGO_PKG_NAME").to_string().replace("-", "_"), + command.log_level().as_ref().unwrap_or(&LogLevel::info) + ); + + let tracing_filter = EnvFilter::try_new(filter).unwrap_or_else(|_| EnvFilter::new("info")); + + tracing_subscriber::fmt() + .with_env_filter(tracing_filter) + .compact() + .init(); + let launch_message = format!( "{} {} ...", "Launching:".bold(), diff --git a/tests/render_template_tests.rs b/tests/render_template_tests.rs index 6ea9075..a2867e6 100644 --- a/tests/render_template_tests.rs +++ b/tests/render_template_tests.rs @@ -112,6 +112,7 @@ fn test_detect_render_markers_valid() { template_file: None, mcp_server_cmd: vec!["mcp-server".to_string()], template_string: None, + log_level: None, }; let server_info = default_mcp_server_info(); let result = detect_render_markers(&options, &server_info); @@ -138,6 +139,7 @@ fn test_detect_render_markers_duplicate_template_start() { template_file: None, mcp_server_cmd: vec!["mcp-server".to_string()], template_string: None, + log_level: None, }; let server_info = default_mcp_server_info(); let result = detect_render_markers(&options, &server_info); @@ -163,6 +165,7 @@ fn test_detect_render_markers_template_outside_render() { template_file: None, mcp_server_cmd: vec!["mcp-server".to_string()], template_string: None, + log_level: None, }; let server_info = default_mcp_server_info(); let result = detect_render_markers(&options, &server_info); @@ -188,6 +191,7 @@ fn test_detect_render_markers_unmatched_template_end() { template_file: None, mcp_server_cmd: vec!["mcp-server".to_string()], template_string: None, + log_level: None, }; let server_info = default_mcp_server_info(); let result = detect_render_markers(&options, &server_info); @@ -214,6 +218,7 @@ fn test_detect_render_markers_conflicting_templates() { template_file: None, mcp_server_cmd: vec!["mcp-server".to_string()], template_string: None, + log_level: None, }; let server_info = default_mcp_server_info(); let result = detect_render_markers(&options, &server_info); diff --git a/tests/test_cli.rs b/tests/test_cli.rs index 662f674..86571bc 100644 --- a/tests/test_cli.rs +++ b/tests/test_cli.rs @@ -113,6 +113,7 @@ fn test_file_options_match_template_builtin() { template_file: None, mcp_server_cmd: vec!["mcp-server".to_string()], template_string: None, + log_level: None, }; let result = file_options.match_template(); @@ -128,6 +129,7 @@ fn test_file_options_match_template_custom() { template_file: Some(PathBuf::from("templates/markdown/markdown_template.md")), mcp_server_cmd: vec!["mcp-server".to_string()], template_string: None, + log_level: None, }; let result = file_options.match_template();