├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples ├── echo.rs └── echo_streamable.rs ├── src ├── cli.rs ├── core.rs ├── main.rs └── state.rs └── tests ├── advanced_test.rs ├── basic_test.rs └── echo └── mod.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Build examples 22 | run: cargo build --examples 23 | - name: Run tests 24 | run: cargo test --verbose 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Rust Release 2 | 3 | on: 4 | push: 5 | branches: 6 | # Always run on main branch. 7 | - main 8 | tags: 9 | - '*' 10 | workflow_dispatch: 11 | 12 | 13 | jobs: 14 | release: 15 | name: ${{ matrix.platform.os-name }} 16 | permissions: 17 | contents: write 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | platform: 22 | - os-name: FreeBSD-x86_64 23 | runs-on: ubuntu-24.04 24 | target: x86_64-unknown-freebsd 25 | 26 | - os-name: Linux-x86_64 27 | runs-on: ubuntu-24.04 28 | target: x86_64-unknown-linux-gnu 29 | 30 | - os-name: Linux-aarch64 31 | runs-on: ubuntu-24.04 32 | target: aarch64-unknown-linux-gnu 33 | 34 | - os-name: Linux-x86_64-musl 35 | runs-on: ubuntu-24.04 36 | target: x86_64-unknown-linux-musl 37 | 38 | - os-name: Linux-aarch64-musl 39 | runs-on: ubuntu-24.04 40 | target: aarch64-unknown-linux-musl 41 | 42 | - os-name: Windows-x86_64 43 | runs-on: windows-latest 44 | target: x86_64-pc-windows-msvc 45 | 46 | - os-name: macOS-x86_64 47 | runs-on: macOS-latest 48 | target: x86_64-apple-darwin 49 | 50 | - os-name: macOS-arm64 51 | runs-on: macos-latest 52 | target: aarch64-apple-darwin 53 | 54 | runs-on: ${{ matrix.platform.runs-on }} 55 | steps: 56 | - name: Checkout 57 | uses: actions/checkout@v4 58 | - name: Build binary 59 | uses: houseabsolute/actions-rust-cross@f7da4010930154943c99d13df0151dece91a924f 60 | with: 61 | command: build 62 | target: ${{ matrix.platform.target }} 63 | args: "--locked --release" 64 | strip: true 65 | - name: Create archive (Unix) 66 | if: matrix.platform.os-name != 'Windows-x86_64' 67 | run: tar -czvf mcp-proxy-${{ matrix.platform.target }}.tar.gz -C target/*/release mcp-proxy 68 | - name: Create archive (Windows) 69 | if: matrix.platform.os-name == 'Windows-x86_64' 70 | working-directory: target\\${{ matrix.platform.target }}\\release 71 | run: 7z a ..\\..\\..\\mcp-proxy-${{ matrix.platform.target }}.zip mcp-proxy.exe 72 | - name: Upload artifacts 73 | uses: actions/upload-artifact@v4 74 | with: 75 | name: mcp-proxy-${{ matrix.platform.target }} 76 | path: mcp-proxy-${{ matrix.platform.target }}.* 77 | - name: Release 78 | uses: softprops/action-gh-release@39ba0b9d81217c984acfad95ddcc7db226387497 79 | if: github.ref_type == 'tag' 80 | with: 81 | files: 'mcp-proxy-${{ matrix.platform.target }}.*' 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.2.0 (2025-05-20) 4 | 5 | * Enhancements 6 | * support streamable HTTP transport: the proxy tries to automatically detect the correct transport to use 7 | * Bug fixes 8 | * fix `annotations` field being sent as `null` causing issues in Cursor (upstream bug in the SDK) 9 | 10 | ## 0.1.1 (2025-05-02) 11 | 12 | * Refactor code to use the [Rust MCP SDK](https://github.com/modelcontextprotocol/rust-sdk). 13 | 14 | ## 0.1.0 (2025-04-29) 15 | 16 | Initial release. 17 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "android-tzdata" 22 | version = "0.1.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 25 | 26 | [[package]] 27 | name = "android_system_properties" 28 | version = "0.1.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 31 | dependencies = [ 32 | "libc", 33 | ] 34 | 35 | [[package]] 36 | name = "anstream" 37 | version = "0.6.18" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 40 | dependencies = [ 41 | "anstyle", 42 | "anstyle-parse", 43 | "anstyle-query", 44 | "anstyle-wincon", 45 | "colorchoice", 46 | "is_terminal_polyfill", 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle" 52 | version = "1.0.10" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 55 | 56 | [[package]] 57 | name = "anstyle-parse" 58 | version = "0.2.6" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 61 | dependencies = [ 62 | "utf8parse", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle-query" 67 | version = "1.1.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 70 | dependencies = [ 71 | "windows-sys 0.59.0", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle-wincon" 76 | version = "3.0.7" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 79 | dependencies = [ 80 | "anstyle", 81 | "once_cell", 82 | "windows-sys 0.59.0", 83 | ] 84 | 85 | [[package]] 86 | name = "anyhow" 87 | version = "1.0.98" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 90 | 91 | [[package]] 92 | name = "atomic-waker" 93 | version = "1.1.2" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 96 | 97 | [[package]] 98 | name = "autocfg" 99 | version = "1.4.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 102 | 103 | [[package]] 104 | name = "axum" 105 | version = "0.8.4" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" 108 | dependencies = [ 109 | "axum-core", 110 | "axum-macros", 111 | "bytes", 112 | "form_urlencoded", 113 | "futures-util", 114 | "http", 115 | "http-body", 116 | "http-body-util", 117 | "hyper", 118 | "hyper-util", 119 | "itoa", 120 | "matchit", 121 | "memchr", 122 | "mime", 123 | "percent-encoding", 124 | "pin-project-lite", 125 | "rustversion", 126 | "serde", 127 | "serde_json", 128 | "serde_path_to_error", 129 | "serde_urlencoded", 130 | "sync_wrapper", 131 | "tokio", 132 | "tower", 133 | "tower-layer", 134 | "tower-service", 135 | "tracing", 136 | ] 137 | 138 | [[package]] 139 | name = "axum-core" 140 | version = "0.5.2" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" 143 | dependencies = [ 144 | "bytes", 145 | "futures-core", 146 | "http", 147 | "http-body", 148 | "http-body-util", 149 | "mime", 150 | "pin-project-lite", 151 | "rustversion", 152 | "sync_wrapper", 153 | "tower-layer", 154 | "tower-service", 155 | "tracing", 156 | ] 157 | 158 | [[package]] 159 | name = "axum-macros" 160 | version = "0.5.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" 163 | dependencies = [ 164 | "proc-macro2", 165 | "quote", 166 | "syn", 167 | ] 168 | 169 | [[package]] 170 | name = "backtrace" 171 | version = "0.3.74" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 174 | dependencies = [ 175 | "addr2line", 176 | "cfg-if", 177 | "libc", 178 | "miniz_oxide", 179 | "object", 180 | "rustc-demangle", 181 | "windows-targets 0.52.6", 182 | ] 183 | 184 | [[package]] 185 | name = "base64" 186 | version = "0.21.7" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 189 | 190 | [[package]] 191 | name = "base64" 192 | version = "0.22.1" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 195 | 196 | [[package]] 197 | name = "bitflags" 198 | version = "2.9.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 201 | 202 | [[package]] 203 | name = "bumpalo" 204 | version = "3.17.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 207 | 208 | [[package]] 209 | name = "bytes" 210 | version = "1.10.1" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 213 | 214 | [[package]] 215 | name = "cc" 216 | version = "1.2.20" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" 219 | dependencies = [ 220 | "shlex", 221 | ] 222 | 223 | [[package]] 224 | name = "cfg-if" 225 | version = "1.0.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 228 | 229 | [[package]] 230 | name = "cfg_aliases" 231 | version = "0.2.1" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 234 | 235 | [[package]] 236 | name = "chrono" 237 | version = "0.4.41" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 240 | dependencies = [ 241 | "android-tzdata", 242 | "iana-time-zone", 243 | "js-sys", 244 | "num-traits", 245 | "serde", 246 | "wasm-bindgen", 247 | "windows-link", 248 | ] 249 | 250 | [[package]] 251 | name = "clap" 252 | version = "4.5.37" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" 255 | dependencies = [ 256 | "clap_builder", 257 | "clap_derive", 258 | ] 259 | 260 | [[package]] 261 | name = "clap_builder" 262 | version = "4.5.37" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" 265 | dependencies = [ 266 | "anstream", 267 | "anstyle", 268 | "clap_lex", 269 | "strsim", 270 | ] 271 | 272 | [[package]] 273 | name = "clap_derive" 274 | version = "4.5.32" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 277 | dependencies = [ 278 | "heck", 279 | "proc-macro2", 280 | "quote", 281 | "syn", 282 | ] 283 | 284 | [[package]] 285 | name = "clap_lex" 286 | version = "0.7.4" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 289 | 290 | [[package]] 291 | name = "colorchoice" 292 | version = "1.0.3" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 295 | 296 | [[package]] 297 | name = "core-foundation" 298 | version = "0.9.4" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 301 | dependencies = [ 302 | "core-foundation-sys", 303 | "libc", 304 | ] 305 | 306 | [[package]] 307 | name = "core-foundation-sys" 308 | version = "0.8.7" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 311 | 312 | [[package]] 313 | name = "displaydoc" 314 | version = "0.2.5" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 317 | dependencies = [ 318 | "proc-macro2", 319 | "quote", 320 | "syn", 321 | ] 322 | 323 | [[package]] 324 | name = "dyn-clone" 325 | version = "1.0.19" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" 328 | 329 | [[package]] 330 | name = "encoding_rs" 331 | version = "0.8.35" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 334 | dependencies = [ 335 | "cfg-if", 336 | ] 337 | 338 | [[package]] 339 | name = "equivalent" 340 | version = "1.0.2" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 343 | 344 | [[package]] 345 | name = "errno" 346 | version = "0.3.11" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 349 | dependencies = [ 350 | "libc", 351 | "windows-sys 0.59.0", 352 | ] 353 | 354 | [[package]] 355 | name = "fastrand" 356 | version = "2.3.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 359 | 360 | [[package]] 361 | name = "fnv" 362 | version = "1.0.7" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 365 | 366 | [[package]] 367 | name = "foreign-types" 368 | version = "0.3.2" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 371 | dependencies = [ 372 | "foreign-types-shared", 373 | ] 374 | 375 | [[package]] 376 | name = "foreign-types-shared" 377 | version = "0.1.1" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 380 | 381 | [[package]] 382 | name = "form_urlencoded" 383 | version = "1.2.1" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 386 | dependencies = [ 387 | "percent-encoding", 388 | ] 389 | 390 | [[package]] 391 | name = "futures" 392 | version = "0.3.31" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 395 | dependencies = [ 396 | "futures-channel", 397 | "futures-core", 398 | "futures-executor", 399 | "futures-io", 400 | "futures-sink", 401 | "futures-task", 402 | "futures-util", 403 | ] 404 | 405 | [[package]] 406 | name = "futures-channel" 407 | version = "0.3.31" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 410 | dependencies = [ 411 | "futures-core", 412 | "futures-sink", 413 | ] 414 | 415 | [[package]] 416 | name = "futures-core" 417 | version = "0.3.31" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 420 | 421 | [[package]] 422 | name = "futures-executor" 423 | version = "0.3.31" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 426 | dependencies = [ 427 | "futures-core", 428 | "futures-task", 429 | "futures-util", 430 | ] 431 | 432 | [[package]] 433 | name = "futures-io" 434 | version = "0.3.31" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 437 | 438 | [[package]] 439 | name = "futures-macro" 440 | version = "0.3.31" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 443 | dependencies = [ 444 | "proc-macro2", 445 | "quote", 446 | "syn", 447 | ] 448 | 449 | [[package]] 450 | name = "futures-sink" 451 | version = "0.3.31" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 454 | 455 | [[package]] 456 | name = "futures-task" 457 | version = "0.3.31" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 460 | 461 | [[package]] 462 | name = "futures-util" 463 | version = "0.3.31" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 466 | dependencies = [ 467 | "futures-channel", 468 | "futures-core", 469 | "futures-io", 470 | "futures-macro", 471 | "futures-sink", 472 | "futures-task", 473 | "memchr", 474 | "pin-project-lite", 475 | "pin-utils", 476 | "slab", 477 | ] 478 | 479 | [[package]] 480 | name = "getrandom" 481 | version = "0.2.16" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 484 | dependencies = [ 485 | "cfg-if", 486 | "js-sys", 487 | "libc", 488 | "wasi 0.11.0+wasi-snapshot-preview1", 489 | "wasm-bindgen", 490 | ] 491 | 492 | [[package]] 493 | name = "getrandom" 494 | version = "0.3.2" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 497 | dependencies = [ 498 | "cfg-if", 499 | "js-sys", 500 | "libc", 501 | "r-efi", 502 | "wasi 0.14.2+wasi-0.2.4", 503 | "wasm-bindgen", 504 | ] 505 | 506 | [[package]] 507 | name = "gimli" 508 | version = "0.31.1" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 511 | 512 | [[package]] 513 | name = "h2" 514 | version = "0.4.9" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" 517 | dependencies = [ 518 | "atomic-waker", 519 | "bytes", 520 | "fnv", 521 | "futures-core", 522 | "futures-sink", 523 | "http", 524 | "indexmap", 525 | "slab", 526 | "tokio", 527 | "tokio-util", 528 | "tracing", 529 | ] 530 | 531 | [[package]] 532 | name = "hashbrown" 533 | version = "0.15.3" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" 536 | 537 | [[package]] 538 | name = "heck" 539 | version = "0.5.0" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 542 | 543 | [[package]] 544 | name = "http" 545 | version = "1.3.1" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 548 | dependencies = [ 549 | "bytes", 550 | "fnv", 551 | "itoa", 552 | ] 553 | 554 | [[package]] 555 | name = "http-body" 556 | version = "1.0.1" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 559 | dependencies = [ 560 | "bytes", 561 | "http", 562 | ] 563 | 564 | [[package]] 565 | name = "http-body-util" 566 | version = "0.1.3" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 569 | dependencies = [ 570 | "bytes", 571 | "futures-core", 572 | "http", 573 | "http-body", 574 | "pin-project-lite", 575 | ] 576 | 577 | [[package]] 578 | name = "httparse" 579 | version = "1.10.1" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 582 | 583 | [[package]] 584 | name = "httpdate" 585 | version = "1.0.3" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 588 | 589 | [[package]] 590 | name = "hyper" 591 | version = "1.6.0" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 594 | dependencies = [ 595 | "bytes", 596 | "futures-channel", 597 | "futures-util", 598 | "h2", 599 | "http", 600 | "http-body", 601 | "httparse", 602 | "httpdate", 603 | "itoa", 604 | "pin-project-lite", 605 | "smallvec", 606 | "tokio", 607 | "want", 608 | ] 609 | 610 | [[package]] 611 | name = "hyper-rustls" 612 | version = "0.27.5" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" 615 | dependencies = [ 616 | "futures-util", 617 | "http", 618 | "hyper", 619 | "hyper-util", 620 | "rustls", 621 | "rustls-pki-types", 622 | "tokio", 623 | "tokio-rustls", 624 | "tower-service", 625 | "webpki-roots 0.26.11", 626 | ] 627 | 628 | [[package]] 629 | name = "hyper-tls" 630 | version = "0.6.0" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 633 | dependencies = [ 634 | "bytes", 635 | "http-body-util", 636 | "hyper", 637 | "hyper-util", 638 | "native-tls", 639 | "tokio", 640 | "tokio-native-tls", 641 | "tower-service", 642 | ] 643 | 644 | [[package]] 645 | name = "hyper-util" 646 | version = "0.1.11" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" 649 | dependencies = [ 650 | "bytes", 651 | "futures-channel", 652 | "futures-util", 653 | "http", 654 | "http-body", 655 | "hyper", 656 | "libc", 657 | "pin-project-lite", 658 | "socket2", 659 | "tokio", 660 | "tower-service", 661 | "tracing", 662 | ] 663 | 664 | [[package]] 665 | name = "iana-time-zone" 666 | version = "0.1.63" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 669 | dependencies = [ 670 | "android_system_properties", 671 | "core-foundation-sys", 672 | "iana-time-zone-haiku", 673 | "js-sys", 674 | "log", 675 | "wasm-bindgen", 676 | "windows-core", 677 | ] 678 | 679 | [[package]] 680 | name = "iana-time-zone-haiku" 681 | version = "0.1.2" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 684 | dependencies = [ 685 | "cc", 686 | ] 687 | 688 | [[package]] 689 | name = "icu_collections" 690 | version = "1.5.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 693 | dependencies = [ 694 | "displaydoc", 695 | "yoke", 696 | "zerofrom", 697 | "zerovec", 698 | ] 699 | 700 | [[package]] 701 | name = "icu_locid" 702 | version = "1.5.0" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 705 | dependencies = [ 706 | "displaydoc", 707 | "litemap", 708 | "tinystr", 709 | "writeable", 710 | "zerovec", 711 | ] 712 | 713 | [[package]] 714 | name = "icu_locid_transform" 715 | version = "1.5.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 718 | dependencies = [ 719 | "displaydoc", 720 | "icu_locid", 721 | "icu_locid_transform_data", 722 | "icu_provider", 723 | "tinystr", 724 | "zerovec", 725 | ] 726 | 727 | [[package]] 728 | name = "icu_locid_transform_data" 729 | version = "1.5.1" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" 732 | 733 | [[package]] 734 | name = "icu_normalizer" 735 | version = "1.5.0" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 738 | dependencies = [ 739 | "displaydoc", 740 | "icu_collections", 741 | "icu_normalizer_data", 742 | "icu_properties", 743 | "icu_provider", 744 | "smallvec", 745 | "utf16_iter", 746 | "utf8_iter", 747 | "write16", 748 | "zerovec", 749 | ] 750 | 751 | [[package]] 752 | name = "icu_normalizer_data" 753 | version = "1.5.1" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" 756 | 757 | [[package]] 758 | name = "icu_properties" 759 | version = "1.5.1" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 762 | dependencies = [ 763 | "displaydoc", 764 | "icu_collections", 765 | "icu_locid_transform", 766 | "icu_properties_data", 767 | "icu_provider", 768 | "tinystr", 769 | "zerovec", 770 | ] 771 | 772 | [[package]] 773 | name = "icu_properties_data" 774 | version = "1.5.1" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" 777 | 778 | [[package]] 779 | name = "icu_provider" 780 | version = "1.5.0" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 783 | dependencies = [ 784 | "displaydoc", 785 | "icu_locid", 786 | "icu_provider_macros", 787 | "stable_deref_trait", 788 | "tinystr", 789 | "writeable", 790 | "yoke", 791 | "zerofrom", 792 | "zerovec", 793 | ] 794 | 795 | [[package]] 796 | name = "icu_provider_macros" 797 | version = "1.5.0" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 800 | dependencies = [ 801 | "proc-macro2", 802 | "quote", 803 | "syn", 804 | ] 805 | 806 | [[package]] 807 | name = "idna" 808 | version = "1.0.3" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 811 | dependencies = [ 812 | "idna_adapter", 813 | "smallvec", 814 | "utf8_iter", 815 | ] 816 | 817 | [[package]] 818 | name = "idna_adapter" 819 | version = "1.2.0" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 822 | dependencies = [ 823 | "icu_normalizer", 824 | "icu_properties", 825 | ] 826 | 827 | [[package]] 828 | name = "indexmap" 829 | version = "2.9.0" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 832 | dependencies = [ 833 | "equivalent", 834 | "hashbrown", 835 | ] 836 | 837 | [[package]] 838 | name = "ipnet" 839 | version = "2.11.0" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 842 | 843 | [[package]] 844 | name = "is_terminal_polyfill" 845 | version = "1.70.1" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 848 | 849 | [[package]] 850 | name = "itoa" 851 | version = "1.0.15" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 854 | 855 | [[package]] 856 | name = "js-sys" 857 | version = "0.3.77" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 860 | dependencies = [ 861 | "once_cell", 862 | "wasm-bindgen", 863 | ] 864 | 865 | [[package]] 866 | name = "lazy_static" 867 | version = "1.5.0" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 870 | 871 | [[package]] 872 | name = "libc" 873 | version = "0.2.172" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 876 | 877 | [[package]] 878 | name = "linux-raw-sys" 879 | version = "0.9.4" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 882 | 883 | [[package]] 884 | name = "litemap" 885 | version = "0.7.5" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" 888 | 889 | [[package]] 890 | name = "lock_api" 891 | version = "0.4.12" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 894 | dependencies = [ 895 | "autocfg", 896 | "scopeguard", 897 | ] 898 | 899 | [[package]] 900 | name = "log" 901 | version = "0.4.27" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 904 | 905 | [[package]] 906 | name = "lru-slab" 907 | version = "0.1.2" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 910 | 911 | [[package]] 912 | name = "matchit" 913 | version = "0.8.4" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 916 | 917 | [[package]] 918 | name = "mcp-proxy" 919 | version = "0.2.0" 920 | dependencies = [ 921 | "anyhow", 922 | "axum", 923 | "clap", 924 | "futures", 925 | "openssl-sys", 926 | "reqwest", 927 | "rmcp", 928 | "serde", 929 | "serde_json", 930 | "tokio", 931 | "tokio-util", 932 | "tracing", 933 | "tracing-subscriber", 934 | "uuid", 935 | ] 936 | 937 | [[package]] 938 | name = "memchr" 939 | version = "2.7.4" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 942 | 943 | [[package]] 944 | name = "mime" 945 | version = "0.3.17" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 948 | 949 | [[package]] 950 | name = "miniz_oxide" 951 | version = "0.8.8" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 954 | dependencies = [ 955 | "adler2", 956 | ] 957 | 958 | [[package]] 959 | name = "mio" 960 | version = "1.0.3" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 963 | dependencies = [ 964 | "libc", 965 | "wasi 0.11.0+wasi-snapshot-preview1", 966 | "windows-sys 0.52.0", 967 | ] 968 | 969 | [[package]] 970 | name = "native-tls" 971 | version = "0.2.14" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 974 | dependencies = [ 975 | "libc", 976 | "log", 977 | "openssl", 978 | "openssl-probe", 979 | "openssl-sys", 980 | "schannel", 981 | "security-framework", 982 | "security-framework-sys", 983 | "tempfile", 984 | ] 985 | 986 | [[package]] 987 | name = "nix" 988 | version = "0.30.1" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" 991 | dependencies = [ 992 | "bitflags", 993 | "cfg-if", 994 | "cfg_aliases", 995 | "libc", 996 | ] 997 | 998 | [[package]] 999 | name = "nu-ansi-term" 1000 | version = "0.46.0" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1003 | dependencies = [ 1004 | "overload", 1005 | "winapi", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "num-traits" 1010 | version = "0.2.19" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1013 | dependencies = [ 1014 | "autocfg", 1015 | ] 1016 | 1017 | [[package]] 1018 | name = "object" 1019 | version = "0.36.7" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1022 | dependencies = [ 1023 | "memchr", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "once_cell" 1028 | version = "1.21.3" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1031 | 1032 | [[package]] 1033 | name = "openssl" 1034 | version = "0.10.72" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" 1037 | dependencies = [ 1038 | "bitflags", 1039 | "cfg-if", 1040 | "foreign-types", 1041 | "libc", 1042 | "once_cell", 1043 | "openssl-macros", 1044 | "openssl-sys", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "openssl-macros" 1049 | version = "0.1.1" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1052 | dependencies = [ 1053 | "proc-macro2", 1054 | "quote", 1055 | "syn", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "openssl-probe" 1060 | version = "0.1.6" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1063 | 1064 | [[package]] 1065 | name = "openssl-src" 1066 | version = "300.5.0+3.5.0" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" 1069 | dependencies = [ 1070 | "cc", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "openssl-sys" 1075 | version = "0.9.108" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" 1078 | dependencies = [ 1079 | "cc", 1080 | "libc", 1081 | "openssl-src", 1082 | "pkg-config", 1083 | "vcpkg", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "overload" 1088 | version = "0.1.1" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1091 | 1092 | [[package]] 1093 | name = "parking_lot" 1094 | version = "0.12.3" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1097 | dependencies = [ 1098 | "lock_api", 1099 | "parking_lot_core", 1100 | ] 1101 | 1102 | [[package]] 1103 | name = "parking_lot_core" 1104 | version = "0.9.10" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1107 | dependencies = [ 1108 | "cfg-if", 1109 | "libc", 1110 | "redox_syscall", 1111 | "smallvec", 1112 | "windows-targets 0.52.6", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "paste" 1117 | version = "1.0.15" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1120 | 1121 | [[package]] 1122 | name = "percent-encoding" 1123 | version = "2.3.1" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1126 | 1127 | [[package]] 1128 | name = "pin-project-lite" 1129 | version = "0.2.16" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1132 | 1133 | [[package]] 1134 | name = "pin-utils" 1135 | version = "0.1.0" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1138 | 1139 | [[package]] 1140 | name = "pkg-config" 1141 | version = "0.3.32" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1144 | 1145 | [[package]] 1146 | name = "ppv-lite86" 1147 | version = "0.2.21" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1150 | dependencies = [ 1151 | "zerocopy", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "proc-macro2" 1156 | version = "1.0.95" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 1159 | dependencies = [ 1160 | "unicode-ident", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "process-wrap" 1165 | version = "8.2.1" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "a3ef4f2f0422f23a82ec9f628ea2acd12871c81a9362b02c43c1aa86acfc3ba1" 1168 | dependencies = [ 1169 | "futures", 1170 | "indexmap", 1171 | "nix", 1172 | "tokio", 1173 | "tracing", 1174 | "windows", 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "quinn" 1179 | version = "0.11.8" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" 1182 | dependencies = [ 1183 | "bytes", 1184 | "cfg_aliases", 1185 | "pin-project-lite", 1186 | "quinn-proto", 1187 | "quinn-udp", 1188 | "rustc-hash", 1189 | "rustls", 1190 | "socket2", 1191 | "thiserror", 1192 | "tokio", 1193 | "tracing", 1194 | "web-time", 1195 | ] 1196 | 1197 | [[package]] 1198 | name = "quinn-proto" 1199 | version = "0.11.12" 1200 | source = "registry+https://github.com/rust-lang/crates.io-index" 1201 | checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" 1202 | dependencies = [ 1203 | "bytes", 1204 | "getrandom 0.3.2", 1205 | "lru-slab", 1206 | "rand", 1207 | "ring", 1208 | "rustc-hash", 1209 | "rustls", 1210 | "rustls-pki-types", 1211 | "slab", 1212 | "thiserror", 1213 | "tinyvec", 1214 | "tracing", 1215 | "web-time", 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "quinn-udp" 1220 | version = "0.5.12" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" 1223 | dependencies = [ 1224 | "cfg_aliases", 1225 | "libc", 1226 | "once_cell", 1227 | "socket2", 1228 | "tracing", 1229 | "windows-sys 0.52.0", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "quote" 1234 | version = "1.0.40" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1237 | dependencies = [ 1238 | "proc-macro2", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "r-efi" 1243 | version = "5.2.0" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 1246 | 1247 | [[package]] 1248 | name = "rand" 1249 | version = "0.9.1" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" 1252 | dependencies = [ 1253 | "rand_chacha", 1254 | "rand_core", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "rand_chacha" 1259 | version = "0.9.0" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1262 | dependencies = [ 1263 | "ppv-lite86", 1264 | "rand_core", 1265 | ] 1266 | 1267 | [[package]] 1268 | name = "rand_core" 1269 | version = "0.9.3" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1272 | dependencies = [ 1273 | "getrandom 0.3.2", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "redox_syscall" 1278 | version = "0.5.11" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" 1281 | dependencies = [ 1282 | "bitflags", 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "reqwest" 1287 | version = "0.12.15" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" 1290 | dependencies = [ 1291 | "base64 0.22.1", 1292 | "bytes", 1293 | "encoding_rs", 1294 | "futures-core", 1295 | "futures-util", 1296 | "h2", 1297 | "http", 1298 | "http-body", 1299 | "http-body-util", 1300 | "hyper", 1301 | "hyper-rustls", 1302 | "hyper-tls", 1303 | "hyper-util", 1304 | "ipnet", 1305 | "js-sys", 1306 | "log", 1307 | "mime", 1308 | "native-tls", 1309 | "once_cell", 1310 | "percent-encoding", 1311 | "pin-project-lite", 1312 | "quinn", 1313 | "rustls", 1314 | "rustls-pemfile", 1315 | "rustls-pki-types", 1316 | "serde", 1317 | "serde_json", 1318 | "serde_urlencoded", 1319 | "sync_wrapper", 1320 | "system-configuration", 1321 | "tokio", 1322 | "tokio-native-tls", 1323 | "tokio-rustls", 1324 | "tokio-util", 1325 | "tower", 1326 | "tower-service", 1327 | "url", 1328 | "wasm-bindgen", 1329 | "wasm-bindgen-futures", 1330 | "wasm-streams", 1331 | "web-sys", 1332 | "webpki-roots 0.26.11", 1333 | "windows-registry", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "ring" 1338 | version = "0.17.14" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1341 | dependencies = [ 1342 | "cc", 1343 | "cfg-if", 1344 | "getrandom 0.2.16", 1345 | "libc", 1346 | "untrusted", 1347 | "windows-sys 0.52.0", 1348 | ] 1349 | 1350 | [[package]] 1351 | name = "rmcp" 1352 | version = "0.1.5" 1353 | source = "git+https://github.com/modelcontextprotocol/rust-sdk.git?rev=076dc2c2cd8910bee56bae13f29bbcff8c279666#076dc2c2cd8910bee56bae13f29bbcff8c279666" 1354 | dependencies = [ 1355 | "axum", 1356 | "base64 0.21.7", 1357 | "chrono", 1358 | "futures", 1359 | "http", 1360 | "paste", 1361 | "pin-project-lite", 1362 | "process-wrap", 1363 | "rand", 1364 | "reqwest", 1365 | "rmcp-macros", 1366 | "schemars", 1367 | "serde", 1368 | "serde_json", 1369 | "sse-stream", 1370 | "thiserror", 1371 | "tokio", 1372 | "tokio-stream", 1373 | "tokio-util", 1374 | "tracing", 1375 | "uuid", 1376 | ] 1377 | 1378 | [[package]] 1379 | name = "rmcp-macros" 1380 | version = "0.1.5" 1381 | source = "git+https://github.com/modelcontextprotocol/rust-sdk.git?rev=076dc2c2cd8910bee56bae13f29bbcff8c279666#076dc2c2cd8910bee56bae13f29bbcff8c279666" 1382 | dependencies = [ 1383 | "proc-macro2", 1384 | "quote", 1385 | "serde_json", 1386 | "syn", 1387 | ] 1388 | 1389 | [[package]] 1390 | name = "rustc-demangle" 1391 | version = "0.1.24" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1394 | 1395 | [[package]] 1396 | name = "rustc-hash" 1397 | version = "2.1.1" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 1400 | 1401 | [[package]] 1402 | name = "rustix" 1403 | version = "1.0.7" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" 1406 | dependencies = [ 1407 | "bitflags", 1408 | "errno", 1409 | "libc", 1410 | "linux-raw-sys", 1411 | "windows-sys 0.59.0", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "rustls" 1416 | version = "0.23.26" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" 1419 | dependencies = [ 1420 | "once_cell", 1421 | "ring", 1422 | "rustls-pki-types", 1423 | "rustls-webpki", 1424 | "subtle", 1425 | "zeroize", 1426 | ] 1427 | 1428 | [[package]] 1429 | name = "rustls-pemfile" 1430 | version = "2.2.0" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1433 | dependencies = [ 1434 | "rustls-pki-types", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "rustls-pki-types" 1439 | version = "1.11.0" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 1442 | dependencies = [ 1443 | "web-time", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "rustls-webpki" 1448 | version = "0.103.1" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" 1451 | dependencies = [ 1452 | "ring", 1453 | "rustls-pki-types", 1454 | "untrusted", 1455 | ] 1456 | 1457 | [[package]] 1458 | name = "rustversion" 1459 | version = "1.0.20" 1460 | source = "registry+https://github.com/rust-lang/crates.io-index" 1461 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 1462 | 1463 | [[package]] 1464 | name = "ryu" 1465 | version = "1.0.20" 1466 | source = "registry+https://github.com/rust-lang/crates.io-index" 1467 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1468 | 1469 | [[package]] 1470 | name = "schannel" 1471 | version = "0.1.27" 1472 | source = "registry+https://github.com/rust-lang/crates.io-index" 1473 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1474 | dependencies = [ 1475 | "windows-sys 0.59.0", 1476 | ] 1477 | 1478 | [[package]] 1479 | name = "schemars" 1480 | version = "0.8.22" 1481 | source = "registry+https://github.com/rust-lang/crates.io-index" 1482 | checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" 1483 | dependencies = [ 1484 | "chrono", 1485 | "dyn-clone", 1486 | "schemars_derive", 1487 | "serde", 1488 | "serde_json", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "schemars_derive" 1493 | version = "0.8.22" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" 1496 | dependencies = [ 1497 | "proc-macro2", 1498 | "quote", 1499 | "serde_derive_internals", 1500 | "syn", 1501 | ] 1502 | 1503 | [[package]] 1504 | name = "scopeguard" 1505 | version = "1.2.0" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1508 | 1509 | [[package]] 1510 | name = "security-framework" 1511 | version = "2.11.1" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1514 | dependencies = [ 1515 | "bitflags", 1516 | "core-foundation", 1517 | "core-foundation-sys", 1518 | "libc", 1519 | "security-framework-sys", 1520 | ] 1521 | 1522 | [[package]] 1523 | name = "security-framework-sys" 1524 | version = "2.14.0" 1525 | source = "registry+https://github.com/rust-lang/crates.io-index" 1526 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 1527 | dependencies = [ 1528 | "core-foundation-sys", 1529 | "libc", 1530 | ] 1531 | 1532 | [[package]] 1533 | name = "serde" 1534 | version = "1.0.219" 1535 | source = "registry+https://github.com/rust-lang/crates.io-index" 1536 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1537 | dependencies = [ 1538 | "serde_derive", 1539 | ] 1540 | 1541 | [[package]] 1542 | name = "serde_derive" 1543 | version = "1.0.219" 1544 | source = "registry+https://github.com/rust-lang/crates.io-index" 1545 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1546 | dependencies = [ 1547 | "proc-macro2", 1548 | "quote", 1549 | "syn", 1550 | ] 1551 | 1552 | [[package]] 1553 | name = "serde_derive_internals" 1554 | version = "0.29.1" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" 1557 | dependencies = [ 1558 | "proc-macro2", 1559 | "quote", 1560 | "syn", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "serde_json" 1565 | version = "1.0.140" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1568 | dependencies = [ 1569 | "itoa", 1570 | "memchr", 1571 | "ryu", 1572 | "serde", 1573 | ] 1574 | 1575 | [[package]] 1576 | name = "serde_path_to_error" 1577 | version = "0.1.17" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" 1580 | dependencies = [ 1581 | "itoa", 1582 | "serde", 1583 | ] 1584 | 1585 | [[package]] 1586 | name = "serde_urlencoded" 1587 | version = "0.7.1" 1588 | source = "registry+https://github.com/rust-lang/crates.io-index" 1589 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1590 | dependencies = [ 1591 | "form_urlencoded", 1592 | "itoa", 1593 | "ryu", 1594 | "serde", 1595 | ] 1596 | 1597 | [[package]] 1598 | name = "sharded-slab" 1599 | version = "0.1.7" 1600 | source = "registry+https://github.com/rust-lang/crates.io-index" 1601 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1602 | dependencies = [ 1603 | "lazy_static", 1604 | ] 1605 | 1606 | [[package]] 1607 | name = "shlex" 1608 | version = "1.3.0" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1611 | 1612 | [[package]] 1613 | name = "signal-hook-registry" 1614 | version = "1.4.5" 1615 | source = "registry+https://github.com/rust-lang/crates.io-index" 1616 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 1617 | dependencies = [ 1618 | "libc", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "slab" 1623 | version = "0.4.9" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1626 | dependencies = [ 1627 | "autocfg", 1628 | ] 1629 | 1630 | [[package]] 1631 | name = "smallvec" 1632 | version = "1.15.0" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 1635 | 1636 | [[package]] 1637 | name = "socket2" 1638 | version = "0.5.9" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" 1641 | dependencies = [ 1642 | "libc", 1643 | "windows-sys 0.52.0", 1644 | ] 1645 | 1646 | [[package]] 1647 | name = "sse-stream" 1648 | version = "0.1.4" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "0baa01c3efebe4050c7bb999ae87d5b17256b7f71391389e5fc08cdcd98ad550" 1651 | dependencies = [ 1652 | "bytes", 1653 | "futures-util", 1654 | "http-body", 1655 | "http-body-util", 1656 | "pin-project-lite", 1657 | ] 1658 | 1659 | [[package]] 1660 | name = "stable_deref_trait" 1661 | version = "1.2.0" 1662 | source = "registry+https://github.com/rust-lang/crates.io-index" 1663 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1664 | 1665 | [[package]] 1666 | name = "strsim" 1667 | version = "0.11.1" 1668 | source = "registry+https://github.com/rust-lang/crates.io-index" 1669 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1670 | 1671 | [[package]] 1672 | name = "subtle" 1673 | version = "2.6.1" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1676 | 1677 | [[package]] 1678 | name = "syn" 1679 | version = "2.0.101" 1680 | source = "registry+https://github.com/rust-lang/crates.io-index" 1681 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 1682 | dependencies = [ 1683 | "proc-macro2", 1684 | "quote", 1685 | "unicode-ident", 1686 | ] 1687 | 1688 | [[package]] 1689 | name = "sync_wrapper" 1690 | version = "1.0.2" 1691 | source = "registry+https://github.com/rust-lang/crates.io-index" 1692 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1693 | dependencies = [ 1694 | "futures-core", 1695 | ] 1696 | 1697 | [[package]] 1698 | name = "synstructure" 1699 | version = "0.13.2" 1700 | source = "registry+https://github.com/rust-lang/crates.io-index" 1701 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1702 | dependencies = [ 1703 | "proc-macro2", 1704 | "quote", 1705 | "syn", 1706 | ] 1707 | 1708 | [[package]] 1709 | name = "system-configuration" 1710 | version = "0.6.1" 1711 | source = "registry+https://github.com/rust-lang/crates.io-index" 1712 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 1713 | dependencies = [ 1714 | "bitflags", 1715 | "core-foundation", 1716 | "system-configuration-sys", 1717 | ] 1718 | 1719 | [[package]] 1720 | name = "system-configuration-sys" 1721 | version = "0.6.0" 1722 | source = "registry+https://github.com/rust-lang/crates.io-index" 1723 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 1724 | dependencies = [ 1725 | "core-foundation-sys", 1726 | "libc", 1727 | ] 1728 | 1729 | [[package]] 1730 | name = "tempfile" 1731 | version = "3.19.1" 1732 | source = "registry+https://github.com/rust-lang/crates.io-index" 1733 | checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" 1734 | dependencies = [ 1735 | "fastrand", 1736 | "getrandom 0.3.2", 1737 | "once_cell", 1738 | "rustix", 1739 | "windows-sys 0.59.0", 1740 | ] 1741 | 1742 | [[package]] 1743 | name = "thiserror" 1744 | version = "2.0.12" 1745 | source = "registry+https://github.com/rust-lang/crates.io-index" 1746 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 1747 | dependencies = [ 1748 | "thiserror-impl", 1749 | ] 1750 | 1751 | [[package]] 1752 | name = "thiserror-impl" 1753 | version = "2.0.12" 1754 | source = "registry+https://github.com/rust-lang/crates.io-index" 1755 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 1756 | dependencies = [ 1757 | "proc-macro2", 1758 | "quote", 1759 | "syn", 1760 | ] 1761 | 1762 | [[package]] 1763 | name = "thread_local" 1764 | version = "1.1.8" 1765 | source = "registry+https://github.com/rust-lang/crates.io-index" 1766 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1767 | dependencies = [ 1768 | "cfg-if", 1769 | "once_cell", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "tinystr" 1774 | version = "0.7.6" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1777 | dependencies = [ 1778 | "displaydoc", 1779 | "zerovec", 1780 | ] 1781 | 1782 | [[package]] 1783 | name = "tinyvec" 1784 | version = "1.9.0" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 1787 | dependencies = [ 1788 | "tinyvec_macros", 1789 | ] 1790 | 1791 | [[package]] 1792 | name = "tinyvec_macros" 1793 | version = "0.1.1" 1794 | source = "registry+https://github.com/rust-lang/crates.io-index" 1795 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1796 | 1797 | [[package]] 1798 | name = "tokio" 1799 | version = "1.44.2" 1800 | source = "registry+https://github.com/rust-lang/crates.io-index" 1801 | checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" 1802 | dependencies = [ 1803 | "backtrace", 1804 | "bytes", 1805 | "libc", 1806 | "mio", 1807 | "parking_lot", 1808 | "pin-project-lite", 1809 | "signal-hook-registry", 1810 | "socket2", 1811 | "tokio-macros", 1812 | "windows-sys 0.52.0", 1813 | ] 1814 | 1815 | [[package]] 1816 | name = "tokio-macros" 1817 | version = "2.5.0" 1818 | source = "registry+https://github.com/rust-lang/crates.io-index" 1819 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 1820 | dependencies = [ 1821 | "proc-macro2", 1822 | "quote", 1823 | "syn", 1824 | ] 1825 | 1826 | [[package]] 1827 | name = "tokio-native-tls" 1828 | version = "0.3.1" 1829 | source = "registry+https://github.com/rust-lang/crates.io-index" 1830 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1831 | dependencies = [ 1832 | "native-tls", 1833 | "tokio", 1834 | ] 1835 | 1836 | [[package]] 1837 | name = "tokio-rustls" 1838 | version = "0.26.2" 1839 | source = "registry+https://github.com/rust-lang/crates.io-index" 1840 | checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" 1841 | dependencies = [ 1842 | "rustls", 1843 | "tokio", 1844 | ] 1845 | 1846 | [[package]] 1847 | name = "tokio-stream" 1848 | version = "0.1.17" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" 1851 | dependencies = [ 1852 | "futures-core", 1853 | "pin-project-lite", 1854 | "tokio", 1855 | ] 1856 | 1857 | [[package]] 1858 | name = "tokio-util" 1859 | version = "0.7.15" 1860 | source = "registry+https://github.com/rust-lang/crates.io-index" 1861 | checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" 1862 | dependencies = [ 1863 | "bytes", 1864 | "futures-core", 1865 | "futures-sink", 1866 | "pin-project-lite", 1867 | "tokio", 1868 | ] 1869 | 1870 | [[package]] 1871 | name = "tower" 1872 | version = "0.5.2" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1875 | dependencies = [ 1876 | "futures-core", 1877 | "futures-util", 1878 | "pin-project-lite", 1879 | "sync_wrapper", 1880 | "tokio", 1881 | "tower-layer", 1882 | "tower-service", 1883 | "tracing", 1884 | ] 1885 | 1886 | [[package]] 1887 | name = "tower-layer" 1888 | version = "0.3.3" 1889 | source = "registry+https://github.com/rust-lang/crates.io-index" 1890 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1891 | 1892 | [[package]] 1893 | name = "tower-service" 1894 | version = "0.3.3" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1897 | 1898 | [[package]] 1899 | name = "tracing" 1900 | version = "0.1.41" 1901 | source = "registry+https://github.com/rust-lang/crates.io-index" 1902 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1903 | dependencies = [ 1904 | "log", 1905 | "pin-project-lite", 1906 | "tracing-attributes", 1907 | "tracing-core", 1908 | ] 1909 | 1910 | [[package]] 1911 | name = "tracing-attributes" 1912 | version = "0.1.28" 1913 | source = "registry+https://github.com/rust-lang/crates.io-index" 1914 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 1915 | dependencies = [ 1916 | "proc-macro2", 1917 | "quote", 1918 | "syn", 1919 | ] 1920 | 1921 | [[package]] 1922 | name = "tracing-core" 1923 | version = "0.1.33" 1924 | source = "registry+https://github.com/rust-lang/crates.io-index" 1925 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1926 | dependencies = [ 1927 | "once_cell", 1928 | "valuable", 1929 | ] 1930 | 1931 | [[package]] 1932 | name = "tracing-log" 1933 | version = "0.2.0" 1934 | source = "registry+https://github.com/rust-lang/crates.io-index" 1935 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1936 | dependencies = [ 1937 | "log", 1938 | "once_cell", 1939 | "tracing-core", 1940 | ] 1941 | 1942 | [[package]] 1943 | name = "tracing-subscriber" 1944 | version = "0.3.19" 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" 1946 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 1947 | dependencies = [ 1948 | "nu-ansi-term", 1949 | "sharded-slab", 1950 | "smallvec", 1951 | "thread_local", 1952 | "tracing-core", 1953 | "tracing-log", 1954 | ] 1955 | 1956 | [[package]] 1957 | name = "try-lock" 1958 | version = "0.2.5" 1959 | source = "registry+https://github.com/rust-lang/crates.io-index" 1960 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1961 | 1962 | [[package]] 1963 | name = "unicode-ident" 1964 | version = "1.0.18" 1965 | source = "registry+https://github.com/rust-lang/crates.io-index" 1966 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1967 | 1968 | [[package]] 1969 | name = "untrusted" 1970 | version = "0.9.0" 1971 | source = "registry+https://github.com/rust-lang/crates.io-index" 1972 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1973 | 1974 | [[package]] 1975 | name = "url" 1976 | version = "2.5.4" 1977 | source = "registry+https://github.com/rust-lang/crates.io-index" 1978 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1979 | dependencies = [ 1980 | "form_urlencoded", 1981 | "idna", 1982 | "percent-encoding", 1983 | ] 1984 | 1985 | [[package]] 1986 | name = "utf16_iter" 1987 | version = "1.0.5" 1988 | source = "registry+https://github.com/rust-lang/crates.io-index" 1989 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1990 | 1991 | [[package]] 1992 | name = "utf8_iter" 1993 | version = "1.0.4" 1994 | source = "registry+https://github.com/rust-lang/crates.io-index" 1995 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1996 | 1997 | [[package]] 1998 | name = "utf8parse" 1999 | version = "0.2.2" 2000 | source = "registry+https://github.com/rust-lang/crates.io-index" 2001 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2002 | 2003 | [[package]] 2004 | name = "uuid" 2005 | version = "1.16.0" 2006 | source = "registry+https://github.com/rust-lang/crates.io-index" 2007 | checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" 2008 | dependencies = [ 2009 | "getrandom 0.3.2", 2010 | "rand", 2011 | ] 2012 | 2013 | [[package]] 2014 | name = "valuable" 2015 | version = "0.1.1" 2016 | source = "registry+https://github.com/rust-lang/crates.io-index" 2017 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 2018 | 2019 | [[package]] 2020 | name = "vcpkg" 2021 | version = "0.2.15" 2022 | source = "registry+https://github.com/rust-lang/crates.io-index" 2023 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2024 | 2025 | [[package]] 2026 | name = "want" 2027 | version = "0.3.1" 2028 | source = "registry+https://github.com/rust-lang/crates.io-index" 2029 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2030 | dependencies = [ 2031 | "try-lock", 2032 | ] 2033 | 2034 | [[package]] 2035 | name = "wasi" 2036 | version = "0.11.0+wasi-snapshot-preview1" 2037 | source = "registry+https://github.com/rust-lang/crates.io-index" 2038 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2039 | 2040 | [[package]] 2041 | name = "wasi" 2042 | version = "0.14.2+wasi-0.2.4" 2043 | source = "registry+https://github.com/rust-lang/crates.io-index" 2044 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 2045 | dependencies = [ 2046 | "wit-bindgen-rt", 2047 | ] 2048 | 2049 | [[package]] 2050 | name = "wasm-bindgen" 2051 | version = "0.2.100" 2052 | source = "registry+https://github.com/rust-lang/crates.io-index" 2053 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2054 | dependencies = [ 2055 | "cfg-if", 2056 | "once_cell", 2057 | "rustversion", 2058 | "wasm-bindgen-macro", 2059 | ] 2060 | 2061 | [[package]] 2062 | name = "wasm-bindgen-backend" 2063 | version = "0.2.100" 2064 | source = "registry+https://github.com/rust-lang/crates.io-index" 2065 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2066 | dependencies = [ 2067 | "bumpalo", 2068 | "log", 2069 | "proc-macro2", 2070 | "quote", 2071 | "syn", 2072 | "wasm-bindgen-shared", 2073 | ] 2074 | 2075 | [[package]] 2076 | name = "wasm-bindgen-futures" 2077 | version = "0.4.50" 2078 | source = "registry+https://github.com/rust-lang/crates.io-index" 2079 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 2080 | dependencies = [ 2081 | "cfg-if", 2082 | "js-sys", 2083 | "once_cell", 2084 | "wasm-bindgen", 2085 | "web-sys", 2086 | ] 2087 | 2088 | [[package]] 2089 | name = "wasm-bindgen-macro" 2090 | version = "0.2.100" 2091 | source = "registry+https://github.com/rust-lang/crates.io-index" 2092 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2093 | dependencies = [ 2094 | "quote", 2095 | "wasm-bindgen-macro-support", 2096 | ] 2097 | 2098 | [[package]] 2099 | name = "wasm-bindgen-macro-support" 2100 | version = "0.2.100" 2101 | source = "registry+https://github.com/rust-lang/crates.io-index" 2102 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2103 | dependencies = [ 2104 | "proc-macro2", 2105 | "quote", 2106 | "syn", 2107 | "wasm-bindgen-backend", 2108 | "wasm-bindgen-shared", 2109 | ] 2110 | 2111 | [[package]] 2112 | name = "wasm-bindgen-shared" 2113 | version = "0.2.100" 2114 | source = "registry+https://github.com/rust-lang/crates.io-index" 2115 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2116 | dependencies = [ 2117 | "unicode-ident", 2118 | ] 2119 | 2120 | [[package]] 2121 | name = "wasm-streams" 2122 | version = "0.4.2" 2123 | source = "registry+https://github.com/rust-lang/crates.io-index" 2124 | checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 2125 | dependencies = [ 2126 | "futures-util", 2127 | "js-sys", 2128 | "wasm-bindgen", 2129 | "wasm-bindgen-futures", 2130 | "web-sys", 2131 | ] 2132 | 2133 | [[package]] 2134 | name = "web-sys" 2135 | version = "0.3.77" 2136 | source = "registry+https://github.com/rust-lang/crates.io-index" 2137 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 2138 | dependencies = [ 2139 | "js-sys", 2140 | "wasm-bindgen", 2141 | ] 2142 | 2143 | [[package]] 2144 | name = "web-time" 2145 | version = "1.1.0" 2146 | source = "registry+https://github.com/rust-lang/crates.io-index" 2147 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 2148 | dependencies = [ 2149 | "js-sys", 2150 | "wasm-bindgen", 2151 | ] 2152 | 2153 | [[package]] 2154 | name = "webpki-roots" 2155 | version = "0.26.11" 2156 | source = "registry+https://github.com/rust-lang/crates.io-index" 2157 | checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" 2158 | dependencies = [ 2159 | "webpki-roots 1.0.0", 2160 | ] 2161 | 2162 | [[package]] 2163 | name = "webpki-roots" 2164 | version = "1.0.0" 2165 | source = "registry+https://github.com/rust-lang/crates.io-index" 2166 | checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" 2167 | dependencies = [ 2168 | "rustls-pki-types", 2169 | ] 2170 | 2171 | [[package]] 2172 | name = "winapi" 2173 | version = "0.3.9" 2174 | source = "registry+https://github.com/rust-lang/crates.io-index" 2175 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2176 | dependencies = [ 2177 | "winapi-i686-pc-windows-gnu", 2178 | "winapi-x86_64-pc-windows-gnu", 2179 | ] 2180 | 2181 | [[package]] 2182 | name = "winapi-i686-pc-windows-gnu" 2183 | version = "0.4.0" 2184 | source = "registry+https://github.com/rust-lang/crates.io-index" 2185 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2186 | 2187 | [[package]] 2188 | name = "winapi-x86_64-pc-windows-gnu" 2189 | version = "0.4.0" 2190 | source = "registry+https://github.com/rust-lang/crates.io-index" 2191 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2192 | 2193 | [[package]] 2194 | name = "windows" 2195 | version = "0.61.1" 2196 | source = "registry+https://github.com/rust-lang/crates.io-index" 2197 | checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" 2198 | dependencies = [ 2199 | "windows-collections", 2200 | "windows-core", 2201 | "windows-future", 2202 | "windows-link", 2203 | "windows-numerics", 2204 | ] 2205 | 2206 | [[package]] 2207 | name = "windows-collections" 2208 | version = "0.2.0" 2209 | source = "registry+https://github.com/rust-lang/crates.io-index" 2210 | checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 2211 | dependencies = [ 2212 | "windows-core", 2213 | ] 2214 | 2215 | [[package]] 2216 | name = "windows-core" 2217 | version = "0.61.0" 2218 | source = "registry+https://github.com/rust-lang/crates.io-index" 2219 | checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" 2220 | dependencies = [ 2221 | "windows-implement", 2222 | "windows-interface", 2223 | "windows-link", 2224 | "windows-result", 2225 | "windows-strings 0.4.0", 2226 | ] 2227 | 2228 | [[package]] 2229 | name = "windows-future" 2230 | version = "0.2.0" 2231 | source = "registry+https://github.com/rust-lang/crates.io-index" 2232 | checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" 2233 | dependencies = [ 2234 | "windows-core", 2235 | "windows-link", 2236 | ] 2237 | 2238 | [[package]] 2239 | name = "windows-implement" 2240 | version = "0.60.0" 2241 | source = "registry+https://github.com/rust-lang/crates.io-index" 2242 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 2243 | dependencies = [ 2244 | "proc-macro2", 2245 | "quote", 2246 | "syn", 2247 | ] 2248 | 2249 | [[package]] 2250 | name = "windows-interface" 2251 | version = "0.59.1" 2252 | source = "registry+https://github.com/rust-lang/crates.io-index" 2253 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 2254 | dependencies = [ 2255 | "proc-macro2", 2256 | "quote", 2257 | "syn", 2258 | ] 2259 | 2260 | [[package]] 2261 | name = "windows-link" 2262 | version = "0.1.1" 2263 | source = "registry+https://github.com/rust-lang/crates.io-index" 2264 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 2265 | 2266 | [[package]] 2267 | name = "windows-numerics" 2268 | version = "0.2.0" 2269 | source = "registry+https://github.com/rust-lang/crates.io-index" 2270 | checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 2271 | dependencies = [ 2272 | "windows-core", 2273 | "windows-link", 2274 | ] 2275 | 2276 | [[package]] 2277 | name = "windows-registry" 2278 | version = "0.4.0" 2279 | source = "registry+https://github.com/rust-lang/crates.io-index" 2280 | checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" 2281 | dependencies = [ 2282 | "windows-result", 2283 | "windows-strings 0.3.1", 2284 | "windows-targets 0.53.0", 2285 | ] 2286 | 2287 | [[package]] 2288 | name = "windows-result" 2289 | version = "0.3.2" 2290 | source = "registry+https://github.com/rust-lang/crates.io-index" 2291 | checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" 2292 | dependencies = [ 2293 | "windows-link", 2294 | ] 2295 | 2296 | [[package]] 2297 | name = "windows-strings" 2298 | version = "0.3.1" 2299 | source = "registry+https://github.com/rust-lang/crates.io-index" 2300 | checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" 2301 | dependencies = [ 2302 | "windows-link", 2303 | ] 2304 | 2305 | [[package]] 2306 | name = "windows-strings" 2307 | version = "0.4.0" 2308 | source = "registry+https://github.com/rust-lang/crates.io-index" 2309 | checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" 2310 | dependencies = [ 2311 | "windows-link", 2312 | ] 2313 | 2314 | [[package]] 2315 | name = "windows-sys" 2316 | version = "0.52.0" 2317 | source = "registry+https://github.com/rust-lang/crates.io-index" 2318 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2319 | dependencies = [ 2320 | "windows-targets 0.52.6", 2321 | ] 2322 | 2323 | [[package]] 2324 | name = "windows-sys" 2325 | version = "0.59.0" 2326 | source = "registry+https://github.com/rust-lang/crates.io-index" 2327 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2328 | dependencies = [ 2329 | "windows-targets 0.52.6", 2330 | ] 2331 | 2332 | [[package]] 2333 | name = "windows-targets" 2334 | version = "0.52.6" 2335 | source = "registry+https://github.com/rust-lang/crates.io-index" 2336 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2337 | dependencies = [ 2338 | "windows_aarch64_gnullvm 0.52.6", 2339 | "windows_aarch64_msvc 0.52.6", 2340 | "windows_i686_gnu 0.52.6", 2341 | "windows_i686_gnullvm 0.52.6", 2342 | "windows_i686_msvc 0.52.6", 2343 | "windows_x86_64_gnu 0.52.6", 2344 | "windows_x86_64_gnullvm 0.52.6", 2345 | "windows_x86_64_msvc 0.52.6", 2346 | ] 2347 | 2348 | [[package]] 2349 | name = "windows-targets" 2350 | version = "0.53.0" 2351 | source = "registry+https://github.com/rust-lang/crates.io-index" 2352 | checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" 2353 | dependencies = [ 2354 | "windows_aarch64_gnullvm 0.53.0", 2355 | "windows_aarch64_msvc 0.53.0", 2356 | "windows_i686_gnu 0.53.0", 2357 | "windows_i686_gnullvm 0.53.0", 2358 | "windows_i686_msvc 0.53.0", 2359 | "windows_x86_64_gnu 0.53.0", 2360 | "windows_x86_64_gnullvm 0.53.0", 2361 | "windows_x86_64_msvc 0.53.0", 2362 | ] 2363 | 2364 | [[package]] 2365 | name = "windows_aarch64_gnullvm" 2366 | version = "0.52.6" 2367 | source = "registry+https://github.com/rust-lang/crates.io-index" 2368 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2369 | 2370 | [[package]] 2371 | name = "windows_aarch64_gnullvm" 2372 | version = "0.53.0" 2373 | source = "registry+https://github.com/rust-lang/crates.io-index" 2374 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 2375 | 2376 | [[package]] 2377 | name = "windows_aarch64_msvc" 2378 | version = "0.52.6" 2379 | source = "registry+https://github.com/rust-lang/crates.io-index" 2380 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2381 | 2382 | [[package]] 2383 | name = "windows_aarch64_msvc" 2384 | version = "0.53.0" 2385 | source = "registry+https://github.com/rust-lang/crates.io-index" 2386 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 2387 | 2388 | [[package]] 2389 | name = "windows_i686_gnu" 2390 | version = "0.52.6" 2391 | source = "registry+https://github.com/rust-lang/crates.io-index" 2392 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2393 | 2394 | [[package]] 2395 | name = "windows_i686_gnu" 2396 | version = "0.53.0" 2397 | source = "registry+https://github.com/rust-lang/crates.io-index" 2398 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 2399 | 2400 | [[package]] 2401 | name = "windows_i686_gnullvm" 2402 | version = "0.52.6" 2403 | source = "registry+https://github.com/rust-lang/crates.io-index" 2404 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2405 | 2406 | [[package]] 2407 | name = "windows_i686_gnullvm" 2408 | version = "0.53.0" 2409 | source = "registry+https://github.com/rust-lang/crates.io-index" 2410 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 2411 | 2412 | [[package]] 2413 | name = "windows_i686_msvc" 2414 | version = "0.52.6" 2415 | source = "registry+https://github.com/rust-lang/crates.io-index" 2416 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2417 | 2418 | [[package]] 2419 | name = "windows_i686_msvc" 2420 | version = "0.53.0" 2421 | source = "registry+https://github.com/rust-lang/crates.io-index" 2422 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 2423 | 2424 | [[package]] 2425 | name = "windows_x86_64_gnu" 2426 | version = "0.52.6" 2427 | source = "registry+https://github.com/rust-lang/crates.io-index" 2428 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2429 | 2430 | [[package]] 2431 | name = "windows_x86_64_gnu" 2432 | version = "0.53.0" 2433 | source = "registry+https://github.com/rust-lang/crates.io-index" 2434 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 2435 | 2436 | [[package]] 2437 | name = "windows_x86_64_gnullvm" 2438 | version = "0.52.6" 2439 | source = "registry+https://github.com/rust-lang/crates.io-index" 2440 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2441 | 2442 | [[package]] 2443 | name = "windows_x86_64_gnullvm" 2444 | version = "0.53.0" 2445 | source = "registry+https://github.com/rust-lang/crates.io-index" 2446 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 2447 | 2448 | [[package]] 2449 | name = "windows_x86_64_msvc" 2450 | version = "0.52.6" 2451 | source = "registry+https://github.com/rust-lang/crates.io-index" 2452 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2453 | 2454 | [[package]] 2455 | name = "windows_x86_64_msvc" 2456 | version = "0.53.0" 2457 | source = "registry+https://github.com/rust-lang/crates.io-index" 2458 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 2459 | 2460 | [[package]] 2461 | name = "wit-bindgen-rt" 2462 | version = "0.39.0" 2463 | source = "registry+https://github.com/rust-lang/crates.io-index" 2464 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 2465 | dependencies = [ 2466 | "bitflags", 2467 | ] 2468 | 2469 | [[package]] 2470 | name = "write16" 2471 | version = "1.0.0" 2472 | source = "registry+https://github.com/rust-lang/crates.io-index" 2473 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2474 | 2475 | [[package]] 2476 | name = "writeable" 2477 | version = "0.5.5" 2478 | source = "registry+https://github.com/rust-lang/crates.io-index" 2479 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2480 | 2481 | [[package]] 2482 | name = "yoke" 2483 | version = "0.7.5" 2484 | source = "registry+https://github.com/rust-lang/crates.io-index" 2485 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2486 | dependencies = [ 2487 | "serde", 2488 | "stable_deref_trait", 2489 | "yoke-derive", 2490 | "zerofrom", 2491 | ] 2492 | 2493 | [[package]] 2494 | name = "yoke-derive" 2495 | version = "0.7.5" 2496 | source = "registry+https://github.com/rust-lang/crates.io-index" 2497 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2498 | dependencies = [ 2499 | "proc-macro2", 2500 | "quote", 2501 | "syn", 2502 | "synstructure", 2503 | ] 2504 | 2505 | [[package]] 2506 | name = "zerocopy" 2507 | version = "0.8.25" 2508 | source = "registry+https://github.com/rust-lang/crates.io-index" 2509 | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" 2510 | dependencies = [ 2511 | "zerocopy-derive", 2512 | ] 2513 | 2514 | [[package]] 2515 | name = "zerocopy-derive" 2516 | version = "0.8.25" 2517 | source = "registry+https://github.com/rust-lang/crates.io-index" 2518 | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" 2519 | dependencies = [ 2520 | "proc-macro2", 2521 | "quote", 2522 | "syn", 2523 | ] 2524 | 2525 | [[package]] 2526 | name = "zerofrom" 2527 | version = "0.1.6" 2528 | source = "registry+https://github.com/rust-lang/crates.io-index" 2529 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2530 | dependencies = [ 2531 | "zerofrom-derive", 2532 | ] 2533 | 2534 | [[package]] 2535 | name = "zerofrom-derive" 2536 | version = "0.1.6" 2537 | source = "registry+https://github.com/rust-lang/crates.io-index" 2538 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2539 | dependencies = [ 2540 | "proc-macro2", 2541 | "quote", 2542 | "syn", 2543 | "synstructure", 2544 | ] 2545 | 2546 | [[package]] 2547 | name = "zeroize" 2548 | version = "1.8.1" 2549 | source = "registry+https://github.com/rust-lang/crates.io-index" 2550 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2551 | 2552 | [[package]] 2553 | name = "zerovec" 2554 | version = "0.10.4" 2555 | source = "registry+https://github.com/rust-lang/crates.io-index" 2556 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2557 | dependencies = [ 2558 | "yoke", 2559 | "zerofrom", 2560 | "zerovec-derive", 2561 | ] 2562 | 2563 | [[package]] 2564 | name = "zerovec-derive" 2565 | version = "0.10.3" 2566 | source = "registry+https://github.com/rust-lang/crates.io-index" 2567 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2568 | dependencies = [ 2569 | "proc-macro2", 2570 | "quote", 2571 | "syn", 2572 | ] 2573 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mcp-proxy" 3 | version = "0.2.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk.git", rev = "076dc2c2cd8910bee56bae13f29bbcff8c279666", features = [ 8 | "server", 9 | "client", 10 | "reqwest", 11 | "client-side-sse", 12 | "transport-sse-client", 13 | "transport-streamable-http-client", 14 | "transport-worker", 15 | "transport-child-process" 16 | ] } 17 | clap = { version = "4.5.37", features = ["derive"] } 18 | tokio = { version = "1", features = ["full"] } 19 | tracing = "0.1.41" 20 | tracing-subscriber = "0.3.19" 21 | anyhow = "1.0.98" 22 | uuid = { version = "1.6", features = ["v7", "fast-rng"] } 23 | futures = "0.3.31" 24 | tokio-util = "0.7.15" 25 | reqwest = { version = "0.12", features = ["json", "stream"] } 26 | 27 | [dependencies.openssl-sys] 28 | version = "0.9" 29 | features = ["vendored"] 30 | 31 | [dev-dependencies] 32 | rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk.git", rev = "076dc2c2cd8910bee56bae13f29bbcff8c279666", features = [ 33 | "server", 34 | "client", 35 | "reqwest", 36 | "client-side-sse", 37 | "transport-sse-client", 38 | "transport-sse-server", 39 | "transport-child-process", 40 | "transport-streamable-http-server", 41 | "macros" 42 | ] } 43 | axum = { version = "0.8", features = ["macros"] } 44 | serde = { version = "1.0", features = ["derive"] } 45 | serde_json = "1.0" 46 | 47 | [profile.release] 48 | opt-level = "s" 49 | lto = true 50 | codegen-units = 1 51 | strip = true 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mcp-proxy 2 | 3 | A standalone binary for connecting STDIO based MCP clients to HTTP (SSE) based MCP servers. 4 | 5 | Note: the proxy supports both SSE according to the `2024-11-05` as well as streamable HTTP according to the `2025-03-26` specification. 6 | It may happen though that the connecting client does **not** support the version sent by the server. 7 | 8 | ## Installation 9 | 10 | The latest releases are available on the [releases page](https://github.com/tidewave-ai/mcp_proxy_rust/releases). 11 | 12 | ### macOS 13 | 14 | Depending on your Mac, you can download the latest version with one of the following commands: 15 | 16 | Apple Silicon: 17 | 18 | ```bash 19 | curl -sL https://github.com/tidewave-ai/mcp_proxy_rust/releases/latest/download/mcp-proxy-aarch64-apple-darwin.tar.gz | tar xv 20 | ``` 21 | 22 | Intel: 23 | 24 | ```bash 25 | curl -sL https://github.com/tidewave-ai/mcp_proxy_rust/releases/latest/download/mcp-proxy-x86_64-apple-darwin.tar.gz | tar xv 26 | ``` 27 | 28 | which will put the `mcp-proxy` binary in the current working directory (`pwd`). 29 | 30 | Note that the binaries are not notarized, so if you download the release with the browser, you won't be able to open it. 31 | 32 | Alternatively, you can remove the quarantine flag with: 33 | 34 | ```bash 35 | xattr -d com.apple.quarantine /path/to/mcp-proxy 36 | ``` 37 | 38 | ### Linux 39 | 40 | You can download the latest release from the [Releases page](https://github.com/tidewave-ai/mcp_proxy_rust/releases) or with one command, depending on your architecture: 41 | 42 | x86: 43 | 44 | ```bash 45 | curl -sL https://github.com/tidewave-ai/mcp_proxy_rust/releases/latest/download/mcp-proxy-x86_64-unknown-linux-musl.tar.gz | tar zxv 46 | ``` 47 | 48 | arm64 / aarch64: 49 | 50 | ```bash 51 | curl -sL https://github.com/tidewave-ai/mcp_proxy_rust/releases/latest/download/mcp-proxy-aarch64-unknown-linux-musl.tar.gz | tar zxv 52 | ``` 53 | 54 | ### Windows 55 | 56 | You can download the latest release from the [Releases page](https://github.com/tidewave-ai/mcp_proxy_rust/releases) or with the following Powershell command: 57 | 58 | ```powershell 59 | curl.exe -L -o mcp-proxy.zip https://github.com/tidewave-ai/mcp_proxy_rust/releases/latest/download/mcp-proxy-x86_64-pc-windows-msvc.zip; Expand-Archive -Path mcp-proxy.zip -DestinationPath . 60 | ``` 61 | 62 | ## Building from scratch 63 | 64 | The proxy is built in Rust. If you have Rust and its tools installed, the project can be built with `cargo`: 65 | 66 | ```bash 67 | cargo build --release 68 | ``` 69 | 70 | Then, the binary will be located at `target/release/mcp-proxy`. 71 | 72 | ## Usage 73 | 74 | If you have an SSE MCP server available at `http://localhost:4000/tidewave/mcp`, a client like Claude Desktop would then be configured as follows. 75 | 76 | ### On macos/Linux 77 | 78 | ```json 79 | { 80 | "mcpServers": { 81 | "my-server": { 82 | "command": "/path/to/mcp-proxy", 83 | "args": ["http://localhost:4000/tidewave/mcp"] 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | ### On Windows 90 | 91 | ```json 92 | { 93 | "mcpServers": { 94 | "my-server": { 95 | "command": "c:\\path\\to\\mcp-proxy.exe", 96 | "args": ["http://localhost:4000/tidewave/mcp"] 97 | } 98 | } 99 | } 100 | ``` 101 | 102 | ## Configuration 103 | 104 | `mcp-proxy` either accepts the SSE URL as argument or using the environment variable `SSE_URL`. For debugging purposes, you can also pass `--debug`, which will log debug messages on stderr. 105 | 106 | Other supported flags: 107 | 108 | * `--max-disconnected-time` the maximum amount of time for trying to reconnect while disconnected. When not set, defaults to infinity. 109 | -------------------------------------------------------------------------------- /examples/echo.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use clap::Parser; 3 | use rmcp::transport::SseServer; 4 | use tracing_subscriber::FmtSubscriber; 5 | 6 | use rmcp::{ 7 | ServerHandler, 8 | model::{ServerCapabilities, ServerInfo}, 9 | schemars, tool, 10 | }; 11 | #[derive(Debug, Clone, Default)] 12 | pub struct Echo; 13 | #[tool(tool_box)] 14 | impl Echo { 15 | #[tool(description = "Echo a message")] 16 | fn echo(&self, #[tool(param)] message: String) -> String { 17 | message 18 | } 19 | } 20 | 21 | #[tool(tool_box)] 22 | impl ServerHandler for Echo { 23 | fn get_info(&self) -> ServerInfo { 24 | ServerInfo { 25 | instructions: Some("A simple echo server".into()), 26 | capabilities: ServerCapabilities::builder().enable_tools().build(), 27 | ..Default::default() 28 | } 29 | } 30 | } 31 | 32 | #[derive(Parser)] 33 | #[command(author, version, about, long_about = None)] 34 | struct Args { 35 | /// Address to bind the server to 36 | #[arg(short, long, default_value = "127.0.0.1:8080")] 37 | address: std::net::SocketAddr, 38 | } 39 | 40 | #[tokio::main] 41 | async fn main() -> anyhow::Result<()> { 42 | let subscriber = FmtSubscriber::builder() 43 | .with_max_level(tracing::Level::DEBUG) 44 | .with_writer(std::io::stderr) 45 | .finish(); 46 | 47 | // Parse command line arguments 48 | let args = Args::parse(); 49 | 50 | tracing::subscriber::set_global_default(subscriber).context("Failed to set up logging")?; 51 | 52 | let ct = SseServer::serve(args.address) 53 | .await? 54 | .with_service(Echo::default); 55 | 56 | tokio::signal::ctrl_c().await?; 57 | ct.cancel(); 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /examples/echo_streamable.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use clap::Parser; 3 | use rmcp::transport::StreamableHttpServer; 4 | use tracing_subscriber::FmtSubscriber; 5 | 6 | use rmcp::{ 7 | ServerHandler, 8 | model::{ServerCapabilities, ServerInfo}, 9 | schemars, tool, 10 | }; 11 | #[derive(Debug, Clone, Default)] 12 | pub struct Echo; 13 | #[tool(tool_box)] 14 | impl Echo { 15 | #[tool(description = "Echo a message")] 16 | fn echo(&self, #[tool(param)] message: String) -> String { 17 | message 18 | } 19 | } 20 | 21 | #[tool(tool_box)] 22 | impl ServerHandler for Echo { 23 | fn get_info(&self) -> ServerInfo { 24 | ServerInfo { 25 | instructions: Some("A simple echo server".into()), 26 | capabilities: ServerCapabilities::builder().enable_tools().build(), 27 | ..Default::default() 28 | } 29 | } 30 | } 31 | 32 | #[derive(Parser)] 33 | #[command(author, version, about, long_about = None)] 34 | struct Args { 35 | /// Address to bind the server to 36 | #[arg(short, long, default_value = "127.0.0.1:8080")] 37 | address: std::net::SocketAddr, 38 | } 39 | 40 | #[tokio::main] 41 | async fn main() -> anyhow::Result<()> { 42 | let subscriber = FmtSubscriber::builder() 43 | .with_max_level(tracing::Level::DEBUG) 44 | .with_writer(std::io::stderr) 45 | .finish(); 46 | 47 | // Parse command line arguments 48 | let args = Args::parse(); 49 | 50 | tracing::subscriber::set_global_default(subscriber).context("Failed to set up logging")?; 51 | 52 | let ct = StreamableHttpServer::serve(args.address) 53 | .await? 54 | .with_service(Echo::default); 55 | 56 | tokio::signal::ctrl_c().await?; 57 | ct.cancel(); 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(Parser, Debug)] 4 | #[command(author, version, about, long_about = None)] 5 | pub struct Args { 6 | /// The URL of the SSE endpoint to connect to 7 | #[arg(value_name = "URL")] 8 | pub sse_url: Option, 9 | 10 | /// Enable debug logging 11 | #[arg(long)] 12 | pub debug: bool, 13 | 14 | /// Maximum time to try reconnecting in seconds 15 | #[arg(long)] 16 | pub max_disconnected_time: Option, 17 | 18 | /// Initial retry interval in seconds. Default is 5 seconds 19 | #[arg(long, default_value = "5")] 20 | pub initial_retry_interval: u64, 21 | } 22 | -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | use crate::state::{AppState, BufferMode, ProxyState, ReconnectFailureReason}; 2 | use crate::{DISCONNECTED_ERROR_CODE, SseClientType, StdoutSink, TRANSPORT_SEND_ERROR_CODE}; 3 | use anyhow::{Result, anyhow}; 4 | use futures::FutureExt; 5 | use futures::SinkExt; 6 | use rmcp::model::{ 7 | ClientJsonRpcMessage, ClientNotification, ClientRequest, ErrorData, RequestId, 8 | ServerJsonRpcMessage, 9 | }; 10 | use std::time::Duration; 11 | use tracing::{debug, error, info}; 12 | use uuid::Uuid; 13 | 14 | /// Generates a random UUID for request IDs 15 | pub(crate) fn generate_id() -> String { 16 | Uuid::now_v7().to_string() 17 | } 18 | 19 | /// Sends a disconnected error response 20 | pub(crate) async fn reply_disconnected(id: &RequestId, stdout_sink: &mut StdoutSink) -> Result<()> { 21 | let error_response = ServerJsonRpcMessage::error( 22 | ErrorData::new( 23 | DISCONNECTED_ERROR_CODE, 24 | "Server not connected. The SSE endpoint is currently not available. Please ensure it is running and retry.".to_string(), 25 | None, 26 | ), 27 | id.clone(), 28 | ); 29 | 30 | if let Err(e) = stdout_sink.send(error_response).await { 31 | error!("Error writing disconnected error response to stdout: {}", e); 32 | } 33 | 34 | Ok(()) 35 | } 36 | 37 | pub(crate) async fn connect(app_state: &AppState) -> Result { 38 | // this function should try sending a POST request to the sse_url and see if 39 | // the server responds with 405 method not supported. If so, it should call 40 | // connect_with_sse, otherwise it should call connect_with_streamable. 41 | let result = reqwest::Client::new() 42 | .post(app_state.url.clone()) 43 | .header("Accept", "application/json,text/event-stream") 44 | .header("Content-Type", "application/json") 45 | .body(r#"{"jsonrpc":"2.0","id":"init","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.1.0"}}}"#) 46 | .send() 47 | .await?; 48 | 49 | if result.status() == reqwest::StatusCode::METHOD_NOT_ALLOWED { 50 | debug!("Server responded with 405, using SSE transport"); 51 | return connect_with_sse(app_state).await; 52 | } else if result.status().is_success() { 53 | debug!("Server responded successfully, using streamable transport"); 54 | return connect_with_streamable(app_state).await; 55 | } else { 56 | error!("Server returned unexpected status: {}", result.status()); 57 | anyhow::bail!("Server returned unexpected status: {}", result.status()); 58 | } 59 | } 60 | 61 | pub(crate) async fn connect_with_streamable(app_state: &AppState) -> Result { 62 | let result = rmcp::transport::StreamableHttpClientTransport::with_client( 63 | reqwest::Client::default(), 64 | rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig { 65 | uri: app_state.url.clone().into(), 66 | // we don't want the sdk to perform any retries 67 | retry_config: std::sync::Arc::new( 68 | rmcp::transport::common::client_side_sse::FixedInterval { 69 | max_times: Some(0), 70 | duration: Duration::from_millis(0), 71 | }, 72 | ), 73 | channel_buffer_capacity: 16, 74 | }, 75 | ); 76 | 77 | Ok(SseClientType::Streamable(result)) 78 | } 79 | 80 | pub(crate) async fn connect_with_sse(app_state: &AppState) -> Result { 81 | let result = rmcp::transport::SseClientTransport::start_with_client( 82 | reqwest::Client::default(), 83 | rmcp::transport::sse_client::SseClientConfig { 84 | sse_endpoint: app_state.url.clone().into(), 85 | // we don't want the sdk to perform any retries 86 | retry_policy: std::sync::Arc::new( 87 | rmcp::transport::common::client_side_sse::FixedInterval { 88 | max_times: Some(0), 89 | duration: Duration::from_millis(0), 90 | }, 91 | ), 92 | use_message_endpoint: None, 93 | }, 94 | ) 95 | .await; 96 | 97 | match result { 98 | Ok(transport) => { 99 | info!("Successfully reconnected to SSE server"); 100 | Ok(SseClientType::Sse(transport)) 101 | } 102 | Err(e) => { 103 | error!("Failed to reconnect: {}", e); 104 | Err(anyhow!("Connection failed: {}", e)) 105 | } 106 | } 107 | } 108 | 109 | /// Attempts to reconnect to the SSE server with backoff. 110 | /// Does not mutate AppState directly. 111 | pub(crate) async fn try_reconnect( 112 | app_state: &AppState, 113 | ) -> Result { 114 | let backoff = app_state.get_backoff_duration(); 115 | info!( 116 | "Attempting to reconnect in {}s (attempt {})", 117 | backoff.as_secs(), 118 | app_state.connect_tries 119 | ); 120 | 121 | if app_state.disconnected_too_long() { 122 | error!("Reconnect timeout exceeded, giving up reconnection attempts"); 123 | return Err(ReconnectFailureReason::TimeoutExceeded); 124 | } 125 | 126 | let result = connect(app_state).await; 127 | 128 | match result { 129 | Ok(transport) => { 130 | info!("Successfully reconnected to SSE server"); 131 | Ok(transport) 132 | } 133 | Err(e) => { 134 | error!("Failed to reconnect: {}", e); 135 | Err(ReconnectFailureReason::ConnectionFailed(e)) 136 | } 137 | } 138 | } 139 | 140 | /// Sends a JSON-RPC request to the SSE server and handles any transport errors. 141 | /// Returns true if the send was successful, false otherwise. 142 | pub(crate) async fn send_request_to_sse( 143 | transport: &mut SseClientType, 144 | request: ClientJsonRpcMessage, 145 | original_message: ClientJsonRpcMessage, 146 | stdout_sink: &mut StdoutSink, 147 | app_state: &mut AppState, 148 | ) -> Result { 149 | debug!("Sending request to SSE: {:?}", request); 150 | match transport.send(request.clone()).await { 151 | Ok(_) => Ok(true), 152 | Err(e) => { 153 | error!("Error sending to SSE: {}", e); 154 | app_state.handle_fatal_transport_error(); 155 | app_state 156 | .maybe_handle_message_while_disconnected(original_message, stdout_sink) 157 | .await?; 158 | 159 | Ok(false) 160 | } 161 | } 162 | } 163 | 164 | /// Processes a client request message, handles ID mapping, sends it to the SSE server, 165 | /// and handles any transport errors. 166 | pub(crate) async fn process_client_request( 167 | message: ClientJsonRpcMessage, 168 | app_state: &mut AppState, 169 | transport: &mut SseClientType, 170 | stdout_sink: &mut StdoutSink, 171 | ) -> Result<()> { 172 | // Try mapping the ID first (for Response/Error cases). 173 | // If it returns None, the ID was unknown, so we skip processing/forwarding. 174 | let message = match app_state.map_client_response_error_id(message) { 175 | Some(msg) => msg, 176 | None => return Ok(()), // Skip forwarding if ID was not mapped 177 | }; 178 | 179 | // Handle ping directly if disconnected 180 | match app_state 181 | .maybe_handle_message_while_disconnected(message.clone(), stdout_sink) 182 | .await 183 | { 184 | Err(_) => {} 185 | Ok(_) => return Ok(()), 186 | } 187 | 188 | match &message { 189 | ClientJsonRpcMessage::Request(req) => { 190 | if app_state.init_message.is_none() { 191 | if let ClientRequest::InitializeRequest(_) = req.request { 192 | debug!("Stored client initialization message"); 193 | app_state.init_message = Some(message.clone()); 194 | app_state.state = ProxyState::WaitingForServerInit(req.id.clone()); 195 | } 196 | } 197 | } 198 | ClientJsonRpcMessage::Notification(notification) => { 199 | if let ClientNotification::InitializedNotification(_) = notification.notification { 200 | if app_state.state == ProxyState::WaitingForClientInitialized { 201 | debug!("Received client initialized notification, proxy fully connected."); 202 | app_state.connected(); 203 | } else { 204 | debug!("Forwarding client initialized notification outside of expected state."); 205 | } 206 | } 207 | } 208 | _ => {} 209 | } 210 | 211 | // Process requests separately to map their IDs before sending 212 | let original_message = message.clone(); 213 | if let ClientJsonRpcMessage::Request(req) = message { 214 | let request_id = req.id.clone(); 215 | let mut req = req.clone(); 216 | debug!("Forwarding request from stdin to SSE: {:?}", req); 217 | 218 | let new_id = generate_id(); 219 | let new_request_id = RequestId::String(new_id.clone().into()); 220 | req.id = new_request_id; 221 | app_state.id_map.insert(new_id, request_id.clone()); 222 | 223 | let _success = send_request_to_sse( 224 | transport, 225 | ClientJsonRpcMessage::Request(req), 226 | original_message, 227 | stdout_sink, 228 | app_state, 229 | ) 230 | .await?; 231 | return Ok(()); 232 | } 233 | 234 | // Send other message types (Notifications, mapped Responses/Errors) 235 | debug!("Forwarding message from stdin to SSE: {:?}", message); 236 | if let Err(e) = transport.send(message).await { 237 | error!("Error sending message to SSE: {}", e); 238 | app_state.handle_fatal_transport_error(); 239 | } 240 | 241 | Ok(()) 242 | } 243 | 244 | /// Process buffered messages after a successful reconnection 245 | pub(crate) async fn process_buffered_messages( 246 | app_state: &mut AppState, 247 | transport: &mut SseClientType, 248 | stdout_sink: &mut StdoutSink, 249 | ) -> Result<()> { 250 | let buffered_messages = std::mem::take(&mut app_state.in_buf); 251 | debug!("Processing {} buffered messages", buffered_messages.len()); 252 | 253 | for message in buffered_messages { 254 | match &message { 255 | ClientJsonRpcMessage::Request(req) => { 256 | let request_id = req.id.clone(); 257 | let mut req = req.clone(); 258 | 259 | let new_id = generate_id(); 260 | req.id = RequestId::String(new_id.clone().into()); 261 | app_state.id_map.insert(new_id, request_id.clone()); 262 | 263 | if let Err(e) = transport.send(ClientJsonRpcMessage::Request(req)).await { 264 | error!("Error sending buffered request: {}", e); 265 | let error_response = ServerJsonRpcMessage::error( 266 | ErrorData::new( 267 | TRANSPORT_SEND_ERROR_CODE, 268 | format!("Transport error: {}", e), 269 | None, 270 | ), 271 | request_id, 272 | ); 273 | if let Err(write_err) = stdout_sink.send(error_response).await { 274 | error!("Error writing error response to stdout: {}", write_err); 275 | } 276 | } 277 | } 278 | _ => { 279 | // Notifications etc. 280 | if let Err(e) = transport.send(message.clone()).await { 281 | error!("Error sending buffered message: {}", e); 282 | // If sending a buffered notification fails, we probably just log it. 283 | // Triggering another disconnect cycle might be excessive. 284 | } 285 | } 286 | } 287 | } 288 | Ok(()) 289 | } 290 | 291 | /// Sends error responses for all buffered messages 292 | pub(crate) async fn flush_buffer_with_errors( 293 | app_state: &mut AppState, 294 | stdout_sink: &mut StdoutSink, 295 | ) -> Result<()> { 296 | debug!( 297 | "Flushing buffer with errors: {} messages", 298 | app_state.in_buf.len() 299 | ); 300 | 301 | let buffered_messages = std::mem::take(&mut app_state.in_buf); 302 | app_state.buf_mode = BufferMode::Fail; 303 | 304 | if !app_state.id_map.is_empty() { 305 | debug!("Clearing ID map with {} entries", app_state.id_map.len()); 306 | app_state.id_map.clear(); 307 | } 308 | 309 | for message in buffered_messages { 310 | if let ClientJsonRpcMessage::Request(request) = message { 311 | debug!("Sending error response for buffered request"); 312 | reply_disconnected(&request.id, stdout_sink).await?; 313 | } 314 | } 315 | 316 | Ok(()) 317 | } 318 | 319 | /// Initiates the post-reconnection handshake by sending the initialize request. 320 | /// Sets the state to WaitingForServerInitHidden. 321 | /// Returns Ok(true) if handshake initiated successfully (or not needed). 322 | /// Returns Ok(false) if sending the init message failed (triggers disconnect). 323 | pub(crate) async fn initiate_post_reconnect_handshake( 324 | app_state: &mut AppState, 325 | transport: &mut SseClientType, 326 | stdout_sink: &mut StdoutSink, 327 | ) -> Result { 328 | if let Some(init_msg) = &app_state.init_message { 329 | let id = if let ClientJsonRpcMessage::Request(req) = init_msg { 330 | req.id.clone() 331 | } else { 332 | error!("Stored init_message is not a request: {:?}", init_msg); 333 | return Ok(false); 334 | }; 335 | 336 | debug!( 337 | "Initiating post-reconnect handshake by sending: {:?}", 338 | init_msg 339 | ); 340 | app_state.state = ProxyState::WaitingForServerInitHidden(id.clone()); 341 | 342 | if let Err(e) = 343 | process_client_request(init_msg.clone(), app_state, transport, stdout_sink).await 344 | { 345 | info!("Error resending init message during handshake: {}", e); 346 | app_state.handle_fatal_transport_error(); 347 | Ok(false) 348 | } else { 349 | Ok(true) 350 | } 351 | } else { 352 | // If the init_message is None during a reconnect attempt, it's a fatal error. 353 | error!( 354 | "No initialization message stored. Cannot reconnect! This indicates a critical state issue." 355 | ); 356 | // Return an Err to signal a fatal condition that should terminate the proxy. 357 | Err(anyhow::anyhow!( 358 | "Cannot perform reconnect handshake: init_message is missing" 359 | )) 360 | } 361 | } 362 | 363 | /// Send a heartbeat ping to check if the transport is still connected. 364 | /// Returns Some(true) if alive, Some(false) if dead, None if check not needed. 365 | pub(crate) async fn send_heartbeat_if_needed( 366 | app_state: &AppState, 367 | transport: &mut SseClientType, 368 | ) -> Option { 369 | if app_state.last_heartbeat.elapsed() > Duration::from_secs(5) { 370 | debug!("Checking SSE connection state due to inactivity..."); 371 | match transport.receive().now_or_never() { 372 | Some(Some(_)) => { 373 | debug!("Heartbeat check: Received message/event, connection alive."); 374 | Some(true) 375 | } 376 | Some(None) => { 377 | debug!("Heartbeat check: Stream terminated, connection dead."); 378 | Some(false) 379 | } 380 | None => { 381 | debug!( 382 | "Heartbeat check: No immediate message/event, state uncertain but assuming alive for now." 383 | ); 384 | Some(true) 385 | } 386 | } 387 | } else { 388 | None 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result, anyhow}; 2 | use clap::Parser; 3 | use futures::StreamExt; 4 | use rmcp::{ 5 | model::{ClientJsonRpcMessage, ErrorCode, ServerJsonRpcMessage}, 6 | transport::{StreamableHttpClientTransport, Transport, sse_client::SseClientTransport}, 7 | }; 8 | use std::env; 9 | use tokio::io::{Stdin, Stdout}; 10 | use tokio::time::{Duration, Instant, sleep}; 11 | use tokio_util::codec::{FramedRead, FramedWrite}; 12 | use tracing::{debug, error, info, warn}; 13 | use tracing_subscriber::FmtSubscriber; 14 | 15 | // Modules 16 | mod cli; 17 | mod core; 18 | mod state; 19 | 20 | use crate::cli::Args; 21 | use crate::core::{connect, flush_buffer_with_errors}; 22 | use crate::state::{AppState, ProxyState}; // Only needed directly by main for final check 23 | 24 | // Custom Error Codes (Keep here or move to common/state? Keeping here for now) 25 | const DISCONNECTED_ERROR_CODE: ErrorCode = ErrorCode(-32010); 26 | const TRANSPORT_SEND_ERROR_CODE: ErrorCode = ErrorCode(-32011); 27 | 28 | enum SseClientType { 29 | Sse(SseClientTransport), 30 | Streamable(StreamableHttpClientTransport), 31 | } 32 | 33 | impl SseClientType { 34 | async fn send( 35 | &mut self, 36 | item: ClientJsonRpcMessage, 37 | ) -> Result<(), Box> { 38 | match self { 39 | SseClientType::Sse(transport) => transport.send(item).await.map_err(|e| e.into()), 40 | SseClientType::Streamable(transport) => { 41 | transport.send(item).await.map_err(|e| e.into()) 42 | } 43 | } 44 | } 45 | 46 | async fn receive(&mut self) -> Option { 47 | match self { 48 | SseClientType::Sse(transport) => transport.receive().await, 49 | SseClientType::Streamable(transport) => transport.receive().await, 50 | } 51 | } 52 | } 53 | 54 | type StdinCodec = rmcp::transport::async_rw::JsonRpcMessageCodec; 55 | type StdoutCodec = rmcp::transport::async_rw::JsonRpcMessageCodec; 56 | type StdinStream = FramedRead; 57 | type StdoutSink = FramedWrite; 58 | 59 | // --- Helper for Initial Connection --- 60 | const INITIAL_CONNECT_TIMEOUT: Duration = Duration::from_secs(5 * 60); // 5 minutes 61 | 62 | /// Attempts to establish the initial SSE connection, retrying on failure. 63 | async fn connect_with_retry(app_state: &AppState, delay: Duration) -> Result { 64 | let start_time = Instant::now(); 65 | let mut attempts = 0; 66 | 67 | loop { 68 | attempts += 1; 69 | info!( 70 | "Attempting initial SSE connection (attempt {})...", 71 | attempts 72 | ); 73 | 74 | let result = connect(app_state).await; 75 | 76 | // Try creating the transport 77 | match result { 78 | Ok(transport) => { 79 | info!("Initial connection successful!"); 80 | return Ok(transport); 81 | } 82 | Err(e) => { 83 | warn!("Attempt {} failed to start transport: {}", attempts, e); 84 | } 85 | } 86 | 87 | if start_time.elapsed() >= INITIAL_CONNECT_TIMEOUT { 88 | error!( 89 | "Failed to connect after {} attempts over {:?}. Giving up.", 90 | attempts, INITIAL_CONNECT_TIMEOUT 91 | ); 92 | return Err(anyhow!( 93 | "Initial connection timed out after {:?}", 94 | INITIAL_CONNECT_TIMEOUT 95 | )); 96 | } 97 | 98 | info!("Retrying in {:?}...", delay); 99 | sleep(delay).await; 100 | } 101 | } 102 | 103 | // --- Main Function --- 104 | #[tokio::main] 105 | async fn main() -> Result<()> { 106 | let args = Args::parse(); 107 | let log_level = if args.debug { 108 | tracing::Level::DEBUG 109 | } else { 110 | tracing::Level::INFO 111 | }; 112 | 113 | let subscriber = FmtSubscriber::builder() 114 | .with_max_level(log_level) 115 | .with_writer(std::io::stderr) 116 | .finish(); 117 | 118 | tracing::subscriber::set_global_default(subscriber).context("Failed to set up logging")?; 119 | 120 | // Get the SSE URL from args or environment 121 | let sse_url = match args.sse_url { 122 | Some(url) => url, 123 | None => env::var("SSE_URL").context( 124 | "Either the URL must be passed as the first argument or the SSE_URL environment variable must be set", 125 | )?, 126 | }; 127 | 128 | debug!("Starting MCP proxy with URL: {}", sse_url); 129 | debug!("Max disconnected time: {:?}s", args.max_disconnected_time); 130 | 131 | // Set up communication channels 132 | let (reconnect_tx, mut reconnect_rx) = tokio::sync::mpsc::channel(10); 133 | let (timer_tx, mut timer_rx) = tokio::sync::mpsc::channel(10); 134 | 135 | // Initialize application state 136 | let mut app_state = AppState::new(sse_url.clone(), args.max_disconnected_time); 137 | // Pass channel senders to state 138 | app_state.reconnect_tx = Some(reconnect_tx.clone()); 139 | app_state.timer_tx = Some(timer_tx.clone()); 140 | 141 | // Establish initial SSE connection using the retry helper 142 | info!("Attempting initial connection to {}...", sse_url); 143 | let mut transport = 144 | connect_with_retry(&app_state, Duration::from_secs(args.initial_retry_interval)).await?; 145 | 146 | info!("Connection established. Proxy operational."); 147 | app_state.state = ProxyState::WaitingForClientInit; 148 | 149 | let stdin = tokio::io::stdin(); 150 | let stdout = tokio::io::stdout(); 151 | let mut stdin_stream: StdinStream = FramedRead::new(stdin, StdinCodec::default()); 152 | let mut stdout_sink: StdoutSink = FramedWrite::new(stdout, StdoutCodec::default()); 153 | 154 | info!("Connected to SSE endpoint, starting proxy"); 155 | 156 | // Set up heartbeat interval 157 | let mut heartbeat_interval = tokio::time::interval(Duration::from_secs(1)); 158 | 159 | // Main event loop 160 | loop { 161 | tokio::select! { 162 | // Bias select towards checking cheaper/more frequent events first if needed, 163 | // but default Tokio select is fair. 164 | biased; 165 | // Handle message from stdin 166 | msg = stdin_stream.next() => { 167 | if !app_state.handle_stdin_message(msg, &mut transport, &mut stdout_sink).await? { 168 | break; 169 | } 170 | } 171 | // Handle message from SSE server 172 | result = transport.receive(), if app_state.transport_valid => { 173 | if !app_state.handle_sse_message(result, &mut transport, &mut stdout_sink).await? { 174 | break; 175 | } 176 | } 177 | // Handle reconnect signal 178 | Some(_) = reconnect_rx.recv() => { 179 | // Call method on app_state 180 | if let Some(new_transport) = app_state.handle_reconnect_signal(&mut stdout_sink).await? { 181 | transport = new_transport; 182 | } 183 | // Check if disconnected too long *after* attempting reconnect 184 | if app_state.disconnected_too_long() { 185 | error!("Giving up after failed reconnection attempts and exceeding max disconnected time."); 186 | // Ensure buffer is flushed if not already done by handle_reconnect_signal 187 | if !app_state.in_buf.is_empty() && app_state.buf_mode == state::BufferMode::Store { 188 | flush_buffer_with_errors(&mut app_state, &mut stdout_sink).await?; 189 | } 190 | break; 191 | } 192 | } 193 | // Handle flush timer signal 194 | Some(_) = timer_rx.recv() => app_state.handle_timer_signal(&mut stdout_sink).await?, 195 | // Handle heartbeat tick 196 | _ = heartbeat_interval.tick() => app_state.handle_heartbeat_tick(&mut transport).await?, 197 | // Exit if no events are ready (shouldn't happen with interval timers unless others close) 198 | else => break, 199 | } 200 | } 201 | 202 | info!("Proxy terminated"); 203 | Ok(()) 204 | } 205 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{ 2 | flush_buffer_with_errors, generate_id, initiate_post_reconnect_handshake, 3 | process_buffered_messages, process_client_request, reply_disconnected, 4 | send_heartbeat_if_needed, try_reconnect, 5 | }; 6 | use crate::{SseClientType, StdoutSink}; 7 | use anyhow::Result; 8 | use futures::SinkExt; 9 | use rmcp::model::{ 10 | ClientJsonRpcMessage, ClientNotification, ClientRequest, EmptyResult, InitializedNotification, 11 | InitializedNotificationMethod, RequestId, ServerJsonRpcMessage, 12 | }; 13 | use std::collections::HashMap; 14 | use std::time::{Duration, Instant}; 15 | use tokio::sync::mpsc::Sender; 16 | use tokio::time::sleep; 17 | use tracing::{debug, error, info, warn}; 18 | 19 | /// Reasons why a reconnection attempt might fail. 20 | #[derive(Debug)] 21 | pub enum ReconnectFailureReason { 22 | TimeoutExceeded, 23 | ConnectionFailed(anyhow::Error), 24 | } 25 | 26 | /// Buffer mode for message handling during disconnection 27 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 28 | pub enum BufferMode { 29 | Store, 30 | Fail, 31 | } 32 | 33 | /// Proxy state to track connection and message handling 34 | #[derive(Debug, Clone, PartialEq, Eq)] 35 | pub enum ProxyState { 36 | Connecting, 37 | Connected, 38 | Disconnected, 39 | WaitingForClientInit, 40 | WaitingForServerInit(RequestId), 41 | WaitingForServerInitHidden(RequestId), 42 | WaitingForClientInitialized, 43 | } 44 | 45 | /// Application state for the proxy 46 | #[derive(Debug)] 47 | pub struct AppState { 48 | /// URL of the SSE server 49 | pub url: String, 50 | /// Maximum time to try reconnecting in seconds (None = infinity) 51 | pub max_disconnected_time: Option, 52 | /// When we were disconnected 53 | pub disconnected_since: Option, 54 | /// Current state of the application 55 | pub state: ProxyState, 56 | /// Number of connection attempts 57 | pub connect_tries: u32, 58 | /// The initialization message (for reconnection) 59 | pub init_message: Option, 60 | /// Map of generated IDs to original IDs (Client -> Server flow) 61 | pub id_map: HashMap, 62 | /// Buffer for holding messages during reconnection 63 | pub in_buf: Vec, 64 | /// Buffer mode (store or fail) 65 | pub buf_mode: BufferMode, 66 | /// Whether a flush timer is in progress 67 | pub flush_timer_active: bool, 68 | /// Channel sender for reconnect events 69 | pub reconnect_tx: Option>, 70 | /// Channel sender for timer events 71 | pub timer_tx: Option>, 72 | /// Whether reconnect is already scheduled 73 | pub reconnect_scheduled: bool, 74 | /// Whether the transport is still valid 75 | pub transport_valid: bool, 76 | /// Time of last heartbeat check 77 | pub last_heartbeat: Instant, 78 | } 79 | 80 | impl AppState { 81 | pub fn new(url: String, max_disconnected_time: Option) -> Self { 82 | Self { 83 | url, 84 | max_disconnected_time, 85 | disconnected_since: None, 86 | state: ProxyState::Connecting, 87 | connect_tries: 0, 88 | init_message: None, 89 | id_map: HashMap::new(), 90 | in_buf: Vec::new(), 91 | buf_mode: BufferMode::Store, 92 | flush_timer_active: false, 93 | reconnect_tx: None, 94 | timer_tx: None, 95 | reconnect_scheduled: false, 96 | transport_valid: true, 97 | last_heartbeat: Instant::now(), 98 | } 99 | } 100 | 101 | pub fn connected(&mut self) { 102 | self.state = ProxyState::Connected; 103 | self.connect_tries = 0; 104 | self.disconnected_since = None; 105 | self.buf_mode = BufferMode::Store; 106 | self.reconnect_scheduled = false; 107 | self.transport_valid = true; 108 | self.last_heartbeat = Instant::now(); 109 | } 110 | 111 | pub fn disconnected(&mut self) { 112 | if self.state != ProxyState::Disconnected { 113 | debug!("State changing to disconnected"); 114 | self.state = ProxyState::Disconnected; 115 | self.disconnected_since = Some(Instant::now()); 116 | self.buf_mode = BufferMode::Store; 117 | self.transport_valid = false; 118 | } 119 | self.connect_tries += 1; 120 | } 121 | 122 | pub fn disconnected_too_long(&self) -> bool { 123 | match (self.max_disconnected_time, self.disconnected_since) { 124 | (Some(max_time), Some(since)) => since.elapsed().as_secs() > max_time, 125 | _ => false, 126 | } 127 | } 128 | 129 | pub fn get_backoff_duration(&self) -> Duration { 130 | let seconds = std::cmp::min(2u64.pow(self.connect_tries.saturating_sub(1)), 8); 131 | Duration::from_secs(seconds) 132 | } 133 | 134 | pub fn schedule_reconnect(&mut self) { 135 | if !self.reconnect_scheduled { 136 | if let Some(tx) = &self.reconnect_tx { 137 | let tx_clone = tx.clone(); 138 | let backoff = self.get_backoff_duration(); 139 | debug!("Scheduling reconnect in {}s", backoff.as_secs()); 140 | tokio::spawn(async move { 141 | sleep(backoff).await; 142 | let _ = tx_clone.send(()).await; 143 | }); 144 | self.reconnect_scheduled = true; 145 | } 146 | } else { 147 | debug!("Reconnect already scheduled, skipping"); 148 | } 149 | } 150 | 151 | pub fn schedule_flush_timer(&mut self) { 152 | if !self.flush_timer_active { 153 | if let Some(tx) = &self.timer_tx { 154 | debug!("Scheduling flush timer for 20s"); 155 | self.flush_timer_active = true; 156 | let tx_clone = tx.clone(); 157 | tokio::spawn(async move { 158 | sleep(Duration::from_secs(20)).await; 159 | let _ = tx_clone.send(()).await; 160 | }); 161 | } 162 | } else { 163 | debug!("Flush timer already active, skipping"); 164 | } 165 | } 166 | 167 | pub fn update_heartbeat(&mut self) { 168 | self.last_heartbeat = Instant::now(); 169 | } 170 | 171 | /// Handles common logic for fatal transport errors: 172 | /// Sets state to disconnected and schedules timer/reconnect. 173 | pub fn handle_fatal_transport_error(&mut self) { 174 | if self.state != ProxyState::Disconnected { 175 | self.disconnected(); 176 | self.schedule_flush_timer(); 177 | self.schedule_reconnect(); 178 | } 179 | } 180 | 181 | /// Handles messages received from stdin. 182 | /// Returns Ok(true) to continue processing, Ok(false) to break the main loop. 183 | pub(crate) async fn handle_stdin_message( 184 | &mut self, 185 | msg: Option< 186 | Result, 187 | >, 188 | transport: &mut SseClientType, 189 | stdout_sink: &mut StdoutSink, 190 | ) -> Result { 191 | match msg { 192 | Some(Ok(message)) => { 193 | process_client_request(message, self, transport, stdout_sink).await?; 194 | Ok(true) 195 | } 196 | Some(Err(e)) => { 197 | error!("Error reading from stdin: {}", e); 198 | Ok(false) 199 | } 200 | None => { 201 | info!("Stdin stream ended."); 202 | Ok(false) 203 | } 204 | } 205 | } 206 | 207 | /// Handles messages received from the SSE transport. 208 | /// Returns Ok(true) to continue processing, Ok(false) to break the main loop. 209 | pub(crate) async fn handle_sse_message( 210 | &mut self, 211 | result: Option, 212 | transport: &mut SseClientType, 213 | stdout_sink: &mut StdoutSink, 214 | ) -> Result { 215 | debug!("Received SSE message: {:?}", result); 216 | match result { 217 | Some(mut message) => { 218 | self.update_heartbeat(); 219 | 220 | // --- Handle Server-Initiated Request --- 221 | if let ServerJsonRpcMessage::Request(mut req) = message { 222 | let server_id = req.id.clone(); 223 | let proxy_id_str = generate_id(); 224 | let proxy_id = RequestId::String(proxy_id_str.clone().into()); 225 | debug!( 226 | "Mapping server request ID {} to proxy ID {}", 227 | server_id, proxy_id 228 | ); 229 | self.id_map.insert(proxy_id_str, server_id); 230 | req.id = proxy_id; 231 | message = ServerJsonRpcMessage::Request(req); 232 | // Now fall through to forward the modified request 233 | } 234 | // --- End Server-Initiated Request Handling --- 235 | else { 236 | match self.map_server_response_error_id(message) { 237 | Some(mapped_message) => message = mapped_message, 238 | None => return Ok(true), // Skip forwarding this message 239 | } 240 | // --- Handle Initialization Response --- (Only for Response/Error) 241 | let is_init_response = match &message { 242 | ServerJsonRpcMessage::Response(response) => match self.state { 243 | ProxyState::WaitingForServerInit(ref init_request_id) => { 244 | *init_request_id == response.id 245 | } 246 | ProxyState::WaitingForServerInitHidden(ref init_request_id) => { 247 | *init_request_id == response.id 248 | } 249 | _ => false, 250 | }, 251 | // Don't treat errors related to init ID as special init responses 252 | _ => false, 253 | }; 254 | 255 | debug!( 256 | "Handling initialization response - state: {:?}, message: {:?}, is_init_response: {}", 257 | self.state, message, is_init_response 258 | ); 259 | 260 | if is_init_response { 261 | let was_hidden = 262 | matches!(self.state, ProxyState::WaitingForServerInitHidden(_)); 263 | if was_hidden { 264 | self.connected(); 265 | debug!("Reconnection successful, received hidden init response"); 266 | let initialized_notification = ClientJsonRpcMessage::notification( 267 | ClientNotification::InitializedNotification( 268 | InitializedNotification { 269 | method: InitializedNotificationMethod, 270 | extensions: rmcp::model::Extensions::default(), 271 | }, 272 | ), 273 | ); 274 | if let Err(e) = transport.send(initialized_notification).await { 275 | error!( 276 | "Error sending initialized notification post-reconnect: {}", 277 | e 278 | ); 279 | self.handle_fatal_transport_error(); 280 | } else { 281 | process_buffered_messages(self, transport, stdout_sink).await?; 282 | } 283 | return Ok(true); // Don't forward the init response 284 | } else { 285 | debug!( 286 | "Initial connection successful, received init response. Waiting for client initialized." 287 | ); 288 | self.state = ProxyState::WaitingForClientInitialized; 289 | } 290 | } 291 | // --- End Initialization Response Handling --- 292 | } 293 | 294 | // Forward the (potentially modified) message to stdout 295 | // This now handles mapped server requests, mapped responses/errors, and notifications 296 | debug!("Forwarding from SSE to stdout: {:?}", message); 297 | if let Err(e) = stdout_sink.send(message).await { 298 | error!("Error writing to stdout: {}", e); 299 | return Ok(false); 300 | } 301 | 302 | Ok(true) 303 | } 304 | None => { 305 | debug!("SSE stream ended (Fatal error in transport) - trying to reconnect"); 306 | self.handle_fatal_transport_error(); 307 | Ok(true) 308 | } 309 | } 310 | } 311 | 312 | pub(crate) async fn maybe_handle_message_while_disconnected( 313 | &mut self, 314 | message: ClientJsonRpcMessage, 315 | stdout_sink: &mut StdoutSink, 316 | ) -> Result<()> { 317 | if self.state != ProxyState::Disconnected { 318 | return Err(anyhow::anyhow!("Not disconnected")); 319 | } 320 | 321 | // Handle ping directly if disconnected 322 | if let ClientJsonRpcMessage::Request(ref req) = message { 323 | if let ClientRequest::PingRequest(_) = &req.request { 324 | debug!( 325 | "Received Ping request while disconnected, replying directly: {:?}", 326 | req.id 327 | ); 328 | let response = ServerJsonRpcMessage::response( 329 | rmcp::model::ServerResult::EmptyResult(EmptyResult {}), 330 | req.id.clone(), 331 | ); 332 | if let Err(e) = stdout_sink.send(response).await { 333 | error!("Error sending direct ping response to stdout: {}", e); 334 | } 335 | return Ok(()); 336 | } 337 | if self.buf_mode == BufferMode::Store { 338 | debug!("Buffering request for later retry"); 339 | self.in_buf.push(message); 340 | } else { 341 | reply_disconnected(&req.id, stdout_sink).await?; 342 | } 343 | } 344 | 345 | Ok(()) 346 | } 347 | 348 | /// Handles the reconnect signal. 349 | /// Returns the potentially new transport if reconnection was successful. 350 | pub(crate) async fn handle_reconnect_signal( 351 | &mut self, 352 | stdout_sink: &mut StdoutSink, 353 | ) -> Result> { 354 | debug!("Received reconnect signal"); 355 | self.reconnect_scheduled = false; 356 | 357 | if self.state == ProxyState::Disconnected { 358 | match try_reconnect(self).await { 359 | Ok(mut new_transport) => { 360 | self.transport_valid = true; 361 | 362 | initiate_post_reconnect_handshake(self, &mut new_transport, stdout_sink) 363 | .await 364 | .map(|success| { 365 | if success { 366 | Some(new_transport) 367 | } else { 368 | None // Handshake failed non-fatally, no new transport 369 | } 370 | }) 371 | } 372 | Err(reason) => { 373 | self.connect_tries += 1; 374 | match reason { 375 | ReconnectFailureReason::TimeoutExceeded => { 376 | error!( 377 | "Reconnect attempt {} failed: Timeout exceeded", 378 | self.connect_tries 379 | ); 380 | info!("Disconnected too long, flushing buffer."); 381 | flush_buffer_with_errors(self, stdout_sink).await?; 382 | } 383 | ReconnectFailureReason::ConnectionFailed(e) => { 384 | error!( 385 | "Reconnect attempt {} failed: Connection error: {}", 386 | self.connect_tries, e 387 | ); 388 | if !self.disconnected_too_long() { 389 | self.schedule_reconnect(); 390 | } else { 391 | info!( 392 | "Disconnected too long after failed connect, flushing buffer." 393 | ); 394 | flush_buffer_with_errors(self, stdout_sink).await?; 395 | } 396 | } 397 | } 398 | Ok(None) 399 | } 400 | } 401 | } else { 402 | Ok(None) 403 | } 404 | } 405 | 406 | /// Handles the flush timer signal. 407 | pub(crate) async fn handle_timer_signal(&mut self, stdout_sink: &mut StdoutSink) -> Result<()> { 408 | debug!("Received flush timer signal"); 409 | self.flush_timer_active = false; 410 | if self.state == ProxyState::Disconnected { 411 | info!("Still disconnected after 20 seconds, flushing buffer with errors"); 412 | flush_buffer_with_errors(self, stdout_sink).await?; 413 | } 414 | Ok(()) 415 | } 416 | 417 | /// Handles the heartbeat interval tick. 418 | pub(crate) async fn handle_heartbeat_tick( 419 | &mut self, 420 | transport: &mut SseClientType, 421 | ) -> Result<()> { 422 | if self.state == ProxyState::Connected { 423 | let check_result = send_heartbeat_if_needed(self, transport).await; 424 | match check_result { 425 | Some(true) => { 426 | self.update_heartbeat(); 427 | } 428 | Some(false) => { 429 | self.handle_fatal_transport_error(); 430 | } 431 | None => {} 432 | } 433 | } 434 | Ok(()) 435 | } 436 | 437 | // --- ID Mapping Helpers --- 438 | fn lookup_and_remove_original_id(&mut self, current_id: &RequestId) -> Option { 439 | let lookup_key = current_id.to_string(); 440 | self.id_map.remove(&lookup_key) 441 | } 442 | 443 | // Add the client mapping logic here 444 | pub(crate) fn map_client_response_error_id( 445 | &mut self, 446 | message: ClientJsonRpcMessage, 447 | ) -> Option { 448 | let (id_to_check, is_response_or_error) = match &message { 449 | ClientJsonRpcMessage::Response(res) => (Some(res.id.clone()), true), 450 | ClientJsonRpcMessage::Error(err) => (Some(err.id.clone()), true), 451 | _ => (None, false), // Requests or Notifications are not mapped back this way 452 | }; 453 | 454 | if is_response_or_error { 455 | if let Some(current_id) = id_to_check { 456 | if let Some(original_id) = self.lookup_and_remove_original_id(¤t_id) { 457 | debug!( 458 | "Mapping client message ID {} back to original server ID: {}", 459 | current_id, original_id 460 | ); 461 | return Some(match message { 462 | ClientJsonRpcMessage::Response(mut res) => { 463 | res.id = original_id; 464 | ClientJsonRpcMessage::Response(res) 465 | } 466 | ClientJsonRpcMessage::Error(mut err) => { 467 | err.id = original_id; 468 | ClientJsonRpcMessage::Error(err) 469 | } 470 | _ => message, // Should not happen 471 | }); 472 | } else { 473 | // ID not found, return None to prevent forwarding 474 | warn!( 475 | "Received client response/error with unknown ID: {}, skipping forwarding.", 476 | current_id 477 | ); 478 | return None; 479 | } 480 | } else { 481 | // Error message has no ID (should not happen for JSON-RPC errors) 482 | warn!("Received client error message without an ID, skipping forwarding."); 483 | return None; 484 | } 485 | } 486 | // Not a response/error, return Some(original_message) 487 | Some(message) 488 | } 489 | 490 | /// Checks if a server message (Response or Error) has an ID corresponding 491 | /// to a mapped client request ID. If found, replaces the message's ID with 492 | /// the original client ID and returns `Some` modified message. 493 | /// Otherwise, returns `None`. 494 | // Renamed from try_map_server_message_id and made private 495 | pub(crate) fn map_server_response_error_id( 496 | &mut self, 497 | message: ServerJsonRpcMessage, 498 | ) -> Option { 499 | let (id_to_check, is_response_or_error) = match &message { 500 | ServerJsonRpcMessage::Response(res) => (Some(res.id.clone()), true), 501 | ServerJsonRpcMessage::Error(err) => (Some(err.id.clone()), true), 502 | _ => (None, false), // Notifications or Requests are not mapped back 503 | }; 504 | 505 | if is_response_or_error { 506 | if let Some(current_id) = id_to_check { 507 | if let Some(original_id) = self.lookup_and_remove_original_id(¤t_id) { 508 | debug!( 509 | "Mapping server message ID {} back to original client ID: {}", 510 | current_id, original_id 511 | ); 512 | return Some(match message { 513 | ServerJsonRpcMessage::Response(mut res) => { 514 | res.id = original_id; 515 | ServerJsonRpcMessage::Response(res) 516 | } 517 | ServerJsonRpcMessage::Error(mut err) => { 518 | err.id = original_id; 519 | ServerJsonRpcMessage::Error(err) 520 | } 521 | _ => message, // Should not happen due to is_response_or_error check 522 | }); 523 | } else { 524 | // ID not found, return None to prevent forwarding 525 | warn!( 526 | "Received server response/error with unknown ID: {}, skipping forwarding.", 527 | current_id 528 | ); 529 | return None; 530 | } 531 | } else { 532 | // Error message has no ID (should not happen for JSON-RPC errors) 533 | warn!("Received server error message without an ID, skipping forwarding."); 534 | return None; 535 | } 536 | } 537 | // Not a response/error, return Some(original_message) 538 | Some(message) 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /tests/advanced_test.rs: -------------------------------------------------------------------------------- 1 | mod echo; 2 | use anyhow::Result; 3 | use rmcp::{ 4 | ServiceExt, 5 | model::CallToolRequestParam, 6 | object, 7 | transport::{ConfigureCommandExt, TokioChildProcess}, 8 | }; 9 | use std::{ 10 | net::SocketAddr, 11 | sync::{Arc, Mutex}, 12 | time::Duration, 13 | }; 14 | use tokio::{ 15 | io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, 16 | time::{sleep, timeout}, 17 | }; 18 | 19 | /// A guard that ensures processes are killed on drop, especially on test failures (panics) 20 | struct TestGuard { 21 | child: Option, 22 | server_handle: Option, 23 | stderr_buffer: Arc>>, 24 | } 25 | 26 | impl TestGuard { 27 | fn new( 28 | child: tokio::process::Child, 29 | server_handle: tokio::process::Child, 30 | stderr_buffer: Arc>>, 31 | ) -> Self { 32 | Self { 33 | child: Some(child), 34 | server_handle: Some(server_handle), 35 | stderr_buffer, 36 | } 37 | } 38 | } 39 | 40 | impl Drop for TestGuard { 41 | fn drop(&mut self) { 42 | // If we're dropping because of a panic, print the stderr content 43 | if std::thread::panicking() { 44 | eprintln!("Test failed! Process stderr output:"); 45 | for line in self.stderr_buffer.lock().unwrap().iter() { 46 | eprintln!("{}", line); 47 | } 48 | } 49 | 50 | // Force kill both processes 51 | if let Some(mut child) = self.child.take() { 52 | let _ = child.start_kill(); 53 | } 54 | if let Some(mut server_handle) = self.server_handle.take() { 55 | let _ = server_handle.start_kill(); 56 | } 57 | } 58 | } 59 | 60 | /// Spawns a proxy process with stdin, stdout, and stderr all captured 61 | async fn spawn_proxy( 62 | server_url: &str, 63 | extra_args: Vec<&str>, 64 | ) -> Result<( 65 | tokio::process::Child, 66 | tokio::io::BufReader, 67 | tokio::io::BufReader, 68 | tokio::process::ChildStdin, 69 | )> { 70 | let mut cmd = tokio::process::Command::new("./target/debug/mcp-proxy"); 71 | cmd.arg(server_url) 72 | .args(extra_args) 73 | .stdout(std::process::Stdio::piped()) 74 | .stderr(std::process::Stdio::piped()) 75 | .stdin(std::process::Stdio::piped()); 76 | 77 | let mut child = cmd.spawn()?; 78 | let stdin = child.stdin.take().unwrap(); 79 | let stdout = BufReader::new(child.stdout.take().unwrap()); 80 | let stderr = BufReader::new(child.stderr.take().unwrap()); 81 | 82 | Ok((child, stdout, stderr, stdin)) 83 | } 84 | 85 | /// Collects stderr lines in the background 86 | fn collect_stderr( 87 | mut stderr_reader: BufReader, 88 | ) -> Arc>> { 89 | let stderr_buffer = Arc::new(Mutex::new(Vec::new())); 90 | let buffer_clone = stderr_buffer.clone(); 91 | 92 | tokio::spawn(async move { 93 | let mut line = String::new(); 94 | while let Ok(bytes_read) = stderr_reader.read_line(&mut line).await { 95 | if bytes_read == 0 { 96 | break; 97 | } 98 | buffer_clone.lock().unwrap().push(line.clone()); 99 | line.clear(); 100 | } 101 | }); 102 | 103 | stderr_buffer 104 | } 105 | 106 | // Creates a new SSE server for testing 107 | // Starts the echo-server as a subprocess 108 | async fn create_sse_server( 109 | server_name: &str, 110 | address: SocketAddr, 111 | ) -> Result<(tokio::process::Child, String)> { 112 | let url = if server_name == "echo_streamable" { 113 | format!("http://{}", address) 114 | } else { 115 | format!("http://{}/sse", address) 116 | }; 117 | 118 | tracing::info!("Starting echo-server at {}", url); 119 | 120 | // Create echo-server process 121 | let mut cmd = tokio::process::Command::new(format!("./target/debug/examples/{}", server_name)); 122 | cmd.arg("--address").arg(address.to_string()); 123 | 124 | tracing::debug!("cmd: {:?}", cmd); 125 | 126 | // Start the process with stdout/stderr redirected to null 127 | let child = cmd 128 | .stdout(std::process::Stdio::null()) 129 | .stderr(std::process::Stdio::null()) 130 | .spawn()?; 131 | 132 | // Give the server time to start up 133 | sleep(Duration::from_millis(500)).await; 134 | tracing::info!("{} server started successfully", server_name); 135 | 136 | Ok((child, url)) 137 | } 138 | 139 | async fn protocol_initialization(server_name: &str) -> Result<()> { 140 | const BIND_ADDRESS: &str = "127.0.0.1:8181"; 141 | let (server_handle, server_url) = create_sse_server(server_name, BIND_ADDRESS.parse()?).await?; 142 | 143 | // Create a child process for the proxy with stderr capture 144 | let (child, mut reader, stderr_reader, mut stdin) = spawn_proxy(&server_url, vec![]).await?; 145 | let stderr_buffer = collect_stderr(stderr_reader); 146 | let _guard = TestGuard::new(child, server_handle, stderr_buffer); 147 | 148 | // Send initialization message 149 | let init_message = r#"{"jsonrpc":"2.0","id":"init-1","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0.1.0"}}}"#; 150 | stdin.write_all(init_message.as_bytes()).await?; 151 | stdin.write_all(b"\n").await?; 152 | 153 | // Read the response 154 | let mut response = String::new(); 155 | reader.read_line(&mut response).await?; 156 | 157 | // Verify the response contains expected data 158 | assert!(response.contains("\"id\":\"init-1\"")); 159 | assert!(response.contains("\"result\"")); 160 | 161 | // Send initialized notification 162 | let initialized_message = r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#; 163 | stdin.write_all(initialized_message.as_bytes()).await?; 164 | stdin.write_all(b"\n").await?; 165 | 166 | // Call the echo tool 167 | let echo_call = r#"{"jsonrpc":"2.0","id":"call-1","method":"tools/call","params":{"name":"echo","arguments":{"message":"Hey!"}}}"#; 168 | stdin.write_all(echo_call.as_bytes()).await?; 169 | stdin.write_all(b"\n").await?; 170 | 171 | // Read the response 172 | let mut echo_response = String::new(); 173 | reader.read_line(&mut echo_response).await?; 174 | 175 | // Verify the echo response 176 | assert!(echo_response.contains("\"id\":\"call-1\"")); 177 | assert!(echo_response.contains("Hey!")); 178 | 179 | Ok(()) 180 | } 181 | 182 | #[tokio::test] 183 | async fn test_protocol_initialization() -> Result<()> { 184 | protocol_initialization("echo").await?; 185 | protocol_initialization("echo_streamable").await?; 186 | 187 | Ok(()) 188 | } 189 | 190 | async fn reconnection_handling(server_name: &str) -> Result<()> { 191 | let subscriber = tracing_subscriber::fmt() 192 | .with_max_level(tracing::Level::DEBUG) 193 | .with_test_writer() 194 | .finish(); 195 | let _guard = tracing::subscriber::set_default(subscriber); 196 | 197 | const BIND_ADDRESS: &str = "127.0.0.1:8182"; 198 | 199 | // Start the SSE server 200 | tracing::info!("Test: Starting initial SSE server"); 201 | let (server_handle, server_url) = create_sse_server(server_name, BIND_ADDRESS.parse()?).await?; 202 | 203 | // Create a child process for the proxy 204 | tracing::info!("Test: Creating proxy process"); 205 | let (child, mut reader, stderr_reader, mut stdin) = spawn_proxy(&server_url, vec![]).await?; 206 | let stderr_buffer = collect_stderr(stderr_reader); 207 | let mut test_guard = TestGuard::new(child, server_handle, stderr_buffer); 208 | 209 | // Send initialization message 210 | let init_message = r#"{"jsonrpc":"2.0","id":"init-1","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0.1.0"}}}"#; 211 | stdin.write_all(init_message.as_bytes()).await?; 212 | stdin.write_all(b"\n").await?; 213 | 214 | // Read the response 215 | let mut response = String::new(); 216 | reader.read_line(&mut response).await?; 217 | 218 | // Send initialized notification 219 | let initialized_message = r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#; 220 | stdin.write_all(initialized_message.as_bytes()).await?; 221 | stdin.write_all(b"\n").await?; 222 | 223 | // Call the echo tool to ensure server is working 224 | let initial_echo_call = r#"{"jsonrpc":"2.0","id":"call-1","method":"tools/call","params":{"name":"echo","arguments":{"message":"Initial Call"}}}"#; 225 | stdin.write_all(initial_echo_call.as_bytes()).await?; 226 | stdin.write_all(b"\n").await?; 227 | 228 | let mut initial_echo_response = String::new(); 229 | reader.read_line(&mut initial_echo_response).await?; 230 | assert!( 231 | initial_echo_response.contains("Initial Call"), 232 | "Initial echo call failed" 233 | ); 234 | 235 | // Shutdown the server 236 | if let Some(mut server) = test_guard.server_handle.take() { 237 | server.kill().await?; 238 | } 239 | 240 | // Give the server time to shut down 241 | sleep(Duration::from_millis(1000)).await; 242 | 243 | // Create a new server on the same address 244 | tracing::info!("Test: Starting new SSE server"); 245 | let (new_server_handle, new_url) = 246 | create_sse_server(server_name, BIND_ADDRESS.parse()?).await?; 247 | assert_eq!( 248 | server_url, new_url, 249 | "New server URL should match the original" 250 | ); 251 | 252 | // Update the test guard with the new server handle 253 | test_guard.server_handle = Some(new_server_handle); 254 | 255 | // Give the proxy time to reconnect 256 | sleep(Duration::from_millis(3000)).await; 257 | 258 | // Call the echo tool after reconnection 259 | let echo_call = r#"{"jsonrpc":"2.0","id":"call-2","method":"tools/call","params":{"name":"echo","arguments":{"message":"After Reconnect"}}}"#; 260 | stdin.write_all(echo_call.as_bytes()).await?; 261 | stdin.write_all(b"\n").await?; 262 | 263 | // Read the response 264 | let mut echo_response = String::new(); 265 | reader.read_line(&mut echo_response).await?; 266 | 267 | tracing::info!("Test: Received echo response: {}", echo_response.trim()); 268 | 269 | // Even if the response contains an error, we should at least get a response 270 | assert!( 271 | echo_response.contains("\"id\":\"call-2\""), 272 | "No response received after reconnection" 273 | ); 274 | 275 | Ok(()) 276 | } 277 | 278 | #[tokio::test] 279 | async fn test_reconnection_handling() -> Result<()> { 280 | reconnection_handling("echo").await?; 281 | reconnection_handling("echo_streamable").await?; 282 | 283 | Ok(()) 284 | } 285 | 286 | async fn server_info_and_capabilities(server_name: &str) -> Result<()> { 287 | const BIND_ADDRESS: &str = "127.0.0.1:8183"; 288 | // Start the SSE server 289 | let (mut server_handle, server_url) = 290 | create_sse_server(server_name, BIND_ADDRESS.parse()?).await?; 291 | 292 | // Create a transport for the proxy 293 | let transport = TokioChildProcess::new( 294 | tokio::process::Command::new("./target/debug/mcp-proxy").configure(|cmd| { 295 | cmd.arg(&server_url); 296 | }), 297 | )?; 298 | 299 | // Connect a client to the proxy 300 | let client = ().serve(transport).await?; 301 | 302 | // List available tools 303 | let tools = client.list_all_tools().await?; 304 | 305 | // Verify the echo tool is available 306 | assert!(tools.iter().any(|t| t.name == "echo")); 307 | 308 | // Call the echo tool with a test message 309 | if let Some(echo_tool) = tools.iter().find(|t| t.name.contains("echo")) { 310 | let result = client 311 | .call_tool(CallToolRequestParam { 312 | name: echo_tool.name.clone(), 313 | arguments: Some(object!({ 314 | "message": "Testing server capabilities" 315 | })), 316 | }) 317 | .await?; 318 | 319 | // Verify the response 320 | let result_str = format!("{:?}", result); 321 | assert!(result_str.contains("Testing server capabilities")); 322 | } else { 323 | panic!("Echo tool not found"); 324 | } 325 | 326 | // Clean up 327 | drop(client); 328 | server_handle.kill().await?; 329 | 330 | Ok(()) 331 | } 332 | 333 | #[tokio::test] 334 | async fn test_server_info_and_capabilities() -> Result<()> { 335 | server_info_and_capabilities("echo").await?; 336 | server_info_and_capabilities("echo_streamable").await?; 337 | 338 | Ok(()) 339 | } 340 | 341 | async fn initial_connection_retry(server_name: &str) -> Result<()> { 342 | // Set up custom logger for this test to clearly see what's happening 343 | let subscriber = tracing_subscriber::fmt() 344 | .with_max_level(tracing::Level::INFO) 345 | .with_test_writer() 346 | .finish(); 347 | let _guard = tracing::subscriber::set_default(subscriber); 348 | 349 | const BIND_ADDRESS: &str = "127.0.0.1:8184"; 350 | let server_url = if server_name == "echo_streamable" { 351 | format!("http://{}", BIND_ADDRESS) 352 | } else { 353 | format!("http://{}/sse", BIND_ADDRESS) 354 | }; 355 | let bind_addr: SocketAddr = BIND_ADDRESS.parse()?; 356 | 357 | // 1. Start the proxy process BEFORE the server 358 | tracing::info!("Test: Starting proxy process..."); 359 | let (child, mut reader, stderr_reader, mut stdin) = 360 | spawn_proxy(&server_url, vec!["--initial-retry-interval", "1"]).await?; 361 | 362 | let stderr_buffer = collect_stderr(stderr_reader); 363 | 364 | // 2. Wait for slightly longer than the proxy's retry delay 365 | // This ensures the proxy has attempted connection at least once and is retrying. 366 | let retry_wait = Duration::from_secs(2); 367 | tracing::info!( 368 | "Test: Waiting {:?} for proxy to attempt connection...", 369 | retry_wait 370 | ); 371 | sleep(retry_wait).await; 372 | 373 | // Send initialize message WHILE proxy is still trying to connect 374 | // (it will be buffered by the OS pipe until proxy reads stdin) 375 | tracing::info!("Test: Sending initialize request (before server starts)..."); 376 | let init_message = r#"{"jsonrpc":"2.0","id":"init-retry","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"retry-test","version":"0.1.0"}}}"#; 377 | stdin.write_all(init_message.as_bytes()).await?; 378 | stdin.write_all(b"\n").await?; 379 | 380 | // 3. Start the SSE server AFTER the wait and AFTER sending init 381 | tracing::info!("Test: Starting SSE server on {}", BIND_ADDRESS); 382 | let (server_handle, returned_url) = create_sse_server(server_name, bind_addr).await?; 383 | assert_eq!(server_url, returned_url, "Server URL mismatch"); 384 | 385 | let _test_guard = TestGuard::new(child, server_handle, stderr_buffer); 386 | 387 | // 4. Proceed with initialization handshake (Proxy should now process buffered init) 388 | // Read the initialize response (with a timeout) 389 | tracing::info!("Test: Waiting for initialize response..."); 390 | let mut init_response = String::new(); 391 | match timeout( 392 | Duration::from_secs(10), 393 | reader.read_line(&mut init_response), 394 | ) 395 | .await 396 | { 397 | Ok(Ok(_)) => { 398 | tracing::info!( 399 | "Test: Received initialize response: {}", 400 | init_response.trim() 401 | ); 402 | assert!( 403 | init_response.contains("\"id\":\"init-retry\""), 404 | "Init response missing correct ID" 405 | ); 406 | assert!( 407 | init_response.contains("\"result\""), 408 | "Init response missing result" 409 | ); 410 | } 411 | Ok(Err(e)) => return Err(anyhow::anyhow!("Error reading init response: {}", e)), 412 | Err(_) => return Err(anyhow::anyhow!("Timed out waiting for init response")), 413 | } 414 | 415 | tracing::info!("Test: Sending initialized notification..."); 416 | let initialized_message = r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#; 417 | stdin.write_all(initialized_message.as_bytes()).await?; 418 | stdin.write_all(b"\n").await?; 419 | 420 | // 5. Test basic functionality (e.g., echo tool call) 421 | tracing::info!("Test: Sending echo request..."); 422 | let echo_call = r#"{"jsonrpc":"2.0","id":"call-retry","method":"tools/call","params":{"name":"echo","arguments":{"message":"Hello after initial retry!"}}}"#; 423 | stdin.write_all(echo_call.as_bytes()).await?; 424 | stdin.write_all(b"\n").await?; 425 | 426 | tracing::info!("Test: Waiting for echo response..."); 427 | let mut echo_response = String::new(); 428 | match timeout(Duration::from_secs(5), reader.read_line(&mut echo_response)).await { 429 | Ok(Ok(_)) => { 430 | tracing::info!("Test: Received echo response: {}", echo_response.trim()); 431 | assert!( 432 | echo_response.contains("\"id\":\"call-retry\""), 433 | "Echo response missing correct ID" 434 | ); 435 | assert!( 436 | echo_response.contains("Hello after initial retry!"), 437 | "Echo response missing correct message" 438 | ); 439 | } 440 | Ok(Err(e)) => return Err(anyhow::anyhow!("Error reading echo response: {}", e)), 441 | Err(_) => return Err(anyhow::anyhow!("Timed out waiting for echo response")), 442 | } 443 | 444 | tracing::info!("Test: Completed successfully"); 445 | Ok(()) 446 | } 447 | 448 | #[tokio::test] 449 | async fn test_initial_connection_retry() -> Result<()> { 450 | initial_connection_retry("echo").await?; 451 | initial_connection_retry("echo_streamable").await?; 452 | 453 | Ok(()) 454 | } 455 | 456 | async fn ping_when_disconnected(server_name: &str) -> Result<()> { 457 | const BIND_ADDRESS: &str = "127.0.0.1:8185"; 458 | let subscriber = tracing_subscriber::fmt() 459 | .with_max_level(tracing::Level::DEBUG) 460 | .with_test_writer() 461 | .finish(); 462 | let _guard = tracing::subscriber::set_default(subscriber); 463 | 464 | // 1. Start the SSE server 465 | tracing::info!("Test: Starting SSE server for ping test"); 466 | let (server_handle, server_url) = create_sse_server(server_name, BIND_ADDRESS.parse()?).await?; 467 | 468 | // Create a child process for the proxy 469 | tracing::info!("Test: Creating proxy process"); 470 | let (child, mut reader, stderr_reader, mut stdin) = 471 | spawn_proxy(&server_url, vec!["--debug"]).await?; 472 | 473 | let stderr_buffer = collect_stderr(stderr_reader); 474 | 475 | let mut test_guard = TestGuard::new(child, server_handle, stderr_buffer); 476 | 477 | // 2. Initializes everything 478 | let init_message = r#"{"jsonrpc":"2.0","id":"init-ping","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ping-test","version":"0.1.0"}}}"#; 479 | tracing::info!("Test: Sending initialize request"); 480 | stdin.write_all(init_message.as_bytes()).await?; 481 | stdin.write_all(b"\n").await?; 482 | 483 | // Read the initialize response 484 | let mut init_response = String::new(); 485 | match timeout(Duration::from_secs(5), reader.read_line(&mut init_response)).await { 486 | Ok(Ok(_)) => { 487 | tracing::info!( 488 | "Test: Received initialize response: {}", 489 | init_response.trim() 490 | ); 491 | assert!(init_response.contains("\"id\":\"init-ping\"")); 492 | } 493 | Ok(Err(e)) => panic!("Failed to read init response: {}", e), 494 | Err(_) => panic!("Timed out waiting for init response"), 495 | } 496 | 497 | // Send initialized notification 498 | let initialized_message = r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#; 499 | tracing::info!("Test: Sending initialized notification"); 500 | stdin.write_all(initialized_message.as_bytes()).await?; 501 | stdin.write_all(b"\n").await?; 502 | // Allow time for proxy to process initialized and potentially send buffered msgs (if any) 503 | sleep(Duration::from_millis(100)).await; 504 | 505 | // 3. Kills the SSE server 506 | tracing::info!("Test: Shutting down SSE server"); 507 | if let Some(mut server) = test_guard.server_handle.take() { 508 | server.kill().await?; 509 | } 510 | // Give the server time to shut down and the proxy time to notice 511 | sleep(Duration::from_secs(3)).await; 512 | 513 | // 4. Sends a ping request 514 | let ping_message = r#"{"jsonrpc":"2.0","id":"ping-1","method":"ping"}"#; 515 | tracing::info!("Test: Sending ping request while server is down"); 516 | stdin.write_all(ping_message.as_bytes()).await?; 517 | stdin.write_all(b"\n").await?; 518 | 519 | // 5. Checks that it receives a response 520 | let mut ping_response = String::new(); 521 | match timeout(Duration::from_secs(2), reader.read_line(&mut ping_response)).await { 522 | Ok(Ok(_)) => { 523 | tracing::info!("Test: Received ping response: {}", ping_response.trim()); 524 | // Expecting: {"jsonrpc":"2.0","id":"ping-1","result":{}} 525 | assert!( 526 | ping_response.contains("\"id\":\"ping-1\""), 527 | "Response ID mismatch" 528 | ); 529 | assert!( 530 | ping_response.contains("\"result\":{}"), 531 | "Expected empty result object" 532 | ); 533 | } 534 | Ok(Err(e)) => panic!("Failed to read ping response: {}", e), 535 | Err(_) => panic!("Timed out waiting for ping response"), 536 | } 537 | 538 | Ok(()) 539 | } 540 | 541 | #[tokio::test] 542 | async fn test_ping_when_disconnected() -> Result<()> { 543 | ping_when_disconnected("echo").await?; 544 | ping_when_disconnected("echo_streamable").await?; 545 | 546 | Ok(()) 547 | } 548 | -------------------------------------------------------------------------------- /tests/basic_test.rs: -------------------------------------------------------------------------------- 1 | mod echo; 2 | use rmcp::{ 3 | ServiceExt, 4 | transport::{ConfigureCommandExt, SseServer, TokioChildProcess}, 5 | }; 6 | 7 | const BIND_ADDRESS: &str = "127.0.0.1:8099"; 8 | const TEST_SERVER_URL: &str = "http://localhost:8099/sse"; 9 | 10 | #[tokio::test] 11 | async fn test_proxy_connects_to_real_server() -> anyhow::Result<()> { 12 | let ct = SseServer::serve(BIND_ADDRESS.parse()?) 13 | .await? 14 | .with_service(echo::Echo::default); 15 | 16 | let transport = TokioChildProcess::new( 17 | tokio::process::Command::new("./target/debug/mcp-proxy").configure(|cmd| { 18 | cmd.arg(TEST_SERVER_URL); 19 | }), 20 | )?; 21 | 22 | let client = ().serve(transport).await?; 23 | let tools = client.list_all_tools().await?; 24 | 25 | // assert that the echo tool is available 26 | assert!(tools.iter().any(|t| t.name == "echo")); 27 | 28 | if let Some(echo_tool) = tools.iter().find(|t| t.name.contains("echo")) { 29 | let result = client 30 | .call_tool(rmcp::model::CallToolRequestParam { 31 | name: echo_tool.name.clone(), 32 | arguments: Some(rmcp::object!({ 33 | "message": "Hello, world!" 34 | })), 35 | }) 36 | .await?; 37 | 38 | // Assert that the result contains our expected text 39 | let result_debug = format!("{:?}", result); 40 | assert!( 41 | result_debug.contains("Hello, world!"), 42 | "Expected result to contain 'Hello, world!', but got: {}", 43 | result_debug 44 | ); 45 | 46 | println!("Result: {}", result_debug); 47 | } else { 48 | assert!(false, "No echo tool found"); 49 | } 50 | 51 | // Properly shutdown the client and kill the child process 52 | drop(client); 53 | // Wait for the server to shut down 54 | ct.cancel(); 55 | 56 | Ok(()) 57 | } 58 | -------------------------------------------------------------------------------- /tests/echo/mod.rs: -------------------------------------------------------------------------------- 1 | use rmcp::{ 2 | ServerHandler, 3 | model::{ServerCapabilities, ServerInfo}, 4 | schemars, tool, 5 | }; 6 | #[derive(Debug, Clone, Default)] 7 | pub struct Echo; 8 | #[tool(tool_box)] 9 | impl Echo { 10 | #[tool(description = "Echo a message")] 11 | fn echo(&self, #[tool(param)] message: String) -> String { 12 | message 13 | } 14 | } 15 | 16 | #[tool(tool_box)] 17 | impl ServerHandler for Echo { 18 | fn get_info(&self) -> ServerInfo { 19 | ServerInfo { 20 | instructions: Some("A simple echo server".into()), 21 | capabilities: ServerCapabilities::builder().enable_tools().build(), 22 | ..Default::default() 23 | } 24 | } 25 | } 26 | --------------------------------------------------------------------------------