diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml index 71d4bda..80b005e 100644 --- a/.github/workflows/docs-check.yml +++ b/.github/workflows/docs-check.yml @@ -28,9 +28,6 @@ jobs: restore-keys: | ${{ runner.os }}-cargo- - - name: Install mcp-discovery - run: cargo install mcp-discovery - - name: Build project run: cargo build --release diff --git a/Cargo.lock b/Cargo.lock index 015c5c2..7cb2c41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,27 +3,21 @@ version = 4 [[package]] -name = "addr2line" -version = "0.25.1" +name = "aho-corasick" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ - "gimli", + "memchr", ] [[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aho-corasick" -version = "1.1.3" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "memchr", + "libc", ] [[package]] @@ -50,25 +44,10 @@ dependencies = [ ] [[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "backtrace" -version = "0.3.76" +name = "autocfg" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link 0.2.1", -] +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64" @@ -76,12 +55,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bitflags" -version = "2.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" - [[package]] name = "bumpalo" version = "3.19.0" @@ -90,15 +63,15 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.2.41" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "shlex", @@ -106,15 +79,29 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] [[package]] name = "clap" -version = "4.5.48" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -122,9 +109,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstyle", "clap_lex", @@ -133,9 +120,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -145,19 +132,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "core-foundation-sys" @@ -181,67 +158,60 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "deranged" -version = "0.5.4" +name = "darling" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "powerfmt", + "darling_core", + "darling_macro", ] [[package]] -name = "displaydoc" -version = "0.2.5" +name = "darling_core" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ + "fnv", + "ident_case", "proc-macro2", "quote", + "strsim", "syn", ] [[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "encoding_rs" -version = "0.8.35" +name = "darling_macro" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "cfg-if", + "darling_core", + "quote", + "syn", ] [[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" +name = "deranged" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ - "libc", - "windows-sys 0.61.2", + "powerfmt", ] [[package]] -name = "fastrand" -version = "2.3.0" +name = "dyn-clone" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fnv" @@ -249,30 +219,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - [[package]] name = "futures" version = "0.3.31" @@ -363,904 +309,269 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.2.16" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "getrandom" -version = "0.3.3" +name = "iana-time-zone" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - -[[package]] -name = "h2" -version = "0.4.12" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", + "cc", ] [[package]] -name = "hashbrown" -version = "0.16.0" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "heck" -version = "0.5.0" +name = "itoa" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] -name = "http" -version = "1.3.1" +name = "js-sys" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ - "bytes", - "fnv", - "itoa", + "once_cell", + "wasm-bindgen", ] [[package]] -name = "http-body" -version = "1.0.1" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "http-body-util" -version = "0.1.3" +name = "libc" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] -name = "httparse" -version = "1.10.1" +name = "log" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "hyper" -version = "1.7.0" +name = "matchers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", + "regex-automata", ] [[package]] -name = "hyper-rustls" -version = "0.27.7" +name = "memchr" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] -name = "hyper-tls" -version = "0.6.0" +name = "nu-ansi-term" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", + "windows-sys", ] [[package]] -name = "hyper-util" -version = "0.1.17" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] -name = "icu_collections" -version = "2.0.0" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", + "autocfg", ] [[package]] -name = "icu_locale_core" -version = "2.0.0" +name = "once_cell" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "icu_normalizer" -version = "2.0.0" +name = "pastey" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] +checksum = "57d6c094ee800037dff99e02cab0eaf3142826586742a270ab3d7a62656bd27a" [[package]] -name = "icu_normalizer_data" -version = "2.0.0" +name = "pin-project-lite" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] -name = "icu_properties" -version = "2.0.1" +name = "pin-utils" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "icu_properties_data" -version = "2.0.1" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "icu_provider" -version = "2.0.0" +name = "proc-macro2" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", + "unicode-ident", ] [[package]] -name = "idna" -version = "1.1.0" +name = "quote" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", + "proc-macro2", ] [[package]] -name = "idna_adapter" -version = "1.2.1" +name = "ref-cast" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ - "icu_normalizer", - "icu_properties", + "ref-cast-impl", ] [[package]] -name = "indexmap" -version = "2.11.4" +name = "ref-cast-impl" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ - "equivalent", - "hashbrown", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "io-uring" -version = "0.7.10" +name = "regex-automata" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ - "bitflags", - "cfg-if", - "libc", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] -name = "ipnet" -version = "2.11.0" +name = "regex-syntax" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] -name = "iri-string" -version = "0.7.8" +name = "rmcp" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "5df440eaa43f8573491ed4a5899719b6d29099500774abba12214a095a4083ed" dependencies = [ - "memchr", + "async-trait", + "base64", + "chrono", + "futures", + "pastey", + "pin-project-lite", + "rmcp-macros", + "schemars", "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.81" +name = "rmcp-macros" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "9ef03779cccab8337dd8617c53fce5c98ec21794febc397531555472ca28f8c3" dependencies = [ - "once_cell", - "wasm-bindgen", + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn", ] [[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +name = "rust-mcp-server" +version = "0.2.7" +dependencies = [ + "anyhow", + "clap", + "rmcp", + "schemars", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-appender", + "tracing-subscriber", +] [[package]] -name = "libc" -version = "0.2.177" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "linux-raw-sys" -version = "0.11.0" +name = "ryu" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "openssl" -version = "0.10.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "phf" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_macros", - "phf_shared", - "serde", -] - -[[package]] -name = "phf_generator" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" -dependencies = [ - "fastrand", - "phf_shared", -] - -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "phf_shared" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "potential_utf" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "proc-macro2" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "regex-automata" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298" - -[[package]] -name = "reqwest" -version = "0.12.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rust-mcp-macros" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b647a85c9da2eaf14e67d39cb067a8157a66bd2c0dc53ef1051a84f45edfae24" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn", -] - -[[package]] -name = "rust-mcp-schema" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba217e6fcb043bba9e194209bff92c35294093187504d1443832ca2051816753" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "rust-mcp-sdk" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961ec01d0bedecf488388e6b1cf04170f9badab4927061c6592ffa385c02c6c9" -dependencies = [ - "async-trait", - "base64", - "futures", - "rust-mcp-macros", - "rust-mcp-schema", - "rust-mcp-transport", - "serde", - "serde_json", - "thiserror 2.0.17", - "tokio", - "tracing", - "uuid", -] - -[[package]] -name = "rust-mcp-server" -version = "0.2.7" -dependencies = [ - "anyhow", - "async-trait", - "clap", - "phf", - "reqwest", - "rust-mcp-sdk", - "schemars", - "serde", - "serde_json", - "tokio", - "tracing", - "tracing-appender", - "tracing-subscriber", -] - -[[package]] -name = "rust-mcp-transport" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35feabc5e4667019dc262178724c94cbced6f43959af15e214b52f79243f55ed" -dependencies = [ - "async-trait", - "bytes", - "futures", - "rust-mcp-schema", - "serde", - "serde_json", - "thiserror 2.0.17", - "tokio", - "tokio-stream", - "tracing", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] -name = "rustix" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -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 = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "schemars" -version = "1.1.0" +name = "schemars" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" dependencies = [ + "chrono", "dyn-clone", "ref-cast", "schemars_derive", @@ -1280,35 +591,6 @@ dependencies = [ "syn", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.228" @@ -1363,18 +645,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -1390,133 +660,33 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook-registry" -version = "1.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" -dependencies = [ - "libc", -] - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - [[package]] name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] -name = "system-configuration-sys" -version = "0.6.0" +name = "smallvec" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "tempfile" -version = "3.23.0" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "thiserror" -version = "1.0.69" +name = "syn" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ - "thiserror-impl 1.0.69", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] @@ -1525,18 +695,7 @@ version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.17", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -1590,83 +749,33 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", - "libc", - "mio", - "parking_lot", "pin-project-lite", - "signal-hook-registry", - "slab", - "socket2", "tokio-macros", - "windows-sys 0.59.0", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -1675,56 +784,11 @@ dependencies = [ "tokio", ] -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1733,21 +797,21 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 1.0.69", + "thiserror", "time", "tracing-subscriber", ] [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -1756,9 +820,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", "valuable", @@ -1777,9 +841,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -1793,52 +857,11 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "unicode-ident" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.18.1" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" -dependencies = [ - "getrandom 0.3.3", - "js-sys", - "wasm-bindgen", -] +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "valuable" @@ -1846,50 +869,11 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -1898,38 +882,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1937,260 +894,90 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] -name = "web-sys" -version = "0.3.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-registry" -version = "0.5.3" +name = "windows-core" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-link 0.1.3", + "windows-implement", + "windows-interface", + "windows-link", "windows-result", "windows-strings", ] [[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", "syn", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", ] [[package]] -name = "zerofrom-derive" -version = "0.1.6" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", "syn", - "synstructure", ] [[package]] -name = "zeroize" -version = "1.8.2" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "zerotrie" -version = "0.2.2" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "displaydoc", - "yoke", - "zerofrom", + "windows-link", ] [[package]] -name = "zerovec" -version = "0.11.4" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", + "windows-link", ] [[package]] -name = "zerovec-derive" -version = "0.11.1" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-link", ] diff --git a/Cargo.toml b/Cargo.toml index bc99b25..8f3004d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "rust-mcp-server" version = "0.2.7" edition = "2024" -rust-version = "1.88" +rust-version = "1.90" description = "An MCP server for Rust development" authors = ["Vaiz"] license = "Unlicense" @@ -22,18 +22,12 @@ include = [ [dependencies] anyhow = "1.0.98" -async-trait = "0.1.88" clap = { version = "4.5.40", default-features = false, features = ["std", "help", "error-context", "usage", "suggestions", "derive", "string"] } -phf = { version = "0.13.1", features = ["macros"] } -reqwest = { version = "0.12.21" } -rust-mcp-sdk = { version = "0.7.0", default-features = false, features = ["server", "2025_06_18", "macros", "stdio"] } +rmcp = { version = "0.11.0", default-features = false, features = ["base64", "server", "macros", "transport-io"] } schemars = "1.1.0" serde = { version ="1.0.219", features = ["derive"] } serde_json = "1.0.140" -tokio = { version = "1.45.1", default-features = false, features = [] } +tokio = { version = "1.45.1", default-features = false, features = ["rt-multi-thread"] } tracing = "0.1.41" tracing-appender = "0.2.3" tracing-subscriber = { version = "0.3.20", features = ["env-filter"] } - -[package.metadata.cargo-machete] -ignored = ["serde_json"] diff --git a/README.md b/README.md index 5aa9345..2c8553d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ By exposing local tools and project context to the LLM, rust-mcp-server allows t - [Dependency Management](#dependency-management) - [Code Quality & Security](#code-quality--security) - [Rust Toolchain Management](#rust-toolchain-management) - - [Experimental Features](#experimental-features) - [Command Line Arguments](#command-line-arguments) - [Configuring with VS Code](#configuring-with-vs-code) - [GitHub Copilot Coding Agent Integration](#github-copilot-coding-agent-integration) @@ -71,21 +70,12 @@ rust-mcp-server exposes a comprehensive set of Rust development tools to the LLM * **`rustup-toolchain-add`**: Install or update toolchains * **`rustup-update`**: Update Rust toolchains and rustup -### Experimental Features -The server provides **experimental** access to the official [Cargo Book](https://doc.rust-lang.org/cargo/) documentation through MCP resources. This feature allows LLMs to fetch and reference about 95 pages of Cargo documentation directly from the official repository, including command references, guides, and technical specifications. Resources are accessible using the `cargo-book://` URI scheme and require network connectivity. - For a complete list with detailed descriptions and parameters, see [tools.md](tools.md). ## Command Line Arguments The rust-mcp-server supports several command line arguments to customize its behavior: -### `--timeout ` -- **Description**: Sets the timeout for processing a request in seconds. It might be useful to change this option depending on - the size of your project. -- **Default**: 600 (10 minutes) -- **Example**: `--timeout 300` (5 minutes) - ### `--log-level ` - **Description**: Sets the logging level for the server - **Options**: `error`, `warn`, `info`, `debug`, `trace` @@ -107,6 +97,11 @@ The rust-mcp-server supports several command line arguments to customize its beh - **Default**: Current directory - **Example**: `--workspace /path/to/rust/project` +### `--generate-docs ` +- **Description**: Generates markdown documentation file and exits without starting the server +- **Default**: None +- **Example**: `--generate-docs tools.md` + ### `-h, --help` - **Description**: Displays help information about available command line arguments - **Example**: `rust-mcp-server --help` diff --git a/deny.toml b/deny.toml index be52fff..cb9df38 100644 --- a/deny.toml +++ b/deny.toml @@ -91,11 +91,6 @@ ignore = [ allow = [ "MIT", "Apache-2.0", - "Apache-2.0 WITH LLVM-exception", - "BSD-2-Clause", - "BSD-3-Clause", - "ISC", - "Unicode-DFS-2016", "Unicode-3.0", "Unlicense", ] @@ -204,16 +199,7 @@ deny = [ # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ - # thiserror v1.0.69 is required by tracing-appender, while v2.0.17 is used by rust-mcp-sdk - { crate = "thiserror@1.0.69", reason = "tracing-appender requires older version for compatibility" }, - { crate = "thiserror-impl@1.0.69", reason = "thiserror v1.0.69 dependency" }, - # wasi duplicates for different snapshot versions - { crate = "wasi@0.11.1+wasi-snapshot-preview1", reason = "mio requires older WASI version for compatibility" }, - # Windows crates have multiple versions for different dependencies - { crate = "windows-link@0.1.3", reason = "windows-registry compatibility requirement" }, - { crate = "windows-link@0.2.1", reason = "backtrace and parking_lot_core compatibility requirement" }, - { crate = "windows-sys@0.59.0", reason = "mio, socket2, and tokio compatibility requirement" }, - { crate = "windows-sys@0.61.2", reason = "errno, nu-ansi-term, rustix, schannel, and tempfile compatibility requirement" }, + ] # Similarly to `skip` allows you to skip certain crates during duplicate # detection. Unlike skip, it also includes the entire tree of transitive diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 46ffc34..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -# sudo docker build -f docker/Dockerfile -t rust-mcp-server . - -FROM rust:latest AS builder - -RUN apt update && apt install -y musl-tools musl-dev -RUN rustup target add x86_64-unknown-linux-musl - -WORKDIR /app - -COPY ../Cargo.toml ../Cargo.lock ./ -COPY ../src ./src -COPY ../prompts ./prompts -COPY ../docs ./docs - -RUN cargo build --release --target x86_64-unknown-linux-musl - -FROM scratch -COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/rust-mcp-server /usr/bin/rust-mcp-server - -USER 1000 -ENTRYPOINT ["/usr/bin/rust-mcp-server"] diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index c5b71f0..0000000 --- a/docker/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Docker - -**WIP** diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 7bcc1d2..0000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: "3.8" - -services: - mcp: - build: - context: .. - dockerfile: docker/Dockerfile - image: vaiz2/rust-mcp-server:latest - - volumes: - - ./logs:/var/log/rust-mcp-server # host ./logs -> container log folder - - stdin_open: true - tty: true - restart: unless-stopped - - command: - - "--timeout=600" - - "--log-level=info" - - "--log-file=/var/log/rust-mcp-server/server.log" - # example: disable cargo-test tool - # - "--disable-tool=cargo-test" diff --git a/prompts/add-tool.prompt.md b/prompts/add-tool.prompt.md index 2282187..42d8b2e 100644 --- a/prompts/add-tool.prompt.md +++ b/prompts/add-tool.prompt.md @@ -50,7 +50,7 @@ use crate::{ description = "Clear, concise description", openWorldHint = false )] -#[derive(Debug, ::serde::Deserialize, ::serde::Serialize, JsonSchema)] +#[derive(Debug, ::serde::Deserialize, ::serde::Serialize, ::schemars::JsonSchema)] pub struct YourToolName { /// The toolchain to use (for Rust tools) #[serde(default, deserialize_with = "deserialize_string")] @@ -202,7 +202,7 @@ pub locked: bool, **Simple installation tool**: ```rust #[mcp_tool(name = "tool-install", description = "Installs the tool")] -#[derive(Debug, ::serde::Deserialize, ::serde::Serialize, JsonSchema)] +#[derive(Debug, ::serde::Deserialize, ::serde::Serialize, ::schemars::JsonSchema)] pub struct ToolInstallTool {} impl ToolInstallTool { diff --git a/prompts/update-toolset.md b/prompts/update-toolset.md deleted file mode 100644 index ba87f50..0000000 --- a/prompts/update-toolset.md +++ /dev/null @@ -1,30 +0,0 @@ -# Update Toolset and Fix Rust/Clippy Warnings via MCP - -You are an AI agent operating as an MCP (Model Context Protocol) tool integrator for a Rust project. Your task is to ensure the toolset is up-to-date and that the codebase is free of new Rust and Clippy warnings. - -## Instructions - -1. **Discover MCP tools** - - Use the MCP protocol to discover and list all available tools. - -2. **Update Toolset** - - Update Rust toolchain using `#rustup-update` tool. - -3. **Fix warnings using clippy** - - Try to fix warnings automatically using `#cargo-clippy` with `fix` flag, be aware that clippy cannot fix all the warnings - -3. **Check and Fix Remaining Warnings** - - Run the following tools via MCP: - - `#cargo-check` to check for compiler warnings and errors - - `#cargo-clippy` to lint the codebase for best practices - - Collect all new warnings and errors reported by the Rust compiler and Clippy linter. - - Refactor the codebase to resolve these warnings, following Rust best practices and idioms. - -4. **Format code** - - Reformat code using `#cargo-fmt`. - -5. **Verification** - - Run `#cargo-check`, `#cargo-build`, and `#cargo-clippy` to confirm all warnings are resolved. - - Summarize the actions taken and any remaining issues, if applicable. - -**Note:** All actions must be performed via the MCP protocol, using JSON-RPC over stdio, and should comply with the project’s async/await and error handling guidelines. diff --git a/scripts/generate-docs.py b/scripts/generate-docs.py index 8b553fc..f796e55 100644 --- a/scripts/generate-docs.py +++ b/scripts/generate-docs.py @@ -10,7 +10,7 @@ def main(): - parser = argparse.ArgumentParser(description="Generate documentation using mcp-discovery") + parser = argparse.ArgumentParser(description="Generate documentation for MCP server") parser.add_argument("filename", nargs="?", default="tools.md", help="Output filename (default: tools.md)") args = parser.parse_args() @@ -27,14 +27,12 @@ def main(): os.chdir(project_root) subprocess.run(["cargo", "build", "--release"], check=True) - print("📝 Generating documentation using mcp-discovery...") + print("📝 Generating documentation...") print(f" - Creating {args.filename} documentation...") subprocess.run([ - "mcp-discovery", "create", - "--template", "md-plain", - "--filename", str(output_file), - "--", str(server_binary) + str(server_binary), + "--generate-docs", str(output_file) ], check=True) print(" - Removing git hash from version string for CI stability...") diff --git a/scripts/start-mcp-server.py b/scripts/start-mcp-server.py deleted file mode 100644 index acfec5a..0000000 --- a/scripts/start-mcp-server.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import shutil -import subprocess -import sys -import tempfile -import re -from pathlib import Path - -def run_command(cmd, cwd=None, capture_output=True, env=None): - """Run a command and return the result.""" - try: - result = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=capture_output, - text=True, check=False, env=env) - return result.returncode == 0, result.stdout.strip() if capture_output else "" - except Exception as e: - print(f"Error running command '{cmd}': {e}") - return False, "" - -def get_remote_commit(repo_url, tag): - """Get remote commit hash for tag without cloning.""" - success, output = run_command(f'git ls-remote --tags "{repo_url}" "refs/tags/{tag}"') - if success and output: - try: - commit_hash = output.split()[0][:7] - return commit_hash - except (IndexError, AttributeError): - pass - return None - -def get_binary_version(binary_path): - """Get existing binary version.""" - if not os.path.exists(binary_path): - return None - - success, output = run_command(f'"{binary_path}" --version') - return output if success else None - -def remove_dir(path): - if not path.exists(): - return - if os.name == 'nt': - # Use command line to remove directory on Windows - run_command(f'rmdir /s /q "{path}"') - else: - shutil.rmtree(path, ignore_errors=False) - -def main(): - parser = argparse.ArgumentParser(description='Install and start rust-mcp-server') - parser.add_argument('--install-folder', default='./rust-mcp-server', - help='Folder to install the executable (default: ./rust-mcp-server)') - parser.add_argument('--tag', default='stable', - help='Git tag to checkout (default: stable)') - parser.add_argument('--keep-temp', action='store_true', - help='Keep temp directory for faster subsequent builds') - parser.add_argument('--force', action='store_true', - help='Force rebuild by cleaning temp directory and ignoring version checks') - parser.add_argument('server_args', nargs='*', - help='Arguments to pass to the server') - - args = parser.parse_args() - - repo_url = "https://github.com/Vaiz/rust-mcp-server.git" - install_folder = Path(args.install_folder).resolve() - - exe_name = "rust-mcp-server.exe" if os.name == 'nt' else "rust-mcp-server" - binary_path = install_folder / exe_name - - temp_base = Path(tempfile.gettempdir()) / "rust-mcp-server-build" - print(f"Using temp directory: {temp_base}") - - needs_build = True - - remote_commit = get_remote_commit(repo_url, args.tag) - print(f"Remote commit for tag '{args.tag}': {remote_commit}") - - if args.force and temp_base.exists(): - print("Force flag set, cleaning temp directory...") - remove_dir(temp_base) - else: - existing_version = get_binary_version(binary_path) - print(f"Existing binary version: {existing_version}") - - if existing_version and remote_commit: - # Extract commit hash from version string (format: "rust-mcp-server 0.1.0.d7c5bac") - match = re.search(r'\.([a-f0-9]+)$', existing_version) - if match: - existing_commit = match.group(1) - if existing_commit == remote_commit: - print(f"Binary up-to-date (commit: {remote_commit})") - needs_build = False - else: - print(f"Binary outdated ({existing_commit} → {remote_commit})") - - # Clone/update and build if needed - if needs_build: - install_folder.mkdir(parents=True, exist_ok=True) - - if temp_base.exists() and (temp_base / ".git").is_dir(): - print("Updating existing temp directory...") - success, _ = run_command("git fetch --all --tags", cwd=temp_base) - if success: - run_command("git reset --hard HEAD", cwd=temp_base) - else: - print("Cloning to temp directory...") - success, _ = run_command(f'git clone "{repo_url}" "{temp_base}"') - if not success: - print("Failed to clone repository") - sys.exit(1) - - success, _ = run_command(f"git checkout {args.tag}", cwd=temp_base) - if not success: - print(f"Failed to checkout tag: {args.tag}") - sys.exit(1) - - print("Building...") - env = os.environ.copy() - env['GIT_HASH'] = remote_commit - cargo_cmd = f'cargo build --release --manifest-path "{temp_base / "Cargo.toml"}"' - success, _ = run_command(cargo_cmd, capture_output=False, env=env) - if not success: - print("Build failed") - sys.exit(1) - - src_binary = temp_base / "target" / "release" / exe_name - shutil.copy2(src_binary, binary_path) - - if not args.keep_temp: - remove_dir(temp_base) - - print(f"Installed to: {binary_path}") - - # Start server - print("Starting server...") - cmd = [str(binary_path)] + args.server_args - try: - subprocess.run(cmd, check=False) - except KeyboardInterrupt: - print("\nServer stopped by user") - except Exception as e: - print(f"Error starting server: {e}") - sys.exit(1) - -if __name__ == "__main__": - main() diff --git a/scripts/update-stable-tag.py b/scripts/update-stable-tag.py deleted file mode 100644 index 8a13483..0000000 --- a/scripts/update-stable-tag.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import subprocess -import sys - -def run_cmd(cmd): - """Run command and exit on failure.""" - result = subprocess.run(cmd, shell=True, check=False) - if result.returncode != 0: - sys.exit(result.returncode) - -def main(): - parser = argparse.ArgumentParser(description='Update stable tag') - parser.add_argument('--commit', help='Commit hash (default: latest master)') - args = parser.parse_args() - - # Get target commit - if args.commit: - commit = args.commit - else: - result = subprocess.run("git rev-parse origin/master", shell=True, - capture_output=True, text=True, check=False) - if result.returncode != 0: - print("Error: Could not find origin/master or origin/main") - sys.exit(1) - commit = result.stdout.strip() - - print(f"Updating stable tag to {commit[:7]}") - - run_cmd("git fetch --tags") - run_cmd("git push origin :refs/tags/stable || true") # Delete remote (ignore errors) - run_cmd("git tag -d stable || true") # Delete local (ignore errors) - run_cmd(f"git tag stable {commit}") - run_cmd("git push origin stable") - - print("✅ Done") - -if __name__ == "__main__": - main() diff --git a/src/handler.rs b/src/handler.rs deleted file mode 100644 index 65b1b07..0000000 --- a/src/handler.rs +++ /dev/null @@ -1,118 +0,0 @@ -use async_trait::async_trait; -use rust_mcp_sdk::schema::{ - CallToolRequest, CallToolResult, ListPromptsRequest, ListPromptsResult, ListResourcesRequest, - ListResourcesResult, ListToolsRequest, ListToolsResult, ReadResourceRequest, - ReadResourceResult, RpcError, schema_utils::CallToolError, -}; -use rust_mcp_sdk::schema::{GetPromptRequest, GetPromptResult}; -use rust_mcp_sdk::{McpServer, mcp_server::ServerHandler}; -use std::sync::Arc; - -use crate::resources::ResourceHandler; -use crate::tools::AllTools; - -// Custom Handler to handle MCP Messages -pub struct McpServerHandler { - disabled_tools: Vec, - prompt_handler: crate::prompts::PromptHandler, - resource_handler: ResourceHandler, -} - -impl McpServerHandler { - /// Create a new instance of MyServerHandler with the provided disabled tools. - pub fn new(disabled_tools: Vec) -> Self { - let this = Self { - disabled_tools, - prompt_handler: crate::prompts::PromptHandler::new(), - resource_handler: ResourceHandler::new(), - }; - let enabled_tools = this.enabled_tools(); - tracing::info!(enabled_tools = ?enabled_tools, disabled_tools = ?this.disabled_tools, "Starting MCP Server"); - this - } - - fn enabled_tools(&self) -> Vec { - AllTools::tools() - .into_iter() - .filter(|t| !self.disabled_tools.contains(&t.name)) - .map(|t| t.name) - .collect() - } -} - -// To check out a list of all the methods in the trait that you can override, take a look at -// https://github.com/rust-mcp-stack/rust-mcp-sdk/blob/main/crates/rust-mcp-sdk/src/mcp_handlers/mcp_server_handler.rs -#[async_trait] -impl ServerHandler for McpServerHandler { - async fn handle_list_tools_request( - &self, - _request: ListToolsRequest, - _runtime: Arc, - ) -> Result { - let mut tools = AllTools::tools(); - tools.retain(|t| !self.disabled_tools.contains(&t.name)); - - Ok(ListToolsResult { - meta: None, - next_cursor: None, - tools, - }) - } - - async fn handle_call_tool_request( - &self, - request: CallToolRequest, - _runtime: Arc, - ) -> Result { - crate::tools::handle_request(request, &self.disabled_tools) - } - - async fn handle_list_prompts_request( - &self, - _request: ListPromptsRequest, - _runtime: Arc, - ) -> Result { - Ok(ListPromptsResult { - meta: None, - next_cursor: None, - prompts: self.prompt_handler.list_prompts(), - }) - } - - async fn handle_get_prompt_request( - &self, - request: GetPromptRequest, - runtime: Arc, - ) -> std::result::Result { - let name = &request.params.name; - tracing::info!(name, "Handling get prompt request"); - - if let Some(result) = self.prompt_handler.get_prompt_result(name) { - return Ok(result.clone()); - } - - tracing::warn!(name, "Prompt not found"); - runtime.assert_server_request_capabilities(request.method())?; - Err(RpcError::method_not_found().with_message(format!("Prompt not found for '{name}'."))) - } - - async fn handle_list_resources_request( - &self, - request: ListResourcesRequest, - _runtime: Arc, - ) -> Result { - self.resource_handler - .handle_list_resources_request(request) - .await - } - - async fn handle_read_resource_request( - &self, - request: ReadResourceRequest, - _runtime: Arc, - ) -> Result { - self.resource_handler - .handle_read_resource_request(request) - .await - } -} diff --git a/src/main.rs b/src/main.rs index e90d5f1..09dc544 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,51 +1,21 @@ -mod handler; -mod prompts; -mod resources; +mod rmcp_server; pub(crate) mod serde_utils; +mod tool; mod tools; +mod version; +use anyhow::Context; use clap::Parser; -use rust_mcp_sdk::McpServer; -use rust_mcp_sdk::schema::ServerCapabilitiesPrompts; -use rust_mcp_sdk::{ - StdioTransport, TransportOptions, - error::SdkResult, - mcp_server::server_runtime, - schema::{ - Implementation, InitializeResult, LATEST_PROTOCOL_VERSION, ServerCapabilities, - ServerCapabilitiesResources, ServerCapabilitiesTools, - }, -}; +use rmcp::ServiceExt; +use rmcp::service::QuitReason; +pub(crate) use tool::{Tool, execute_rmcp_command}; use tracing_appender::rolling; use tracing_subscriber::{EnvFilter, fmt}; - -const VERSION: &str = env!("CARGO_PKG_VERSION"); -const GIT_HASH: Option<&str> = option_env!("GIT_HASH"); - -struct AppVersion; - -impl AppVersion { - fn version() -> String { - match GIT_HASH { - Some(hash) => format!("{VERSION}.{hash}"), - None => VERSION.into(), - } - } -} - -impl From for clap::builder::Str { - fn from(_: AppVersion) -> Self { - AppVersion::version().into() - } -} +use version::AppVersion; #[derive(Parser, Debug)] #[command(author, version = AppVersion, about = "Rust MCP Server", long_about = None)] struct Args { - /// Timeout for processing a request (seconds) - #[arg(long, default_value_t = 600)] - timeout: u64, - /// Log level (error, warn, info, debug, trace) #[arg(long, default_value = "info")] log_level: String, @@ -61,11 +31,14 @@ struct Args { /// Rust project workspace path. By default, uses the current directory. #[arg(long)] workspace: Option, + + /// Generate tools.md documentation file and exit + #[arg(long)] + generate_docs: Option, } #[tokio::main] -async fn main() -> SdkResult<()> { - // Parse command line arguments +async fn main() -> anyhow::Result<()> { let args = Args::parse(); // Set up logging @@ -85,58 +58,48 @@ async fn main() -> SdkResult<()> { .with_ansi(false) .init(); } - tracing::info!(?args, "Starting Rust MCP Server"); - - // Warn about long-running requests - if args.timeout < 60 { - tracing::warn!( - timeout = args.timeout, - "Short timeout may interrupt long-running requests like cargo-build" - ); - } else if args.timeout >= 600 { - tracing::info!( - timeout = args.timeout, - "Long timeout set; some requests (e.g., cargo-build) may take a while" - ); - } - - tracing::info!(version = AppVersion::version(), "Server version"); + tracing::info!("Starting Rust MCP Server: {:?}", args); + tracing::info!("Server version: {}", AppVersion::version()); if let Some(workspace) = args.workspace { - tracing::info!(workspace = %workspace, "Workspace root has been overridden"); + tracing::info!("Workspace root has been overridden: {}", workspace); tools::set_workspace_root(workspace); } else { tracing::info!("No workspace root specified, using current directory"); } - let server_details = InitializeResult { - server_info: Implementation { - name: "Rust MCP Server".into(), - title: Some("Rust MCP Server".into()), - version: AppVersion::version(), - }, - capabilities: ServerCapabilities { - tools: Some(ServerCapabilitiesTools { list_changed: None }), - prompts: Some(ServerCapabilitiesPrompts { list_changed: None }), - resources: Some(ServerCapabilitiesResources { - list_changed: None, - subscribe: None, - }), - ..Default::default() - }, - meta: None, - instructions: Some(include_str!("../docs/instructions.md").into()), - protocol_version: LATEST_PROTOCOL_VERSION.into(), - }; - - let transport = StdioTransport::new(TransportOptions { - timeout: std::time::Duration::from_secs(args.timeout), - })?; - - let server = server_runtime::create_server( - server_details, - transport, - handler::McpServerHandler::new(args.disabled_tools), - ); - server.start().await + let server = rmcp_server::Server::new(&args.disabled_tools); + + // Handle documentation generation mode + if let Some(output_file) = args.generate_docs { + tracing::info!("Generating documentation to: {}", output_file); + let docs = server.generate_markdown_docs(); + std::fs::write(&output_file, docs).context("Failed to write documentation file")?; + println!("Documentation generated successfully: {}", output_file); + return Ok(()); + } + + let service = server + .serve(rmcp::transport::stdio()) + .await + .context("Failed to start server")?; + + eprintln!("Rust MCP Server started on stdio"); + + let result = service.waiting().await; + + match result { + Ok(QuitReason::Closed) => tracing::info!("Server closed normally"), + Ok(QuitReason::Cancelled) => tracing::info!("Server was cancelled"), + Ok(QuitReason::JoinError(error)) => { + tracing::error!("Server join error: {error}"); + return Err(error.into()); + } + Err(error) => { + tracing::error!("Server encountered an error: {error}"); + return Err(error.into()); + } + } + + Ok(()) } diff --git a/src/prompts.rs b/src/prompts.rs deleted file mode 100644 index 764918a..0000000 --- a/src/prompts.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::collections::HashMap; - -use rust_mcp_sdk::schema::{GetPromptResult, Prompt, PromptMessage, Role, TextContent}; - -pub struct PromptHandler { - prompts: HashMap, -} - -impl Default for PromptHandler { - fn default() -> Self { - let mut prompts = HashMap::new(); - - let prompt = Prompt { - name: "rustup-update-toolset".into(), - title: Some("Update Rust Toolset".into()), - description: Some("Provides instruction on how to update Rust toolset".into()), - arguments: Vec::new(), - meta: None, - }; - - let prompt_message = PromptMessage { - role: Role::Assistant, - content: TextContent::new( - include_str!("../prompts/update-toolset.md").into(), - None, - None, - ) - .into(), - }; - - let result = GetPromptResult { - description: prompt.description.clone(), - meta: None, - messages: vec![prompt_message], - }; - - prompts.insert(prompt.name.clone(), (prompt, result)); - - Self { prompts } - } -} - -impl PromptHandler { - pub fn new() -> Self { - Self::default() - } - - pub fn list_prompts(&self) -> Vec { - self.prompts - .values() - .map(|(prompt, _)| prompt.clone()) - .collect() - } - - pub fn get_prompt_result(&self, name: &str) -> Option<&GetPromptResult> { - self.prompts.get(name).map(|(_, result)| result) - } -} diff --git a/src/resources.rs b/src/resources.rs deleted file mode 100644 index a057e21..0000000 --- a/src/resources.rs +++ /dev/null @@ -1,224 +0,0 @@ -use phf::{Map, phf_map}; -use rust_mcp_sdk::schema::{ - ListResourcesRequest, ListResourcesResult, ReadResourceRequest, ReadResourceResult, Resource, - RpcError, TextResourceContents, -}; -use tracing::{error, info, warn}; - -static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); - -const CARGO_BOOK_BASE_URL: &str = - "https://raw.githubusercontent.com/rust-lang/cargo/refs/tags/0.89.0/src/doc/src"; -const CARGO_BOOK_SCHEME: &str = "cargo-book://"; - -static CARGO_BOOK_PAGES: Map<&'static str, &'static str> = phf_map! { - "index.md" => "Introduction to Cargo", - "getting-started/index.md" => "Getting Started with Cargo", - "getting-started/installation.md" => "Installing Cargo", - "getting-started/first-steps.md" => "First Steps with Cargo", - "guide/index.md" => "Cargo Guide", - "guide/why-cargo-exists.md" => "Why Cargo Exists", - "guide/creating-a-new-project.md" => "Creating a New Package", - "guide/working-on-an-existing-project.md" => "Working on an Existing Package", - "guide/dependencies.md" => "Dependencies", - "guide/project-layout.md" => "Package Layout", - "guide/cargo-toml-vs-cargo-lock.md" => "Cargo.toml vs Cargo.lock", - "guide/tests.md" => "Tests", - "guide/continuous-integration.md" => "Continuous Integration", - "guide/cargo-home.md" => "Cargo Home", - "reference/index.md" => "Cargo Reference", - "reference/manifest.md" => "The Manifest Format", - "reference/cargo-targets.md" => "Cargo Targets", - "reference/rust-version.md" => "Rust Version", - "reference/workspaces.md" => "Workspaces", - "reference/specifying-dependencies.md" => "Specifying Dependencies", - "reference/overriding-dependencies.md" => "Overriding Dependencies", - "reference/source-replacement.md" => "Source Replacement", - "reference/resolver.md" => "Dependency Resolution", - "reference/features.md" => "Features", - "reference/features-examples.md" => "Features Examples", - "reference/profiles.md" => "Profiles", - "reference/config.md" => "Configuration", - "reference/environment-variables.md" => "Environment Variables", - "reference/build-scripts.md" => "Build Scripts", - "reference/build-script-examples.md" => "Build Script Examples", - "reference/build-cache.md" => "Build Cache", - "reference/pkgid-spec.md" => "Package ID Specifications", - "reference/external-tools.md" => "External Tools", - "reference/registries.md" => "Registries", - "reference/registry-authentication.md" => "Registry Authentication", - "reference/credential-provider-protocol.md" => "Credential Provider Protocol", - "reference/running-a-registry.md" => "Running a Registry", - "reference/registry-index.md" => "Registry Index", - "reference/registry-web-api.md" => "Registry Web API", - "reference/semver.md" => "SemVer Compatibility", - "reference/future-incompat-report.md" => "Future incompat report", - "reference/timings.md" => "Reporting build timings", - "reference/lints.md" => "Lints", - "reference/unstable.md" => "Unstable Features", - "reference/publishing.md" => "Publishing on crates.io", - "commands/index.md" => "Cargo Commands", - "commands/general-commands.md" => "General Commands", - "commands/cargo.md" => "cargo", - "commands/cargo-help.md" => "cargo help", - "commands/cargo-version.md" => "cargo version", - "commands/build-commands.md" => "Build Commands", - "commands/cargo-bench.md" => "cargo bench", - "commands/cargo-build.md" => "cargo build", - "commands/cargo-check.md" => "cargo check", - "commands/cargo-clean.md" => "cargo clean", - "commands/cargo-clippy.md" => "cargo clippy", - "commands/cargo-doc.md" => "cargo doc", - "commands/cargo-fetch.md" => "cargo fetch", - "commands/cargo-fix.md" => "cargo fix", - "commands/cargo-fmt.md" => "cargo fmt", - "commands/cargo-miri.md" => "cargo miri", - "commands/cargo-report.md" => "cargo report", - "commands/cargo-run.md" => "cargo run", - "commands/cargo-rustc.md" => "cargo rustc", - "commands/cargo-rustdoc.md" => "cargo rustdoc", - "commands/cargo-test.md" => "cargo test", - "commands/manifest-commands.md" => "Manifest Commands", - "commands/cargo-add.md" => "cargo add", - "commands/cargo-generate-lockfile.md" => "cargo generate-lockfile", - "commands/cargo-info.md" => "cargo info", - "commands/cargo-locate-project.md" => "cargo locate-project", - "commands/cargo-metadata.md" => "cargo metadata", - "commands/cargo-pkgid.md" => "cargo pkgid", - "commands/cargo-remove.md" => "cargo remove", - "commands/cargo-tree.md" => "cargo tree", - "commands/cargo-update.md" => "cargo update", - "commands/cargo-vendor.md" => "cargo vendor", - "commands/package-commands.md" => "Package Commands", - "commands/cargo-init.md" => "cargo init", - "commands/cargo-install.md" => "cargo install", - "commands/cargo-new.md" => "cargo new", - "commands/cargo-search.md" => "cargo search", - "commands/cargo-uninstall.md" => "cargo uninstall", - "commands/publishing-commands.md" => "Publishing Commands", - "commands/cargo-login.md" => "cargo login", - "commands/cargo-logout.md" => "cargo logout", - "commands/cargo-owner.md" => "cargo owner", - "commands/cargo-package.md" => "cargo package", - "commands/cargo-publish.md" => "cargo publish", - "commands/cargo-yank.md" => "cargo yank", - "commands/deprecated-and-removed.md" => "Deprecated and Removed Commands", - "faq.md" => "FAQ", - "CHANGELOG.md" => "Changelog", - "appendix/glossary.md" => "Appendix: Glossary", - "appendix/git-authentication.md" => "Appendix: Git Authentication", -}; - -pub struct ResourceHandler { - http_client: reqwest::Client, -} - -impl ResourceHandler { - pub fn new() -> Self { - let http_client = reqwest::Client::builder() - .user_agent(APP_USER_AGENT) - .build() - .expect("Failed to create HTTP client"); - Self { http_client } - } - - pub async fn handle_list_resources_request( - &self, - _request: ListResourcesRequest, - ) -> Result { - info!("Listing available Cargo Book resources"); - - let mut resources = Vec::new(); - - for (path, description) in &CARGO_BOOK_PAGES { - resources.push(Resource { - uri: format!("{CARGO_BOOK_SCHEME}{path}"), - name: description.to_string(), - title: Some(description.to_string()), - description: None, - meta: None, - mime_type: Some("text/markdown".to_string()), - size: None, - annotations: None, - }); - } - - // Sort resources by URI for consistent ordering - resources.sort_by(|a, b| a.uri.cmp(&b.uri)); - - Ok(ListResourcesResult { - meta: None, - next_cursor: None, - resources, - }) - } - - pub async fn handle_read_resource_request( - &self, - request: ReadResourceRequest, - ) -> Result { - let uri = &request.params.uri; - info!(uri = %uri, "Reading resource"); - - if let Some(page_path) = uri.strip_prefix(CARGO_BOOK_SCHEME) { - // Validate that the page path exists in our map - if CARGO_BOOK_PAGES.contains_key(page_path) { - self.read_cargo_book_page(page_path).await - } else { - warn!(uri = %uri, page_path = %page_path, "Unknown Cargo Book page path"); - Err(RpcError::invalid_params() - .with_message(format!("Unknown Cargo Book page: {page_path}"))) - } - } else { - warn!(uri = %uri, "Unknown resource URI scheme"); - Err(RpcError::invalid_params().with_message(format!("Unknown resource URI: {uri}"))) - } - } - - async fn read_cargo_book_page(&self, page_path: &str) -> Result { - let url = format!("{CARGO_BOOK_BASE_URL}/{page_path}"); - info!(url = %url, "Fetching Cargo Book page"); - - let response = match self.http_client.get(&url).send().await { - Ok(response) => response, - Err(e) => { - error!(page_path = %page_path, error = %e, "Failed to fetch Cargo Book page"); - return Err( - RpcError::internal_error().with_message(format!("Failed to fetch page: {e}")) - ); - } - }; - - if response.status().is_success() { - match response.text().await { - Ok(content) => { - info!(page_path = %page_path, content_length = content.len(), "Successfully fetched Cargo Book page"); - Ok(ReadResourceResult { - contents: vec![ - TextResourceContents { - uri: format!("{CARGO_BOOK_SCHEME}{page_path}"), - mime_type: Some("text/markdown".to_string()), - text: content, - meta: None, - } - .into(), - ], - meta: None, - }) - } - Err(e) => { - error!(page_path = %page_path, error = %e, "Failed to read response text"); - Err(RpcError::internal_error() - .with_message(format!("Failed to read response: {e}"))) - } - } - } else { - warn!(page_path = %page_path, status = %response.status(), "HTTP request failed"); - Err(RpcError::internal_error().with_message(format!( - "HTTP {} for page: {}", - response.status(), - page_path - ))) - } - } -} diff --git a/src/rmcp_server.rs b/src/rmcp_server.rs new file mode 100644 index 0000000..f21c037 --- /dev/null +++ b/src/rmcp_server.rs @@ -0,0 +1,239 @@ +use std::{collections::HashMap, sync::Arc}; + +use rmcp::{ + ErrorData, + model::{ListToolsResult, PaginatedRequestParam, ServerInfo}, + service::RequestContext, +}; + +use crate::{ + Tool, + tool::DynTool, + tools::{ + cargo::{ + CargoAddRmcpTool, CargoBuildRmcpTool, CargoCheckRmcpTool, CargoCleanRmcpTool, + CargoClippyRmcpTool, CargoDocRmcpTool, CargoFmtRmcpTool, CargoGenerateLockfileRmcpTool, + CargoInfoRmcpTool, CargoListRmcpTool, CargoMetadataRmcpTool, CargoNewRmcpTool, + CargoPackageRmcpTool, CargoRemoveRmcpTool, CargoSearchRmcpTool, CargoTestRmcpTool, + CargoUpdateRmcpTool, + }, + cargo_deny::{ + CargoDenyCheckRmcpTool, CargoDenyInitRmcpTool, CargoDenyInstallRmcpTool, + CargoDenyListRmcpTool, + }, + cargo_hack::{CargoHackInstallRmcpTool, CargoHackRmcpTool}, + cargo_machete::{CargoMacheteInstallRmcpTool, CargoMacheteRmcpTool}, + rustc::RustcExplainRmcpTool, + rustup::{RustupShowRmcpTool, RustupToolchainAddRmcpTool, RustupUpdateRmcpTool}, + }, + version::AppVersion, +}; + +pub struct Server { + tools: HashMap<&'static str, Box>, +} + +impl Server { + pub fn new(disabled_tools: &[String]) -> Self { + let mut tools: HashMap<&'static str, Box> = HashMap::new(); + + // Cargo tools + tools.insert(CargoAddRmcpTool::NAME, Box::new(CargoAddRmcpTool)); + tools.insert(CargoBuildRmcpTool::NAME, Box::new(CargoBuildRmcpTool)); + tools.insert(CargoCheckRmcpTool::NAME, Box::new(CargoCheckRmcpTool)); + tools.insert(CargoCleanRmcpTool::NAME, Box::new(CargoCleanRmcpTool)); + tools.insert(CargoClippyRmcpTool::NAME, Box::new(CargoClippyRmcpTool)); + tools.insert(CargoDocRmcpTool::NAME, Box::new(CargoDocRmcpTool)); + tools.insert(CargoFmtRmcpTool::NAME, Box::new(CargoFmtRmcpTool)); + tools.insert( + CargoGenerateLockfileRmcpTool::NAME, + Box::new(CargoGenerateLockfileRmcpTool), + ); + tools.insert(CargoInfoRmcpTool::NAME, Box::new(CargoInfoRmcpTool)); + tools.insert(CargoListRmcpTool::NAME, Box::new(CargoListRmcpTool)); + tools.insert(CargoMetadataRmcpTool::NAME, Box::new(CargoMetadataRmcpTool)); + tools.insert(CargoNewRmcpTool::NAME, Box::new(CargoNewRmcpTool)); + tools.insert(CargoPackageRmcpTool::NAME, Box::new(CargoPackageRmcpTool)); + tools.insert(CargoRemoveRmcpTool::NAME, Box::new(CargoRemoveRmcpTool)); + tools.insert(CargoSearchRmcpTool::NAME, Box::new(CargoSearchRmcpTool)); + tools.insert(CargoTestRmcpTool::NAME, Box::new(CargoTestRmcpTool)); + tools.insert(CargoUpdateRmcpTool::NAME, Box::new(CargoUpdateRmcpTool)); + + // Cargo-deny tools + tools.insert( + CargoDenyCheckRmcpTool::NAME, + Box::new(CargoDenyCheckRmcpTool), + ); + tools.insert(CargoDenyInitRmcpTool::NAME, Box::new(CargoDenyInitRmcpTool)); + tools.insert( + CargoDenyInstallRmcpTool::NAME, + Box::new(CargoDenyInstallRmcpTool), + ); + tools.insert(CargoDenyListRmcpTool::NAME, Box::new(CargoDenyListRmcpTool)); + + // Cargo-hack tools + tools.insert(CargoHackRmcpTool::NAME, Box::new(CargoHackRmcpTool)); + tools.insert( + CargoHackInstallRmcpTool::NAME, + Box::new(CargoHackInstallRmcpTool), + ); + + // Cargo-machete tools + tools.insert(CargoMacheteRmcpTool::NAME, Box::new(CargoMacheteRmcpTool)); + tools.insert( + CargoMacheteInstallRmcpTool::NAME, + Box::new(CargoMacheteInstallRmcpTool), + ); + + // Rustc tools + tools.insert(RustcExplainRmcpTool::NAME, Box::new(RustcExplainRmcpTool)); + + // Rustup tools + tools.insert(RustupShowRmcpTool::NAME, Box::new(RustupShowRmcpTool)); + tools.insert( + RustupToolchainAddRmcpTool::NAME, + Box::new(RustupToolchainAddRmcpTool), + ); + tools.insert(RustupUpdateRmcpTool::NAME, Box::new(RustupUpdateRmcpTool)); + + if !disabled_tools.is_empty() { + tracing::info!("Disabled tools: {}", disabled_tools.join(", ")); + for tool_name in disabled_tools { + if tools.remove(tool_name.as_str()).is_none() { + tracing::warn!("Tool not found: {}", tool_name); + } + } + } + + Self { tools } + } + + /// Generate markdown documentation for all tools + pub fn generate_markdown_docs(&self) -> String { + let mut output = String::new(); + + // Header + output.push_str("## Rust MCP Server\n"); + output.push_str(&format!("| 🟢 Tools ({}) | 🟢 Prompts (0) | 🟢 Resources (0) | 🔴 Logging | 🔴 Completions | 🔴 Experimental |\n", self.tools.len())); + output.push_str("| --- | --- | --- | --- | --- | --- |\n\n"); + + // Tools section + output.push_str(&format!("## 🛠️ Tools ({})\n\n\n", self.tools.len())); + + // Sort tools by name for consistent output + let mut tool_names: Vec<&str> = self.tools.keys().copied().collect(); + tool_names.sort(); + + for tool_name in tool_names { + let tool = &self.tools[tool_name]; + output.push_str(&format!("- **{}**\n", tool.name())); + output.push_str(&format!(" - {}\n", tool.description())); + + let schema = tool.json_schema(); + if let Some(serde_json::Value::Object(properties)) = schema.get("properties") + && !properties.is_empty() + { + output.push_str(" - **Inputs:**\n"); + + // Sort properties for consistent output + let mut prop_names: Vec<&String> = properties.keys().collect(); + prop_names.sort(); + + for prop_name in prop_names { + let prop = &properties[prop_name]; + let type_str = self.format_property_type(prop); + output.push_str(&format!( + " - {} : {}
\n", + prop_name, type_str + )); + } + } + output.push('\n'); + } + + output.pop(); + output + } + + fn format_property_type(&self, prop: &serde_json::Value) -> String { + if let Some(type_val) = prop.get("type") { + match type_val.as_str() { + Some("array") => { + if let Some(items) = prop.get("items") + && let Some(item_type) = items.get("type") + { + return format!("{} [ ]", item_type.as_str().unwrap_or("unknown")); + } + "array".to_string() + } + Some(type_str) => type_str.to_string(), + None => "unknown".to_string(), + } + } else { + "unknown".to_string() + } + } +} + +impl rmcp::ServerHandler for Server { + fn get_info(&self) -> ServerInfo { + use rmcp::model::{ + Implementation, InitializeResult, ProtocolVersion, ServerCapabilities, ToolsCapability, + }; + + InitializeResult { + protocol_version: ProtocolVersion::LATEST, + capabilities: ServerCapabilities { + tools: Some(ToolsCapability { list_changed: None }), + ..Default::default() + }, + server_info: Implementation { + name: "Rust MCP Server".to_owned(), + version: AppVersion::version(), + icons: None, + title: Some("Rust MCP Server".to_owned()), + website_url: Some("https://github.com/Vaiz/rust-mcp-server".to_owned()), + }, + instructions: Some(include_str!("../docs/instructions.md").to_owned()), + } + } + async fn list_tools( + &self, + _request: Option, + _context: RequestContext, + ) -> Result { + let mut tools: Vec = Vec::new(); + + for tool in self.tools.values() { + let schema = Arc::new(tool.json_schema()); + tools.push(rmcp::model::Tool { + name: tool.name().into(), + title: Some(tool.title().into()), + description: Some(tool.description().trim().trim_matches('\n').into()), + input_schema: schema, + output_schema: None, + annotations: None, + icons: None, + meta: None, + }); + } + + Ok(ListToolsResult { + meta: None, + next_cursor: None, + tools, + }) + } + + async fn call_tool( + &self, + request: rmcp::model::CallToolRequestParam, + _context: RequestContext, + ) -> Result { + let tool = self.tools.get(request.name.as_ref()).ok_or_else(|| { + ErrorData::invalid_request(format!("Tool '{}' not found", request.name), None) + })?; + + tool.call_rmcp_tool(request) + } +} diff --git a/src/serde_utils.rs b/src/serde_utils.rs index c6e1c45..94a364c 100644 --- a/src/serde_utils.rs +++ b/src/serde_utils.rs @@ -1,4 +1,4 @@ -use rust_mcp_sdk::schema::schema_utils::CallToolError; +use rmcp::ErrorData; /// Utility function for parsing Option fields in serde, /// returning None if the string is "null" (case-insensitive) or empty. @@ -54,17 +54,18 @@ where pub fn locking_mode_to_cli_flags( mode: Option<&str>, preferred: &str, -) -> Result, CallToolError> { +) -> Result, ErrorData> { Ok(match mode.unwrap_or(preferred) { "locked" => vec!["--locked"], "unlocked" => vec![], // No flags needed "offline" => vec!["--offline"], "frozen" => vec!["--frozen"], unknown => { - return Err(CallToolError( - anyhow::anyhow!( - "Unknown locking mode: {unknown}. Valid options are: locked, unlocked, offline, frozen" - ).into() + return Err(ErrorData::invalid_params( + format!( + "Unknown locking mode: {unknown}. Valid options are: locked, unlocked, offline, frozen" + ), + None, )); } }) @@ -77,19 +78,17 @@ pub fn locking_mode_to_cli_flags( /// - "quiet" (default): Show only the essential command output /// - "normal": Show standard output (no additional flags) /// - "verbose": Show detailed output including build information -pub fn output_verbosity_to_cli_flags( - mode: Option<&str>, -) -> Result, CallToolError> { +pub fn output_verbosity_to_cli_flags(mode: Option<&str>) -> Result, ErrorData> { Ok(match mode.unwrap_or("quiet") { "quiet" => vec!["--quiet"], "normal" => vec![], // No flags needed "verbose" => vec!["--verbose"], unknown => { - return Err(CallToolError( - anyhow::anyhow!( + return Err(ErrorData::invalid_params( + format!( "Unknown output verbosity: {unknown}. Valid options are: quiet, normal, verbose" - ) - .into(), + ), + None, )); } }) @@ -134,43 +133,6 @@ impl PackageWithVersion { } } -pub trait Tool: schemars::JsonSchema { - /// Returns the JSON schema for this type. - fn json_schema() -> serde_json::Map { - use schemars::schema_for; - use serde_json::Value; - - let schema = schema_for!(Self).to_value(); - if let serde_json::Value::Object(mut map) = schema { - map.remove("$schema"); - - // Gemini doesn't like "type": ["string", "null"] - let null_string = Value::String("null".to_string()); - if let Some(Value::Object(props_map)) = map.get_mut("properties") { - for value in props_map.values_mut() { - if let Value::Object(prop_obj) = value - && let Some(Value::Array(ty)) = prop_obj.get("type") - && ty.len() == 2 - && ty.contains(&null_string) - { - let new_ty = ty.iter().find(|v| v != &&null_string).cloned(); - - if let Some(new_ty) = new_ty { - prop_obj.insert("type".to_string(), new_ty); - } - } - } - } - - map - } else { - panic!("Expected schema to be an object, got: {schema:?}"); - } - } -} - -impl Tool for T {} - #[cfg(test)] mod tests { use super::*; @@ -322,111 +284,6 @@ mod tests { assert_eq!(pkg3.to_spec(), "clap@4.0.0-beta.1"); } - #[test] - fn test_tool_json_schema_removes_null_type_first() { - #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)] - struct Example { - #[serde(default)] - value: Option, - } - - let schema = Example::json_schema(); - let props = schema.get("properties").unwrap(); - let value_schema = props.get("value").unwrap(); - if let serde_json::Value::Object(obj) = value_schema { - // Should not be an array of types, just "string" - let ty = obj.get("type").unwrap(); - assert_eq!(ty, "string"); - } else { - panic!("Expected value property to be an object"); - } - } - - #[test] - fn test_tool_json_schema_removes_null_type_second() { - #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)] - struct Example { - #[serde(default)] - value: Option, - } - - let schema = Example::json_schema(); - let props = schema.get("properties").unwrap(); - let value_schema = props.get("value").unwrap(); - if let serde_json::Value::Object(obj) = value_schema { - // Should not be an array of types, just "integer" - let ty = obj.get("type").unwrap(); - assert_eq!(ty, "integer"); - } else { - panic!("Expected value property to be an object"); - } - } - - #[test] - fn test_tool_json_schema_leaves_non_null_type_untouched() { - #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)] - struct Example { - value: String, - } - - let schema = Example::json_schema(); - let props = schema.get("properties").unwrap(); - let value_schema = props.get("value").unwrap(); - if let serde_json::Value::Object(obj) = value_schema { - let ty = obj.get("type").unwrap(); - assert_eq!(ty, "string"); - } else { - panic!("Expected value property to be an object"); - } - } - - #[test] - fn test_tool_json_schema_handles_multiple_properties() { - #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)] - struct Example { - #[serde(default)] - opt: Option, - num: i32, - } - - let schema = Example::json_schema(); - let props = schema.get("properties").unwrap(); - let opt_schema = props.get("opt").unwrap(); - let num_schema = props.get("num").unwrap(); - - if let serde_json::Value::Object(obj) = opt_schema { - let ty = obj.get("type").unwrap(); - assert_eq!(ty, "string"); - } else { - panic!("Expected opt property to be an object"); - } - - if let serde_json::Value::Object(obj) = num_schema { - let ty = obj.get("type").unwrap(); - assert_eq!(ty, "integer"); - } else { - panic!("Expected num property to be an object"); - } - } - - #[test] - fn test_tool_json_schema_ignores_non_array_type() { - #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)] - struct Example { - value: bool, - } - - let schema = Example::json_schema(); - let props = schema.get("properties").unwrap(); - let value_schema = props.get("value").unwrap(); - if let serde_json::Value::Object(obj) = value_schema { - let ty = obj.get("type").unwrap(); - assert_eq!(ty, "boolean"); - } else { - panic!("Expected value property to be an object"); - } - } - #[test] fn test_locking_mode_cli_flags() { // Test default (locked) diff --git a/src/tool.rs b/src/tool.rs new file mode 100644 index 0000000..fe1df59 --- /dev/null +++ b/src/tool.rs @@ -0,0 +1,293 @@ +use rmcp::ErrorData; +use rmcp::model::{ + AnnotateAble, Annotated, Annotations, CallToolRequestParam, CallToolResult, RawContent, Role, +}; +use schemars::JsonSchema; + +use crate::tools::apply_workspace_root; + +/// Dyn compatible Tool trait +pub(crate) trait DynTool { + fn name(&self) -> &'static str; + fn title(&self) -> &'static str; + fn description(&self) -> &'static str; + fn json_schema(&self) -> serde_json::Map; + fn call_rmcp_tool(&self, request: CallToolRequestParam) -> Result; +} + +/// Actual trait that all tools must implement +pub(crate) trait Tool { + const NAME: &'static str; + const TITLE: &'static str; + const DESCRIPTION: &'static str; + type RequestArgs: serde::de::DeserializeOwned + schemars::JsonSchema; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result; +} + +impl DynTool for T +where + T: Tool, +{ + fn name(&self) -> &'static str { + T::NAME + } + + fn title(&self) -> &'static str { + T::TITLE + } + + fn description(&self) -> &'static str { + T::DESCRIPTION + } + + fn json_schema(&self) -> serde_json::Map { + json_schema_impl::() + } + + fn call_rmcp_tool(&self, request: CallToolRequestParam) -> Result { + let Some(args) = request.arguments else { + return Err(ErrorData::invalid_params("Missing tool arguments", None)); + }; + + let args: T::RequestArgs = serde_json::from_value(args.into()).map_err(|e| { + ErrorData::invalid_params(format!("Failed to parse tool arguments: {e}"), None) + })?; + + self.call_rmcp_tool(args) + } +} + +pub(crate) fn execute_rmcp_command( + mut cmd: std::process::Command, + tool_name: &str, +) -> Result { + apply_workspace_root(&mut cmd); + tracing::info!("Executing command for {}: {:?}", tool_name, cmd); + let output = cmd.output(); + + match output { + Ok(output) => { + let stdout = String::from_utf8_lossy(output.stdout.trim_ascii()); + let stderr = String::from_utf8_lossy(output.stderr.trim_ascii()); + + let mut content: Vec> = Vec::new(); + if output.status.success() { + tracing::info!( + "Command executed successfully for {tool_name}\nstdout=\n{stdout}\n\nstderr=\n{stderr}", + ); + content.push( + RawContent::text(format!("✅ {tool_name}: Success")).annotate(Annotations { + audience: Some(vec![Role::User, Role::Assistant]), + last_modified: None, + priority: Some(0.3), + }), + ); + } else { + tracing::warn!( + "Command execution failed for {tool_name} (status: {:?}): stdout='\n{stdout}\n', stderr='\n{stderr}\n'", + output.status.code(), + ); + content.push( + RawContent::text(format!("❌ {tool_name}: Failure")).annotate(Annotations { + audience: Some(vec![Role::User, Role::Assistant]), + last_modified: None, + priority: Some(0.3), + }), + ); + } + + if !stdout.is_empty() { + content.push(RawContent::text(stdout).annotate(Annotations { + audience: Some(vec![Role::User, Role::Assistant]), + last_modified: None, + priority: Some(0.2), + })); + } + if !stderr.is_empty() { + content.push(RawContent::text(stderr).annotate(Annotations { + audience: Some(vec![Role::User, Role::Assistant]), + last_modified: None, + priority: Some(1.), + })); + } + Ok(CallToolResult { + content, + is_error: Some(!output.status.success()), + meta: None, + structured_content: None, + }) + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + tracing::error!("Command not found: {e}"); + let program = cmd.get_program().to_string_lossy(); + let args = cmd + .get_args() + .map(|arg| arg.to_string_lossy()) + .collect::>() + .join(" "); + let item = RawContent::text( + format!( + "The command `{program}` was not found, please ensure it is installed and accessible. You can try running the following command yourself to verify: `{program} {args}`", + )).annotate( + Annotations { + audience: Some(vec![Role::User, Role::Assistant]), + last_modified: None, + priority: Some(1.), + }); + + Ok(CallToolResult { + content: vec![item], + is_error: Some(true), + meta: None, + structured_content: None, + }) + } + Err(e) => { + tracing::error!("Failed to execute command: {}", e); + Err(ErrorData::internal_error(e.to_string(), None)) + } + } +} + +fn json_schema_impl() -> serde_json::Map { + use schemars::schema_for; + use serde_json::Value; + + let schema = schema_for!(T).to_value(); + if let serde_json::Value::Object(mut map) = schema { + map.remove("$schema"); + + // Gemini doesn't like "type": ["string", "null"] + let null_string = Value::String("null".to_string()); + if let Some(Value::Object(props_map)) = map.get_mut("properties") { + for value in props_map.values_mut() { + if let Value::Object(prop_obj) = value + && let Some(Value::Array(ty)) = prop_obj.get("type") + && ty.len() == 2 + && ty.contains(&null_string) + { + let new_ty = ty.iter().find(|v| v != &&null_string).cloned(); + + if let Some(new_ty) = new_ty { + prop_obj.insert("type".to_string(), new_ty); + } + } + } + } + + map + } else { + panic!("Expected schema to be an object, got: {schema:?}"); + } +} + +#[cfg(test)] +mod tests { + use super::json_schema_impl; + + #[test] + fn test_tool_json_schema_removes_null_type_first() { + #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)] + struct Example { + #[serde(default)] + value: Option, + } + + let schema = json_schema_impl::(); + let props = schema.get("properties").unwrap(); + let value_schema = props.get("value").unwrap(); + if let serde_json::Value::Object(obj) = value_schema { + // Should not be an array of types, just "string" + let ty = obj.get("type").unwrap(); + assert_eq!(ty, "string"); + } else { + panic!("Expected value property to be an object"); + } + } + + #[test] + fn test_tool_json_schema_removes_null_type_second() { + #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)] + struct Example { + #[serde(default)] + value: Option, + } + + let schema = json_schema_impl::(); + let props = schema.get("properties").unwrap(); + let value_schema = props.get("value").unwrap(); + if let serde_json::Value::Object(obj) = value_schema { + // Should not be an array of types, just "integer" + let ty = obj.get("type").unwrap(); + assert_eq!(ty, "integer"); + } else { + panic!("Expected value property to be an object"); + } + } + + #[test] + fn test_tool_json_schema_leaves_non_null_type_untouched() { + #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)] + struct Example { + value: String, + } + + let schema = json_schema_impl::(); + let props = schema.get("properties").unwrap(); + let value_schema = props.get("value").unwrap(); + if let serde_json::Value::Object(obj) = value_schema { + let ty = obj.get("type").unwrap(); + assert_eq!(ty, "string"); + } else { + panic!("Expected value property to be an object"); + } + } + + #[test] + fn test_tool_json_schema_handles_multiple_properties() { + #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)] + struct Example { + #[serde(default)] + opt: Option, + num: i32, + } + + let schema = json_schema_impl::(); + let props = schema.get("properties").unwrap(); + let opt_schema = props.get("opt").unwrap(); + let num_schema = props.get("num").unwrap(); + + if let serde_json::Value::Object(obj) = opt_schema { + let ty = obj.get("type").unwrap(); + assert_eq!(ty, "string"); + } else { + panic!("Expected opt property to be an object"); + } + + if let serde_json::Value::Object(obj) = num_schema { + let ty = obj.get("type").unwrap(); + assert_eq!(ty, "integer"); + } else { + panic!("Expected num property to be an object"); + } + } + + #[test] + fn test_tool_json_schema_ignores_non_array_type() { + #[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema)] + struct Example { + value: bool, + } + + let schema = json_schema_impl::(); + let props = schema.get("properties").unwrap(); + let value_schema = props.get("value").unwrap(); + if let serde_json::Value::Object(obj) = value_schema { + let ty = obj.get("type").unwrap(); + assert_eq!(ty, "boolean"); + } else { + panic!("Expected value property to be an object"); + } + } +} diff --git a/src/tools/cargo/add_remove.rs b/src/tools/cargo/add_remove.rs index 9647841..ea43082 100644 --- a/src/tools/cargo/add_remove.rs +++ b/src/tools/cargo/add_remove.rs @@ -1,42 +1,34 @@ use std::process::Command; -use crate::serde_utils::Tool; use crate::{ + Tool, execute_rmcp_command, serde_utils::{ PackageWithVersion, deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, output_verbosity_to_cli_flags, }, - tools::execute_command, -}; -use rust_mcp_sdk::{ - macros::mcp_tool, - schema::{CallToolResult, schema_utils::CallToolError}, }; +use rmcp::{ErrorData, model::CallToolResult}; fn dependency_type_to_cli_flag( dependency_type: Option<&str>, -) -> Result, CallToolError> { +) -> Result, ErrorData> { Ok(match dependency_type { None => None, Some("regular") => None, Some("dev") => Some("--dev"), Some("build") => Some("--build"), Some(dep) => { - return Err(CallToolError( - anyhow::anyhow!("Unknown dependency type: {dep}").into(), + return Err(ErrorData::invalid_params( + format!("Unknown dependency type: {dep}"), + None, )); } }) } /// Adds a dependency to a Rust project using cargo add. -#[mcp_tool( - name = "cargo-add", - description = "Adds a dependency to a Rust project using cargo add.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoAddTool { +pub struct CargoAddRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -136,8 +128,8 @@ pub struct CargoAddTool { pub output_verbosity: Option, } -impl CargoAddTool { - pub fn call_tool(&self) -> Result { +impl CargoAddRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -227,18 +219,26 @@ impl CargoAddTool { let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?; cmd.args(output_flags); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoAddRmcpTool; + +impl Tool for CargoAddRmcpTool { + const NAME: &'static str = "cargo-add"; + const TITLE: &'static str = "Add Rust dependency"; + const DESCRIPTION: &'static str = "Adds a dependency to a Rust project using cargo add."; + type RequestArgs = CargoAddRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } /// Remove dependencies from a Cargo.toml manifest file. -#[mcp_tool( - name = "cargo-remove", - description = "Remove dependencies from a Cargo.toml manifest file.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoRemoveTool { +pub struct CargoRemoveRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -293,8 +293,8 @@ pub struct CargoRemoveTool { pub output_verbosity: Option, } -impl CargoRemoveTool { - pub fn call_tool(&self) -> Result { +impl CargoRemoveRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -342,12 +342,27 @@ impl CargoRemoveTool { let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?; cmd.args(output_flags); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoRemoveRmcpTool; + +impl Tool for CargoRemoveRmcpTool { + const NAME: &'static str = "cargo-remove"; + const TITLE: &'static str = "Remove Rust dependency"; + const DESCRIPTION: &'static str = "Remove dependencies from a Cargo.toml manifest file."; + type RequestArgs = CargoRemoveRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } #[cfg(test)] mod tests { + use crate::tool::DynTool; + use super::*; #[test] @@ -506,19 +521,19 @@ mod tests { "package", "target_package" ], - "title": "CargoAddTool", + "title": "CargoAddRequest", "type": "object" }"##; - let schema = serde_json::Value::from(CargoAddTool::json_schema()); + let schema = serde_json::Value::from(CargoAddRmcpTool {}.json_schema()); println!( - "CargoAddTool schema: {}", + "CargoAddRequest schema: {}", serde_json::to_string_pretty(&schema).unwrap() ); let expected_schema: serde_json::Value = serde_json::from_str(EXPECTED_SCHEMA).unwrap(); assert_eq!( schema, expected_schema, - "CargoAddTool schema should match expected structure" + "CargoAddRequest schema should match expected structure" ); } } diff --git a/src/tools/cargo/build.rs b/src/tools/cargo/build.rs index 314913e..10479f0 100644 --- a/src/tools/cargo/build.rs +++ b/src/tools/cargo/build.rs @@ -1,24 +1,16 @@ use std::process::Command; use crate::{ + Tool, execute_rmcp_command, serde_utils::{ deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, output_verbosity_to_cli_flags, }, - tools::execute_command, -}; -use rust_mcp_sdk::{ - macros::{JsonSchema, mcp_tool}, - schema::{CallToolResult, schema_utils::CallToolError}, }; +use rmcp::ErrorData; -#[mcp_tool( - name = "cargo-build", - description = "Builds a Rust project using Cargo. Usually, run without any additional arguments.", - openWorldHint = false -)] -#[derive(Debug, ::serde::Deserialize, JsonSchema)] -pub struct CargoBuildTool { +#[derive(Debug, ::serde::Deserialize, ::schemars::JsonSchema)] +pub struct CargoBuildRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -147,8 +139,8 @@ pub struct CargoBuildTool { warnings_as_errors: Option, } -impl CargoBuildTool { - pub fn call_tool(&self) -> Result { +impl CargoBuildRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -277,7 +269,24 @@ impl CargoBuildTool { if self.warnings_as_errors.unwrap_or(false) { cmd.env("RUSTFLAGS", "-D warnings"); } + Ok(cmd) + } +} - execute_command(cmd, &Self::tool_name()) +pub struct CargoBuildRmcpTool; + +impl Tool for CargoBuildRmcpTool { + const NAME: &'static str = "cargo-build"; + const TITLE: &'static str = "cargo build"; + const DESCRIPTION: &'static str = + "Builds a Rust project using Cargo. Usually, run without any additional arguments."; + type RequestArgs = CargoBuildRequest; + + fn call_rmcp_tool( + &self, + request: Self::RequestArgs, + ) -> Result { + let cmd = request.build_cmd()?; + execute_rmcp_command(cmd, Self::NAME) } } diff --git a/src/tools/cargo/check.rs b/src/tools/cargo/check.rs index 3799223..a927ae9 100644 --- a/src/tools/cargo/check.rs +++ b/src/tools/cargo/check.rs @@ -1,24 +1,16 @@ use std::process::Command; use crate::{ + Tool, execute_rmcp_command, serde_utils::{ deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, output_verbosity_to_cli_flags, }, - tools::execute_command, -}; -use rust_mcp_sdk::{ - macros::{JsonSchema, mcp_tool}, - schema::{CallToolResult, schema_utils::CallToolError}, }; +use rmcp::ErrorData; -#[mcp_tool( - name = "cargo-check", - description = "Checks a Rust package and all of its dependencies for errors. Usually, run without any additional arguments.", - openWorldHint = false -)] -#[derive(Debug, ::serde::Deserialize, JsonSchema)] -pub struct CargoCheckTool { +#[derive(Debug, ::serde::Deserialize, ::schemars::JsonSchema)] +pub struct CargoCheckRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -147,8 +139,8 @@ pub struct CargoCheckTool { warnings_as_errors: Option, } -impl CargoCheckTool { - pub fn call_tool(&self) -> Result { +impl CargoCheckRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -277,7 +269,23 @@ impl CargoCheckTool { if self.warnings_as_errors.unwrap_or(false) { cmd.env("RUSTFLAGS", "-D warnings"); } + Ok(cmd) + } +} + +pub struct CargoCheckRmcpTool; + +impl Tool for CargoCheckRmcpTool { + const NAME: &'static str = "cargo-check"; + const TITLE: &'static str = "cargo check"; + const DESCRIPTION: &'static str = "Checks a Rust package and all of its dependencies for errors. Usually, run without any additional arguments."; + type RequestArgs = CargoCheckRequest; - execute_command(cmd, &Self::tool_name()) + fn call_rmcp_tool( + &self, + request: Self::RequestArgs, + ) -> Result { + let cmd = request.build_cmd()?; + execute_rmcp_command(cmd, Self::NAME) } } diff --git a/src/tools/cargo/clippy.rs b/src/tools/cargo/clippy.rs index cb3e6a2..475f0d6 100644 --- a/src/tools/cargo/clippy.rs +++ b/src/tools/cargo/clippy.rs @@ -1,26 +1,16 @@ use std::process::Command; use crate::{ + Tool, execute_rmcp_command, serde_utils::{ deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, output_verbosity_to_cli_flags, }, - tools::execute_command, }; -use rust_mcp_sdk::{ - macros::mcp_tool, - schema::{CallToolResult, schema_utils::CallToolError}, -}; - -use crate::serde_utils::Tool; +use rmcp::ErrorData; -#[mcp_tool( - name = "cargo-clippy", - description = "Checks a Rust package to catch common mistakes and improve code quality using Clippy", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoClippyTool { +pub struct CargoClippyRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -148,9 +138,8 @@ pub struct CargoClippyTool { #[serde(default)] warnings_as_errors: Option, } - -impl CargoClippyTool { - pub fn call_tool(&self) -> Result { +impl CargoClippyRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -281,10 +270,27 @@ impl CargoClippyTool { cmd.env("RUSTFLAGS", "-D warnings"); } - execute_command(cmd, &Self::tool_name()) + Ok(cmd) } } +pub struct CargoClippyRmcpTool; + +impl Tool for CargoClippyRmcpTool { + const NAME: &'static str = "cargo-clippy"; + const TITLE: &'static str = "cargo clippy"; + const DESCRIPTION: &'static str = + "Checks a Rust package to catch common mistakes and improve code quality using Clippy"; + type RequestArgs = CargoClippyRequest; + + fn call_rmcp_tool( + &self, + request: Self::RequestArgs, + ) -> Result { + let cmd = request.build_cmd()?; + execute_rmcp_command(cmd, Self::NAME) + } +} #[cfg(test)] mod tests { use super::*; @@ -314,7 +320,7 @@ mod tests { "warnings_as_errors": false }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool .expect("Deserialization should succeed even if `package` is missing (it's Option)"); @@ -331,7 +337,7 @@ mod tests { "package": ["my_package"], }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool.expect("Deserialization should succeed"); assert_eq!(tool.package.unwrap(), ["my_package".to_owned()]); diff --git a/src/tools/cargo/doc.rs b/src/tools/cargo/doc.rs index ff1f052..550eef2 100644 --- a/src/tools/cargo/doc.rs +++ b/src/tools/cargo/doc.rs @@ -1,25 +1,19 @@ use std::path::Path; use std::process::Command; +use rmcp::{ErrorData, model::RawContent}; + use crate::{ + Tool, execute_rmcp_command, serde_utils::{ deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, output_verbosity_to_cli_flags, }, - tools::{WORKSPACE_ROOT, execute_command}, -}; -use rust_mcp_sdk::{ - macros::{JsonSchema, mcp_tool}, - schema::{Annotations, CallToolResult, Role, TextContent, schema_utils::CallToolError}, + tools::WORKSPACE_ROOT, }; -#[mcp_tool( - name = "cargo-doc", - description = "Build documentation for a Rust package using Cargo. Recommended to use with no_deps and specific package for faster builds. Returns path to generated documentation index.", - openWorldHint = false -)] -#[derive(Debug, ::serde::Deserialize, JsonSchema)] -pub struct CargoDocTool { +#[derive(Debug, ::serde::Deserialize, ::schemars::JsonSchema)] +pub struct CargoDocRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -141,8 +135,8 @@ pub struct CargoDocTool { message_format: Option, } -impl CargoDocTool { - pub fn call_tool(&self) -> Result { +impl CargoDocRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -266,32 +260,7 @@ impl CargoDocTool { cmd.arg("--message-format").arg(message_format); } - // Execute the command and get the result - let mut result = execute_command(cmd, &Self::tool_name())?; - - // Add documentation path information only if the command was successful - if result.is_error != Some(true) { - let doc_path = self.get_doc_path(); - let doc_info = if let Some(doc_path) = doc_path { - format!( - "\n📚 Documentation generated successfully!\n📄 Documentation file: {doc_path}\n💡 Open this file in your browser to view the docs" - ) - } else { - "\n📚 Documentation generated successfully!".to_owned() - }; - - let annotations = Some(Annotations { - audience: vec![Role::User, Role::Assistant], - last_modified: None, - priority: Some(0.5), - }); - - result - .content - .push(TextContent::new(doc_info, annotations, None).into()); - } - - Ok(result) + Ok(cmd) } fn get_doc_path(&self) -> Option { @@ -355,3 +324,45 @@ impl CargoDocTool { absolute_path.to_string_lossy().into_owned() } } + +pub struct CargoDocRmcpTool; + +impl Tool for CargoDocRmcpTool { + const NAME: &'static str = "cargo-doc"; + const TITLE: &'static str = "Build Rust documentation"; + const DESCRIPTION: &'static str = "Build documentation for a Rust package using Cargo. Recommended to use with no_deps and specific package for faster builds. Returns path to generated documentation index."; + type RequestArgs = CargoDocRequest; + + fn call_rmcp_tool( + &self, + request: Self::RequestArgs, + ) -> Result { + let cmd = request.build_cmd()?; + let mut result = execute_rmcp_command(cmd, Self::NAME)?; + + // Add documentation path information only if the command was successful + if result.is_error != Some(true) { + let doc_path = request.get_doc_path(); + let doc_info = if let Some(doc_path) = doc_path { + format!( + "\n📚 Documentation generated successfully!\n📄 Documentation file: {doc_path}\n💡 Open this file in your browser to view the docs" + ) + } else { + "\n📚 Documentation generated successfully!".to_owned() + }; + + use rmcp::model::{AnnotateAble, Annotations, Role}; + let annotations = Annotations { + audience: Some(vec![Role::User, Role::Assistant]), + last_modified: None, + priority: Some(0.5), + }; + + result + .content + .push(RawContent::text(doc_info).annotate(annotations)); + } + + Ok(result) + } +} diff --git a/src/tools/cargo/info.rs b/src/tools/cargo/info.rs index 4c62e7f..075bcf1 100644 --- a/src/tools/cargo/info.rs +++ b/src/tools/cargo/info.rs @@ -1,26 +1,17 @@ use std::process::Command; -use crate::serde_utils::Tool; use crate::{ + Tool, execute_rmcp_command, serde_utils::{ PackageWithVersion, deserialize_string, locking_mode_to_cli_flags, output_verbosity_to_cli_flags, }, - tools::execute_command, -}; -use rust_mcp_sdk::{ - macros::mcp_tool, - schema::{CallToolResult, schema_utils::CallToolError}, }; +use rmcp::ErrorData; /// Display information about a package. Information includes package description, list of available features, etc. Equivalent to 'cargo info '. -#[mcp_tool( - name = "cargo-info", - description = "Display information about a package. Information includes package description, list of available features, etc. Equivalent to 'cargo info '.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoInfoTool { +pub struct CargoInfoRequest { /// Package with optional version (e.g., {"package": "serde", "version": "1.0.0"}) #[serde(flatten)] pub package_spec: PackageWithVersion, @@ -56,9 +47,8 @@ pub struct CargoInfoTool { #[serde(default, deserialize_with = "deserialize_string")] pub locking_mode: Option, } - -impl CargoInfoTool { - pub fn call_tool(&self) -> Result { +impl CargoInfoRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg("info"); @@ -84,12 +74,30 @@ impl CargoInfoTool { let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?; cmd.args(output_flags); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) } } +pub struct CargoInfoRmcpTool; + +impl Tool for CargoInfoRmcpTool { + const NAME: &'static str = "cargo-info"; + const TITLE: &'static str = "cargo info"; + const DESCRIPTION: &'static str = "Display information about a package. Information includes package description, list of available features, etc. Equivalent to 'cargo info '."; + type RequestArgs = CargoInfoRequest; + + fn call_rmcp_tool( + &self, + request: Self::RequestArgs, + ) -> Result { + let cmd = request.build_cmd()?; + execute_rmcp_command(cmd, Self::NAME) + } +} #[cfg(test)] mod tests { + use crate::tool::DynTool; + use super::*; #[test] @@ -135,10 +143,10 @@ mod tests { "required": [ "package" ], - "title": "CargoInfoTool", + "title": "CargoInfoRequest", "type": "object" }"##; - let schema = serde_json::Value::from(CargoInfoTool::json_schema()); + let schema = serde_json::Value::from(CargoInfoRmcpTool {}.json_schema()); println!( "CargoInfoTool schema: {}", serde_json::to_string_pretty(&schema).unwrap() diff --git a/src/tools/cargo/metadata.rs b/src/tools/cargo/metadata.rs index 83ba610..772d940 100644 --- a/src/tools/cargo/metadata.rs +++ b/src/tools/cargo/metadata.rs @@ -1,22 +1,15 @@ use std::process::Command; -use crate::serde_utils::{ - deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, - output_verbosity_to_cli_flags, +use crate::{ + Tool, execute_rmcp_command, + serde_utils::{ + deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, + output_verbosity_to_cli_flags, + }, }; -use crate::tools::execute_command; -use rust_mcp_sdk::{ - macros::{JsonSchema, mcp_tool}, - schema::{CallToolResult, schema_utils::CallToolError}, -}; - -#[mcp_tool( - name = "cargo-metadata", - description = "Outputs a listing of a project's resolved dependencies and metadata in machine-readable format (JSON).", - openWorldHint = false -)] -#[derive(Debug, ::serde::Deserialize, JsonSchema)] -pub struct CargoMetadataTool { +use rmcp::ErrorData; +#[derive(Debug, ::serde::Deserialize, ::schemars::JsonSchema)] +pub struct CargoMetadataRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -73,9 +66,8 @@ pub struct CargoMetadataTool { #[serde(default, deserialize_with = "deserialize_string")] locking_mode: Option, } - -impl CargoMetadataTool { - pub fn call_tool(&self) -> Result { +impl CargoMetadataRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -127,10 +119,26 @@ impl CargoMetadataTool { let locking_flags = locking_mode_to_cli_flags(self.locking_mode.as_deref(), "locked")?; cmd.args(locking_flags); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) } } +pub struct CargoMetadataRmcpTool; + +impl Tool for CargoMetadataRmcpTool { + const NAME: &'static str = "cargo-metadata"; + const TITLE: &'static str = "cargo metadata"; + const DESCRIPTION: &'static str = "Outputs a listing of a project's resolved dependencies and metadata in machine-readable format (JSON)."; + type RequestArgs = CargoMetadataRequest; + + fn call_rmcp_tool( + &self, + request: Self::RequestArgs, + ) -> Result { + let cmd = request.build_cmd()?; + execute_rmcp_command(cmd, Self::NAME) + } +} #[cfg(test)] mod tests { use super::*; @@ -142,7 +150,7 @@ mod tests { "features": ["serde", "tokio"], }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool.expect("Deserialization should succeed with features array"); assert_eq!( @@ -157,7 +165,7 @@ mod tests { "features": "serde", }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool.expect("Deserialization should succeed with single feature string"); assert_eq!(tool.features.unwrap(), ["serde".to_owned()]); @@ -169,7 +177,7 @@ mod tests { "features": "[\"serde\",\"tokio\"]", }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool .expect("Deserialization should succeed with features string that looks like array"); diff --git a/src/tools/cargo/mod.rs b/src/tools/cargo/mod.rs index e878233..e361e92 100644 --- a/src/tools/cargo/mod.rs +++ b/src/tools/cargo/mod.rs @@ -10,40 +10,31 @@ mod search; mod test; mod update; -pub use add_remove::{CargoAddTool, CargoRemoveTool}; -pub use build::CargoBuildTool; -pub use check::CargoCheckTool; -pub use clippy::CargoClippyTool; -pub use doc::CargoDocTool; -pub use info::CargoInfoTool; -pub use metadata::CargoMetadataTool; -pub use package::CargoPackageTool; -pub use search::CargoSearchTool; -pub use test::CargoTestTool; -pub use update::CargoUpdateTool; +pub use add_remove::{CargoAddRmcpTool, CargoRemoveRmcpTool}; +pub use build::CargoBuildRmcpTool; +pub use check::CargoCheckRmcpTool; +pub use clippy::CargoClippyRmcpTool; +pub use doc::CargoDocRmcpTool; +pub use info::CargoInfoRmcpTool; +pub use metadata::CargoMetadataRmcpTool; +pub use package::CargoPackageRmcpTool; +pub use search::CargoSearchRmcpTool; +pub use test::CargoTestRmcpTool; +pub use update::CargoUpdateRmcpTool; use std::process::Command; -use rust_mcp_sdk::{ - macros::mcp_tool, - schema::{CallToolResult, schema_utils::CallToolError}, +use crate::{ + Tool, execute_rmcp_command, + serde_utils::{ + deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, + output_verbosity_to_cli_flags, + }, }; +use rmcp::{ErrorData, model::CallToolResult}; -use crate::serde_utils::{ - deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, - output_verbosity_to_cli_flags, -}; -use crate::tools::execute_command; - -use crate::serde_utils::Tool; - -#[mcp_tool( - name = "cargo-generate_lockfile", - description = "Generates or updates the Cargo.lock file for a Rust project. Usually, run without any additional arguments.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoGenerateLockfileTool { +pub struct CargoGenerateLockfileRequest { /// Path to Cargo.toml #[serde(default, deserialize_with = "deserialize_string")] manifest_path: Option, @@ -76,8 +67,8 @@ pub struct CargoGenerateLockfileTool { output_verbosity: Option, } -impl CargoGenerateLockfileTool { - pub fn call_tool(&self) -> Result { +impl CargoGenerateLockfileRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg("generate-lockfile"); @@ -101,17 +92,25 @@ impl CargoGenerateLockfileTool { let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?; cmd.args(output_flags); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoGenerateLockfileRmcpTool; + +impl Tool for CargoGenerateLockfileRmcpTool { + const NAME: &'static str = "cargo-generate_lockfile"; + const TITLE: &'static str = "Generate Cargo.lock"; + const DESCRIPTION: &'static str = "Generates or updates the Cargo.lock file for a Rust project. Usually, run without any additional arguments."; + type RequestArgs = CargoGenerateLockfileRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } -#[mcp_tool( - name = "cargo-clean", - description = "Cleans the target directory for a Rust project using Cargo. By default, it cleans the entire workspace.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoCleanTool { +pub struct CargoCleanRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -175,8 +174,8 @@ pub struct CargoCleanTool { output_verbosity: Option, } -impl CargoCleanTool { - pub fn call_tool(&self) -> Result { +impl CargoCleanRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -231,17 +230,25 @@ impl CargoCleanTool { let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?; cmd.args(output_flags); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoCleanRmcpTool; + +impl Tool for CargoCleanRmcpTool { + const NAME: &'static str = "cargo-clean"; + const TITLE: &'static str = "Clean Cargo artifacts"; + const DESCRIPTION: &'static str = "Cleans the target directory for a Rust project using Cargo. By default, it cleans the entire workspace."; + type RequestArgs = CargoCleanRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } -#[mcp_tool( - name = "cargo-fmt", - description = "Formats Rust code using rustfmt. Usually, run without any additional arguments.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoFmtTool { +pub struct CargoFmtRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -276,8 +283,8 @@ pub struct CargoFmtTool { output_verbosity: Option, } -impl CargoFmtTool { - pub fn call_tool(&self) -> Result { +impl CargoFmtRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -313,17 +320,26 @@ impl CargoFmtTool { let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?; cmd.args(output_flags); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoFmtRmcpTool; + +impl Tool for CargoFmtRmcpTool { + const NAME: &'static str = "cargo-fmt"; + const TITLE: &'static str = "Format Rust code"; + const DESCRIPTION: &'static str = + "Formats Rust code using rustfmt. Usually, run without any additional arguments."; + type RequestArgs = CargoFmtRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } -#[mcp_tool( - name = "cargo-new", - description = "Create a new cargo package at . Creates a new Rust project with the specified name and template.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoNewTool { +pub struct CargoNewRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -377,8 +393,8 @@ pub struct CargoNewTool { pub output_verbosity: Option, } -impl CargoNewTool { - pub fn call_tool(&self) -> Result { +impl CargoNewRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -420,22 +436,43 @@ impl CargoNewTool { let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?; cmd.args(output_flags); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoNewRmcpTool; + +impl Tool for CargoNewRmcpTool { + const NAME: &'static str = "cargo-new"; + const TITLE: &'static str = "Create new Rust project"; + const DESCRIPTION: &'static str = "Create a new cargo package at . Creates a new Rust project with the specified name and template."; + type RequestArgs = CargoNewRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } -#[mcp_tool( - name = "cargo-list", - description = "Lists installed cargo commands using 'cargo --list'.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoListTool {} +pub struct CargoListRequest {} -impl CargoListTool { - pub fn call_tool(&self) -> Result { +impl CargoListRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg("--list"); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoListRmcpTool; + +impl Tool for CargoListRmcpTool { + const NAME: &'static str = "cargo-list"; + const TITLE: &'static str = "List cargo commands"; + const DESCRIPTION: &'static str = "Lists installed cargo commands using 'cargo --list'."; + type RequestArgs = CargoListRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } diff --git a/src/tools/cargo/package.rs b/src/tools/cargo/package.rs index 4d2ad78..f41ee35 100644 --- a/src/tools/cargo/package.rs +++ b/src/tools/cargo/package.rs @@ -1,37 +1,16 @@ use std::process::Command; -use rust_mcp_sdk::{ - macros::mcp_tool, - schema::{CallToolResult, schema_utils::CallToolError}, +use crate::{ + Tool, execute_rmcp_command, + serde_utils::{ + deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, + output_verbosity_to_cli_flags, + }, }; +use rmcp::ErrorData; -use crate::serde_utils::{ - deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, - output_verbosity_to_cli_flags, -}; -use crate::tools::execute_command; - -use crate::serde_utils::Tool; - -#[mcp_tool( - name = "cargo-package", - description = "Assemble the local package into a distributable tarball for publishing or distribution. - - Common use cases: - - Create a .crate file for publishing to crates.io or a private registry - - Generate distribution packages for deployment or sharing - - Validate package contents before publishing (using --list) - - Test packaging process without verification (using --no-verify) - - Package workspace members selectively or all at once - - The generated tarball contains all files needed to build the package, excluding files listed in .gitignore or .cargo_vcs_info.json. - By default, the package is also built to verify it can be compiled successfully. - - Usually run without any additional arguments for single-package projects.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoPackageTool { +pub struct CargoPackageRequest { /// [Optional] The toolchain to use for packaging, e.g., "stable", "nightly", or "1.70.0". /// When specified, cargo will use this specific Rust toolchain version. #[serde(default, deserialize_with = "deserialize_string")] @@ -158,9 +137,8 @@ pub struct CargoPackageTool { #[serde(default, deserialize_with = "deserialize_string")] output_verbosity: Option, } - -impl CargoPackageTool { - pub fn call_tool(&self) -> Result { +impl CargoPackageRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); // Add toolchain if specified @@ -272,6 +250,23 @@ impl CargoPackageTool { let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?; cmd.args(output_flags); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoPackageRmcpTool; + +impl Tool for CargoPackageRmcpTool { + const NAME: &'static str = "cargo-package"; + const TITLE: &'static str = "cargo package"; + const DESCRIPTION: &'static str = "Assemble the local package into a distributable tarball for publishing or distribution.

Common use cases:
- Create a .crate file for publishing to crates.io or a private registry
- Generate distribution packages for deployment or sharing
- Validate package contents before publishing (using --list)
- Test packaging process without verification (using --no-verify)
- Package workspace members selectively or all at once

The generated tarball contains all files needed to build the package, excluding files listed in .gitignore or .cargo_vcs_info.json.
By default, the package is also built to verify it can be compiled successfully.

Usually run without any additional arguments for single-package projects."; + type RequestArgs = CargoPackageRequest; + + fn call_rmcp_tool( + &self, + request: Self::RequestArgs, + ) -> Result { + let cmd = request.build_cmd()?; + execute_rmcp_command(cmd, Self::NAME) } } diff --git a/src/tools/cargo/search.rs b/src/tools/cargo/search.rs index eea59f1..4415d8d 100644 --- a/src/tools/cargo/search.rs +++ b/src/tools/cargo/search.rs @@ -1,20 +1,13 @@ use std::process::Command; -use rust_mcp_sdk::{ - macros::{JsonSchema, mcp_tool}, - schema::{CallToolResult, schema_utils::CallToolError}, +use crate::{ + Tool, execute_rmcp_command, + serde_utils::{deserialize_string, output_verbosity_to_cli_flags}, }; +use rmcp::ErrorData; -use crate::serde_utils::{deserialize_string, output_verbosity_to_cli_flags}; -use crate::tools::execute_command; - -#[mcp_tool( - name = "cargo-search", - description = "Search packages in the registry. Default registry is crates.io. Equivalent to 'cargo search [QUERY]'.", - openWorldHint = false -)] -#[derive(Debug, ::serde::Deserialize, JsonSchema)] -pub struct CargoSearchTool { +#[derive(Debug, ::serde::Deserialize, ::schemars::JsonSchema)] +pub struct CargoSearchRequest { /// The query to search for. Generally, this is a substring of the package name or description. pub query: String, /// Limit the number of results (default: 10, max: 100) @@ -31,9 +24,8 @@ pub struct CargoSearchTool { #[serde(default, deserialize_with = "deserialize_string")] output_verbosity: Option, } - -impl CargoSearchTool { - pub fn call_tool(&self) -> Result { +impl CargoSearchRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg("search"); cmd.arg(&self.query); @@ -45,6 +37,23 @@ impl CargoSearchTool { } let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?; cmd.args(output_flags); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoSearchRmcpTool; + +impl Tool for CargoSearchRmcpTool { + const NAME: &'static str = "cargo-search"; + const TITLE: &'static str = "cargo search"; + const DESCRIPTION: &'static str = "Search packages in the registry. Default registry is crates.io. Equivalent to 'cargo search QUERY'."; + type RequestArgs = CargoSearchRequest; + + fn call_rmcp_tool( + &self, + request: Self::RequestArgs, + ) -> Result { + let cmd = request.build_cmd()?; + execute_rmcp_command(cmd, Self::NAME) } } diff --git a/src/tools/cargo/test.rs b/src/tools/cargo/test.rs index 76236d4..935d131 100644 --- a/src/tools/cargo/test.rs +++ b/src/tools/cargo/test.rs @@ -1,24 +1,16 @@ use std::process::Command; use crate::{ + Tool, execute_rmcp_command, serde_utils::{ deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, output_verbosity_to_cli_flags, }, - tools::execute_command, -}; -use rust_mcp_sdk::{ - macros::{JsonSchema, mcp_tool}, - schema::{CallToolResult, schema_utils::CallToolError}, }; +use rmcp::ErrorData; -#[mcp_tool( - name = "cargo-test", - description = "Run `cargo test` to execute Rust tests in the current project.", - openWorldHint = false -)] -#[derive(Debug, ::serde::Deserialize, JsonSchema)] -pub struct CargoTestTool { +#[derive(Debug, ::serde::Deserialize, ::schemars::JsonSchema)] +pub struct CargoTestRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -158,9 +150,8 @@ pub struct CargoTestTool { #[serde(default, deserialize_with = "deserialize_string")] output_verbosity: Option, } - -impl CargoTestTool { - pub fn call_tool(&self) -> Result { +impl CargoTestRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -310,10 +301,27 @@ impl CargoTestTool { } } - execute_command(cmd, &Self::tool_name()) + Ok(cmd) } } +pub struct CargoTestRmcpTool; + +impl Tool for CargoTestRmcpTool { + const NAME: &'static str = "cargo-test"; + const TITLE: &'static str = "cargo test"; + const DESCRIPTION: &'static str = + "Run `cargo test` to execute Rust tests in the current project."; + type RequestArgs = CargoTestRequest; + + fn call_rmcp_tool( + &self, + request: Self::RequestArgs, + ) -> Result { + let cmd = request.build_cmd()?; + execute_rmcp_command(cmd, Self::NAME) + } +} #[cfg(test)] mod tests { use super::*; @@ -330,7 +338,7 @@ mod tests { "all_targets": true }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool .expect("Deserialization should succeed even if `package` is missing (it's Option)"); @@ -346,7 +354,7 @@ mod tests { "package": ["my_package", "another_package"], }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool.expect("Deserialization should succeed with package array"); assert_eq!( @@ -363,7 +371,7 @@ mod tests { "package": ["single_package"], }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool.expect("Deserialization should succeed with single-item package array"); assert_eq!(tool.package.unwrap(), ["single_package".to_owned()]); @@ -375,7 +383,7 @@ mod tests { "package": "single_package", }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool.expect("Deserialization should succeed with single-item package array"); assert_eq!(tool.package.unwrap(), ["single_package".to_owned()]); @@ -387,7 +395,7 @@ mod tests { "features": ["serde", "tokio"], }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool.expect("Deserialization should succeed with features array"); assert_eq!( @@ -402,7 +410,7 @@ mod tests { "features": "serde", }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool.expect("Deserialization should succeed with single feature string"); assert_eq!(tool.features.unwrap(), ["serde".to_owned()]); @@ -414,7 +422,7 @@ mod tests { "features": "[\"serde\",\"tokio\"]", }); - let tool: Result = serde_json::from_value(input); + let tool: Result = serde_json::from_value(input); let tool = tool .expect("Deserialization should succeed with features string that looks like array"); diff --git a/src/tools/cargo/update.rs b/src/tools/cargo/update.rs index 13e07ad..11c9e5c 100644 --- a/src/tools/cargo/update.rs +++ b/src/tools/cargo/update.rs @@ -1,24 +1,16 @@ use std::process::Command; use crate::{ + Tool, execute_rmcp_command, serde_utils::{ deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags, output_verbosity_to_cli_flags, }, - tools::execute_command, -}; -use rust_mcp_sdk::{ - macros::{JsonSchema, mcp_tool}, - schema::{CallToolResult, schema_utils::CallToolError}, }; +use rmcp::ErrorData; -#[mcp_tool( - name = "cargo-update", - description = "Update dependencies as recorded in the local lock file. Updates the dependencies in Cargo.lock to their latest compatible versions.", - openWorldHint = false -)] -#[derive(Debug, ::serde::Deserialize, JsonSchema)] -pub struct CargoUpdateTool { +#[derive(Debug, ::serde::Deserialize, ::schemars::JsonSchema)] +pub struct CargoUpdateRequest { /// The toolchain to use, e.g., "stable" or "nightly". #[serde(default, deserialize_with = "deserialize_string")] toolchain: Option, @@ -87,9 +79,8 @@ pub struct CargoUpdateTool { #[serde(default, deserialize_with = "deserialize_string")] output_verbosity: Option, } - -impl CargoUpdateTool { - pub fn call_tool(&self) -> Result { +impl CargoUpdateRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); if let Some(toolchain) = &self.toolchain { cmd.arg(format!("+{toolchain}")); @@ -157,6 +148,23 @@ impl CargoUpdateTool { let output_flags = output_verbosity_to_cli_flags(self.output_verbosity.as_deref())?; cmd.args(output_flags); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoUpdateRmcpTool; + +impl Tool for CargoUpdateRmcpTool { + const NAME: &'static str = "cargo-update"; + const TITLE: &'static str = "cargo update"; + const DESCRIPTION: &'static str = "Update dependencies as recorded in the local lock file. Updates the dependencies in Cargo.lock to their latest compatible versions."; + type RequestArgs = CargoUpdateRequest; + + fn call_rmcp_tool( + &self, + request: Self::RequestArgs, + ) -> Result { + let cmd = request.build_cmd()?; + execute_rmcp_command(cmd, Self::NAME) } } diff --git a/src/tools/cargo_deny.rs b/src/tools/cargo_deny.rs index 2c175d2..316b422 100644 --- a/src/tools/cargo_deny.rs +++ b/src/tools/cargo_deny.rs @@ -1,21 +1,13 @@ -use crate::serde_utils::Tool; use std::process::Command; -use rust_mcp_sdk::{ - macros::mcp_tool, - schema::{CallToolResult, schema_utils::CallToolError}, +use crate::{ + Tool, execute_rmcp_command, + serde_utils::{deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags}, }; +use rmcp::{ErrorData, model::CallToolResult}; -use crate::serde_utils::{deserialize_string, deserialize_string_vec, locking_mode_to_cli_flags}; -use crate::tools::execute_command; - -#[mcp_tool( - name = "cargo-deny-check", - description = "Checks a project's crate graph for security advisories, license compliance, banned crates.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoDenyCheckTool { +pub struct CargoDenyCheckRequest { /// The check(s) to perform. Options: advisories, ban, bans, license, licenses, sources, all #[serde(default, deserialize_with = "deserialize_string_vec")] which: Option>, @@ -118,8 +110,8 @@ pub struct CargoDenyCheckTool { exclude_unpublished: Option, } -impl CargoDenyCheckTool { - pub fn call_tool(&self) -> Result { +impl CargoDenyCheckRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg("deny"); @@ -236,24 +228,32 @@ impl CargoDenyCheckTool { } } - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoDenyCheckRmcpTool; + +impl Tool for CargoDenyCheckRmcpTool { + const NAME: &'static str = "cargo-deny-check"; + const TITLE: &'static str = "Check dependencies"; + const DESCRIPTION: &'static str = "Checks a project's crate graph for security advisories, license compliance, banned crates."; + type RequestArgs = CargoDenyCheckRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } -#[mcp_tool( - name = "cargo-deny-init", - description = "Creates a cargo-deny config from a template", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoDenyInitTool { +pub struct CargoDenyInitRequest { /// The path to create. Defaults to /deny.toml #[serde(default, deserialize_with = "deserialize_string")] config: Option, } -impl CargoDenyInitTool { - pub fn call_tool(&self) -> Result { +impl CargoDenyInitRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg("deny").arg("init"); @@ -261,17 +261,25 @@ impl CargoDenyInitTool { cmd.arg(config); } - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoDenyInitRmcpTool; + +impl Tool for CargoDenyInitRmcpTool { + const NAME: &'static str = "cargo-deny-init"; + const TITLE: &'static str = "Initialize cargo-deny config"; + const DESCRIPTION: &'static str = "Creates a cargo-deny config from a template"; + type RequestArgs = CargoDenyInitRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } -#[mcp_tool( - name = "cargo-deny-list", - description = "Outputs a listing of all licenses and the crates that use them", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoDenyListTool { +pub struct CargoDenyListRequest { /// Path to the config to use. Defaults to a deny.toml in the same folder as the manifest path #[serde(default, deserialize_with = "deserialize_string")] config: Option, @@ -288,8 +296,8 @@ pub struct CargoDenyListTool { layout: Option, } -impl CargoDenyListTool { - pub fn call_tool(&self) -> Result { +impl CargoDenyListRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg("deny").arg("list"); @@ -309,23 +317,46 @@ impl CargoDenyListTool { cmd.arg("--layout").arg(layout); } - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoDenyListRmcpTool; + +impl Tool for CargoDenyListRmcpTool { + const NAME: &'static str = "cargo-deny-list"; + const TITLE: &'static str = "List licenses"; + const DESCRIPTION: &'static str = + "Outputs a listing of all licenses and the crates that use them"; + type RequestArgs = CargoDenyListRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } -#[mcp_tool( - name = "cargo-deny-install", - description = "Installs cargo-deny tool for dependency graph analysis and security checks", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoDenyInstallTool {} +pub struct CargoDenyInstallRequest {} -impl CargoDenyInstallTool { - pub fn call_tool(&self) -> Result { +impl CargoDenyInstallRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg("install").arg("cargo-deny"); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoDenyInstallRmcpTool; + +impl Tool for CargoDenyInstallRmcpTool { + const NAME: &'static str = "cargo-deny-install"; + const TITLE: &'static str = "Install cargo-deny"; + const DESCRIPTION: &'static str = + "Installs cargo-deny tool for dependency graph analysis and security checks"; + type RequestArgs = CargoDenyInstallRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } diff --git a/src/tools/cargo_hack.rs b/src/tools/cargo_hack.rs index e55e246..9b88e6b 100644 --- a/src/tools/cargo_hack.rs +++ b/src/tools/cargo_hack.rs @@ -1,28 +1,17 @@ use std::process::Command; -use rust_mcp_sdk::{ - macros::mcp_tool, - schema::{CallToolResult, schema_utils::CallToolError}, +use crate::{ + Tool, execute_rmcp_command, + serde_utils::{deserialize_string, deserialize_string_vec, output_verbosity_to_cli_flags}, }; - -use crate::serde_utils::{ - deserialize_string, deserialize_string_vec, output_verbosity_to_cli_flags, -}; -use crate::tools::execute_command; +use rmcp::{ErrorData, model::CallToolResult}; fn default_check() -> String { "check".to_string() } -use crate::serde_utils::Tool; - -#[mcp_tool( - name = "cargo-hack", - description = "Cargo subcommand to provide various options useful for testing and continuous integration, including feature testing and multi-version compatibility. Available commands: check, test, build, clippy. Recommend using 'check' for fast validation. Example: cargo-hack with \"feature_powerset\": true, \"depth\": 3, \"keep_going\": true", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoHackTool { +pub struct CargoHackRequest { /// The cargo command to run (check, test, build, clippy) #[serde(default = "default_check")] command: String, @@ -163,8 +152,8 @@ pub struct CargoHackTool { output_verbosity: Option, } -impl CargoHackTool { - pub fn call_tool(&self) -> Result { +impl CargoHackRequest { + pub fn build_cmd(&self) -> Result { // Validate command let allowed_commands = ["check", "test", "build", "clippy"]; if !allowed_commands.contains(&self.command.as_str()) { @@ -173,10 +162,7 @@ impl CargoHackTool { self.command, allowed_commands.join(", ") ); - return Err(CallToolError::new(Box::new(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - error_msg, - )))); + return Err(ErrorData::invalid_params(error_msg, None)); } let mut cmd = Command::new("cargo"); @@ -339,23 +325,45 @@ impl CargoHackTool { // Add the cargo command to run (e.g., check, test, build) cmd.arg(&self.command); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoHackRmcpTool; + +impl Tool for CargoHackRmcpTool { + const NAME: &'static str = "cargo-hack"; + const TITLE: &'static str = "Run cargo-hack"; + const DESCRIPTION: &'static str = "Cargo subcommand to provide various options useful for testing and continuous integration, including feature testing and multi-version compatibility. Available commands: check, test, build, clippy. Recommend using 'check' for fast validation. Example: cargo-hack with \"feature_powerset\": true, \"depth\": 3, \"keep_going\": true"; + type RequestArgs = CargoHackRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } -#[mcp_tool( - name = "cargo-hack-install", - description = "Installs cargo-hack tool for feature testing and continuous integration", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoHackInstallTool {} +pub struct CargoHackInstallRequest {} -impl CargoHackInstallTool { - pub fn call_tool(&self) -> Result { +impl CargoHackInstallRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg("install").arg("cargo-hack"); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoHackInstallRmcpTool; + +impl Tool for CargoHackInstallRmcpTool { + const NAME: &'static str = "cargo-hack-install"; + const TITLE: &'static str = "Install cargo-hack"; + const DESCRIPTION: &'static str = + "Installs cargo-hack tool for feature testing and continuous integration"; + type RequestArgs = CargoHackInstallRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } diff --git a/src/tools/cargo_machete.rs b/src/tools/cargo_machete.rs index 3ee042f..19c9021 100644 --- a/src/tools/cargo_machete.rs +++ b/src/tools/cargo_machete.rs @@ -1,22 +1,10 @@ use std::process::Command; -use rust_mcp_sdk::{ - macros::mcp_tool, - schema::{CallToolResult, schema_utils::CallToolError}, -}; +use crate::{Tool, execute_rmcp_command, serde_utils::deserialize_string_vec}; +use rmcp::{ErrorData, model::CallToolResult}; -use crate::serde_utils::deserialize_string_vec; -use crate::tools::execute_command; - -use crate::serde_utils::Tool; - -#[mcp_tool( - name = "cargo-machete", - description = "Finds unused dependencies in a fast yet imprecise way. Helps identify dependencies that are declared in Cargo.toml but not actually used in the code.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoMacheteTool { +pub struct CargoMacheteRequest { /// Uses cargo-metadata to figure out the dependencies' names. May be useful if some dependencies are renamed. #[serde(default)] with_metadata: Option, @@ -39,8 +27,8 @@ pub struct CargoMacheteTool { paths: Option>, } -impl CargoMacheteTool { - pub fn call_tool(&self) -> Result { +impl CargoMacheteRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg("machete"); @@ -66,23 +54,44 @@ impl CargoMacheteTool { } } - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoMacheteRmcpTool; + +impl Tool for CargoMacheteRmcpTool { + const NAME: &'static str = "cargo-machete"; + const TITLE: &'static str = "Find unused dependencies"; + const DESCRIPTION: &'static str = "Finds unused dependencies in a fast yet imprecise way. Helps identify dependencies that are declared in Cargo.toml but not actually used in the code."; + type RequestArgs = CargoMacheteRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } -#[mcp_tool( - name = "cargo-machete-install", - description = "Installs cargo-machete tool for finding unused dependencies", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct CargoMacheteInstallTool {} +pub struct CargoMacheteInstallRequest {} -impl CargoMacheteInstallTool { - pub fn call_tool(&self) -> Result { +impl CargoMacheteInstallRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("cargo"); cmd.arg("install").arg("cargo-machete"); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct CargoMacheteInstallRmcpTool; + +impl Tool for CargoMacheteInstallRmcpTool { + const NAME: &'static str = "cargo-machete-install"; + const TITLE: &'static str = "Install cargo-machete"; + const DESCRIPTION: &'static str = "Installs cargo-machete tool for finding unused dependencies"; + type RequestArgs = CargoMacheteInstallRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } diff --git a/src/tools/mod.rs b/src/tools/mod.rs index 7ebd4b3..d251f51 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -5,22 +5,6 @@ pub mod cargo_machete; pub mod rustc; pub mod rustup; -use rust_mcp_sdk::schema::{ - Annotations, CallToolRequest, CallToolResult, Role, TextContent, schema_utils::CallToolError, -}; - -use cargo::{ - CargoAddTool, CargoBuildTool, CargoCheckTool, CargoCleanTool, CargoClippyTool, CargoDocTool, - CargoFmtTool, CargoGenerateLockfileTool, CargoInfoTool, CargoListTool, CargoMetadataTool, - CargoNewTool, CargoPackageTool, CargoRemoveTool, CargoSearchTool, CargoTestTool, - CargoUpdateTool, -}; -use cargo_deny::{CargoDenyCheckTool, CargoDenyInitTool, CargoDenyInstallTool, CargoDenyListTool}; -use cargo_hack::{CargoHackInstallTool, CargoHackTool}; -use cargo_machete::{CargoMacheteInstallTool, CargoMacheteTool}; -use rustc::RustcExplainTool; -use rustup::{RustupShowTool, RustupToolchainAddTool, RustupUpdateTool}; - static WORKSPACE_ROOT: std::sync::OnceLock = std::sync::OnceLock::new(); pub fn set_workspace_root(root: String) { @@ -29,232 +13,8 @@ pub fn set_workspace_root(root: String) { .expect("Workspace root can only be set once"); } -fn apply_workspace_root(cmd: &mut std::process::Command) { +pub fn apply_workspace_root(cmd: &mut std::process::Command) { if let Some(root) = WORKSPACE_ROOT.get() { cmd.current_dir(root); } } - -fn execute_command( - mut cmd: std::process::Command, - tool_name: &str, -) -> Result { - apply_workspace_root(&mut cmd); - tracing::info!( - command = ?cmd, - tool_name = tool_name, - "Executing command" - ); - let output = cmd.output(); - - match output { - Ok(output) => { - let stdout = String::from_utf8_lossy(output.stdout.trim_ascii()); - let stderr = String::from_utf8_lossy(output.stderr.trim_ascii()); - - let mut content = Vec::new(); - if output.status.success() { - tracing::info!( - stdout = ?stdout, - stderr = ?stderr, - tool_name = tool_name, - "Command executed successfully" - ); - let annotations = Some(Annotations { - audience: vec![Role::User, Role::Assistant], - last_modified: None, - priority: Some(0.3), - }); - content.push( - TextContent::new(format!("✅ {tool_name}: Success"), annotations, None).into(), - ); - } else { - tracing::warn!( - stdout = ?stdout, - stderr = ?stderr, - status = ?output.status, - tool_name = tool_name, - "Command execution failed", - ); - let annotations = Some(Annotations { - audience: vec![Role::User, Role::Assistant], - last_modified: None, - priority: Some(0.3), - }); - content.push( - TextContent::new(format!("❌ {tool_name}: Failure"), annotations, None).into(), - ); - } - - if !stdout.is_empty() { - let annotations = Some(Annotations { - audience: vec![Role::User, Role::Assistant], - last_modified: None, - priority: Some(0.2), - }); - content.push(TextContent::new(stdout.into(), annotations, None).into()); - } - if !stderr.is_empty() { - let annotations = Some(Annotations { - audience: vec![Role::User, Role::Assistant], - last_modified: None, - priority: Some(1.), - }); - content.push(TextContent::new(stderr.into(), annotations, None).into()); - } - Ok(CallToolResult { - content, - is_error: Some(!output.status.success()), - meta: None, - structured_content: None, - }) - } - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - tracing::error!(error = ?e, "Command not found"); - let annotations = Some(Annotations { - audience: vec![Role::User, Role::Assistant], - last_modified: None, - priority: Some(1.), - }); - let program = cmd.get_program().to_string_lossy(); - let item = TextContent::new( - format!( - "The command `{program}` was not found, please ensure it is installed and accessible. You can try running the following command yourself to verify: `{program} {}`", - cmd.get_args() - .map(|arg| arg.to_string_lossy()) - .collect::>() - .join(" ") - ), - annotations, - None, - ); - - Ok(CallToolResult { - content: vec![item.into()], - is_error: Some(true), - meta: None, - structured_content: None, - }) - } - Err(e) => { - tracing::error!(error = ?e, "Failed to execute command"); - Err(CallToolError::new(e)) - } - } -} - -rust_mcp_sdk::tool_box!( - AllTools, - [ - CargoBuildTool, - CargoCleanTool, - CargoFmtTool, - CargoCheckTool, - CargoClippyTool, - CargoDocTool, - CargoGenerateLockfileTool, - CargoHackTool, - CargoHackInstallTool, - CargoMacheteTool, - CargoMacheteInstallTool, - CargoDenyCheckTool, - CargoDenyInitTool, - CargoDenyListTool, - CargoDenyInstallTool, - CargoAddTool, - CargoRemoveTool, - CargoNewTool, - CargoListTool, - CargoPackageTool, - CargoUpdateTool, - CargoTestTool, - CargoMetadataTool, - RustcExplainTool, - RustupShowTool, - RustupToolchainAddTool, - RustupUpdateTool, - CargoSearchTool, - CargoInfoTool, - ] -); - -/// Handles incoming CallToolRequest and processes it using the appropriate tool. -pub fn handle_request( - request: CallToolRequest, - disabled_tools: &[String], -) -> Result { - if disabled_tools.contains(&request.params.name) { - tracing::warn!( - tool_name = ?request.params.name, - "Tool is disabled, returning error" - ); - return Err(CallToolError::unknown_tool(request.params.name)); - } - - let tool_params: AllTools = AllTools::try_from(request.params.clone()).map_err(|e| { - tracing::error!( - error = ?e, - request = ?request, - "Failed to parse request parameters" - ); - CallToolError::new(e) - })?; - - match tool_params { - AllTools::CargoBuildTool(tool) => tool.call_tool(), - AllTools::CargoCleanTool(tool) => tool.call_tool(), - AllTools::CargoFmtTool(tool) => tool.call_tool(), - AllTools::CargoCheckTool(tool) => tool.call_tool(), - AllTools::CargoClippyTool(tool) => tool.call_tool(), - AllTools::CargoDocTool(tool) => tool.call_tool(), - AllTools::CargoGenerateLockfileTool(tool) => tool.call_tool(), - AllTools::CargoHackTool(tool) => tool.call_tool(), - AllTools::CargoHackInstallTool(tool) => tool.call_tool(), - AllTools::CargoMacheteTool(tool) => tool.call_tool(), - AllTools::CargoMacheteInstallTool(tool) => tool.call_tool(), - AllTools::CargoDenyCheckTool(tool) => tool.call_tool(), - AllTools::CargoDenyInitTool(tool) => tool.call_tool(), - AllTools::CargoDenyListTool(tool) => tool.call_tool(), - AllTools::CargoDenyInstallTool(tool) => tool.call_tool(), - AllTools::CargoAddTool(tool) => tool.call_tool(), - AllTools::CargoRemoveTool(tool) => tool.call_tool(), - AllTools::CargoNewTool(tool) => tool.call_tool(), - AllTools::CargoListTool(tool) => tool.call_tool(), - AllTools::CargoPackageTool(tool) => tool.call_tool(), - AllTools::CargoUpdateTool(tool) => tool.call_tool(), - AllTools::CargoTestTool(tool) => tool.call_tool(), - AllTools::CargoMetadataTool(tool) => tool.call_tool(), - AllTools::RustcExplainTool(tool) => tool.call_tool(), - AllTools::RustupShowTool(tool) => tool.call_tool(), - AllTools::RustupToolchainAddTool(tool) => tool.call_tool(), - AllTools::RustupUpdateTool(tool) => tool.call_tool(), - AllTools::CargoSearchTool(tool) => tool.call_tool(), - AllTools::CargoInfoTool(tool) => tool.call_tool(), - } -} - -#[cfg(test)] -mod tests { - use std::process::Command; - - use super::*; - - #[test] - fn test_execute_command_with_nonexistent_tool() { - // Try to execute a command that does not exist - let mut cmd = Command::new("this_tool_does_not_exist"); - cmd.args(["--arg1", "value1", "--arg2", "value2"]); - - let result = execute_command(cmd, "test-tool").expect("Command execution should not panic"); - let text = &result.content[0] - .as_text_content() - .expect("First content item should be text") - .text; - - println!("Command output: {text}"); - - assert_eq!(result.is_error, Some(true)); - assert!(!result.content.is_empty()); - assert!(text.contains("The command `this_tool_does_not_exist` was not found")); - } -} diff --git a/src/tools/rustc.rs b/src/tools/rustc.rs index 7b69dd7..4dd2ba9 100644 --- a/src/tools/rustc.rs +++ b/src/tools/rustc.rs @@ -1,20 +1,10 @@ use std::process::Command; -use rust_mcp_sdk::{ - macros::{JsonSchema, mcp_tool}, - schema::{CallToolResult, schema_utils::CallToolError}, -}; - -use crate::serde_utils::deserialize_string; -use crate::tools::execute_command; - -#[mcp_tool( - name = "rustc-explain", - description = "Provide a detailed explanation of a Rust compiler error code. This tool allows AI agents to request more information about compilation errors by providing the error code (e.g., E0001, E0308, etc.). Very useful for understanding and resolving Rust compilation errors.", - openWorldHint = false -)] -#[derive(Debug, ::serde::Deserialize, JsonSchema)] -pub struct RustcExplainTool { +use crate::{Tool, execute_rmcp_command, serde_utils::deserialize_string}; +use rmcp::{ErrorData, model::CallToolResult}; + +#[derive(Debug, ::serde::Deserialize, ::schemars::JsonSchema)] +pub struct RustcExplainRequest { /// The Rust compiler error code to explain (e.g., "E0001", "E0308", "E0432") pub error_code: String, @@ -23,8 +13,8 @@ pub struct RustcExplainTool { pub toolchain: Option, } -impl RustcExplainTool { - pub fn call_tool(&self) -> Result { +impl RustcExplainRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("rustc"); if let Some(toolchain) = &self.toolchain { @@ -34,6 +24,19 @@ impl RustcExplainTool { cmd.arg("--explain").arg(&self.error_code); - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct RustcExplainRmcpTool; + +impl Tool for RustcExplainRmcpTool { + const NAME: &'static str = "rustc-explain"; + const TITLE: &'static str = "Explain Rust error"; + const DESCRIPTION: &'static str = "Provide a detailed explanation of a Rust compiler error code. This tool allows AI agents to request more information about compilation errors by providing the error code (e.g., E0001, E0308, etc.). Very useful for understanding and resolving Rust compilation errors."; + type RequestArgs = RustcExplainRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } diff --git a/src/tools/rustup.rs b/src/tools/rustup.rs index 8e885c6..9ce6b5c 100644 --- a/src/tools/rustup.rs +++ b/src/tools/rustup.rs @@ -1,29 +1,20 @@ use std::process::Command; -use rust_mcp_sdk::{ - macros::mcp_tool, - schema::{CallToolResult, schema_utils::CallToolError}, +use crate::{ + Tool, execute_rmcp_command, + serde_utils::{deserialize_string, deserialize_string_vec}, }; +use rmcp::{ErrorData, model::CallToolResult}; -use crate::serde_utils::{deserialize_string, deserialize_string_vec}; -use crate::tools::execute_command; - -use crate::serde_utils::Tool; - -#[mcp_tool( - name = "rustup-show", - description = "Show the active and installed toolchains or profiles. Shows the name of the active toolchain and the version of rustc. If the active toolchain has installed support for additional compilation targets, then they are listed as well.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct RustupShowTool { +pub struct RustupShowRequest { /// Enable verbose output with rustc information for all installed toolchains #[serde(default)] verbose: bool, } -impl RustupShowTool { - pub fn call_tool(&self) -> Result { +impl RustupShowRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("rustup"); cmd.arg("show"); @@ -31,17 +22,25 @@ impl RustupShowTool { cmd.arg("--verbose"); } - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct RustupShowRmcpTool; + +impl Tool for RustupShowRmcpTool { + const NAME: &'static str = "rustup-show"; + const TITLE: &'static str = "Show Rust toolchains"; + const DESCRIPTION: &'static str = "Show the active and installed toolchains or profiles. Shows the name of the active toolchain and the version of rustc. If the active toolchain has installed support for additional compilation targets, then they are listed as well."; + type RequestArgs = RustupShowRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } -#[mcp_tool( - name = "rustup-toolchain-add", - description = "Install or update the given toolchains, or by default the active toolchain. Toolchain name can be 'stable', 'nightly', or a specific version like '1.8.0'.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct RustupToolchainAddTool { +pub struct RustupToolchainAddRequest { /// Toolchain name, such as 'stable', 'nightly', or '1.8.0' pub toolchain: String, @@ -74,8 +73,8 @@ pub struct RustupToolchainAddTool { pub force_non_host: bool, } -impl RustupToolchainAddTool { - pub fn call_tool(&self) -> Result { +impl RustupToolchainAddRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("rustup"); cmd.arg("toolchain").arg("install").arg(&self.toolchain); @@ -111,17 +110,25 @@ impl RustupToolchainAddTool { cmd.arg("--force-non-host"); } - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct RustupToolchainAddRmcpTool; + +impl Tool for RustupToolchainAddRmcpTool { + const NAME: &'static str = "rustup-toolchain-add"; + const TITLE: &'static str = "Install Rust toolchain"; + const DESCRIPTION: &'static str = "Install or update the given toolchains, or by default the active toolchain. Toolchain name can be 'stable', 'nightly', or a specific version like '1.8.0'."; + type RequestArgs = RustupToolchainAddRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } -#[mcp_tool( - name = "rustup-update", - description = "Update Rust toolchains and rustup. With no toolchain specified, updates each of the installed toolchains from the official release channels, then updates rustup itself. If given a toolchain argument then updates that toolchain.", - openWorldHint = false -)] #[derive(Debug, ::serde::Deserialize, schemars::JsonSchema)] -pub struct RustupUpdateTool { +pub struct RustupUpdateRequest { /// Toolchain name to update, such as 'stable', 'nightly', or '1.8.0'. If not specified, updates all installed toolchains #[serde(default, deserialize_with = "deserialize_string")] pub toolchain: Option, @@ -139,8 +146,8 @@ pub struct RustupUpdateTool { pub force_non_host: bool, } -impl RustupUpdateTool { - pub fn call_tool(&self) -> Result { +impl RustupUpdateRequest { + pub fn build_cmd(&self) -> Result { let mut cmd = Command::new("rustup"); cmd.arg("update"); @@ -160,6 +167,19 @@ impl RustupUpdateTool { cmd.arg("--force-non-host"); } - execute_command(cmd, &Self::tool_name()) + Ok(cmd) + } +} + +pub struct RustupUpdateRmcpTool; + +impl Tool for RustupUpdateRmcpTool { + const NAME: &'static str = "rustup-update"; + const TITLE: &'static str = "Update Rust toolchains"; + const DESCRIPTION: &'static str = "Update Rust toolchains and rustup. With no toolchain specified, updates each of the installed toolchains from the official release channels, then updates rustup itself. If given a toolchain argument then updates that toolchain."; + type RequestArgs = RustupUpdateRequest; + + fn call_rmcp_tool(&self, request: Self::RequestArgs) -> Result { + execute_rmcp_command(request.build_cmd()?, Self::NAME) } } diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..ab9d8c9 --- /dev/null +++ b/src/version.rs @@ -0,0 +1,19 @@ +const VERSION: &str = env!("CARGO_PKG_VERSION"); +const GIT_HASH: Option<&str> = option_env!("GIT_HASH"); + +pub struct AppVersion; + +impl AppVersion { + pub fn version() -> String { + match GIT_HASH { + Some(hash) => format!("{VERSION}.{hash}"), + None => VERSION.into(), + } + } +} + +impl From for clap::builder::Str { + fn from(_: AppVersion) -> Self { + AppVersion::version().into() + } +} diff --git a/tools.md b/tools.md index da873b7..2cdb730 100644 --- a/tools.md +++ b/tools.md @@ -1,5 +1,5 @@ -## Rust MCP Server 0.2.6 -| 🟢 Tools (29) | 🟢 Prompts (1) | 🟢 Resources (95) | 🔴 Logging | 🔴 Completions | 🔴 Experimental | +## Rust MCP Server +| 🟢 Tools (29) | 🟢 Prompts (0) | 🟢 Resources (0) | 🔴 Logging | 🔴 Completions | 🔴 Experimental | | --- | --- | --- | --- | --- | --- | ## 🛠️ Tools (29) @@ -243,7 +243,7 @@ - output_verbosity : string
- **cargo-hack** - - Cargo subcommand to provide various options useful for testing and continuous integration, including feature testing and multi-version compatibility. Available commands: check, test, build, clippy. Recommend using check for fast validation. Example: cargo-hack with "feature_powerset": true, "depth": 3, "keep_going": true + - Cargo subcommand to provide various options useful for testing and continuous integration, including feature testing and multi-version compatibility. Available commands: check, test, build, clippy. Recommend using 'check' for fast validation. Example: cargo-hack with "feature_powerset": true, "depth": 3, "keep_going": true - **Inputs:** - clean_per_run : boolean
- clean_per_version : boolean
@@ -453,7 +453,7 @@ - verbose : boolean
- **rustup-toolchain-add** - - Install or update the given toolchains, or by default the active toolchain. Toolchain name can be stable, nightly, or a specific version like '1.8.0'. + - Install or update the given toolchains, or by default the active toolchain. Toolchain name can be 'stable', 'nightly', or a specific version like '1.8.0'. - **Inputs:** - allow_downgrade : boolean
- components : string [ ]
@@ -471,396 +471,3 @@ - force_non_host : boolean
- no_self_update : boolean
- toolchain : string
- - -## 📝 Prompts (1) - - -- **rustup-update-toolset** - - Provides instruction on how to update Rust toolset - -## 📄 Resources (95) - - -- **Changelog** - - - URI: cargo-book://CHANGELOG.md (text/markdown) - -- **Appendix: Git Authentication** - - - URI: cargo-book://appendix/git-authentication.md (text/markdown) - -- **Appendix: Glossary** - - - URI: cargo-book://appendix/glossary.md (text/markdown) - -- **Build Commands** - - - URI: cargo-book://commands/build-commands.md (text/markdown) - -- **cargo add** - - - URI: cargo-book://commands/cargo-add.md (text/markdown) - -- **cargo bench** - - - URI: cargo-book://commands/cargo-bench.md (text/markdown) - -- **cargo build** - - - URI: cargo-book://commands/cargo-build.md (text/markdown) - -- **cargo check** - - - URI: cargo-book://commands/cargo-check.md (text/markdown) - -- **cargo clean** - - - URI: cargo-book://commands/cargo-clean.md (text/markdown) - -- **cargo clippy** - - - URI: cargo-book://commands/cargo-clippy.md (text/markdown) - -- **cargo doc** - - - URI: cargo-book://commands/cargo-doc.md (text/markdown) - -- **cargo fetch** - - - URI: cargo-book://commands/cargo-fetch.md (text/markdown) - -- **cargo fix** - - - URI: cargo-book://commands/cargo-fix.md (text/markdown) - -- **cargo fmt** - - - URI: cargo-book://commands/cargo-fmt.md (text/markdown) - -- **cargo generate-lockfile** - - - URI: cargo-book://commands/cargo-generate-lockfile.md (text/markdown) - -- **cargo help** - - - URI: cargo-book://commands/cargo-help.md (text/markdown) - -- **cargo info** - - - URI: cargo-book://commands/cargo-info.md (text/markdown) - -- **cargo init** - - - URI: cargo-book://commands/cargo-init.md (text/markdown) - -- **cargo install** - - - URI: cargo-book://commands/cargo-install.md (text/markdown) - -- **cargo locate-project** - - - URI: cargo-book://commands/cargo-locate-project.md (text/markdown) - -- **cargo login** - - - URI: cargo-book://commands/cargo-login.md (text/markdown) - -- **cargo logout** - - - URI: cargo-book://commands/cargo-logout.md (text/markdown) - -- **cargo metadata** - - - URI: cargo-book://commands/cargo-metadata.md (text/markdown) - -- **cargo miri** - - - URI: cargo-book://commands/cargo-miri.md (text/markdown) - -- **cargo new** - - - URI: cargo-book://commands/cargo-new.md (text/markdown) - -- **cargo owner** - - - URI: cargo-book://commands/cargo-owner.md (text/markdown) - -- **cargo package** - - - URI: cargo-book://commands/cargo-package.md (text/markdown) - -- **cargo pkgid** - - - URI: cargo-book://commands/cargo-pkgid.md (text/markdown) - -- **cargo publish** - - - URI: cargo-book://commands/cargo-publish.md (text/markdown) - -- **cargo remove** - - - URI: cargo-book://commands/cargo-remove.md (text/markdown) - -- **cargo report** - - - URI: cargo-book://commands/cargo-report.md (text/markdown) - -- **cargo run** - - - URI: cargo-book://commands/cargo-run.md (text/markdown) - -- **cargo rustc** - - - URI: cargo-book://commands/cargo-rustc.md (text/markdown) - -- **cargo rustdoc** - - - URI: cargo-book://commands/cargo-rustdoc.md (text/markdown) - -- **cargo search** - - - URI: cargo-book://commands/cargo-search.md (text/markdown) - -- **cargo test** - - - URI: cargo-book://commands/cargo-test.md (text/markdown) - -- **cargo tree** - - - URI: cargo-book://commands/cargo-tree.md (text/markdown) - -- **cargo uninstall** - - - URI: cargo-book://commands/cargo-uninstall.md (text/markdown) - -- **cargo update** - - - URI: cargo-book://commands/cargo-update.md (text/markdown) - -- **cargo vendor** - - - URI: cargo-book://commands/cargo-vendor.md (text/markdown) - -- **cargo version** - - - URI: cargo-book://commands/cargo-version.md (text/markdown) - -- **cargo yank** - - - URI: cargo-book://commands/cargo-yank.md (text/markdown) - -- **cargo** - - - URI: cargo-book://commands/cargo.md (text/markdown) - -- **Deprecated and Removed Commands** - - - URI: cargo-book://commands/deprecated-and-removed.md (text/markdown) - -- **General Commands** - - - URI: cargo-book://commands/general-commands.md (text/markdown) - -- **Cargo Commands** - - - URI: cargo-book://commands/index.md (text/markdown) - -- **Manifest Commands** - - - URI: cargo-book://commands/manifest-commands.md (text/markdown) - -- **Package Commands** - - - URI: cargo-book://commands/package-commands.md (text/markdown) - -- **Publishing Commands** - - - URI: cargo-book://commands/publishing-commands.md (text/markdown) - -- **FAQ** - - - URI: cargo-book://faq.md (text/markdown) - -- **First Steps with Cargo** - - - URI: cargo-book://getting-started/first-steps.md (text/markdown) - -- **Getting Started with Cargo** - - - URI: cargo-book://getting-started/index.md (text/markdown) - -- **Installing Cargo** - - - URI: cargo-book://getting-started/installation.md (text/markdown) - -- **Cargo Home** - - - URI: cargo-book://guide/cargo-home.md (text/markdown) - -- **Cargo.toml vs Cargo.lock** - - - URI: cargo-book://guide/cargo-toml-vs-cargo-lock.md (text/markdown) - -- **Continuous Integration** - - - URI: cargo-book://guide/continuous-integration.md (text/markdown) - -- **Creating a New Package** - - - URI: cargo-book://guide/creating-a-new-project.md (text/markdown) - -- **Dependencies** - - - URI: cargo-book://guide/dependencies.md (text/markdown) - -- **Cargo Guide** - - - URI: cargo-book://guide/index.md (text/markdown) - -- **Package Layout** - - - URI: cargo-book://guide/project-layout.md (text/markdown) - -- **Tests** - - - URI: cargo-book://guide/tests.md (text/markdown) - -- **Why Cargo Exists** - - - URI: cargo-book://guide/why-cargo-exists.md (text/markdown) - -- **Working on an Existing Package** - - - URI: cargo-book://guide/working-on-an-existing-project.md (text/markdown) - -- **Introduction to Cargo** - - - URI: cargo-book://index.md (text/markdown) - -- **Build Cache** - - - URI: cargo-book://reference/build-cache.md (text/markdown) - -- **Build Script Examples** - - - URI: cargo-book://reference/build-script-examples.md (text/markdown) - -- **Build Scripts** - - - URI: cargo-book://reference/build-scripts.md (text/markdown) - -- **Cargo Targets** - - - URI: cargo-book://reference/cargo-targets.md (text/markdown) - -- **Configuration** - - - URI: cargo-book://reference/config.md (text/markdown) - -- **Credential Provider Protocol** - - - URI: cargo-book://reference/credential-provider-protocol.md (text/markdown) - -- **Environment Variables** - - - URI: cargo-book://reference/environment-variables.md (text/markdown) - -- **External Tools** - - - URI: cargo-book://reference/external-tools.md (text/markdown) - -- **Features Examples** - - - URI: cargo-book://reference/features-examples.md (text/markdown) - -- **Features** - - - URI: cargo-book://reference/features.md (text/markdown) - -- **Future incompat report** - - - URI: cargo-book://reference/future-incompat-report.md (text/markdown) - -- **Cargo Reference** - - - URI: cargo-book://reference/index.md (text/markdown) - -- **Lints** - - - URI: cargo-book://reference/lints.md (text/markdown) - -- **The Manifest Format** - - - URI: cargo-book://reference/manifest.md (text/markdown) - -- **Overriding Dependencies** - - - URI: cargo-book://reference/overriding-dependencies.md (text/markdown) - -- **Package ID Specifications** - - - URI: cargo-book://reference/pkgid-spec.md (text/markdown) - -- **Profiles** - - - URI: cargo-book://reference/profiles.md (text/markdown) - -- **Publishing on crates.io** - - - URI: cargo-book://reference/publishing.md (text/markdown) - -- **Registries** - - - URI: cargo-book://reference/registries.md (text/markdown) - -- **Registry Authentication** - - - URI: cargo-book://reference/registry-authentication.md (text/markdown) - -- **Registry Index** - - - URI: cargo-book://reference/registry-index.md (text/markdown) - -- **Registry Web API** - - - URI: cargo-book://reference/registry-web-api.md (text/markdown) - -- **Dependency Resolution** - - - URI: cargo-book://reference/resolver.md (text/markdown) - -- **Running a Registry** - - - URI: cargo-book://reference/running-a-registry.md (text/markdown) - -- **Rust Version** - - - URI: cargo-book://reference/rust-version.md (text/markdown) - -- **SemVer Compatibility** - - - URI: cargo-book://reference/semver.md (text/markdown) - -- **Source Replacement** - - - URI: cargo-book://reference/source-replacement.md (text/markdown) - -- **Specifying Dependencies** - - - URI: cargo-book://reference/specifying-dependencies.md (text/markdown) - -- **Reporting build timings** - - - URI: cargo-book://reference/timings.md (text/markdown) - -- **Unstable Features** - - - URI: cargo-book://reference/unstable.md (text/markdown) - -- **Workspaces** - - - URI: cargo-book://reference/workspaces.md (text/markdown) - - -◾ generated by [mcp-discovery](https://github.com/rust-mcp-stack/mcp-discovery)