diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 157ad86..a422853 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,19 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} + fuzz-audit: + name: Fuzz dependency audit + runs-on: ubuntu-latest + permissions: + contents: read + checks: write + steps: + - uses: actions/checkout@v7 + - uses: rustsec/audit-check@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + working-directory: fuzz + fmt: name: Format runs-on: ubuntu-latest @@ -32,6 +45,7 @@ jobs: with: components: rustfmt - run: cargo fmt --all -- --check + - run: cargo fmt --manifest-path fuzz/Cargo.toml --all -- --check clippy: name: Clippy @@ -73,6 +87,45 @@ jobs: docker pull redis:7-alpine - run: cargo test ${{ matrix.flags }} + feature-contracts: + name: Feature contracts (${{ matrix.features }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + features: [protocol-2026-07-28, 'oauth,discovery'] + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + key: contracts-${{ matrix.features }} + - run: cargo test --locked --no-default-features --features ${{ matrix.features }} --test protocol_contracts --test invariant_properties --test config_preparation + + fuzz: + name: Bounded fuzz smoke + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2026-10-19 + - uses: Swatinem/rust-cache@v2 + with: + workspaces: fuzz + key: fuzz-nightly-2026-10-19 + - uses: taiki-e/install-action@v2 + with: + tool: cargo-fuzz@0.13.2 + - run: ./scripts/fuzz-smoke.sh + - uses: actions/upload-artifact@v7 + if: failure() + with: + name: fuzz-reproducers + path: fuzz/artifacts/ + if-no-files-found: ignore + msrv: name: MSRV (1.90, ${{ matrix.features }}) runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index ef964ea..e2e1f24 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ CLAUDE.md proxy*.toml !config.example.toml !examples/docker-compose/proxy.toml +/fuzz/target/ +/fuzz/artifacts/ +/fuzz/corpus/ diff --git a/AGENTS.md b/AGENTS.md index b982a3a..63acdce 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,6 +76,8 @@ tests/ chaos.rs # Fault injection and resilience recovery regressions cache_backends.rs # Real Redis containers and temporary SQLite databases config_properties.rs # Property tests for config validation + invariant_properties.rs # Config/authorization/transform properties and paused-clock schedules + protocol_contracts.rs # Configured library/CLI legacy baseline and modern feature contracts examples.rs # Load and validate every example TOML config examples/ @@ -152,6 +154,20 @@ paused-clock competing recovery, late completion and removal/replacement tests. Manual detector accounting is separate and extra releases cannot consume owned reservations. See outlier rustdoc for quota shrink and compatibility semantics. +### Invariant and protocol regressions + +`tests/invariant_properties.rs` and `tests/protocol_contracts.rs` complement the +configured runtime regressions. Preserve the legacy `2025-11-25` negotiation +baseline and feature-gated modern per-request contract; never treat a successful +HTTP response alone as permission to dispatch. Reduced protocol-only and +OAuth/discovery builds must keep feature-negative configuration failures before +effects. Pure parser/transform assertions in `tests/support/invariants.rs` are +shared with the isolated, locked `fuzz/` workspace. Run `scripts/fuzz-smoke.sh` +with the pinned tools for changes to these assertions/targets/runner. Keep finite +input/run/time/RSS limits and temporary mutation corpora; promote minimized +failures to tracked seeds and focused regressions. See CONTRIBUTING.md for the +coverage matrix and reproduction commands. + ### Adding a new middleware layer Every middleware follows the same tower Service pattern. Use `inject.rs` as a template: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5ebbcd4..9b8a399 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -95,6 +95,72 @@ separate test matrix and are not included in this coverage report. Line coverage is a guide to missing tests, not proof of correctness. No percentage gate is set until there is a representative baseline and a reason for the chosen threshold. +## Invariants, protocol versions and bounded fuzzing + +The following checks complement the default/all/no-default-feature suites. They +exercise these contracts, rather than claiming exhaustive protocol conformance: + +| Contract | Regression coverage | +| --- | --- | +| Legacy initialization selects the proxy's supported `2025-11-25` baseline, including when the client requests an older, modern or unknown revision | `protocol_contracts`: configured library and real CLI; initialized notification, exact listing/call/denial, injection and session cleanup | +| `2026-07-28` requests use per-request metadata plus matching HTTP method/name headers, without initialization or a frontend session | `protocol_contracts`: both entry points; explicit unsupported-version error without the feature; malformed metadata/header and invalid credential denials make zero tool calls | +| Feature-disabled configuration fails before backend, store or IdP effects | `config_preparation`: prepare/library/CLI check/startup; OAuth, discovery, telemetry, metrics, WebSocket, Redis, SQLite and CLI YAML rejection | +| Preparation is idempotent; equivalent representable JSON/TOML/YAML revisions agree; invalid revisions do not partially mutate configuration | `invariant_properties`: bounded generated configurations | +| Role order/duplicates cannot widen grants; deny and static filter win; management remains separate | `invariant_properties`: generated JWT roles through a configured proxy, direct/search calls, aliased names, exact transformed arguments and backend call counts | +| Alias/parameter/injection transformations preserve request IDs, continuation/task/meta fields and extensions | `invariant_properties` and `request_transform` fuzz target share `support/invariants.rs` assertions against the actual services | +| Cancelled followers free capacity; completion/cancellation/expiry releases the generation without replay | `invariant_properties`: generated controlled future schedules with a paused clock; existing configured coalescing regressions remain required | +| Bounded parser inputs do not panic, and accepted configurations round-trip | `invariant_properties` and `config_parse` fuzz target; parsing only, never configuration preparation or runtime startup | + +The sessionful proxy advertises `2025-11-25`; recognizing older transport headers +does not promise negotiation of every older protocol revision. The optional +modern protocol is selected by per-request metadata, not by requesting it in +legacy initialization. These tests establish single-process behavior; replica +portability remains separate work. + +CI also tests two reduced builds (`--no-default-features --features +protocol-2026-07-28` and `--no-default-features --features oauth,discovery`) against +`protocol_contracts`, `invariant_properties` and `config_preparation`. Run each +feature mode sequentially when sharing a target directory, as described below. + +Install the pinned fuzz tools and run the same finite, AddressSanitizer and +coverage-instrumented smoke checks used by CI: + +```sh +rustup toolchain install nightly-2026-10-19 --profile minimal +cargo install cargo-fuzz --version 0.13.2 --locked +./scripts/fuzz-smoke.sh +``` + +The isolated `fuzz/Cargo.toml` workspace has its own committed lockfile and uses +the same approved upstream Git revision as the main package. CI audits both +lockfiles for known security advisories. The runner fetches +that lock with `--locked`, runs cargo-fuzz offline, and checks that the lock did +not change (cargo-fuzz 0.13.2 has no `--locked` flag). It explicitly selects the +installed Rust compiler's host target: prebuilt cargo-fuzz binaries may otherwise +select static musl, which is incompatible with AddressSanitizer. Each target has +a 4 KiB input limit, 256 runs, a 30-second total execution limit, a 5-second per-input +timeout and a 1 GiB RSS limit. The CI job has a separate 10-minute build/run +budget. Property cases and generated structures are also bounded; concurrency +properties do not depend on sleeps, handshake retries or timing luck. + +Checked-in `fuzz/seeds/` inputs are also exercised by ordinary tests. The smoke +runner copies them into a temporary corpus, so mutations do not dirty the +checkout. Failures leave reproducers in `fuzz/artifacts/` (uploaded by CI). +To reproduce one using the same host selection: + +```sh +fuzz_host=$(rustc +nightly-2026-10-19 -vV | sed -n 's/^host: //p') +cargo +nightly-2026-10-19 fuzz run TARGET PATH_TO_ARTIFACT --dev --target "$fuzz_host" +``` + +Minimize it with cargo-fuzz's `tmin` (also pass `--target "$fuzz_host"`), then +promote the smallest useful input into `fuzz/seeds/TARGET/` and add a focused +regression for its expected behavior. Fuzz inputs only reach pure parsing and +transformation code; they cannot spawn backends, resolve secrets or contact +stores/identity providers. A bounded +smoke run is an ongoing regression check, not evidence that all inputs or +concurrent schedules have been explored. + ## Configured application contracts Keep fast middleware/unit tests, but use `Proxy::from_config` or the executable diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 0000000..0f1e3d8 --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,3129 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arbitrary" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed" + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faac5829c2b74c28f830747e7818ccfb684261b5f48a1118b1e2a13d36dfab13" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1622d8446a2d4b2ce0c7eefc73dd43a99779028d5ee5c2dd8073a658ba8a2bc" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "base64 0.22.1", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877bf67ac8e52449eb2f9e98a344ea163beb70dd1f9eebe3cc5357dcfeaee4df" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "clap_lex" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "glob-match" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d" + +[[package]] +name = "h2" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "inotify" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "kqueue" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libyaml-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e126dda6f34391ab7b444f9922055facc83c07a910da3eb16f1e4d9c45dc777" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "mcp-proxy" +version = "0.6.0" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "chrono", + "clap", + "glob-match", + "hyper", + "hyper-util", + "moka", + "notify", + "notify-debouncer-mini", + "regex", + "schemars", + "serde", + "serde_json", + "sha2", + "tokio", + "tokio-util", + "toml", + "tower", + "tower-mcp", + "tower-mcp-types", + "tower-resilience", + "tracing", + "tracing-subscriber", + "url", + "uuid", + "yaml_serde", +] + +[[package]] +name = "mcp-proxy-fuzz" +version = "0.0.0" +dependencies = [ + "futures-util", + "libfuzzer-sys", + "mcp-proxy", + "serde_json", + "toml", + "tower", + "tower-mcp", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9" +dependencies = [ + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "event-listener", + "futures-util", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-debouncer-mini" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17849edfaabd9a5fef1c606d99cfc615a8e99f7ac4366406d86c7942a3184cf2" +dependencies = [ + "log", + "notify", + "notify-types", + "tempfile", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.3", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + +[[package]] +name = "rustls-webpki" +version = "0.103.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c3a92094fa7d61aa124645844facb6b554dfc797136d0f5fd1f890e2bffc69" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.9", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "foldhash", + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokio" +version = "1.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c168a474b95bfecfe908a65d4bdffc523144b649a74c09c286afb35d53e0bc" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "1.1.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-mcp" +version = "0.24.0" +source = "git+https://github.com/joshrotenberg/tower-mcp?rev=1eb3e4ec5f004dfb884004b2be2b083fafdbe88c#1eb3e4ec5f004dfb884004b2be2b083fafdbe88c" +dependencies = [ + "async-trait", + "axum", + "base64 0.23.1", + "futures", + "getrandom 0.4.3", + "http", + "http-body-util", + "hyper", + "libc", + "pin-project-lite", + "regex", + "reqwest", + "schemars", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tower-mcp-types", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "tower-mcp-types" +version = "0.24.0" +source = "git+https://github.com/joshrotenberg/tower-mcp?rev=1eb3e4ec5f004dfb884004b2be2b083fafdbe88c#1eb3e4ec5f004dfb884004b2be2b083fafdbe88c" +dependencies = [ + "base64 0.23.1", + "indexmap", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "tower-resilience" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54265ff304016b7ed75bb75dc8c57f23864b5d91f00157ed127aded6b67a4023" +dependencies = [ + "tower-resilience-circuitbreaker", + "tower-resilience-core", + "tower-resilience-hedge", + "tower-resilience-ratelimiter", + "tower-resilience-retry", +] + +[[package]] +name = "tower-resilience-circuitbreaker" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907b3e0e10be24e4b47abb41569882005025ad717ba38f735c22ec1196b72e6c" +dependencies = [ + "futures", + "thiserror", + "tokio", + "tower", + "tower-resilience-core", +] + +[[package]] +name = "tower-resilience-core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25eeb90521cdd34e4ed4ba21b5492311393eac927b015ca78c94658e13f5d137" +dependencies = [ + "thiserror", + "tokio", +] + +[[package]] +name = "tower-resilience-hedge" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc18f52cc06c42646260770d8674cc962b5b7b12deab1efa1a049ad87118b94" +dependencies = [ + "futures", + "tokio", + "tower", + "tower-layer", + "tower-resilience-core", +] + +[[package]] +name = "tower-resilience-ratelimiter" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f3f327e61f8a0bfc959c17d4487df29f1a1186a769111577a79a0281cd6de3" +dependencies = [ + "futures", + "thiserror", + "tokio", + "tower", + "tower-resilience-core", +] + +[[package]] +name = "tower-resilience-retry" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f063106ed2b340de8172d629ae7c965261d9cddef27d33820ce06abadb92d32" +dependencies = [ + "futures", + "rand 0.9.5", + "thiserror", + "tokio", + "tower", + "tower-resilience-core", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.5", + "sha1", + "thiserror", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" +dependencies = [ + "js-sys", + "tokio", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ec80e73fd8baf57bf0d9615554c75348bda990d6d5bbeab964dd068319fecb" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yaml_serde" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b729a08a9a6be689bbad3e2bf8015926db54b6622cc89c3a5f7dc174b9e918" +dependencies = [ + "indexmap", + "itoa", + "libyaml-rs", + "ryu", + "serde", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..4b35df5 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "mcp-proxy-fuzz" +version = "0.0.0" +publish = false +edition = "2024" + +[package.metadata] +cargo-fuzz = true + +# Isolate fuzz dependencies/profiles from the package's feature/MSRV checks. +[workspace] + +[features] +default = ["yaml"] +yaml = ["mcp-proxy/yaml"] + +[dependencies] +libfuzzer-sys = "=0.4.13" +mcp-proxy = { path = "..", default-features = false } +futures-util = "0.3" +serde_json = "1" +toml = "1" +tower = { version = "0.5", features = ["util"] } +tower-mcp = { version = "0.24.0", git = "https://github.com/joshrotenberg/tower-mcp", rev = "1eb3e4ec5f004dfb884004b2be2b083fafdbe88c", features = ["http", "http-client", "proxy"] } + +[[bin]] +name = "config_parse" +path = "fuzz_targets/config_parse.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "request_transform" +path = "fuzz_targets/request_transform.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/config_parse.rs b/fuzz/fuzz_targets/config_parse.rs new file mode 100644 index 0000000..827f26f --- /dev/null +++ b/fuzz/fuzz_targets/config_parse.rs @@ -0,0 +1,6 @@ +#![no_main] +#[path = "../../tests/support/invariants.rs"] +mod invariants; +libfuzzer_sys::fuzz_target!(|data: &[u8]| { + invariants::parsing(data); +}); diff --git a/fuzz/fuzz_targets/request_transform.rs b/fuzz/fuzz_targets/request_transform.rs new file mode 100644 index 0000000..922c8f4 --- /dev/null +++ b/fuzz/fuzz_targets/request_transform.rs @@ -0,0 +1,6 @@ +#![no_main] +#[path = "../../tests/support/invariants.rs"] +mod invariants; +libfuzzer_sys::fuzz_target!(|data: &[u8]| { + invariants::transformation(data); +}); diff --git a/fuzz/seeds/config_parse/invalid.toml b/fuzz/seeds/config_parse/invalid.toml new file mode 100644 index 0000000..7c117a6 --- /dev/null +++ b/fuzz/seeds/config_parse/invalid.toml @@ -0,0 +1,3 @@ +[proxy] +name = "unknown-field" +bad_field = true diff --git a/fuzz/seeds/config_parse/valid.toml b/fuzz/seeds/config_parse/valid.toml new file mode 100644 index 0000000..0480ce3 --- /dev/null +++ b/fuzz/seeds/config_parse/valid.toml @@ -0,0 +1,7 @@ +[proxy] +name = "fuzz" +[proxy.listen] +[[backends]] +name = "api" +transport = "stdio" +command = "not-executed" diff --git a/fuzz/seeds/config_parse/valid.yaml b/fuzz/seeds/config_parse/valid.yaml new file mode 100644 index 0000000..e3434d4 --- /dev/null +++ b/fuzz/seeds/config_parse/valid.yaml @@ -0,0 +1,8 @@ +proxy: + name: fuzz + listen: {} +backends: + - name: api + transport: stdio + command: not-executed + default_args: {nullable: null} diff --git a/fuzz/seeds/request_transform/continuation.json b/fuzz/seeds/request_transform/continuation.json new file mode 100644 index 0000000..64ab896 --- /dev/null +++ b/fuzz/seeds/request_transform/continuation.json @@ -0,0 +1 @@ +{"id":"call","params":{"name":"api/public","arguments":{"public_arg":"rename-me","injected":false},"inputResponses":{},"requestState":"resume","_meta":{"progressToken":"p"},"task":{"ttl":1000}}} diff --git a/fuzz/seeds/request_transform/invalid.json b/fuzz/seeds/request_transform/invalid.json new file mode 100644 index 0000000..fdca368 --- /dev/null +++ b/fuzz/seeds/request_transform/invalid.json @@ -0,0 +1 @@ +{"id":[],"params": diff --git a/fuzz/seeds/request_transform/other.json b/fuzz/seeds/request_transform/other.json new file mode 100644 index 0000000..6965041 --- /dev/null +++ b/fuzz/seeds/request_transform/other.json @@ -0,0 +1 @@ +{"id":42,"params":{"name":"outside/public","arguments":{"internal":null,"region":"caller","injected":false}}} diff --git a/fuzz/seeds/request_transform/scalar.json b/fuzz/seeds/request_transform/scalar.json new file mode 100644 index 0000000..62794bf --- /dev/null +++ b/fuzz/seeds/request_transform/scalar.json @@ -0,0 +1 @@ +{"id":-1,"params":{"name":"api/echo","arguments":null}} diff --git a/scripts/fuzz-smoke.sh b/scripts/fuzz-smoke.sh new file mode 100755 index 0000000..f0f0832 --- /dev/null +++ b/scripts/fuzz-smoke.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Finite, instrumented smoke checks; generated corpus entries stay outside Git. +set -euo pipefail +cd "$(dirname "$0")/.." +toolchain=nightly-2026-10-19 +if ! cargo +"$toolchain" fuzz --version | grep -Fxq 'cargo-fuzz 0.13.2'; then + echo 'Install cargo-fuzz 0.13.2 (see CONTRIBUTING.md).' >&2 + exit 1 +fi +# cargo-fuzz 0.13.2 has no --locked option. Fetch the committed lock explicitly, +# run offline, and reject a lock change instead of silently resolving updates. +cargo +"$toolchain" fetch --manifest-path fuzz/Cargo.toml --locked +lock_before=$(sha256sum fuzz/Cargo.lock) +# Prebuilt cargo-fuzz may itself target musl. Its compiled-in default must not +# select static libc for ASan; use the installed Rust compiler's actual host. +fuzz_host=$(rustc +"$toolchain" -vV | sed -n 's/^host: //p') +test -n "$fuzz_host" +corpus=$(mktemp -d) +trap 'rm -rf "$corpus"' EXIT +for target in config_parse request_transform; do + mkdir "$corpus/$target" + cp "fuzz/seeds/$target/"* "$corpus/$target/" + CARGO_NET_OFFLINE=true cargo +"$toolchain" fuzz run "$target" "$corpus/$target" --dev --target "$fuzz_host" -- \ + -runs=256 -max_len=4096 -max_total_time=30 -timeout=5 \ + -rss_limit_mb=1024 -seed=1 + test "$lock_before" = "$(sha256sum fuzz/Cargo.lock)" +done diff --git a/src/lib.rs b/src/lib.rs index 10466ed..aef697f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -303,7 +303,11 @@ //! conformance test. Validate the actual controller, network path and client //! during deployment. Shared/stateless session scaling, including the experimental //! `protocol-2026-07-28` mode, is separate work; this guide makes no portability -//! claim for that feature. +//! claim for that feature. The optional modern protocol uses per-request metadata +//! and matching HTTP method/name headers without initialization or a frontend +//! session. Requesting it in legacy initialization still selects the proxy's +//! `2025-11-25` baseline. See the protocol coverage matrix in the repository's +//! `CONTRIBUTING.md` for configured library/CLI and feature-negative checks. //! //! # Feature Flags //! diff --git a/tests/config_preparation.rs b/tests/config_preparation.rs index 21aa539..0a6bc1d 100644 --- a/tests/config_preparation.rs +++ b/tests/config_preparation.rs @@ -510,6 +510,21 @@ async fn enabled_features_are_checked_offline_consistently_for_this_binary() { cfg!(feature = "metrics"), json!({"metrics":{"enabled":true}}), ), + ( + "websocket", + cfg!(feature = "websocket"), + json!({"backend":{"transport":"websocket","command":null,"url":"ws://127.0.0.1:9/mcp"}}), + ), + ( + "redis-cache", + cfg!(feature = "redis-cache"), + json!({"cache":{"backend":"redis","url":"redis://127.0.0.1:9/0"}}), + ), + ( + "sqlite-cache", + cfg!(feature = "sqlite-cache"), + json!({"cache":{"backend":"sqlite","url":dir.path().join("not-created.db").to_str().unwrap()}}), + ), ]; for (feature, supported, patch) in checks { let mut value = serde_json::to_value(config(&marker, &json!({}))).unwrap(); @@ -524,6 +539,14 @@ async fn enabled_features_are_checked_offline_consistently_for_this_binary() { value["observability"][field] = patch[field].clone(); } } + if let Some(patch) = patch.get("backend") { + for (field, replacement) in patch.as_object().unwrap() { + value["backends"][1][field] = replacement.clone(); + } + } + if let Some(cache) = patch.get("cache") { + value["cache"] = cache.clone(); + } let mut candidate: ProxyConfig = serde_json::from_value(value.clone()).unwrap(); let result = candidate.prepare(); if supported { @@ -545,6 +568,24 @@ async fn enabled_features_are_checked_offline_consistently_for_this_binary() { } } assert!(!marker.exists()); + assert!(!dir.path().join("not-created.db").exists()); +} + +#[cfg(not(feature = "yaml"))] +#[tokio::test] +async fn yaml_files_fail_explicitly_before_effects_without_feature() { + let dir = tempfile::tempdir().unwrap(); + let marker = dir.path().join("started"); + let path = dir.path().join("proxy.yaml"); + std::fs::write(&path, format!("proxy:\n name: feature-boundary\n listen: {{}}\nbackends:\n - name: first\n transport: stdio\n command: touch\n args: ['{}']\n", marker.display())).unwrap(); + for check in [true, false] { + let error = cli(&path, check).await; + assert!( + error.contains("yaml") && error.contains("feature"), + "{error}" + ); + } + assert!(!marker.exists()); } #[tokio::test] diff --git a/tests/invariant_properties.rs b/tests/invariant_properties.rs new file mode 100644 index 0000000..8b80e38 --- /dev/null +++ b/tests/invariant_properties.rs @@ -0,0 +1,389 @@ +//! Bounded configuration, transformation and cancellation-schedule properties. +#[path = "support/invariants.rs"] +mod invariants; +use mcp_proxy::ProxyConfig; +use proptest::prelude::*; +use serde_json::{Value, json}; + +fn json_value() -> impl Strategy { + let leaf = prop_oneof![ + Just(Value::Null), + any::().prop_map(Value::Bool), + any::().prop_map(|n| json!(n)), + ".{0,24}".prop_map(Value::String) + ]; + leaf.prop_recursive(3, 48, 4, |inner| { + prop_oneof![ + proptest::collection::vec(inner.clone(), 0..4).prop_map(Value::Array), + proptest::collection::btree_map("[a-z_]{1,12}", inner, 0..4) + .prop_map(|map| Value::Object(map.into_iter().collect())) + ] + }) +} +fn raw() -> Value { + json!({"proxy":{"name":"properties","listen":{}},"backends":[{"name":"api","transport":"stdio","command":"never-spawned"}]}) +} +proptest! { + #![proptest_config(ProptestConfig::with_cases(48))] + #[test] + fn preparation_is_idempotent_and_format_independent(port in any::(), timeout in 1u64..500, entries in 1usize..1000, retries in 0u32..8, separator in prop::sample::select(vec!["/","::","|"])) { + let mut value = raw(); + value["proxy"]["listen"]["port"] = json!(port); + value["proxy"]["separator"] = json!(separator); + value["backends"][0]["timeout"] = json!({"seconds":timeout}); + value["backends"][0]["retry"] = json!({"max_retries":retries,"initial_backoff_ms":2,"max_backoff_ms":8,"budget_percent":20.0,"min_retries_per_sec":0}); + value["performance"] = json!({"coalesce_requests":true,"coalesce":{"max_entries":entries,"max_waiters":8,"timeout_seconds":30}}); + let mut from_json: ProxyConfig = serde_json::from_value(value).unwrap(); + from_json.prepare().unwrap(); + let canonical = serde_json::to_value(&from_json).unwrap(); + from_json.prepare().unwrap(); + prop_assert_eq!(serde_json::to_value(&from_json).unwrap(), canonical.clone()); + let text = toml::to_string(&from_json).unwrap(); + let mut from_toml = ProxyConfig::parse(&text).unwrap(); + from_toml.prepare().unwrap(); + prop_assert_eq!(serde_json::to_value(&from_toml).unwrap(), canonical.clone()); + #[cfg(feature = "yaml")] + { + let text = yaml_serde::to_string(&from_json).unwrap(); + let mut from_yaml = ProxyConfig::parse_yaml(&text).unwrap(); + from_yaml.prepare().unwrap(); + prop_assert_eq!(serde_json::to_value(from_yaml).unwrap(), canonical); + } + } + #[test] + fn raw_preparation_and_parser_reject_the_same_generated_invalid_revision(kind in 0usize..5, number in 1_000_001u64..u64::MAX, name in "[a-z]{1,12}") { + let mut value = raw(); + match kind { + 0 => value["backends"][0]["timeout"] = json!({"seconds":number.saturating_add(31_536_000)}), + 1 => value["backends"][0]["concurrency"] = json!({"max_concurrent":number}), + 2 => value["backends"][0]["forward_auth"] = json!(true), + 3 => value["backends"][0]["mirror_of"] = json!(format!("missing-{name}")), + _ => { value["backends"][0]["expose_tools"] = json!([name.clone()]); value["backends"][0]["hide_tools"] = json!([name]); } + } + let mut candidate: ProxyConfig = serde_json::from_value(value.clone()).unwrap(); + let expected = candidate.prepare().unwrap_err().to_string(); + // TOML integers are signed; the JSON/raw contract additionally covers + // values beyond its representation. Test format equivalence when expressible. + if let Ok(text) = toml::to_string(&candidate) { + prop_assert_eq!(ProxyConfig::parse(&text).unwrap_err().to_string(), expected.clone()); + } + #[cfg(feature = "yaml")] + { + let text = yaml_serde::to_string(&candidate).unwrap(); + prop_assert_eq!(ProxyConfig::parse_yaml(&text).unwrap_err().to_string(), expected); + } + prop_assert_eq!(serde_json::to_value(candidate).unwrap(), serde_json::to_value(serde_json::from_value::(value).unwrap()).unwrap(), "rejection must not partially mutate config"); + } + #[test] + fn transformations_preserve_envelope_context_and_unrelated_arguments(args in json_value(), number in any::(), string_id in ".{0,16}", use_string in any::(), name in prop::sample::select(vec!["api/public","api/echo","api/other","outside/public"]), continuation in ".{0,16}") { + let id = if use_string { json!(string_id) } else { json!(number) }; + let wire = json!({"id":id,"params":{"name":name,"arguments":args,"inputResponses":{},"requestState":continuation,"_meta":{"progressToken":"progress","extra":{"preserved":true}},"task":{"ttl":60000}}}); + prop_assert!(invariants::transformation(&serde_json::to_vec(&wire).unwrap()), "generated request must exercise the transforms"); + } + #[test] + fn bounded_parser_inputs_never_panic_and_roundtrip_when_accepted(data in proptest::collection::vec(any::(), 0..4096)) { + invariants::parsing(&data); + } +} + +#[test] +fn checked_in_fuzz_seeds_exercise_the_same_assertions() { + for (directory, invariant) in [ + ("config_parse", invariants::parsing as fn(&[u8]) -> bool), + ( + "request_transform", + invariants::transformation as fn(&[u8]) -> bool, + ), + ] { + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("fuzz/seeds") + .join(directory); + let mut count = 0; + let mut accepted = 0; + for entry in std::fs::read_dir(path).unwrap() { + let bytes = std::fs::read(entry.unwrap().path()).unwrap(); + assert!(bytes.len() <= invariants::MAX_INPUT); + accepted += usize::from(invariant(&bytes)); + count += 1; + } + assert!( + count >= 3, + "each target needs valid and invalid seed inputs" + ); + assert!(accepted > 0, "seed corpus must reach a valid invariant"); + } +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(32))] + #[test] + fn coalescing_model_releases_cancelled_and_expired_generations(followers in 1usize..8, cancelled in any::(), finish in 0usize..3) { + let runtime = tokio::runtime::Builder::new_current_thread().enable_time().start_paused(true).build().unwrap(); + runtime.block_on(async { + use std::sync::{Arc, atomic::{AtomicUsize, Ordering}}; + use std::time::Duration; + use futures_util::{poll, FutureExt}; + use tower::ServiceExt; + use tower_mcp::{RouterRequest, RouterResponse, McpResponse}; + use tower_mcp::protocol::{McpRequest, RequestId}; + let calls = Arc::new(AtomicUsize::new(0)); + let (release, receiver) = tokio::sync::watch::channel(false); + let captured = calls.clone(); + let inner = tower::service_fn(move |request: RouterRequest| { + captured.fetch_add(1, Ordering::SeqCst); + let mut release = receiver.clone(); + async move { + while !*release.borrow_and_update() { release.changed().await.unwrap(); } + Ok::<_, std::convert::Infallible>(RouterResponse { id:request.id, inner:Ok(McpResponse::CallTool(tower_mcp::CallToolResult::text("completed"))) }) + } + }); + let config = mcp_proxy::config::CoalesceConfig { max_entries:1, max_waiters:followers, timeout_seconds:1 }; + let service = mcp_proxy::coalesce::CoalesceService::with_config(inner, &config).unwrap(); + let request = |id| RouterRequest { id:RequestId::Number(id), inner:McpRequest::CallTool(serde_json::from_value(json!({"name":"api/work","arguments":{"shared":true}})).unwrap()), extensions:Default::default() }; + let mut leader = Some(Box::pin(service.clone().oneshot(request(0)))); + assert!(poll!(leader.as_mut().unwrap().as_mut()).is_pending()); + let mut waiting = Vec::new(); + for index in 0..followers { + let mut follower = Box::pin(service.clone().oneshot(request(index as i64 + 1))); + assert!(poll!(follower.as_mut()).is_pending()); + waiting.push((index as i64 + 1, Some(follower))); + } + assert_eq!(calls.load(Ordering::SeqCst), 1); + let mut freed = false; + for (index, (_, future)) in waiting.iter_mut().enumerate() { + if cancelled & (1 << index) != 0 { future.take(); freed = true; } + } + if freed { + let mut replacement = Box::pin(service.clone().oneshot(request(100))); + assert!(poll!(replacement.as_mut()).is_pending(), "cancelled follower must release its slot"); + waiting.push((100, Some(replacement))); + } + match finish { + 0 => { release.send_replace(true); }, + 1 => { leader.take(); }, + _ => tokio::time::advance(Duration::from_secs(1)).await, + } + if let Some(leader) = leader { + let response = leader.now_or_never().expect("leader must be ready after release/expiry").unwrap(); + assert_eq!(response.id, RequestId::Number(0)); + assert_eq!(response.inner.is_ok(), finish == 0); + } + for (id, future) in waiting { + if let Some(future) = future { + let response = future.now_or_never().expect("followers must wake on completion/cancellation/expiry").unwrap(); + assert_eq!(response.id, RequestId::Number(id)); + assert_eq!(response.inner.is_ok(), finish == 0); + } + } + assert_eq!(calls.load(Ordering::SeqCst), 1, "cancellation/expiry must not replay"); + release.send_replace(true); + let next = service.oneshot(request(999)).now_or_never().expect("fresh generation must complete without waiting").unwrap(); + assert_eq!(next.id, RequestId::Number(999)); + assert!(next.inner.is_ok(), "same key must elect a fresh generation"); + assert_eq!(calls.load(Ordering::SeqCst), 2); + }); + } +} + +#[cfg(feature = "oauth")] +#[allow(dead_code)] +#[path = "support/app.rs"] +mod app; +#[cfg(feature = "oauth")] +#[allow(dead_code)] +#[path = "support/backend.rs"] +mod backend; +#[cfg(feature = "oauth")] +#[path = "support/http.rs"] +mod http; +#[cfg(feature = "oauth")] +#[path = "support/oauth.rs"] +mod oauth; + +#[cfg(feature = "oauth")] +#[test] +fn configured_role_union_denies_and_filters_are_order_independent() { + use proptest::test_runner::{Config, TestRunner}; + let mut runner = TestRunner::new(Config { + cases: 16, + source_file: Some(file!()), + max_shrink_iters: 128, + ..Config::default() + }); + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + let strategy = ( + proptest::collection::vec(0usize..4, 0..7), + any::(), + any::(), + json_value(), + ); + let verify = |(roles, hidden, search, value): (Vec, bool, bool, Value)| { + runtime.block_on(async { + let mut authority = oauth::Authority::new().await; + let backend = backend::Backend::new("http").await; + let exposure = if search && cfg!(feature = "discovery") { + "search" + } else { + "direct" + }; + let config = format!( + r#" +[proxy] +name = "policy-properties" +tool_exposure = "{exposure}" +[proxy.listen] +[auth] +type = "jwt" +issuer = "{issuer}" +audience = "{resource}" +jwks_uri = "{keys}/keys" +[[auth.roles]] +name = "reader" +allow_tools = ["api/public"] +[[auth.roles]] +name = "broad" +[[auth.roles]] +name = "ban" +allow_tools = ["api/public"] +deny_tools = ["api/public"] +[auth.role_mapping] +claim = "scope" +default_deny = true +mapping = {{ reader="reader", broad="broad", ban="ban" }} +[security] +admin_token = "manager" +[[backends]] +name = "api" +{backend} +hide_tools = {hide} +[[backends.aliases]] +from = "echo" +to = "public" +[[backends.inject_args]] +tool = "echo" +args = {{ injected=true }} +overwrite = true +"#, + issuer = authority.state.issuer, + resource = oauth::RESOURCE, + keys = authority.server.url, + backend = backend.config, + hide = if hidden { "['echo']" } else { "[]" } + ); + let mut app = app::App::embedded(&config).await; + let expected = + !hidden && !roles.contains(&2) && (roles.contains(&0) || roles.contains(&1)); + let scopes = |indices: &[usize]| { + indices + .iter() + .map(|&i| ["reader", "broad", "ban", "unmapped"][i]) + .collect::>() + .join(" ") + }; + let mut reverse = roles.clone(); + reverse.reverse(); + for order in [&roles, &reverse] { + let mut claims = authority.claims("generated-caller"); + claims["scope"] = json!(scopes(order)); + let token = authority.jwt(&claims); + let before = backend.calls().len(); + if !roles.iter().any(|role| *role < 3) { + // default_deny applies before a session can be established. + let response = http::client() + .post(format!("{}/", app.url)) + .bearer_auth(&token) + .header("accept", "application/json, text/event-stream") + .header("mcp-protocol-version", http::VERSION) + .json(&json!({"jsonrpc":"2.0","id":0,"method":"initialize","params":http::initialization()})) + .send().await.unwrap(); + assert_eq!(response.status(), reqwest::StatusCode::OK); + assert!(response.headers().get("mcp-session-id").is_none()); + let reply = http::payload(response).await; + assert_eq!(reply["id"], 0); + assert_eq!(reply["error"]["code"], -32602); + assert_eq!(backend.calls().len(), before); + continue; + } + let session = http::Session::start(&app.url, &token).await; + let listed = session.rpc("tools/list", json!({})).await; + let names = http::success(&listed)["tools"] + .as_array() + .unwrap() + .iter() + .map(|t| t["name"].as_str().unwrap()) + .collect::>(); + if exposure == "direct" { + assert_eq!(names.contains(&"api/public"), expected); + } else { + assert!(!names.contains(&"api/public")); + } + assert_eq!(backend.calls().len(), before); + let arguments = json!({"value":value,"injected":"caller"}); + let response = if exposure == "search" { + session + .call( + "proxy/call_tool", + json!({"name":"api/public","arguments":arguments}), + ) + .await + } else { + session.call("api/public", arguments).await + }; + if expected { + let content = &http::success(&response)["content"]; + assert_eq!(content.as_array().unwrap().len(), 1); + let expected = json!({"value":value,"injected":true}); + assert_eq!( + serde_json::from_str::(content[0]["text"].as_str().unwrap()) + .unwrap(), + expected + ); + assert_eq!(backend.calls().len(), before + 1); + assert_eq!( + backend.calls().last().unwrap(), + &json!({"name":"echo","arguments":expected}) + ); + } else { + assert_eq!(response["error"]["code"], -32602); + assert_eq!( + backend.calls().len(), + before, + "denied target must never dispatch" + ); + } + let management = http::client() + .get(format!("{}/admin/config", app.url)) + .bearer_auth(&token) + .send() + .await + .unwrap(); + assert_eq!( + management.status(), + reqwest::StatusCode::UNAUTHORIZED, + "even an unrestricted role is ordinary authority" + ); + session.terminate().await; + } + app.shutdown().await; + authority.server.stop().await; + }); + Ok(()) + }; + // Guarantee the important boundaries even in a short random run. + for (roles, hidden, search) in [ + (vec![], false, false), + (vec![3], false, true), + (vec![0], false, false), + (vec![1], false, true), + (vec![0, 1, 2, 0], false, false), + (vec![2, 1], false, true), + (vec![0, 1], true, true), + (vec![1, 0, 1], true, false), + ] { + verify((roles, hidden, search, json!({"fixed":"boundary"}))).unwrap(); + } + runner.run(&strategy, verify).unwrap(); +} diff --git a/tests/protocol_contracts.rs b/tests/protocol_contracts.rs new file mode 100644 index 0000000..ef29e71 --- /dev/null +++ b/tests/protocol_contracts.rs @@ -0,0 +1,321 @@ +//! Configured HTTP contracts for both protocol eras and compile-time boundaries. +#[allow(dead_code)] +#[path = "support/app.rs"] +mod app; +#[allow(dead_code)] +#[path = "support/backend.rs"] +mod backend; +#[path = "support/http.rs"] +mod http; + +use app::App; +use backend::Backend; +use http::{payload, success}; +use reqwest::{Response, StatusCode}; +use serde_json::{Value, json}; + +const MODERN: &str = "2026-07-28"; +async fn start(cli: bool) -> (App, Backend) { + let backend = Backend::new("http").await; + let reservation = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + let port = reservation.local_addr().unwrap().port(); + let config = format!( + "[proxy]\nname='protocol-contract'\n[proxy.listen]\nhost='127.0.0.1'\nport={port}\n[auth]\ntype='bearer'\ntokens=['data']\n[security]\nadmin_token='manager'\n[[backends]]\nname='api'\n{}\nhide_tools=['blocked']\n[[backends.aliases]]\nfrom='echo'\nto='public'\n[[backends.inject_args]]\ntool='echo'\nargs={{injected=true}}\noverwrite=true\n", + backend.config + ); + drop(reservation); + let app = if cli { + App::cli(&config, port).await + } else { + App::embedded(&config).await + }; + (app, backend) +} +async fn count(app: &App, expected: usize) { + let response = http::client() + .get(format!("{}/admin/sessions", app.url)) + .bearer_auth("manager") + .send() + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + response.json::().await.unwrap(), + json!({"active_sessions":expected}) + ); +} +fn request(app: &App, version: &str, body: &Value) -> reqwest::RequestBuilder { + request_token(app, version, body, "data") +} +fn request_token(app: &App, version: &str, body: &Value, token: &str) -> reqwest::RequestBuilder { + http::client() + .post(format!("{}/", app.url)) + .bearer_auth(token) + .header("accept", "application/json, text/event-stream") + .header("mcp-protocol-version", version) + .json(body) +} +fn exact_echo(reply: &Value, id: i64) { + assert_eq!(reply["id"], id); + let result = success(reply); + assert_eq!(result["content"].as_array().unwrap().len(), 1); + assert_eq!(result["content"][0]["type"], "text"); + assert_eq!( + serde_json::from_str::(result["content"][0]["text"].as_str().unwrap()).unwrap(), + json!({"message":"protocol", "injected":true}) + ); +} +async fn legacy(cli: bool) { + let (mut app, backend) = start(cli).await; + for (index, version) in [ + "2025-03-26", + "2025-06-18", + "2025-11-25", + "2026-07-28", + "2099-01-01", + ] + .into_iter() + .enumerate() + { + count(&app, 0).await; + let init = json!({"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":version,"capabilities":{},"clientInfo":{"name":"version-contract","version":"1"}}}); + let response = request(&app, "2025-11-25", &init).send().await.unwrap(); + assert_eq!(response.status(), StatusCode::OK, "{version}"); + let session = response.headers()["mcp-session-id"] + .to_str() + .unwrap() + .to_string(); + let reply = payload(response).await; + assert_eq!(reply["id"], 1); + // The pinned McpProxy advertises the documented session baseline, not + // every transport revision. Clients continue with the selected version. + assert_eq!(success(&reply)["protocolVersion"], "2025-11-25"); + let version = "2025-11-25"; + let notified = request( + &app, + version, + &json!({"jsonrpc":"2.0","method":"notifications/initialized","params":{}}), + ) + .header("mcp-session-id", &session) + .send() + .await + .unwrap(); + assert_eq!(notified.status(), StatusCode::ACCEPTED); + assert!(notified.bytes().await.unwrap().is_empty()); + count(&app, 1).await; + let listed = request( + &app, + version, + &json!({"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}), + ) + .header("mcp-session-id", &session) + .send() + .await + .unwrap(); + assert_eq!(listed.status(), StatusCode::OK); + let reply = payload(listed).await; + assert_eq!(reply["id"], 2); + assert_eq!( + success(&reply)["tools"] + .as_array() + .unwrap() + .iter() + .map(|t| t["name"].as_str().unwrap()) + .collect::>(), + ["api/public"] + ); + let called = request(&app, version, &json!({"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"api/public","arguments":{"message":"protocol"}}})).header("mcp-session-id", &session).send().await.unwrap(); + assert_eq!(called.status(), StatusCode::OK); + exact_echo(&payload(called).await, 3); + let denied = request(&app, version, &json!({"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"api/blocked","arguments":{}}})).header("mcp-session-id", &session).send().await.unwrap(); + assert_eq!(denied.status(), StatusCode::OK); + let reply = payload(denied).await; + assert_eq!(reply["id"], 4); + assert_eq!(reply["error"]["code"], -32602); + assert_eq!( + backend.calls().len(), + index + 1, + "denied call must not execute" + ); + let deleted = http::client() + .delete(format!("{}/", app.url)) + .bearer_auth("data") + .header("mcp-protocol-version", version) + .header("mcp-session-id", &session) + .send() + .await + .unwrap(); + assert_eq!(deleted.status(), StatusCode::OK); + count(&app, 0).await; + } + assert_eq!( + backend.calls(), + vec![json!({"name":"echo","arguments":{"message":"protocol","injected":true}}); 5] + ); + app.shutdown().await; +} +#[tokio::test] +async fn configured_legacy_initialization_selects_supported_baseline() { + legacy(false).await; +} +#[tokio::test] +async fn cli_legacy_initialization_selects_supported_baseline() { + legacy(true).await; +} + +fn modern(method: &str, id: i64, mut params: Value) -> Value { + params["_meta"] = json!({"io.modelcontextprotocol/protocolVersion":MODERN,"io.modelcontextprotocol/clientCapabilities":{}}); + json!({"jsonrpc":"2.0","id":id,"method":method,"params":params}) +} +async fn post_modern(app: &App, body: &Value) -> Response { + let mut builder = + request(app, MODERN, body).header("mcp-method", body["method"].as_str().unwrap()); + if let Some(name) = body["params"]["name"].as_str() { + builder = builder.header("mcp-name", name); + } + builder.send().await.unwrap() +} +async fn modern_contract(cli: bool) { + let (mut app, backend) = start(cli).await; + let body = modern("tools/list", 10, json!({})); + let response = post_modern(&app, &body).await; + if cfg!(feature = "protocol-2026-07-28") { + assert_eq!(response.status(), StatusCode::OK); + assert!(response.headers().get("mcp-session-id").is_none()); + let reply = payload(response).await; + assert_eq!(reply["id"], 10); + assert_eq!(success(&reply)["tools"][0]["name"], "api/public"); + assert_eq!(success(&reply)["tools"].as_array().unwrap().len(), 1); + let called = post_modern( + &app, + &modern( + "tools/call", + 11, + json!({"name":"api/public","arguments":{"message":"protocol"}}), + ), + ) + .await; + assert_eq!(called.status(), StatusCode::OK); + assert!(called.headers().get("mcp-session-id").is_none()); + exact_echo(&payload(called).await, 11); + let denied = post_modern( + &app, + &modern( + "tools/call", + 12, + json!({"name":"api/blocked","arguments":{}}), + ), + ) + .await; + assert_eq!(denied.status(), StatusCode::OK); + let reply = payload(denied).await; + assert_eq!(reply["id"], 12); + assert_eq!(reply["error"]["code"], -32602); + assert_eq!( + backend.calls(), + vec![json!({"name":"echo","arguments":{"message":"protocol","injected":true}})] + ); + } else { + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let reply = payload(response).await; + assert_eq!(reply["id"], 10); + assert_eq!(reply["error"]["code"], -32022); + assert!(backend.calls().is_empty()); + } + count(&app, 0).await; + app.shutdown().await; +} +#[tokio::test] +async fn configured_modern_protocol_is_sessionless_or_explicitly_unsupported() { + modern_contract(false).await; +} +#[tokio::test] +async fn cli_modern_protocol_is_sessionless_or_explicitly_unsupported() { + modern_contract(true).await; +} + +#[tokio::test] +async fn malformed_modern_requests_and_credentials_never_dispatch() { + let (mut app, backend) = start(false).await; + let valid = modern( + "tools/call", + 50, + json!({"name":"api/public","arguments":{}}), + ); + let unauthorized = request_token(&app, MODERN, &valid, "invalid") + .header("mcp-method", "tools/call") + .header("mcp-name", "api/public") + .send() + .await + .unwrap(); + assert_eq!(unauthorized.status(), StatusCode::UNAUTHORIZED); + for (body, version, method, name, code) in [ + ( + json!({"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"api/public","arguments":{}}}), + MODERN, + "tools/call", + "api/public", + -32602, + ), + ( + valid.clone(), + "2025-11-25", + "tools/call", + "api/public", + -32020, + ), + ] { + let response = request(&app, version, &body) + .header("mcp-method", method) + .header("mcp-name", name) + .send() + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let reply = payload(response).await; + assert_eq!(reply["id"], 50); + assert_eq!(reply["error"]["code"], code); + } + #[cfg(feature = "protocol-2026-07-28")] + { + for response in [ + request(&app, MODERN, &valid) + .header("mcp-name", "api/public") + .send() + .await + .unwrap(), + request(&app, MODERN, &valid) + .header("mcp-method", "tools/list") + .header("mcp-name", "api/public") + .send() + .await + .unwrap(), + request(&app, MODERN, &valid) + .header("mcp-method", "tools/call") + .header("mcp-name", "api/blocked") + .send() + .await + .unwrap(), + request(&app, MODERN, &valid) + .header("mcp-method", "tools/call") + .header("mcp-method", "tools/call") + .header("mcp-name", "api/public") + .send() + .await + .unwrap(), + ] { + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let reply = payload(response).await; + assert_eq!(reply["id"], 50); + assert_eq!(reply["error"]["code"], -32020); + } + let removed = post_modern(&app, &modern("initialize", 51, json!({"protocolVersion":MODERN,"capabilities":{},"clientInfo":{"name":"removed","version":"1"}}))).await; + assert_eq!(removed.status(), StatusCode::NOT_FOUND); + let reply = payload(removed).await; + assert_eq!(reply["id"], 51); + assert_eq!(reply["error"]["code"], -32601); + } + assert!(backend.calls().is_empty()); + count(&app, 0).await; + app.shutdown().await; +} diff --git a/tests/support/invariants.rs b/tests/support/invariants.rs new file mode 100644 index 0000000..1d254ba --- /dev/null +++ b/tests/support/invariants.rs @@ -0,0 +1,151 @@ +//! Pure, bounded invariants shared by proptest and the coverage-guided fuzzers. +#![allow(dead_code)] +use futures_util::FutureExt; +use mcp_proxy::{ + ProxyConfig, + alias::{AliasMap, AliasService}, + inject::{InjectArgsService, InjectionRules}, + param_override::{ParamOverrideService, ToolOverride}, +}; +use serde_json::{Value, json}; +use tower::ServiceExt; +use tower_mcp::protocol::{CallToolParams, McpRequest, RequestId}; +use tower_mcp::{Extensions, McpResponse, RequestContext, RouterRequest, RouterResponse}; + +pub const MAX_INPUT: usize = 4096; +fn roundtrip(config: ProxyConfig) { + let canonical = serde_json::to_value(&config).unwrap(); + let recovered: ProxyConfig = serde_json::from_value(canonical.clone()).unwrap(); + assert_eq!(serde_json::to_value(&recovered).unwrap(), canonical); + // TOML cannot represent every YAML/JSON value (notably null arguments). + // When representable, its round-trip must preserve the same configuration. + if let Ok(text) = toml::to_string(&config) { + let parsed = ProxyConfig::parse(&text).unwrap(); + assert_eq!(serde_json::to_value(parsed).unwrap(), canonical); + } +} +pub fn parsing(data: &[u8]) -> bool { + if data.len() > MAX_INPUT { + return false; + } + let Ok(text) = std::str::from_utf8(data) else { + return false; + }; + let mut accepted = false; + if let Ok(config) = ProxyConfig::parse(text) { + roundtrip(config); + accepted = true; + } + #[cfg(feature = "yaml")] + if let Ok(config) = ProxyConfig::parse_yaml(text) { + roundtrip(config); + accepted = true; + } + // Deliberately no prepare/from_config: fuzz inputs cannot read environment + // secrets, connect to stores/IdPs/backends, or launch arbitrary executables. + accepted +} + +pub fn transformation(data: &[u8]) -> bool { + if data.len() > MAX_INPUT { + return false; + } + let Ok(wire) = serde_json::from_slice::(data) else { + return false; + }; + let Ok(params) = serde_json::from_value::(wire["params"].clone()) else { + return false; + }; + let Ok(id) = serde_json::from_value::(wire["id"].clone()) else { + return false; + }; + let original = serde_json::to_value(¶ms).unwrap(); + let mut expected = original.clone(); + let name = if params.name == "api/public" { + "api/echo" + } else { + params.name.as_str() + }; + expected["name"] = json!(name); + if let Some(args) = expected["arguments"].as_object_mut() { + if name == "api/echo" { + args.entry("internal").or_insert(json!(42)); + if let Some(value) = args.remove("public_arg") { + args.insert("original".into(), value); + } + } + if name.starts_with("api/") { + args.entry("region").or_insert(json!("default")); + } + if name == "api/echo" { + args.insert("injected".into(), json!(true)); + } + } + let mut extensions = Extensions::new(); + extensions.insert(String::from("verified-caller")); + let context = RequestContext::new(id.clone()); + context.cancellation_token().cancel(); + extensions.insert(context); + let expected_id = id.clone(); + let inner = tower::service_fn(move |request: RouterRequest| { + assert_eq!(request.id, expected_id); + assert_eq!( + request.extensions.get::().unwrap(), + "verified-caller" + ); + assert!( + request + .extensions + .get::() + .unwrap() + .is_cancelled() + ); + async move { + let McpRequest::CallTool(params) = request.inner else { + panic!("call transformed into another method"); + }; + Ok::<_, std::convert::Infallible>(RouterResponse { + id: request.id, + inner: Ok(McpResponse::CallTool(tower_mcp::CallToolResult::json( + serde_json::to_value(params).unwrap(), + ))), + }) + } + }); + let rules = InjectionRules::new( + "api/".into(), + serde_json::from_value(json!({"region":"default"})).unwrap(), + vec![ + serde_json::from_value( + json!({"tool":"echo","args":{"injected":true},"overwrite":true}), + ) + .unwrap(), + ], + ); + let overrides = ToolOverride::new("api/", &serde_json::from_value(json!({"tool":"echo","hide":["internal"],"defaults":{"internal":42},"rename":{"original":"public_arg"}})).unwrap()); + let service = AliasService::new( + ParamOverrideService::new(InjectArgsService::new(inner, vec![rules]), vec![overrides]), + AliasMap::new(vec![("api/".into(), "echo".into(), "public".into())]).unwrap(), + ); + // These pure transforms wrap an immediately ready capture service. A pending + // result would be a regression, not a reason to wait or start another runtime. + let response = service + .oneshot(RouterRequest { + id: id.clone(), + inner: McpRequest::CallTool(params), + extensions, + }) + .now_or_never() + .expect("pure transforms must complete on first poll") + .unwrap(); + assert_eq!(response.id, id); + let McpResponse::CallTool(result) = response.inner.unwrap() else { + panic!("wrong response method"); + }; + let observed: Value = serde_json::from_str(&result.all_text()).unwrap(); + assert_eq!( + observed, expected, + "only the configured names/arguments may change" + ); + true +}