From 748eb7d5dff930d9c043fa6adfbf418d2b07cfe1 Mon Sep 17 00:00:00 2001 From: "Andrei G." Date: Sat, 3 Jan 2026 22:49:48 +0100 Subject: [PATCH 1/4] feat(cli): add --from-config support to introspect command Add the ability to load server configuration from ~/.claude/mcp.json when using the introspect command, matching existing functionality in the generate command. Changes: - Add --from-config flag to introspect command in main.rs - Update introspect::run() to support config loading - Apply security improvements to error messages - Change config loading logs from info! to debug! level - Update all existing tests for new function signature - Bump dependency versions (handlebars, rmcp, schemars, tempfile, tokio) Security improvements: - Remove server list from "not found" error messages - Use user-friendly path (~/.claude/mcp.json) in error messages --- Cargo.lock | 180 +++++++++++----------- Cargo.toml | 10 +- crates/mcp-cli/src/commands/common.rs | 8 +- crates/mcp-cli/src/commands/generate.rs | 4 +- crates/mcp-cli/src/commands/introspect.rs | 30 +++- crates/mcp-cli/src/main.rs | 24 ++- 6 files changed, 150 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index faea9a5..b891c90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,9 +161,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytes" @@ -179,9 +179,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.49" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "shlex", @@ -242,9 +242,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -252,9 +252,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -264,9 +264,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.61" +version = "4.5.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992" +checksum = "4c0da80818b2d95eca9aa614a30783e42f62bf5fdfee24e68cfb960b071ba8d1" dependencies = [ "clap", ] @@ -306,9 +306,9 @@ dependencies = [ [[package]] name = "console" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" dependencies = [ "encode_unicode", "libc", @@ -421,12 +421,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] @@ -445,11 +445,10 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", @@ -470,11 +469,11 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core 0.21.3", + "darling_core 0.23.0", "quote", "syn", ] @@ -617,9 +616,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "fnv" @@ -768,9 +767,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.3.2" +version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" +checksum = "9b3f9296c208515b87bd915a2f5d1163d4b3f863ba83337d7713cf478055948e" dependencies = [ "derive_builder", "log", @@ -851,9 +850,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" @@ -873,15 +872,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags", "libc", @@ -1169,15 +1168,15 @@ dependencies = [ [[package]] name = "pastey" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d6c094ee800037dff99e02cab0eaf3142826586742a270ab3d7a62656bd27a" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", "ucd-trie", @@ -1185,9 +1184,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -1195,9 +1194,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", @@ -1208,9 +1207,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2", @@ -1267,18 +1266,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" dependencies = [ "unicode-ident", ] [[package]] name = "process-wrap" -version = "8.2.1" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ef4f2f0422f23a82ec9f628ea2acd12871c81a9362b02c43c1aa86acfc3ba1" +checksum = "5e5fd83ab7fa55fd06f5e665e3fc52b8bca451c0486b8ea60ad649cd1c10a5da" dependencies = [ "futures", "indexmap", @@ -1423,9 +1422,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rmcp" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b18323edc657390a6ed4d7a9110b0dec2dc3ed128eb2a123edfbafabdbddc5" +checksum = "528d42f8176e6e5e71ea69182b17d1d0a19a6b3b894b564678b74cd7cab13cfa" dependencies = [ "async-trait", "base64", @@ -1447,11 +1446,11 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75d0a62676bf8c8003c4e3c348e2ceb6a7b3e48323681aaf177fdccdac2ce50" +checksum = "e3f81daaa494eb8e985c9462f7d6ce1ab05e5299f48aafd76cdd3d8b060e6f59" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "serde_json", @@ -1472,9 +1471,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", @@ -1489,12 +1488,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -1506,9 +1499,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "chrono", "dyn-clone", @@ -1521,9 +1514,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301858a4023d78debd2353c7426dc486001bddc91ae31a76fb1f55132f7e2633" +checksum = "4908ad288c5035a8eb12cfdf0d49270def0a268ee162b75eeee0f85d155a7c45" dependencies = [ "proc-macro2", "quote", @@ -1580,22 +1573,22 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -1622,9 +1615,9 @@ dependencies = [ [[package]] name = "shell-words" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shlex" @@ -1634,10 +1627,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -1661,9 +1655,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "21f182278bf2d2bcb3c88b1b08a37df029d71ce3d3ae26168e3c653b213b99d4" dependencies = [ "proc-macro2", "quote", @@ -1672,9 +1666,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", @@ -1730,9 +1724,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -1780,9 +1774,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap", "serde_core", @@ -1795,33 +1789,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1841,9 +1835,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -2341,3 +2335,9 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee2a72b10d087f75fb2e1c2c7343e308fe6970527c22a41caf8372e165ff5c1" diff --git a/Cargo.toml b/Cargo.toml index c02604e..90b4bec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ criterion = "0.8" dhat = "0.3" dialoguer = "0.12" dirs = "6.0" -handlebars = "6.3" +handlebars = "6.4" mcp-codegen = { path = "crates/mcp-codegen" } mcp-core = { path = "crates/mcp-core" } mcp-files = { path = "crates/mcp-files" } @@ -31,14 +31,14 @@ mcp-introspector = { path = "crates/mcp-introspector" } mcp-server = { path = "crates/mcp-server" } rayon = "1.11" regex = "1.12" -rmcp = "0.10" -schemars = "1.1.0" +rmcp = "0.12" +schemars = "1.2" serde = "1.0" serde_json = "1.0" static_assertions = "1.1" -tempfile = "3.23" +tempfile = "3.24" thiserror = "2.0" -tokio = "1.48" +tokio = "1.49" toml = "0.9" tracing = "0.1" tracing-subscriber = "0.3" diff --git a/crates/mcp-cli/src/commands/common.rs b/crates/mcp-cli/src/commands/common.rs index 46f9bc3..1048e03 100644 --- a/crates/mcp-cli/src/commands/common.rs +++ b/crates/mcp-cli/src/commands/common.rs @@ -38,7 +38,7 @@ fn load_mcp_config() -> Result { let config_path = home.join(".claude").join("mcp.json"); let content = std::fs::read_to_string(&config_path) - .with_context(|| format!("failed to read MCP config from {}", config_path.display()))?; + .with_context(|| "failed to read MCP config from ~/.claude/mcp.json")?; let config: McpConfig = serde_json::from_str(&content).context("failed to parse MCP config JSON")?; @@ -74,8 +74,10 @@ pub fn load_server_from_config(name: &str) -> Result<(ServerId, ServerConfig)> { let config = load_mcp_config()?; let server_config = config.mcp_servers.get(name).with_context(|| { - let available: Vec<_> = config.mcp_servers.keys().collect(); - format!("server '{name}' not found in MCP config\nAvailable servers: {available:?}") + format!( + "server '{name}' not found in MCP config at ~/.claude/mcp.json\n\ + Hint: Use 'mcp-execution-cli server list' to see available servers" + ) })?; let id = ServerId::new(name); diff --git a/crates/mcp-cli/src/commands/generate.rs b/crates/mcp-cli/src/commands/generate.rs index 83dadd8..65878e1 100644 --- a/crates/mcp-cli/src/commands/generate.rs +++ b/crates/mcp-cli/src/commands/generate.rs @@ -14,7 +14,7 @@ use mcp_files::FilesBuilder; use mcp_introspector::Introspector; use serde::Serialize; use std::path::PathBuf; -use tracing::{info, warn}; +use tracing::{debug, info, warn}; /// Result of progressive loading code generation. #[derive(Debug, Serialize)] @@ -78,7 +78,7 @@ pub async fn run( ) -> Result { // Build server config: either from mcp.json or from CLI arguments let (server_id, server_config) = if let Some(config_name) = from_config { - info!( + debug!( "Loading server configuration from ~/.claude/mcp.json: {}", config_name ); diff --git a/crates/mcp-cli/src/commands/introspect.rs b/crates/mcp-cli/src/commands/introspect.rs index e2298f6..09a5828 100644 --- a/crates/mcp-cli/src/commands/introspect.rs +++ b/crates/mcp-cli/src/commands/introspect.rs @@ -2,12 +2,12 @@ //! //! Connects to an MCP server and displays its capabilities, tools, and metadata. -use super::common::build_server_config; +use super::common::{build_server_config, load_server_from_config}; use anyhow::{Context, Result}; use mcp_core::cli::{ExitCode, OutputFormat}; use mcp_introspector::{Introspector, ServerInfo, ToolInfo}; use serde::Serialize; -use tracing::info; +use tracing::{debug, info}; /// Result of server introspection. /// @@ -86,7 +86,7 @@ pub struct ToolMetadata { /// /// # Process /// -/// 1. Builds `ServerConfig` from CLI arguments +/// 1. Builds `ServerConfig` from CLI arguments or loads from ~/.claude/mcp.json /// 2. Creates an introspector and connects to the server /// 3. Discovers server capabilities and tools /// 4. Formats the output according to the specified format @@ -94,6 +94,7 @@ pub struct ToolMetadata { /// /// # Arguments /// +/// * `from_config` - Load server config from ~/.claude/mcp.json by name /// * `server` - Server command (binary name or path), None for HTTP/SSE /// * `args` - Arguments to pass to the server command /// * `env` - Environment variables in KEY=VALUE format @@ -121,6 +122,7 @@ pub struct ToolMetadata { /// # async fn example() -> anyhow::Result<()> { /// // Simple server /// let exit_code = introspect::run( +/// None, /// Some("github-mcp-server".to_string()), /// vec!["stdio".to_string()], /// vec![], @@ -135,6 +137,7 @@ pub struct ToolMetadata { /// // HTTP transport /// let exit_code = introspect::run( /// None, +/// None, /// vec![], /// vec![], /// None, @@ -149,6 +152,7 @@ pub struct ToolMetadata { /// ``` #[allow(clippy::too_many_arguments)] pub async fn run( + from_config: Option, server: Option, args: Vec, env: Vec, @@ -159,8 +163,16 @@ pub async fn run( detailed: bool, output_format: OutputFormat, ) -> Result { - // Build ServerConfig from CLI arguments - let (server_id, config) = build_server_config(server, args, env, cwd, http, sse, headers)?; + // Build server config: either from mcp.json or from CLI arguments + let (server_id, config) = if let Some(config_name) = from_config { + debug!( + "Loading server configuration from ~/.claude/mcp.json: {}", + config_name + ); + load_server_from_config(&config_name)? + } else { + build_server_config(server, args, env, cwd, http, sse, headers)? + }; info!("Introspecting server: {}", server_id); info!("Transport: {:?}", config.transport()); @@ -500,6 +512,7 @@ mod tests { #[tokio::test] async fn test_run_server_connection_failure() { let result = run( + None, Some("nonexistent-server-xyz".to_string()), vec![], vec![], @@ -608,6 +621,7 @@ mod tests { async fn test_run_with_text_format() { // Test that Text format output works correctly (compact JSON) let result = run( + None, Some("nonexistent-server".to_string()), vec![], vec![], @@ -628,6 +642,7 @@ mod tests { async fn test_run_with_pretty_format() { // Test that Pretty format output works correctly (colorized) let result = run( + None, Some("nonexistent-server".to_string()), vec![], vec![], @@ -648,6 +663,7 @@ mod tests { async fn test_run_with_detailed_mode() { // Test that detailed mode doesn't cause crashes even with connection failure let result = run( + None, Some("nonexistent-server".to_string()), vec![], vec![], @@ -667,6 +683,7 @@ mod tests { async fn test_run_http_transport() { // Test HTTP transport with invalid URL let result = run( + None, None, vec![], vec![], @@ -688,6 +705,7 @@ mod tests { async fn test_run_sse_transport() { // Test SSE transport with invalid URL let result = run( + None, None, vec![], vec![], @@ -710,6 +728,7 @@ mod tests { // Test all output formats don't cause panics for format in [OutputFormat::Json, OutputFormat::Text, OutputFormat::Pretty] { let result = run( + None, Some("nonexistent".to_string()), vec![], vec![], @@ -731,6 +750,7 @@ mod tests { // Test detailed mode with all output formats for format in [OutputFormat::Json, OutputFormat::Text, OutputFormat::Pretty] { let result = run( + None, Some("nonexistent".to_string()), vec![], vec![], diff --git a/crates/mcp-cli/src/main.rs b/crates/mcp-cli/src/main.rs index 68f086a..fb3a4d6 100644 --- a/crates/mcp-cli/src/main.rs +++ b/crates/mcp-cli/src/main.rs @@ -94,11 +94,31 @@ pub enum Commands { /// --header "Authorization=Bearer ghp_xxx" /// ``` Introspect { + /// Load server configuration from ~/.claude/mcp.json by name + /// + /// When specified, all other server configuration options are ignored. + /// The server must be defined in ~/.claude/mcp.json with matching name. + /// + /// Example mcp.json: + /// ```json + /// { + /// "mcpServers": { + /// "github": { + /// "command": "docker", + /// "args": ["run", "-i", "--rm", "..."], + /// "env": {"GITHUB_PERSONAL_ACCESS_TOKEN": "..."} + /// } + /// } + /// } + /// ``` + #[arg(long = "from-config", conflicts_with_all = ["server", "args", "env", "cwd", "http", "sse"])] + from_config: Option, + /// Server command (binary name or path) /// /// For stdio transport: command to execute (e.g., "docker", "npx", "github-mcp-server") /// Not required when using --http or --sse - #[arg(required_unless_present_any = ["http", "sse"])] + #[arg(required_unless_present_any = ["from_config", "http", "sse"])] server: Option, /// Arguments to pass to the server command @@ -318,6 +338,7 @@ fn init_logging(verbose: bool) -> Result<()> { async fn execute_command(command: Commands, output_format: OutputFormat) -> Result { match command { Commands::Introspect { + from_config, server, args, env, @@ -328,6 +349,7 @@ async fn execute_command(command: Commands, output_format: OutputFormat) -> Resu detailed, } => { commands::introspect::run( + from_config, server, args, env, From cbc323774f8620d1e2c78b1284fa44b7dcb092c6 Mon Sep 17 00:00:00 2001 From: "Andrei G." Date: Sat, 3 Jan 2026 22:55:21 +0100 Subject: [PATCH 2/4] test(cli): add integration tests for --from-config Add 3 tests for introspect --from-config functionality: - test_run_from_config_not_found: config error handling - test_run_from_config_takes_priority: config path selection - test_run_manual_mode_backward_compatible: backward compat --- crates/mcp-cli/src/commands/introspect.rs | 79 +++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/crates/mcp-cli/src/commands/introspect.rs b/crates/mcp-cli/src/commands/introspect.rs index 09a5828..a2eb3fd 100644 --- a/crates/mcp-cli/src/commands/introspect.rs +++ b/crates/mcp-cli/src/commands/introspect.rs @@ -960,4 +960,83 @@ mod tests { assert!(result.server.supports_resources); assert!(!result.server.supports_prompts); } + + #[tokio::test] + async fn test_run_from_config_not_found() { + let result = run( + Some("nonexistent-server-xyz".to_string()), + None, + vec![], + vec![], + None, + None, + None, + vec![], + false, + OutputFormat::Json, + ) + .await; + + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + assert!( + err_msg.contains("not found in MCP config") + || err_msg.contains("failed to read MCP config"), + "Expected config-related error, got: {err_msg}" + ); + } + + #[tokio::test] + async fn test_run_from_config_takes_priority() { + // When from_config is Some, it should be used for config loading + // (server arg should be None due to clap conflicts, but we test the logic) + let result = run( + Some("test-server".to_string()), + None, // server is None when from_config is used + vec![], + vec![], + None, + None, + None, + vec![], + false, + OutputFormat::Json, + ) + .await; + + // Should fail because config doesn't exist, not because of server + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + // Should try to load from config, not use manual server + assert!( + err_msg.contains("MCP config") || err_msg.contains("test-server"), + "Should attempt config loading: {err_msg}" + ); + } + + #[tokio::test] + async fn test_run_manual_mode_backward_compatible() { + // Existing behavior: from_config = None, use server arg + let result = run( + None, // from_config + Some("test-server-direct".to_string()), + vec![], + vec![], + None, + None, + None, + vec![], + false, + OutputFormat::Json, + ) + .await; + + assert!(result.is_err()); + let err_msg = result.unwrap_err().to_string(); + // Should fail with connection error, not config error + assert!( + err_msg.contains("failed to connect") || err_msg.contains("test-server-direct"), + "Should try direct connection: {err_msg}" + ); + } } From b78fc39d8e286bba6a4a29386cd56abf10fb5a62 Mon Sep 17 00:00:00 2001 From: "Andrei G." Date: Sat, 3 Jan 2026 22:57:29 +0100 Subject: [PATCH 3/4] docs(cli): update introspect help text with --from-config examples Add Configuration Modes section and examples showing --from-config usage, matching generate command documentation. --- crates/mcp-cli/src/main.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/mcp-cli/src/main.rs b/crates/mcp-cli/src/main.rs index fb3a4d6..7ae1997 100644 --- a/crates/mcp-cli/src/main.rs +++ b/crates/mcp-cli/src/main.rs @@ -75,16 +75,34 @@ pub enum Commands { /// Connects to an MCP server, discovers its tools, and displays /// detailed information about available capabilities. /// + /// # Configuration Modes + /// + /// 1. Load from ~/.claude/mcp.json (recommended): + /// ```bash + /// mcp-cli introspect --from-config github + /// ``` + /// + /// 2. Manual configuration: + /// ```bash + /// mcp-cli introspect github-mcp-server --arg=stdio + /// ``` + /// /// # Examples /// /// ```bash - /// # Simple binary + /// # Load GitHub server config from mcp.json + /// mcp-cli introspect --from-config github + /// + /// # Load with detailed schemas + /// mcp-cli introspect --from-config github --detailed + /// + /// # Manual: Simple binary /// mcp-cli introspect github-mcp-server /// - /// # With arguments + /// # Manual: With arguments /// mcp-cli introspect github-mcp-server --arg=stdio /// - /// # Docker container + /// # Manual: Docker container /// mcp-cli introspect docker --arg=run --arg=-i --arg=--rm \ /// --arg=ghcr.io/github/github-mcp-server \ /// --env=GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx From 4e3077ed80ed359fc56a073f93964a2c8dc28d44 Mon Sep 17 00:00:00 2001 From: "Andrei G." Date: Sat, 3 Jan 2026 23:03:44 +0100 Subject: [PATCH 4/4] docs: prepare v0.6.3 release - Update version from 0.6.2 to 0.6.3 in Cargo.toml - Add CHANGELOG entry for --from-config introspect feature - Add diff links to all CHANGELOG releases - Expand introspect documentation in mcp-cli README - Fix config path inconsistency (~/.claude/mcp.json) - Update README CLI commands section with v0.6.3 examples --- CHANGELOG.md | 56 ++++++++++++++++++++++++++++++++++++++-- Cargo.lock | 12 ++++----- Cargo.toml | 2 +- README.md | 17 ++++++++---- crates/mcp-cli/README.md | 43 ++++++++++++++++++++++++++++-- 5 files changed, 114 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48cd431..01a64f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [0.6.3] - 2026-01-03 + +### Summary + +**CLI Enhancement: Config-Based Introspection** + +This patch release adds `--from-config` support to the `introspect` command, enabling users to load server configurations from `~/.claude/mcp.json` instead of specifying manual arguments. + +**Key Achievements**: +- New `--from-config` flag for `introspect` command +- Security improvements to error messages +- 556 tests passing (100% pass rate) +- Dependency updates (rmcp 0.12, tokio 1.49) + +### Added + +- **`--from-config` for introspect command**: Load server configuration from `~/.claude/mcp.json` by name + - `mcp-execution-cli introspect --from-config github` instead of manual docker/npx args + - Matches existing `--from-config` in `generate` command + - Configuration Modes section in help text + - 3 new integration tests for config loading + +### Changed + +- **Error messages**: Improved security by removing information disclosure + - Removed server list from "not found" errors (prevents enumeration) + - Use `~/.claude/mcp.json` instead of full filesystem path +- **Logging**: Changed config loading logs from `info!` to `debug!` level +- **Help text**: Added Configuration Modes section with recommended usage + +### Dependencies + +- `rmcp`: 0.10 → 0.12 +- `tokio`: 1.48 → 1.49 +- `handlebars`: 6.3 → 6.4 +- `schemars`: 1.1 → 1.2 +- `tempfile`: 3.23 → 3.24 + +--- + ## [0.6.2] - 2025-12-08 ### Summary @@ -943,5 +983,17 @@ Phase 6 (Optimization) is currently OPTIONAL and DEFERRED because: --- -**Last Updated**: 2025-12-08 -**Version**: 0.6.2 (Production Ready) +**Last Updated**: 2026-01-03 +**Version**: 0.6.3 (Production Ready) + +--- + +[Unreleased]: https://github.com/bug-ops/mcp-execution/compare/v0.6.3...HEAD +[0.6.3]: https://github.com/bug-ops/mcp-execution/compare/v0.6.2...v0.6.3 +[0.6.2]: https://github.com/bug-ops/mcp-execution/compare/v0.6.1...v0.6.2 +[0.6.1]: https://github.com/bug-ops/mcp-execution/compare/v0.6.0...v0.6.1 +[0.6.0]: https://github.com/bug-ops/mcp-execution/compare/v0.5.0...v0.6.0 +[0.5.0]: https://github.com/bug-ops/mcp-execution/compare/v0.4.0...v0.5.0 +[0.4.0]: https://github.com/bug-ops/mcp-execution/compare/v0.3.0...v0.4.0 +[0.3.0]: https://github.com/bug-ops/mcp-execution/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/bug-ops/mcp-execution/releases/tag/v0.2.0 diff --git a/Cargo.lock b/Cargo.lock index b891c90..c1a08a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -918,7 +918,7 @@ dependencies = [ [[package]] name = "mcp-codegen" -version = "0.6.2" +version = "0.6.3" dependencies = [ "criterion", "handlebars", @@ -932,7 +932,7 @@ dependencies = [ [[package]] name = "mcp-core" -version = "0.6.2" +version = "0.6.3" dependencies = [ "async-trait", "chrono", @@ -948,7 +948,7 @@ dependencies = [ [[package]] name = "mcp-execution-cli" -version = "0.6.2" +version = "0.6.3" dependencies = [ "anyhow", "clap", @@ -974,7 +974,7 @@ dependencies = [ [[package]] name = "mcp-files" -version = "0.6.2" +version = "0.6.3" dependencies = [ "criterion", "dhat", @@ -987,7 +987,7 @@ dependencies = [ [[package]] name = "mcp-introspector" -version = "0.6.2" +version = "0.6.3" dependencies = [ "criterion", "mcp-core", @@ -1000,7 +1000,7 @@ dependencies = [ [[package]] name = "mcp-server" -version = "0.6.2" +version = "0.6.3" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 90b4bec..ea0ba36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ default-members = [ "crates/*" ] resolver = "2" [workspace.package] -version = "0.6.2" +version = "0.6.3" edition = "2024" rust-version = "1.89" authors = ["Andrei G "] diff --git a/README.md b/README.md index 6d9e8e6..e743a23 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ cargo install --path crates/mcp-cli ### Generate TypeScript Tools ```bash -# 1. Configure MCP server in ~/.config/claude/mcp.json +# 1. Configure MCP server in ~/.claude/mcp.json # 2. Generate tools mcp-execution-cli generate --from-config github @@ -88,6 +88,7 @@ node ~/.claude/servers/github/createIssue.ts --repo="owner/repo" --title="Bug" | **Type-Safe** | Full TypeScript interfaces from MCP JSON schemas | | **Lightning Fast** | 526x faster than target (0.19ms for 10 tools) | | **100% MCP Compatible** | Works with all MCP servers via [rmcp SDK](https://docs.rs/rmcp) | +| **Thoroughly Tested** | 556 tests with 100% pass rate | ## Workspace Crates @@ -115,19 +116,25 @@ mcp-files → mcp-core See [mcp-cli README](crates/mcp-cli) for full reference. ```bash -# Generate TypeScript tools +# Generate TypeScript tools from config (recommended) mcp-execution-cli generate --from-config github -# Introspect MCP server +# Introspect MCP server from config (v0.6.3+) mcp-execution-cli introspect --from-config github -# View cache statistics -mcp-execution-cli stats +# Introspect with detailed schemas +mcp-execution-cli introspect --from-config github --detailed + +# Manual configuration (alternative) +mcp-execution-cli introspect docker --arg=run --arg=-i --env=TOKEN=xxx # Shell completions mcp-execution-cli completions bash ``` +> [!TIP] +> Use `--from-config` to load server settings from `~/.claude/mcp.json` instead of manual arguments. + ## Performance | Metric | Target | Achieved | diff --git a/crates/mcp-cli/README.md b/crates/mcp-cli/README.md index c16d311..4476d07 100644 --- a/crates/mcp-cli/README.md +++ b/crates/mcp-cli/README.md @@ -54,7 +54,7 @@ mcp-execution-cli generate github-mcp-server --env GITHUB_TOKEN=ghp_xxx ``` > [!TIP] -> Use `--from-config` to load server configuration from `~/.config/claude/mcp.json`. +> Use `--from-config` to load server configuration from `~/.claude/mcp.json`. ### Discover Available Tools @@ -118,7 +118,46 @@ mcp-execution-cli generate docker \ Analyze MCP servers and discover capabilities: ```bash -mcp-execution-cli introspect --from-config github --output json +mcp-execution-cli introspect [OPTIONS] +``` + +**Configuration Modes**: + +1. Load from `~/.claude/mcp.json` (recommended): + ```bash + mcp-execution-cli introspect --from-config github + ``` + +2. Manual configuration: + ```bash + mcp-execution-cli introspect github-mcp-server --arg=stdio + ``` + +**Options**: + +- `--from-config `: Load config from mcp.json +- `--arg `: Server command argument (repeatable) +- `--env `: Environment variable (repeatable) +- `--detailed`: Show full input/output schemas +- `--format `: Output format (json, text, pretty) +- `--http `: Use HTTP transport +- `--sse `: Use SSE transport + +**Examples**: + +```bash +# From config with detailed schemas +mcp-execution-cli introspect --from-config github --detailed + +# Manual with Docker +mcp-execution-cli introspect docker \ + --arg=run --arg=-i --arg=--rm \ + --arg=ghcr.io/github/github-mcp-server \ + --env=GITHUB_TOKEN=ghp_xxx + +# HTTP transport +mcp-execution-cli introspect --http https://api.example.com/mcp \ + --header "Authorization=Bearer token" ``` ### `stats`