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