diff --git a/Cargo.lock b/Cargo.lock index 1dd57d0..2bef5ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -11,6 +20,23 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -77,9 +103,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" @@ -141,12 +167,77 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + [[package]] name = "dyn-clone" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[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" @@ -169,6 +260,18 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -284,10 +387,44 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "http" version = "1.4.0" @@ -394,6 +531,30 @@ dependencies = [ "cc", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + [[package]] name = "inotify" version = "0.11.0" @@ -456,6 +617,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.178" @@ -483,6 +650,15 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "matchit" version = "0.8.4" @@ -529,6 +705,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "tracing-appender", "tracing-subscriber", "walkdir", ] @@ -578,6 +755,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + [[package]] name = "num-traits" version = "0.2.19" @@ -617,10 +800,10 @@ dependencies = [ ] [[package]] -name = "paste" -version = "1.0.15" +name = "pastey" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" [[package]] name = "percent-encoding" @@ -640,6 +823,31 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -664,6 +872,41 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -674,34 +917,83 @@ dependencies = [ ] [[package]] -name = "rmcp" -version = "0.1.5" +name = "ref-cast" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a0110d28bd076f39e14bfd5b0340216dd18effeb5d02b43215944cc3e5c751" +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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rmcp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df440eaa43f8573491ed4a5899719b6d29099500774abba12214a095a4083ed" +dependencies = [ + "async-trait", "base64", + "bytes", "chrono", "futures", - "paste", + "http", + "http-body", + "http-body-util", + "pastey", "pin-project-lite", + "rand", "rmcp-macros", "schemars", "serde", "serde_json", + "sse-stream", "thiserror", "tokio", + "tokio-stream", "tokio-util", + "tower-service", "tracing", + "uuid", ] [[package]] name = "rmcp-macros" -version = "0.1.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e2b2fd7497540489fa2db285edd43b7ed14c49157157438664278da6e42a7a" +checksum = "9ef03779cccab8337dd8617c53fce5c98ec21794febc397531555472ca28f8c3" dependencies = [ + "darling", "proc-macro2", "quote", + "serde_json", "syn", ] @@ -741,11 +1033,13 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.22" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ + "chrono", "dyn-clone", + "ref-cast", "schemars_derive", "serde", "serde_json", @@ -753,9 +1047,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.22" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" dependencies = [ "proc-macro2", "quote", @@ -769,6 +1063,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -892,6 +1192,25 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "sse-stream" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb4dc4d33c68ec1f27d386b5610a351922656e1fdf5c05bbaad930cd1519479a" +dependencies = [ + "bytes", + "futures-util", + "http-body", + "http-body-util", + "pin-project-lite", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.111" @@ -916,7 +1235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", @@ -951,6 +1270,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tokio" version = "1.48.0" @@ -979,6 +1329,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.17" @@ -1032,6 +1393,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -1070,10 +1443,14 @@ version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ + "matchers", "nu-ansi-term", + "once_cell", + "regex-automata", "sharded-slab", "smallvec", "thread_local", + "tracing", "tracing-core", "tracing-log", ] @@ -1084,6 +1461,23 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -1112,7 +1506,16 @@ version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", ] [[package]] @@ -1160,6 +1563,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "winapi-util" version = "0.1.11" @@ -1317,6 +1754,114 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zmij" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 749b15e..9afb80a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,7 @@ [package] name = "mock_server" version = "0.1.0" -edition = "2024" - -[features] -default = [] -mcp = ["rmcp", "schemars"] +edition = "2021" [dependencies] # 核心 Web 框架 @@ -23,16 +19,18 @@ serde_json = "1.0.147" # 物理目录递归扫描工具 walkdir = "2.5.0" -tracing="0.1.44" -tracing-subscriber = "0.3.22" +# 日志系统 +tracing = "0.1.44" +tracing-subscriber = { version = "0.3.22", features = ["fmt", "env-filter"] } +tracing-appender = "0.2" # 热加载支持(扩展功能) notify = "8.2.0" notify-debouncer-mini = "0.6.0" -# MCP Server 支持(可选) -rmcp = { version = "0.1", features = ["server"], optional = true } -schemars = { version = "0.8", optional = true } +# MCP Server 支持 +rmcp = { version = "0.11", features = ["server", "transport-streamable-http-server", "transport-streamable-http-server-session"] } +schemars = "1.0" [dev-dependencies] tempfile = "3.24.0" \ No newline at end of file diff --git a/docs/mcp-implementation.md b/docs/mcp-implementation.md new file mode 100644 index 0000000..d0dfb4c --- /dev/null +++ b/docs/mcp-implementation.md @@ -0,0 +1,427 @@ +# rmcp 0.11 MCP Server 实现指南 + +本文档详细介绍了如何使用 rmcp 0.11 实现 MCP Server,包括核心概念、关键代码模式和常见陷阱。 + +## 目录 + +- [核心概念](#核心概念) +- [Tool 注册三要素](#tool-注册三要素) +- [完整代码示例](#完整代码示例) +- [常见错误和解决方案](#常见错误和解决方案) +- [HTTP 传输配置](#http-传输配置) +- [客户端配置](#客户端配置) + +--- + +## 核心概念 + +### rmcp 简介 + +rmcp 是 Rust 官方的 MCP SDK,提供了实现 MCP (Model Context Protocol) 服务器和客户端的完整工具链。 + +### MCP 协议 + +MCP (Model Context Protocol) 是一个开放协议,用于连接 AI 助手与外部系统。它定义了一套标准化的接口,允许 AI 模型: + +- **Tools**: 调用外部工具/函数 +- **Resources**: 访问外部资源 +- **Prompts**: 使用预定义的提示模板 + +### 依赖配置 + +```toml +# Cargo.toml +[dependencies] +rmcp = { version = "0.11", features = ["server", "transport-streamable-http-server", "transport-streamable-http-server-session"] } +schemars = "1.0" +tokio-util = { version = "0.7", features = ["io"] } +``` + +--- + +## Tool 注册三要素 + +要使 MCP tools 在 rmcp 0.11 中正常工作,**必须**同时具备以下三个要素: + +### 1. `#[tool_router]` 宏 + +放在包含 tool 方法的 impl 块上: + +```rust +#[tool_router] +impl MockMcpServer { + // tool 方法... +} +``` + +### 2. `tool_router: ToolRouter` 字段 + +在结构体中必须包含此字段: + +```rust +#[derive(Clone)] +pub struct MockMcpServer { + manager: Arc, + tool_router: ToolRouter, // 必需字段 +} +``` + +### 3. `#[tool_handler]` 宏 + +放在 `ServerHandler` trait 实现块上: + +```rust +#[tool_handler] +impl ServerHandler for MockMcpServer { + fn get_info(&self) -> ServerInfo { + // ... + } +} +``` + +> **重要**: 缺少 `#[tool_handler]` 会导致 `tools/list` 返回空数组 `{"tools": []}`! + +--- + +## 完整代码示例 + +### 结构体定义 + +```rust +use std::sync::Arc; + +use rmcp::{ + handler::server::tool::ToolRouter, + handler::server::wrapper::Parameters, + model::*, + tool, tool_handler, tool_router, + ErrorData as McpError, ServerHandler, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// MCP Server for Mock Server management +#[derive(Clone)] +pub struct MockMcpServer { + manager: Arc, + tool_router: ToolRouter, // 必需字段 +} +``` + +### 请求参数结构体 + +```rust +/// 使用 schemars 和 serde 自动生成 JSON Schema +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct GetRuleRequest { + #[schemars(description = "Group name (directory name)")] + pub group: String, + + #[schemars(description = "Rule name")] + pub name: String, +} + +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct ListRulesRequest { + #[schemars(description = "Optional group name to filter by")] + pub group: Option, +} +``` + +### Tool 方法实现 + +```rust +#[tool_router] +impl MockMcpServer { + pub fn new(manager: Arc) -> Self { + Self { + manager, + tool_router: Self::tool_router(), // 初始化 tool_router + } + } + + /// 无参数 tool + #[tool(description = "List all groups (directories)")] + async fn list_groups(&self) -> Result { + let groups = self.manager.list_groups(); + let result = serde_json::to_string_pretty(&groups) + .map_err(|e| McpError::internal_error(e.to_string(), None))?; + Ok(CallToolResult::success(vec![Content::text(result)])) + } + + /// 带参数 tool - 使用 Parameters 包装器 + #[tool(description = "Get a specific mock rule by group and name")] + async fn get_mock_rule( + &self, + params: Parameters, + ) -> Result { + match self.manager.get(¶ms.0.group, ¶ms.0.name) { + Some(rule) => { + let result = serde_json::to_string_pretty(&rule) + .map_err(|e| McpError::internal_error(e.to_string(), None))?; + Ok(CallToolResult::success(vec![Content::text(result)])) + } + None => Ok(CallToolResult::error(vec![Content::text( + format!("Rule not found: {}/{}", params.0.group, params.0.name), + )])), + } + } + + /// 可选参数 + #[tool(description = "List all mock rules, optionally filtered by group")] + async fn list_mock_rules( + &self, + params: Parameters, + ) -> Result { + let rules = self.manager.list(params.0.group.as_deref()); + let result = serde_json::to_string_pretty(&rules) + .map_err(|e| McpError::internal_error(e.to_string(), None))?; + Ok(CallToolResult::success(vec![Content::text(result)])) + } +} +``` + +### ServerHandler 实现 + +```rust +#[tool_handler] // 关键宏! +impl ServerHandler for MockMcpServer { + fn get_info(&self) -> ServerInfo { + ServerInfo { + capabilities: ServerCapabilities::builder() + .enable_tools() // 必须启用 tools 能力 + .build(), + instructions: Some("Mock Server MCP - Manage mock API rules.".to_string()), + ..Default::default() + } + } +} +``` + +--- + +## 常见错误和解决方案 + +### 1. Missing `#[tool_handler]` + +**症状**: `tools/list` 返回空数组 `{"tools": []}` + +**原因**: 没有在 `ServerHandler` impl 块上添加 `#[tool_handler]` 宏 + +**解决方案**: + +```rust +// 错误 - 缺少宏 +impl ServerHandler for MockMcpServer { + fn get_info(&self) -> ServerInfo { ... } +} + +// 正确 +#[tool_handler] +impl ServerHandler for MockMcpServer { + fn get_info(&self) -> ServerInfo { ... } +} +``` + +### 2. Missing `enable_tools()` + +**症状**: Tools 不被广播,客户端无法发现 tools + +**原因**: `ServerCapabilities` 没有启用 tools + +**解决方案**: + +```rust +// 错误 +ServerInfo { +capabilities: ServerCapabilities::default (), +... +} + +// 正确 +ServerInfo { +capabilities: ServerCapabilities::builder() +.enable_tools() +.build(), +... +} +``` + +### 3. 使用 `#[tool(aggr)]` + +**症状**: 编译错误 + +**原因**: rmcp 0.11 不支持 `aggr` 参数 + +**解决方案**: + +```rust +// 错误 - rmcp 0.11 不支持 +#[tool(aggr)] +async fn my_tool(&self, params: MyRequest) -> Result<...> + +// 正确 - 使用 Parameters 包装器 +#[tool(description = "...")] +async fn my_tool(&self, params: Parameters) -> Result<...> +``` + +### 4. 使用 `#[tool(tool_box)]` + +**症状**: 编译错误 + +**原因**: rmcp 0.11 使用不同的宏名称 + +**解决方案**: + +```rust +// 错误 +#[tool(tool_box)] +impl MockMcpServer { ... } + +// 正确 +#[tool_router] +impl MockMcpServer { ... } +``` + +--- + +## HTTP 传输配置 + +### StreamableHttpService 无状态模式 + +适用于简单的 HTTP 集成,无需维护会话状态: + +```rust +use rmcp::transport::streamable_http_server::{ + StreamableHttpService, StreamableHttpServerConfig, + session::never::NeverSessionManager, +}; +use tokio_util::sync::CancellationToken; + +/// 创建无状态 MCP HTTP 服务 +pub fn create_mcp_http_service( + manager: Arc, +) -> StreamableHttpService { + StreamableHttpService::new( + // 每次请求创建新的 server 实例 + move || Ok(MockMcpServer::new(manager.clone())), + // 无状态会话管理器 + Arc::new(NeverSessionManager::default()), + StreamableHttpServerConfig { + sse_keep_alive: None, // SSE 保活配置(可选) + stateful_mode: false, // 无状态模式 + cancellation_token: CancellationToken::new(), + }, + ) +} +``` + +### 与 Axum 集成 + +```rust +use axum::{ + routing::post, + Router, + body::Body, + http::Request, +}; + +let mcp_service = create_mcp_http_service(manager); + +let app = Router::new() +.route("/mcp", post({ +let service = mcp_service.clone(); +move | req: Request < Body > | { +let service = service.clone(); +async move { service.handle(req).await } +} +})); +``` + +### 配置选项说明 + +| 选项 | 类型 | 说明 | +|----------------------|---------------------|------------| +| `sse_keep_alive` | `Option` | SSE 连接保活间隔 | +| `stateful_mode` | `bool` | 是否维护会话状态 | +| `cancellation_token` | `CancellationToken` | 用于优雅关闭 | + +--- + +## 客户端配置 + +### Claude Code 配置 + +在 Claude Code 的 `settings.json` 中添加: + +```json +{ + "mcpServers": { + "mock-server": { + "type": "http", + "url": "http://127.0.0.1:8080/mcp" + } + } +} +``` + +### Claude Desktop 配置 + +在 Claude Desktop 的配置文件中添加: + +```json +{ + "mcpServers": { + "mock-server": { + "url": "http://127.0.0.1:8080/mcp" + } + } +} +``` + +--- + +## 架构说明 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ HTTP Server (Axum) │ +│ Port 8080 │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ POST /mcp │ /* (fallback) │ +│ ├─ tools/list │ Mock API endpoints │ +│ ├─ tools/call │ │ +│ └─ other MCP methods │ │ +│ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ StreamableHttpService │ +│ │ +│ ┌─────────────────┐ ┌─────────────────────────────┐ │ +│ │ MockMcpServer │ │ NeverSessionManager │ │ +│ │ │ │ (无状态) │ │ +│ │ - tool_router │ │ │ │ +│ │ - manager │ │ │ │ +│ └─────────────────┘ └─────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ MockManager (Arc) │ +│ │ +│ 共享于 HTTP MCP 端点和 Mock API handler │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 参考资源 + +- [rmcp GitHub Repository](https://github.com/anthropics/rmcp) +- [MCP Specification](https://spec.modelcontextprotocol.io/) +- [schemars Documentation](https://docs.rs/schemars/) diff --git a/src/lib.rs b/src/lib.rs index af0dcdf..ac368d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,5 @@ pub mod loader; pub mod router; pub mod handler; pub mod manager; - -#[cfg(feature = "mcp")] +pub mod logging; pub mod mcp; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 9444cc6..115a445 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,37 +3,39 @@ use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; -use axum::{routing::any, Router}; +use axum::{routing::post, Router}; use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode}; +use tracing::{info, error}; use mock_server::loader::MockLoader; use mock_server::router::MockRouter; use mock_server::handler::{mock_handler, AppState}; +use mock_server::logging; fn print_usage() { - println!("Mock Server - A mock API server with hot-reload support"); + println!("Mock Server - A mock API server with hot-reload and MCP support"); println!(); println!("Usage: mock_server [OPTIONS]"); println!(); println!("Options:"); - println!(" --mcp Run as MCP server (stdio transport)"); println!(" --mocks Mocks directory path (default: ./mocks)"); - println!(" --port Server port (default: 8080)"); + println!(" --port HTTP server port (default: 8080)"); println!(" --help Show this help message"); + println!(); + println!("MCP Endpoint: POST http://127.0.0.1:/mcp"); } #[tokio::main] async fn main() { - tracing_subscriber::fmt::init(); + // Initialize logging system + let log_dir = PathBuf::from("./logs"); + logging::init(log_dir); - // 解析命令行参数 + // Parse command line arguments let args: Vec = std::env::args().collect(); let mut mocks_dir = PathBuf::from("./mocks"); let mut port: u16 = 8080; - #[cfg(feature = "mcp")] - let mut mcp_mode = false; - let mut i = 1; while i < args.len() { match args[i].as_str() { @@ -41,17 +43,6 @@ async fn main() { print_usage(); return; } - "--mcp" => { - #[cfg(feature = "mcp")] - { - mcp_mode = true; - } - #[cfg(not(feature = "mcp"))] - { - eprintln!("MCP feature not enabled. Rebuild with --features mcp"); - std::process::exit(1); - } - } "--mocks" => { if i + 1 < args.len() { mocks_dir = PathBuf::from(&args[i + 1]); @@ -89,37 +80,25 @@ async fn main() { std::fs::create_dir_all(&mocks_dir).unwrap(); } - #[cfg(feature = "mcp")] - if mcp_mode { - run_mcp_server(mocks_dir).await; - return; - } + // Create shared MockManager for both HTTP and MCP + let manager = Arc::new(mock_server::manager::MockManager::new(mocks_dir.clone())); + info!("Loaded {} groups", manager.list_groups().len()); - run_http_server(mocks_dir, port).await; + // Run unified HTTP server (includes MCP endpoint) + run_http_server(mocks_dir, port, manager).await; } -#[cfg(feature = "mcp")] -async fn run_mcp_server(mocks_dir: PathBuf) { - println!("Starting MCP server..."); - let manager = Arc::new(mock_server::manager::MockManager::new(mocks_dir)); - println!("Loaded {} groups", manager.list_groups().len()); - - match mock_server::mcp::run_mcp_server(manager).await { - Ok(_) => println!("MCP server stopped"), - Err(e) => eprintln!("MCP server error: {}", e), - } -} - -async fn run_http_server(mocks_dir: PathBuf, port: u16) { - println!("Scanning mocks directory..."); +async fn run_http_server(mocks_dir: PathBuf, port: u16, manager: Arc) { + info!("Scanning mocks directory..."); let index = MockLoader::load_all_from_dir(&mocks_dir); let shared_state = Arc::new(AppState { router: std::sync::RwLock::new(MockRouter::new(index)), }); - // 设置热加载监听器 + // Setup hot-reload watcher let state_for_watcher = shared_state.clone(); let watch_path = mocks_dir.clone(); + let manager_for_watcher = manager.clone(); let (tx, rx) = std::sync::mpsc::channel(); let mut debouncer = new_debouncer(Duration::from_millis(200), tx).unwrap(); @@ -129,24 +108,37 @@ async fn run_http_server(mocks_dir: PathBuf, port: u16) { while let Ok(res) = rx.recv() { match res { Ok(_) => { - println!("🔄 Detecting changes in mocks/, reloading..."); + info!("Detected changes in mocks/, reloading..."); let new_index = MockLoader::load_all_from_dir(&watch_path); let mut writer = state_for_watcher.router.write().expect("Failed to acquire write lock"); *writer = MockRouter::new(new_index); - println!("✅ Mocks reloaded successfully."); + // Also reload in manager + manager_for_watcher.reload(); + info!("Mocks reloaded successfully."); } - Err(e) => eprintln!("Watcher error: {:?}", e), + Err(e) => error!("Watcher error: {:?}", e), } } }); + // Create MCP HTTP service (stateless) + let mcp_service = mock_server::mcp::create_mcp_http_service(manager.clone()); + let app = Router::new() - .fallback(any(mock_handler)) + // MCP endpoint (stateless HTTP transport) + .route("/mcp", post(|req| async move { + mcp_service.handle(req).await + })) + // Mock API fallback + .fallback(axum::routing::any(mock_handler)) .with_state(shared_state); let addr = SocketAddr::from(([127, 0, 0, 1], port)); - println!("🚀 Server running at http://{}", addr); + info!("HTTP server running at http://{}", addr); + info!("MCP endpoint available at http://{}/mcp", addr); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); - axum::serve(listener, app).await.unwrap(); + if let Err(e) = axum::serve(listener, app).await { + error!("HTTP server error: {}", e); + } } diff --git a/src/mcp/mod.rs b/src/mcp/mod.rs index a944085..50fa0f0 100644 --- a/src/mcp/mod.rs +++ b/src/mcp/mod.rs @@ -1,3 +1,3 @@ mod server; -pub use server::run_mcp_server; +pub use server::create_mcp_http_service; diff --git a/src/mcp/server.rs b/src/mcp/server.rs index f6d4933..333064b 100644 --- a/src/mcp/server.rs +++ b/src/mcp/server.rs @@ -1,13 +1,14 @@ +use std::sync::Arc; + use rmcp::{ handler::server::tool::ToolRouter, + handler::server::wrapper::Parameters, model::*, tool, tool_handler, tool_router, - ServerHandler, ServiceExt, - transport::stdio, + ErrorData as McpError, ServerHandler, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::sync::Arc; use crate::manager::MockManager; use crate::models::MockRule; @@ -19,6 +20,51 @@ pub struct MockMcpServer { tool_router: ToolRouter, } +/// Request for getting a specific mock rule +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct GetRuleRequest { + #[schemars(description = "Group name (directory name)")] + pub group: String, + #[schemars(description = "Rule name")] + pub name: String, +} + +/// Request for creating a new mock rule +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct CreateRuleRequest { + #[schemars(description = "Group name (directory name)")] + pub group: String, + #[schemars(description = "Mock rule definition (JSON object)")] + pub rule_json: String, +} + +/// Request for updating a mock rule +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct UpdateRuleRequest { + #[schemars(description = "Group name")] + pub group: String, + #[schemars(description = "Current rule name")] + pub name: String, + #[schemars(description = "Updated rule definition (JSON object)")] + pub rule_json: String, +} + +/// Request for deleting a mock rule +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct DeleteRuleRequest { + #[schemars(description = "Group name")] + pub group: String, + #[schemars(description = "Rule name")] + pub name: String, +} + +/// Request for listing mock rules with optional filter +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct ListRulesRequest { + #[schemars(description = "Optional group name to filter by")] + pub group: Option, +} + #[tool_router] impl MockMcpServer { pub fn new(manager: Arc) -> Self { @@ -31,10 +77,9 @@ impl MockMcpServer { #[tool(description = "List all mock rules, optionally filtered by group")] async fn list_mock_rules( &self, - #[schemars(description = "Optional group name to filter by")] - group: Option, + params: Parameters, ) -> Result { - let rules = self.manager.list(group.as_deref()); + let rules = self.manager.list(params.0.group.as_deref()); let result = serde_json::to_string_pretty(&rules) .map_err(|e| McpError::internal_error(e.to_string(), None))?; Ok(CallToolResult::success(vec![Content::text(result)])) @@ -43,19 +88,16 @@ impl MockMcpServer { #[tool(description = "Get a specific mock rule by group and name")] async fn get_mock_rule( &self, - #[schemars(description = "Group name (directory name)")] - group: String, - #[schemars(description = "Rule name")] - name: String, + params: Parameters, ) -> Result { - match self.manager.get(&group, &name) { + match self.manager.get(¶ms.0.group, ¶ms.0.name) { Some(rule) => { let result = serde_json::to_string_pretty(&rule) .map_err(|e| McpError::internal_error(e.to_string(), None))?; Ok(CallToolResult::success(vec![Content::text(result)])) } None => Ok(CallToolResult::error(vec![Content::text( - format!("Rule not found: {}/{}", group, name), + format!("Rule not found: {}/{}", params.0.group, params.0.name), )])), } } @@ -63,17 +105,14 @@ impl MockMcpServer { #[tool(description = "Create a new mock rule")] async fn create_mock_rule( &self, - #[schemars(description = "Group name (directory name)")] - group: String, - #[schemars(description = "Mock rule definition (JSON object)")] - rule_json: String, + params: Parameters, ) -> Result { - let rule: MockRule = serde_json::from_str(&rule_json) + let rule: MockRule = serde_json::from_str(¶ms.0.rule_json) .map_err(|e| McpError::invalid_params(format!("Invalid rule JSON: {}", e), None))?; - match self.manager.create(&group, rule) { + match self.manager.create(¶ms.0.group, rule) { Ok(_) => Ok(CallToolResult::success(vec![Content::text( - format!("Created rule in group '{}'", group), + format!("Created rule in group '{}'", params.0.group), )])), Err(e) => Ok(CallToolResult::error(vec![Content::text(e)])), } @@ -82,19 +121,14 @@ impl MockMcpServer { #[tool(description = "Update an existing mock rule")] async fn update_mock_rule( &self, - #[schemars(description = "Group name")] - group: String, - #[schemars(description = "Current rule name")] - name: String, - #[schemars(description = "Updated rule definition (JSON object)")] - rule_json: String, + params: Parameters, ) -> Result { - let rule: MockRule = serde_json::from_str(&rule_json) + let rule: MockRule = serde_json::from_str(¶ms.0.rule_json) .map_err(|e| McpError::invalid_params(format!("Invalid rule JSON: {}", e), None))?; - match self.manager.update(&group, &name, rule) { + match self.manager.update(¶ms.0.group, ¶ms.0.name, rule) { Ok(_) => Ok(CallToolResult::success(vec![Content::text( - format!("Updated rule {}/{}", group, name), + format!("Updated rule {}/{}", params.0.group, params.0.name), )])), Err(e) => Ok(CallToolResult::error(vec![Content::text(e)])), } @@ -103,14 +137,11 @@ impl MockMcpServer { #[tool(description = "Delete a mock rule")] async fn delete_mock_rule( &self, - #[schemars(description = "Group name")] - group: String, - #[schemars(description = "Rule name")] - name: String, + params: Parameters, ) -> Result { - match self.manager.delete(&group, &name) { + match self.manager.delete(¶ms.0.group, ¶ms.0.name) { Ok(_) => Ok(CallToolResult::success(vec![Content::text( - format!("Deleted rule {}/{}", group, name), + format!("Deleted rule {}/{}", params.0.group, params.0.name), )])), Err(e) => Ok(CallToolResult::error(vec![Content::text(e)])), } @@ -120,7 +151,7 @@ impl MockMcpServer { async fn reload_mock_rules(&self) -> Result { self.manager.reload(); Ok(CallToolResult::success(vec![Content::text( - "Reloaded all rules from disk", + "Reloaded all rules from disk".to_string(), )])) } @@ -137,23 +168,32 @@ impl MockMcpServer { impl ServerHandler for MockMcpServer { fn get_info(&self) -> ServerInfo { ServerInfo { - protocol_version: ProtocolVersion::V_2024_11_05, capabilities: ServerCapabilities::builder() .enable_tools() .build(), - server_info: Implementation { - name: "mock-server".to_string(), - version: "0.1.0".to_string(), - }, instructions: Some("Mock Server MCP - Manage mock API rules for development.\n\nUse list_mock_rules to see all rules, create_mock_rule to add new ones, and delete_mock_rule to remove them.".to_string()), + ..Default::default() } } } -/// Run MCP server with stdio transport -pub async fn run_mcp_server(manager: Arc) -> Result<(), Box> { - let server = MockMcpServer::new(manager); - let service = server.serve(stdio()).await?; - service.waiting().await?; - Ok(()) +use rmcp::transport::streamable_http_server::{ + StreamableHttpService, StreamableHttpServerConfig, + session::never::NeverSessionManager, +}; +use tokio_util::sync::CancellationToken; + +/// Create stateless MCP HTTP service for integration with Axum +pub fn create_mcp_http_service( + manager: Arc, +) -> StreamableHttpService { + StreamableHttpService::new( + move || Ok(MockMcpServer::new(manager.clone())), + Arc::new(NeverSessionManager::default()), + StreamableHttpServerConfig { + sse_keep_alive: None, + stateful_mode: false, + cancellation_token: CancellationToken::new(), + }, + ) }