diff --git a/Cargo.lock b/Cargo.lock index 2834070..ddf4aac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,23 +26,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" -[[package]] -name = "async-trait" -version = "0.1.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -51,13 +55,13 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.7.9" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ - "async-trait", "axum-core", "bytes", + "form_urlencoded", "futures-util", "http", "http-body", @@ -76,7 +80,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower", "tower-layer", @@ -86,20 +90,19 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.5" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ - "async-trait", "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -147,6 +150,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + [[package]] name = "byteorder" version = "1.5.0" @@ -159,12 +168,42 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +[[package]] +name = "cc" +version = "1.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.16" @@ -224,6 +263,12 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -239,7 +284,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9fb5a367b9846933e271a3c2a992930743f82ae5e8cb7faa780715a80fa0b15" dependencies = [ - "rand_core", + "rand_core 0.6.4", "sha2", "sha3", "zeroize", @@ -386,7 +431,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -482,6 +539,30 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "instant" version = "0.1.13" @@ -497,6 +578,16 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "keccak" version = "0.1.5" @@ -545,9 +636,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" @@ -578,7 +669,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] @@ -609,6 +700,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.5" @@ -637,6 +737,7 @@ dependencies = [ "futures", "http", "lazy_static", + "rmcp", "rust-embed", "serde", "serde_json", @@ -705,6 +806,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -723,6 +830,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -741,13 +857,48 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha", + "rand_core 0.9.3", +] + +[[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 0.9.3", +] + [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", ] [[package]] @@ -812,6 +963,47 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rmcp" +version = "0.1.5" +source = "git+https://github.com/modelcontextprotocol/rust-sdk?branch=main#a66f66ae345a0fafde1e2ee496ec137d77aef82a" +dependencies = [ + "axum", + "base64", + "bytes", + "chrono", + "futures", + "http", + "http-body", + "http-body-util", + "paste", + "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" +source = "git+https://github.com/modelcontextprotocol/rust-sdk?branch=main#a66f66ae345a0fafde1e2ee496ec137d77aef82a" +dependencies = [ + "proc-macro2", + "quote", + "serde_json", + "syn", +] + [[package]] name = "rust-embed" version = "8.5.0" @@ -873,6 +1065,31 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "chrono", + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -899,6 +1116,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_json" version = "1.0.133" @@ -969,6 +1197,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1025,6 +1259,19 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "sse-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f649a9f9e91db2ed32f3724516eac2bc09fab77fc33be8f670f5619b9dc6c3f" +dependencies = [ + "bytes", + "futures-util", + "http-body", + "http-body-util", + "pin-project-lite", +] + [[package]] name = "syn" version = "2.0.89" @@ -1036,18 +1283,32 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -1060,9 +1321,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -1078,9 +1339,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -1113,14 +1374,14 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 0.1.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -1235,7 +1496,7 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -1266,6 +1527,73 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1297,6 +1625,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1370,6 +1757,35 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/Cargo.toml b/Cargo.toml index af79aab..803ef9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ name = "agent-server" path = "src/main.rs" [dependencies] -axum = { version = "0.7", features = ["multipart"] } +axum = { version = "0.8", features = ["multipart"] } serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.0", features = ["full"] } tracing = "0.1" @@ -30,4 +30,5 @@ sled = "0.34.7" tower-http = { version = "0.6.2", features = ["trace", "cors"] } anyhow = "1.0.97" base64 = "0.22.1" -fips204 = "0.4.6" \ No newline at end of file +fips204 = "0.4.6" +rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main", features = ["server", "transport-streamable-http-server", "transport-sse-server", "transport-io",] } \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 97e3aa9..5687103 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/killport.js b/killport.js old mode 100644 new mode 100755 diff --git a/package.json b/package.json index d43a638..3b13dd7 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "private": true, "scripts": { "clean": "rm -rf .genaiscript && rm -rf dist && rm -rf node_modules && rm -rf open-web-agent-rs && rm -rf target && rm -rf packages/genaiscript-rust-shim/dist", - "dev": "./killport.js 3006 && bun run build && cargo watch -x 'run src/main.rs'", + "dev": "bun i && ./killport.js 3006 && bun run build && cargo watch -x 'run src/main.rs'", "start": "docker compose up --build", "ai:search": "genaiscript run packages/genaiscript/genaisrc/web-search.genai.mts --vars USER_INPUT='who won the 2024 election?'", "shim:ai:search": "pnpm build && ./packages/genaiscript-rust-shim/dist/genaiscript-rust-shim.js --file=packages/genaiscript/genaisrc/web-search.genai.mts USER_INPUT=\"Who won the 2024 presidential election?\"\n", @@ -16,6 +16,7 @@ "ai:url:scrape": "npx genaiscript run packages/genaiscript/genaisrc/web-scrape.genai.mts --vars USER_INPUT='{\"url\":\"https://www.time4learning.com/homeschool-curriculum/high-school/eleventh-grade/math.html\",\"query\":\"What is on this page?\", \"action\": \"scrape\"}'", "prod:logs": "fly logs", "test-http": "test/test-search.ts", + "mcp-inspector": "bunx @modelcontextprotocol/inspector", "build": "(cd packages/genaiscript-rust-shim && bun run buildShim && bun run setupDev && cargo build)" } } diff --git a/src/counter.rs b/src/counter.rs new file mode 100644 index 0000000..0d08cfc --- /dev/null +++ b/src/counter.rs @@ -0,0 +1,211 @@ +use std::sync::Arc; + +use rmcp::{ + Error as McpError, RoleServer, ServerHandler, const_string, model::*, schemars, + service::RequestContext, tool, +}; +use serde_json::json; +use tokio::sync::Mutex; + +#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] +pub struct StructRequest { + pub a: i32, + pub b: i32, +} + +#[derive(Clone)] +pub struct Counter { + counter: Arc>, +} + +#[tool(tool_box)] +impl Counter { + #[allow(dead_code)] + pub fn new() -> Self { + Self { + counter: Arc::new(Mutex::new(0)), + } + } + + fn _create_resource_text(&self, uri: &str, name: &str) -> Resource { + RawResource::new(uri, name.to_string()).no_annotation() + } + + #[tool(description = "Increment the counter by 1")] + async fn increment(&self) -> Result { + let mut counter = self.counter.lock().await; + *counter += 1; + Ok(CallToolResult::success(vec![Content::text( + counter.to_string(), + )])) + } + + #[tool(description = "Decrement the counter by 1")] + async fn decrement(&self) -> Result { + let mut counter = self.counter.lock().await; + *counter -= 1; + Ok(CallToolResult::success(vec![Content::text( + counter.to_string(), + )])) + } + + #[tool(description = "Get the current counter value")] + async fn get_value(&self) -> Result { + let counter = self.counter.lock().await; + Ok(CallToolResult::success(vec![Content::text( + counter.to_string(), + )])) + } + + #[tool(description = "Say hello to the client")] + fn say_hello(&self) -> Result { + Ok(CallToolResult::success(vec![Content::text("hello")])) + } + + #[tool(description = "Repeat what you say")] + fn echo( + &self, + #[tool(param)] + #[schemars(description = "Repeat what you say")] + saying: String, + ) -> Result { + Ok(CallToolResult::success(vec![Content::text(saying)])) + } + + #[tool(description = "Calculate the sum of two numbers")] + fn sum( + &self, + #[tool(aggr)] StructRequest { a, b }: StructRequest, + ) -> Result { + Ok(CallToolResult::success(vec![Content::text( + (a + b).to_string(), + )])) + } +} +const_string!(Echo = "echo"); +#[tool(tool_box)] +impl ServerHandler for Counter { + fn get_info(&self) -> ServerInfo { + ServerInfo { + protocol_version: ProtocolVersion::V_2024_11_05, + capabilities: ServerCapabilities::builder() + .enable_prompts() + .enable_resources() + .enable_tools() + .build(), + server_info: Implementation::from_build_env(), + instructions: Some("This server provides a counter tool that can increment and decrement values. The counter starts at 0 and can be modified using the 'increment' and 'decrement' tools. Use 'get_value' to check the current count.".to_string()), + } + } + + async fn list_resources( + &self, + _request: Option, + _: RequestContext, + ) -> Result { + Ok(ListResourcesResult { + resources: vec![ + self._create_resource_text("str:////Users/to/some/path/", "cwd"), + self._create_resource_text("memo://insights", "memo-name"), + ], + next_cursor: None, + }) + } + + async fn read_resource( + &self, + ReadResourceRequestParam { uri }: ReadResourceRequestParam, + _: RequestContext, + ) -> Result { + match uri.as_str() { + "str:////Users/to/some/path/" => { + let cwd = "/Users/to/some/path/"; + Ok(ReadResourceResult { + contents: vec![ResourceContents::text(cwd, uri)], + }) + } + "memo://insights" => { + let memo = "Business Intelligence Memo\n\nAnalysis has revealed 5 key insights ..."; + Ok(ReadResourceResult { + contents: vec![ResourceContents::text(memo, uri)], + }) + } + _ => Err(McpError::resource_not_found( + "resource_not_found", + Some(json!({ + "uri": uri + })), + )), + } + } + + async fn list_prompts( + &self, + _request: Option, + _: RequestContext, + ) -> Result { + Ok(ListPromptsResult { + next_cursor: None, + prompts: vec![Prompt::new( + "example_prompt", + Some("This is an example prompt that takes one required argument, message"), + Some(vec![PromptArgument { + name: "message".to_string(), + description: Some("A message to put in the prompt".to_string()), + required: Some(true), + }]), + )], + }) + } + + async fn get_prompt( + &self, + GetPromptRequestParam { name, arguments }: GetPromptRequestParam, + _: RequestContext, + ) -> Result { + match name.as_str() { + "example_prompt" => { + let message = arguments + .and_then(|json| json.get("message")?.as_str().map(|s| s.to_string())) + .ok_or_else(|| { + McpError::invalid_params("No message provided to example_prompt", None) + })?; + + let prompt = + format!("This is an example prompt with your message here: '{message}'"); + Ok(GetPromptResult { + description: None, + messages: vec![PromptMessage { + role: PromptMessageRole::User, + content: PromptMessageContent::text(prompt), + }], + }) + } + _ => Err(McpError::invalid_params("prompt not found", None)), + } + } + + async fn list_resource_templates( + &self, + _request: Option, + _: RequestContext, + ) -> Result { + Ok(ListResourceTemplatesResult { + next_cursor: None, + resource_templates: Vec::new(), + }) + } + + async fn initialize( + &self, + _request: InitializeRequestParam, + context: RequestContext, + ) -> Result { + if let Some(http_request_part) = context.extensions.get::() { + let initialize_headers = &http_request_part.headers; + let initialize_uri = &http_request_part.uri; + tracing::info!(?initialize_headers, %initialize_uri, "initialize from http server"); + } + Ok(self.get_info()) + } +} \ No newline at end of file diff --git a/src/handlers/model_context.rs b/src/handlers/model_context.rs new file mode 100644 index 0000000..305ec09 --- /dev/null +++ b/src/handlers/model_context.rs @@ -0,0 +1,185 @@ +use axum::response::Response; +use axum::{ + body::Body, extract::Json, http::StatusCode, response::IntoResponse, +}; +use bytes::Bytes; +use futures::stream::{Stream, StreamExt}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::pin::Pin; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::Command; + +use crate::utils::utils::run_agent; + +// Custom function to format streaming responses according to OpenAI API format +pub fn openai_stream_format( + reader: BufReader, + request_id: String, + model: String, +) -> Pin> + Send>> +where + R: tokio::io::AsyncRead + Unpin + Send + 'static, +{ + let stream = futures::stream::unfold((reader, 0), move |(mut reader, index)| { + let request_id = request_id.clone(); + let model = model.clone(); + async move { + let mut line = String::new(); + match reader.read_line(&mut line).await { + Ok(0) => None, + Ok(_) => { + let content = line.trim(); + // Skip empty lines + if content.is_empty() { + return Some((Ok(Bytes::from("")), (reader, index))); + } + + // Format as OpenAI API streaming response + let chunk = serde_json::json!({ + "id": format!("chatcmpl-{}", request_id), + "object": "chat.completion.chunk", + "created": std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(), + "model": model, + "choices": [{ + "index": index, + "delta": { + "content": content + }, + "finish_reason": null + }] + }); + + Some(( + Ok(Bytes::from(format!("data: {}\n\n", chunk.to_string()))), + (reader, index), + )) + } + Err(e) => Some((Err(e), (reader, index))), + } + } + }); + + // Add the [DONE] message at the end + let stream_with_done = stream.filter(|result| { + futures::future::ready(match result { + Ok(bytes) => !bytes.is_empty(), + Err(_) => true, + }) + }).chain(futures::stream::once(async { + Ok(Bytes::from("data: [DONE]\n\n")) + })); + + Box::pin(stream_with_done) +} + +#[derive(Deserialize, Debug)] +pub struct ModelContextRequest { + messages: Vec, + model: Option, + stream: Option, + temperature: Option, + max_tokens: Option, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct Message { + role: String, + content: String, +} + +#[derive(Serialize, Debug)] +pub struct ModelContextResponse { + id: String, + object: String, + created: u64, + model: String, + choices: Vec, +} + +#[derive(Serialize, Debug)] +pub struct Choice { + index: u32, + message: Message, + finish_reason: String, +} + +pub async fn model_context( + headers: axum::http::HeaderMap, + Json(payload): Json +) -> impl IntoResponse { + // Generate a unique ID for this request + let request_id = uuid::Uuid::new_v4().to_string(); + + // Convert messages to a format that can be passed to the agent + let input = serde_json::to_string(&payload.messages).unwrap_or_default(); + + // Use the web-search agent for now, but this could be customized based on the model parameter + let agent_file = "./packages/genaiscript/genaisrc/web-search.genai.mts"; + + tracing::debug!( + "Executing model context request - Id: {}", + request_id + ); + + // Default timeout of 60 seconds + let mut cmd = match run_agent(&request_id, &input, agent_file, 60).await { + Ok(cmd) => cmd, + Err(e) => { + tracing::error!("Model context execution failed: {}", e); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + }; + + // Check if streaming is requested either via the stream parameter or Accept header + let accept_header = headers.get("accept").and_then(|h| h.to_str().ok()).unwrap_or(""); + let is_streaming = payload.stream.unwrap_or(false) || accept_header.contains("text/event-stream"); + + // If streaming is requested, return a streaming response + if is_streaming { + let stdout = match cmd.stdout.take() { + Some(stdout) => stdout, + None => { + tracing::error!("No stdout available for the command."); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + }; + + let reader = BufReader::new(stdout); + let model = payload.model.clone().unwrap_or_else(|| "default-model".to_string()); + let sse_stream = openai_stream_format(reader, request_id.clone(), model); + + return Response::builder() + .header("Content-Type", "text/event-stream") + .header("Cache-Control", "no-cache, no-transform") + .header("Connection", "keep-alive") + .header("X-Accel-Buffering", "yes") + .body(Body::from_stream(sse_stream)) + .unwrap(); + } else { + // For non-streaming responses, we need to collect all output and return it as a single response + // This is a simplified implementation and might need to be adjusted based on actual requirements + let response = ModelContextResponse { + id: format!("chatcmpl-{}", request_id), + object: "chat.completion".to_string(), + created: std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(), + model: payload.model.unwrap_or_else(|| "default-model".to_string()), + choices: vec![Choice { + index: 0, + message: Message { + role: "assistant".to_string(), + content: "This is a placeholder response. The actual implementation would process the agent's output.".to_string(), + }, + finish_reason: "stop".to_string(), + }], + }; + + return Json(response).into_response(); + } +} diff --git a/src/handlers/models.rs b/src/handlers/models.rs new file mode 100644 index 0000000..06a46b5 --- /dev/null +++ b/src/handlers/models.rs @@ -0,0 +1,48 @@ +use axum::{ + extract::Json, + response::IntoResponse, +}; +use serde::{Deserialize, Serialize}; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[derive(Serialize, Debug)] +pub struct ModelsResponse { + object: String, + data: Vec, +} + +#[derive(Serialize, Debug)] +pub struct Model { + id: String, + object: String, + created: u64, + owned_by: String, +} + +pub async fn list_models() -> impl IntoResponse { + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + // Create a response with a default model + let response = ModelsResponse { + object: "list".to_string(), + data: vec![ + Model { + id: "gpt-3.5-turbo".to_string(), + object: "model".to_string(), + created: current_time, + owned_by: "open-web-agent-rs".to_string(), + }, + Model { + id: "gpt-4".to_string(), + object: "model".to_string(), + created: current_time, + owned_by: "open-web-agent-rs".to_string(), + }, + ], + }; + + Json(response) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b3316f4..f695551 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod setup; mod handlers; mod agents; mod utils; +mod counter; #[tokio::main] async fn main() { diff --git a/src/routes.rs b/src/routes.rs index 000a700..a2b7259 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,17 +1,25 @@ -use crate::handlers::agents::create_agent; -use crate::handlers::{not_found::handle_not_found, ui::serve_ui, agents::use_agent}; -use axum::routing::post; +use crate::handlers::{not_found::handle_not_found, ui::serve_ui}; use axum::routing::{get, Router}; use tower_http::trace::{self, TraceLayer}; use tracing::Level; +use rmcp::transport::streamable_http_server::{ + StreamableHttpService, session::local::LocalSessionManager, +}; +use crate::counter::Counter; + pub fn create_router() -> Router { + + let service = StreamableHttpService::new( + Counter::new, + LocalSessionManager::default().into(), + Default::default(), + ); + + Router::new() + .nest_service("/mcp", service) .route("/", get(serve_ui)) - // create an agent - .route("/api/agents", post(create_agent)) - // connect the agent - .route("/agents/:agent_id", get(use_agent)) .route("/health", get(health)) .layer( TraceLayer::new_for_http()