feat(mcp): 实现HTTP统一方案并添加MCP文档
- 将MCP服务和Mock API合并到单个HTTP服务器(8080端口) - 添加POST /mcp端点,使用无状态StreamableHttpService - 新增docs/mcp-implementation.md文档
This commit is contained in:
581
Cargo.lock
generated
581
Cargo.lock
generated
@@ -2,6 +2,15 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
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]]
|
[[package]]
|
||||||
name = "android_system_properties"
|
name = "android_system_properties"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -11,6 +20,23 @@ dependencies = [
|
|||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "atomic-waker"
|
name = "atomic-waker"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@@ -77,9 +103,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.7"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
@@ -141,12 +167,77 @@ version = "0.8.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
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]]
|
[[package]]
|
||||||
name = "dyn-clone"
|
name = "dyn-clone"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.14"
|
version = "0.3.14"
|
||||||
@@ -169,6 +260,18 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
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]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
@@ -284,10 +387,44 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi",
|
"r-efi 5.3.0",
|
||||||
"wasip2",
|
"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]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -394,6 +531,30 @@ dependencies = [
|
|||||||
"cc",
|
"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]]
|
[[package]]
|
||||||
name = "inotify"
|
name = "inotify"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -456,6 +617,12 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leb128fmt"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.178"
|
version = "0.2.178"
|
||||||
@@ -483,6 +650,15 @@ version = "0.4.29"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
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]]
|
[[package]]
|
||||||
name = "matchit"
|
name = "matchit"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
@@ -529,6 +705,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tracing-appender",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
@@ -578,6 +755,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"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]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -617,10 +800,10 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "pastey"
|
||||||
version = "1.0.15"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
@@ -640,6 +823,31 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
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]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.103"
|
version = "1.0.103"
|
||||||
@@ -664,6 +872,41 @@ version = "5.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
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]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
@@ -674,34 +917,83 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rmcp"
|
name = "ref-cast"
|
||||||
version = "0.1.5"
|
version = "1.0.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33a0110d28bd076f39e14bfd5b0340216dd18effeb5d02b43215944cc3e5c751"
|
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
|
||||||
dependencies = [
|
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",
|
"base64",
|
||||||
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"futures",
|
"futures",
|
||||||
"paste",
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"pastey",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"rand",
|
||||||
"rmcp-macros",
|
"rmcp-macros",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sse-stream",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rmcp-macros"
|
name = "rmcp-macros"
|
||||||
version = "0.1.5"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6e2b2fd7497540489fa2db285edd43b7ed14c49157157438664278da6e42a7a"
|
checksum = "9ef03779cccab8337dd8617c53fce5c98ec21794febc397531555472ca28f8c3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"serde_json",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -741,11 +1033,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars"
|
name = "schemars"
|
||||||
version = "0.8.22"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
|
"ref-cast",
|
||||||
"schemars_derive",
|
"schemars_derive",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -753,9 +1047,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars_derive"
|
name = "schemars_derive"
|
||||||
version = "0.8.22"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -769,6 +1063,12 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -892,6 +1192,25 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"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]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.111"
|
version = "2.0.111"
|
||||||
@@ -916,7 +1235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom",
|
"getrandom 0.3.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
@@ -951,6 +1270,37 @@ dependencies = [
|
|||||||
"cfg-if",
|
"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]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.48.0"
|
version = "1.48.0"
|
||||||
@@ -979,6 +1329,17 @@ dependencies = [
|
|||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.17"
|
version = "0.7.17"
|
||||||
@@ -1032,6 +1393,18 @@ dependencies = [
|
|||||||
"tracing-core",
|
"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]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.31"
|
version = "0.1.31"
|
||||||
@@ -1070,10 +1443,14 @@ version = "0.3.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"matchers",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
|
"once_cell",
|
||||||
|
"regex-automata",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
@@ -1084,6 +1461,23 @@ version = "1.0.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
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]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.1"
|
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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
@@ -1160,6 +1563,40 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.11"
|
version = "0.1.11"
|
||||||
@@ -1317,6 +1754,114 @@ version = "0.46.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
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]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
|||||||
18
Cargo.toml
18
Cargo.toml
@@ -1,11 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mock_server"
|
name = "mock_server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
mcp = ["rmcp", "schemars"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# 核心 Web 框架
|
# 核心 Web 框架
|
||||||
@@ -23,16 +19,18 @@ serde_json = "1.0.147"
|
|||||||
# 物理目录递归扫描工具
|
# 物理目录递归扫描工具
|
||||||
walkdir = "2.5.0"
|
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 = "8.2.0"
|
||||||
notify-debouncer-mini = "0.6.0"
|
notify-debouncer-mini = "0.6.0"
|
||||||
|
|
||||||
# MCP Server 支持(可选)
|
# MCP Server 支持
|
||||||
rmcp = { version = "0.1", features = ["server"], optional = true }
|
rmcp = { version = "0.11", features = ["server", "transport-streamable-http-server", "transport-streamable-http-server-session"] }
|
||||||
schemars = { version = "0.8", optional = true }
|
schemars = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.24.0"
|
tempfile = "3.24.0"
|
||||||
427
docs/mcp-implementation.md
Normal file
427
docs/mcp-implementation.md
Normal file
@@ -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<Self>` 字段
|
||||||
|
|
||||||
|
在结构体中必须包含此字段:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MockMcpServer {
|
||||||
|
manager: Arc<MockManager>,
|
||||||
|
tool_router: ToolRouter<Self>, // 必需字段
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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<MockManager>,
|
||||||
|
tool_router: ToolRouter<Self>, // 必需字段
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 请求参数结构体
|
||||||
|
|
||||||
|
```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<String>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tool 方法实现
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[tool_router]
|
||||||
|
impl MockMcpServer {
|
||||||
|
pub fn new(manager: Arc<MockManager>) -> Self {
|
||||||
|
Self {
|
||||||
|
manager,
|
||||||
|
tool_router: Self::tool_router(), // 初始化 tool_router
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 无参数 tool
|
||||||
|
#[tool(description = "List all groups (directories)")]
|
||||||
|
async fn list_groups(&self) -> Result<CallToolResult, McpError> {
|
||||||
|
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<T> 包装器
|
||||||
|
#[tool(description = "Get a specific mock rule by group and name")]
|
||||||
|
async fn get_mock_rule(
|
||||||
|
&self,
|
||||||
|
params: Parameters<GetRuleRequest>,
|
||||||
|
) -> Result<CallToolResult, McpError> {
|
||||||
|
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<ListRulesRequest>,
|
||||||
|
) -> Result<CallToolResult, McpError> {
|
||||||
|
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<T> 包装器
|
||||||
|
#[tool(description = "...")]
|
||||||
|
async fn my_tool(&self, params: Parameters<MyRequest>) -> 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<MockManager>,
|
||||||
|
) -> StreamableHttpService<MockMcpServer, NeverSessionManager> {
|
||||||
|
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<Duration>` | 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> │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
|
||||||
|
│ │ 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/)
|
||||||
@@ -4,6 +4,5 @@ pub mod loader;
|
|||||||
pub mod router;
|
pub mod router;
|
||||||
pub mod handler;
|
pub mod handler;
|
||||||
pub mod manager;
|
pub mod manager;
|
||||||
|
pub mod logging;
|
||||||
#[cfg(feature = "mcp")]
|
|
||||||
pub mod mcp;
|
pub mod mcp;
|
||||||
86
src/main.rs
86
src/main.rs
@@ -3,37 +3,39 @@ use std::path::PathBuf;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use axum::{routing::any, Router};
|
use axum::{routing::post, Router};
|
||||||
use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode};
|
use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode};
|
||||||
|
use tracing::{info, error};
|
||||||
|
|
||||||
use mock_server::loader::MockLoader;
|
use mock_server::loader::MockLoader;
|
||||||
use mock_server::router::MockRouter;
|
use mock_server::router::MockRouter;
|
||||||
use mock_server::handler::{mock_handler, AppState};
|
use mock_server::handler::{mock_handler, AppState};
|
||||||
|
use mock_server::logging;
|
||||||
|
|
||||||
fn print_usage() {
|
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!();
|
||||||
println!("Usage: mock_server [OPTIONS]");
|
println!("Usage: mock_server [OPTIONS]");
|
||||||
println!();
|
println!();
|
||||||
println!("Options:");
|
println!("Options:");
|
||||||
println!(" --mcp Run as MCP server (stdio transport)");
|
|
||||||
println!(" --mocks <DIR> Mocks directory path (default: ./mocks)");
|
println!(" --mocks <DIR> Mocks directory path (default: ./mocks)");
|
||||||
println!(" --port <PORT> Server port (default: 8080)");
|
println!(" --port <PORT> HTTP server port (default: 8080)");
|
||||||
println!(" --help Show this help message");
|
println!(" --help Show this help message");
|
||||||
|
println!();
|
||||||
|
println!("MCP Endpoint: POST http://127.0.0.1:<PORT>/mcp");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn 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<String> = std::env::args().collect();
|
let args: Vec<String> = std::env::args().collect();
|
||||||
let mut mocks_dir = PathBuf::from("./mocks");
|
let mut mocks_dir = PathBuf::from("./mocks");
|
||||||
let mut port: u16 = 8080;
|
let mut port: u16 = 8080;
|
||||||
|
|
||||||
#[cfg(feature = "mcp")]
|
|
||||||
let mut mcp_mode = false;
|
|
||||||
|
|
||||||
let mut i = 1;
|
let mut i = 1;
|
||||||
while i < args.len() {
|
while i < args.len() {
|
||||||
match args[i].as_str() {
|
match args[i].as_str() {
|
||||||
@@ -41,17 +43,6 @@ async fn main() {
|
|||||||
print_usage();
|
print_usage();
|
||||||
return;
|
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" => {
|
"--mocks" => {
|
||||||
if i + 1 < args.len() {
|
if i + 1 < args.len() {
|
||||||
mocks_dir = PathBuf::from(&args[i + 1]);
|
mocks_dir = PathBuf::from(&args[i + 1]);
|
||||||
@@ -89,37 +80,25 @@ async fn main() {
|
|||||||
std::fs::create_dir_all(&mocks_dir).unwrap();
|
std::fs::create_dir_all(&mocks_dir).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mcp")]
|
// Create shared MockManager for both HTTP and MCP
|
||||||
if mcp_mode {
|
let manager = Arc::new(mock_server::manager::MockManager::new(mocks_dir.clone()));
|
||||||
run_mcp_server(mocks_dir).await;
|
info!("Loaded {} groups", manager.list_groups().len());
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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_http_server(mocks_dir: PathBuf, port: u16, manager: Arc<mock_server::manager::MockManager>) {
|
||||||
async fn run_mcp_server(mocks_dir: PathBuf) {
|
info!("Scanning mocks directory...");
|
||||||
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...");
|
|
||||||
let index = MockLoader::load_all_from_dir(&mocks_dir);
|
let index = MockLoader::load_all_from_dir(&mocks_dir);
|
||||||
let shared_state = Arc::new(AppState {
|
let shared_state = Arc::new(AppState {
|
||||||
router: std::sync::RwLock::new(MockRouter::new(index)),
|
router: std::sync::RwLock::new(MockRouter::new(index)),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设置热加载监听器
|
// Setup hot-reload watcher
|
||||||
let state_for_watcher = shared_state.clone();
|
let state_for_watcher = shared_state.clone();
|
||||||
let watch_path = mocks_dir.clone();
|
let watch_path = mocks_dir.clone();
|
||||||
|
let manager_for_watcher = manager.clone();
|
||||||
let (tx, rx) = std::sync::mpsc::channel();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
let mut debouncer = new_debouncer(Duration::from_millis(200), tx).unwrap();
|
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() {
|
while let Ok(res) = rx.recv() {
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
println!("🔄 Detecting changes in mocks/, reloading...");
|
info!("Detected changes in mocks/, reloading...");
|
||||||
let new_index = MockLoader::load_all_from_dir(&watch_path);
|
let new_index = MockLoader::load_all_from_dir(&watch_path);
|
||||||
let mut writer = state_for_watcher.router.write().expect("Failed to acquire write lock");
|
let mut writer = state_for_watcher.router.write().expect("Failed to acquire write lock");
|
||||||
*writer = MockRouter::new(new_index);
|
*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()
|
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);
|
.with_state(shared_state);
|
||||||
|
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
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();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
pub use server::run_mcp_server;
|
pub use server::create_mcp_http_service;
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rmcp::{
|
use rmcp::{
|
||||||
handler::server::tool::ToolRouter,
|
handler::server::tool::ToolRouter,
|
||||||
|
handler::server::wrapper::Parameters,
|
||||||
model::*,
|
model::*,
|
||||||
tool, tool_handler, tool_router,
|
tool, tool_handler, tool_router,
|
||||||
ServerHandler, ServiceExt,
|
ErrorData as McpError, ServerHandler,
|
||||||
transport::stdio,
|
|
||||||
};
|
};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::manager::MockManager;
|
use crate::manager::MockManager;
|
||||||
use crate::models::MockRule;
|
use crate::models::MockRule;
|
||||||
@@ -19,6 +20,51 @@ pub struct MockMcpServer {
|
|||||||
tool_router: ToolRouter<Self>,
|
tool_router: ToolRouter<Self>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[tool_router]
|
#[tool_router]
|
||||||
impl MockMcpServer {
|
impl MockMcpServer {
|
||||||
pub fn new(manager: Arc<MockManager>) -> Self {
|
pub fn new(manager: Arc<MockManager>) -> Self {
|
||||||
@@ -31,10 +77,9 @@ impl MockMcpServer {
|
|||||||
#[tool(description = "List all mock rules, optionally filtered by group")]
|
#[tool(description = "List all mock rules, optionally filtered by group")]
|
||||||
async fn list_mock_rules(
|
async fn list_mock_rules(
|
||||||
&self,
|
&self,
|
||||||
#[schemars(description = "Optional group name to filter by")]
|
params: Parameters<ListRulesRequest>,
|
||||||
group: Option<String>,
|
|
||||||
) -> Result<CallToolResult, McpError> {
|
) -> Result<CallToolResult, McpError> {
|
||||||
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)
|
let result = serde_json::to_string_pretty(&rules)
|
||||||
.map_err(|e| McpError::internal_error(e.to_string(), None))?;
|
.map_err(|e| McpError::internal_error(e.to_string(), None))?;
|
||||||
Ok(CallToolResult::success(vec![Content::text(result)]))
|
Ok(CallToolResult::success(vec![Content::text(result)]))
|
||||||
@@ -43,19 +88,16 @@ impl MockMcpServer {
|
|||||||
#[tool(description = "Get a specific mock rule by group and name")]
|
#[tool(description = "Get a specific mock rule by group and name")]
|
||||||
async fn get_mock_rule(
|
async fn get_mock_rule(
|
||||||
&self,
|
&self,
|
||||||
#[schemars(description = "Group name (directory name)")]
|
params: Parameters<GetRuleRequest>,
|
||||||
group: String,
|
|
||||||
#[schemars(description = "Rule name")]
|
|
||||||
name: String,
|
|
||||||
) -> Result<CallToolResult, McpError> {
|
) -> Result<CallToolResult, McpError> {
|
||||||
match self.manager.get(&group, &name) {
|
match self.manager.get(¶ms.0.group, ¶ms.0.name) {
|
||||||
Some(rule) => {
|
Some(rule) => {
|
||||||
let result = serde_json::to_string_pretty(&rule)
|
let result = serde_json::to_string_pretty(&rule)
|
||||||
.map_err(|e| McpError::internal_error(e.to_string(), None))?;
|
.map_err(|e| McpError::internal_error(e.to_string(), None))?;
|
||||||
Ok(CallToolResult::success(vec![Content::text(result)]))
|
Ok(CallToolResult::success(vec![Content::text(result)]))
|
||||||
}
|
}
|
||||||
None => Ok(CallToolResult::error(vec![Content::text(
|
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")]
|
#[tool(description = "Create a new mock rule")]
|
||||||
async fn create_mock_rule(
|
async fn create_mock_rule(
|
||||||
&self,
|
&self,
|
||||||
#[schemars(description = "Group name (directory name)")]
|
params: Parameters<CreateRuleRequest>,
|
||||||
group: String,
|
|
||||||
#[schemars(description = "Mock rule definition (JSON object)")]
|
|
||||||
rule_json: String,
|
|
||||||
) -> Result<CallToolResult, McpError> {
|
) -> Result<CallToolResult, McpError> {
|
||||||
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))?;
|
.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(
|
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)])),
|
Err(e) => Ok(CallToolResult::error(vec![Content::text(e)])),
|
||||||
}
|
}
|
||||||
@@ -82,19 +121,14 @@ impl MockMcpServer {
|
|||||||
#[tool(description = "Update an existing mock rule")]
|
#[tool(description = "Update an existing mock rule")]
|
||||||
async fn update_mock_rule(
|
async fn update_mock_rule(
|
||||||
&self,
|
&self,
|
||||||
#[schemars(description = "Group name")]
|
params: Parameters<UpdateRuleRequest>,
|
||||||
group: String,
|
|
||||||
#[schemars(description = "Current rule name")]
|
|
||||||
name: String,
|
|
||||||
#[schemars(description = "Updated rule definition (JSON object)")]
|
|
||||||
rule_json: String,
|
|
||||||
) -> Result<CallToolResult, McpError> {
|
) -> Result<CallToolResult, McpError> {
|
||||||
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))?;
|
.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(
|
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)])),
|
Err(e) => Ok(CallToolResult::error(vec![Content::text(e)])),
|
||||||
}
|
}
|
||||||
@@ -103,14 +137,11 @@ impl MockMcpServer {
|
|||||||
#[tool(description = "Delete a mock rule")]
|
#[tool(description = "Delete a mock rule")]
|
||||||
async fn delete_mock_rule(
|
async fn delete_mock_rule(
|
||||||
&self,
|
&self,
|
||||||
#[schemars(description = "Group name")]
|
params: Parameters<DeleteRuleRequest>,
|
||||||
group: String,
|
|
||||||
#[schemars(description = "Rule name")]
|
|
||||||
name: String,
|
|
||||||
) -> Result<CallToolResult, McpError> {
|
) -> Result<CallToolResult, McpError> {
|
||||||
match self.manager.delete(&group, &name) {
|
match self.manager.delete(¶ms.0.group, ¶ms.0.name) {
|
||||||
Ok(_) => Ok(CallToolResult::success(vec![Content::text(
|
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)])),
|
Err(e) => Ok(CallToolResult::error(vec![Content::text(e)])),
|
||||||
}
|
}
|
||||||
@@ -120,7 +151,7 @@ impl MockMcpServer {
|
|||||||
async fn reload_mock_rules(&self) -> Result<CallToolResult, McpError> {
|
async fn reload_mock_rules(&self) -> Result<CallToolResult, McpError> {
|
||||||
self.manager.reload();
|
self.manager.reload();
|
||||||
Ok(CallToolResult::success(vec![Content::text(
|
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 {
|
impl ServerHandler for MockMcpServer {
|
||||||
fn get_info(&self) -> ServerInfo {
|
fn get_info(&self) -> ServerInfo {
|
||||||
ServerInfo {
|
ServerInfo {
|
||||||
protocol_version: ProtocolVersion::V_2024_11_05,
|
|
||||||
capabilities: ServerCapabilities::builder()
|
capabilities: ServerCapabilities::builder()
|
||||||
.enable_tools()
|
.enable_tools()
|
||||||
.build(),
|
.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()),
|
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
|
use rmcp::transport::streamable_http_server::{
|
||||||
pub async fn run_mcp_server(manager: Arc<MockManager>) -> Result<(), Box<dyn std::error::Error>> {
|
StreamableHttpService, StreamableHttpServerConfig,
|
||||||
let server = MockMcpServer::new(manager);
|
session::never::NeverSessionManager,
|
||||||
let service = server.serve(stdio()).await?;
|
};
|
||||||
service.waiting().await?;
|
use tokio_util::sync::CancellationToken;
|
||||||
Ok(())
|
|
||||||
|
/// Create stateless MCP HTTP service for integration with Axum
|
||||||
|
pub fn create_mcp_http_service(
|
||||||
|
manager: Arc<MockManager>,
|
||||||
|
) -> StreamableHttpService<MockMcpServer, NeverSessionManager> {
|
||||||
|
StreamableHttpService::new(
|
||||||
|
move || Ok(MockMcpServer::new(manager.clone())),
|
||||||
|
Arc::new(NeverSessionManager::default()),
|
||||||
|
StreamableHttpServerConfig {
|
||||||
|
sse_keep_alive: None,
|
||||||
|
stateful_mode: false,
|
||||||
|
cancellation_token: CancellationToken::new(),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user