diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 882dd90..e24ad6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,24 +3,23 @@ on: push: branches: - main + # pull_request: + # branches: + # - "**" workflow_call: -# on: -# push: -# branches: -# - main -# pull_request: -# branches: -# - "**" -# - jobs: rust_check: - name: Rust check - runs-on: ubuntu-latest + name: Rust Checks (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + fail-fast: false # Continue testing other OSes even if one fails steps: - name: Checkout uses: actions/checkout@v4 + - name: Cache Rust uses: actions/cache@v4 with: @@ -29,42 +28,77 @@ jobs: ~/.cargo/registry ~/.cargo/git target - key: ${{ runner.os }}-rust-${{ steps.toolchain.outputs.cachekey }} - restore-keys: ${{ runner.os }}-rust- + key: ${{ matrix.os }}-rust-${{ hashFiles('Cargo.lock') }} + restore-keys: ${{ matrix.os }}-rust- - name: Install Rust Toolchain uses: dtolnay/rust-toolchain@master with: toolchain: stable - components: rustfmt + components: rustfmt, clippy - - uses: davidB/rust-cargo-make@v1 - - uses: taiki-e/install-action@nextest + - name: Install cargo-make + uses: davidB/rust-cargo-make@v1 + + - name: Install nextest + uses: taiki-e/install-action@nextest - name: Run Clippy - run: | - cargo make clippy + run: cargo make clippy + continue-on-error: false - name: Run Rustfmt - run: | - cargo make fmt + run: cargo make fmt -- --check + if: matrix.os == 'ubuntu-latest' # Run fmt only on Linux - name: Run cargo doc + env: + RUSTDOCFLAGS: "-Dwarnings" run: cargo doc --no-deps + if: matrix.os == 'ubuntu-latest' # Run doc only on Linux - name: Spell Check - env: - RUSTDOCFLAGS: "-Dwarnings" uses: crate-ci/typos@master + if: matrix.os == 'ubuntu-latest' # Run typos only on Linux - - name: Audit + - name: Audit Dependencies uses: actions-rust-lang/audit@v1 with: token: ${{ secrets.GITHUB_TOKEN }} + if: matrix.os == 'ubuntu-latest' # Run audit only on Linux + + # install nodejs that is required for tests + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" # Stable Node.js version + # Verify npx is available + - name: Verify npx + run: npx --version + shell: bash + + - name: Install server-everything globally + run: npm install -g @modelcontextprotocol/server-everything + shell: bash + + # install Python and uvx that is required for tests + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" # Stable Python version + + - name: Install uv + run: pip install uv + shell: bash + + - name: Verify uvx + run: uvx --version + shell: bash - name: Run Tests - run: | - cargo make test + run: cargo make test + shell: bash # Ensure consistent shell + - name: Run Doc Tests - run: | - cargo make doc-test + run: cargo make doc-test + shell: bash # Ensure consistent shell diff --git a/crates/rust-mcp-sdk/tests/common/common.rs b/crates/rust-mcp-sdk/tests/common/common.rs new file mode 100644 index 0000000..d896a56 --- /dev/null +++ b/crates/rust-mcp-sdk/tests/common/common.rs @@ -0,0 +1,26 @@ +use async_trait::async_trait; +use rust_mcp_schema::{ + ClientCapabilities, Implementation, InitializeRequestParams, JSONRPC_VERSION, +}; +use rust_mcp_sdk::mcp_client::ClientHandler; + +pub const NPX_SERVER_EVERYTHING: &str = "@modelcontextprotocol/server-everything"; + +#[cfg(unix)] +pub const UVX_SERVER_GIT: &str = "mcp-server-git"; + +pub fn test_client_info() -> InitializeRequestParams { + InitializeRequestParams { + capabilities: ClientCapabilities::default(), + client_info: Implementation { + name: "test-rust-mcp-client".into(), + version: "0.1.0".into(), + }, + protocol_version: JSONRPC_VERSION.into(), + } +} + +pub struct TestClientHandler; + +#[async_trait] +impl ClientHandler for TestClientHandler {} diff --git a/crates/rust-mcp-sdk/tests/test_client_runtime.rs b/crates/rust-mcp-sdk/tests/test_client_runtime.rs new file mode 100644 index 0000000..dfe40bf --- /dev/null +++ b/crates/rust-mcp-sdk/tests/test_client_runtime.rs @@ -0,0 +1,56 @@ +use common::{test_client_info, TestClientHandler, NPX_SERVER_EVERYTHING}; +use rust_mcp_sdk::{mcp_client::client_runtime, McpClient, StdioTransport, TransportOptions}; + +#[cfg(unix)] +use common::UVX_SERVER_GIT; + +#[path = "common/common.rs"] +pub mod common; + +#[tokio::test] +async fn tets_client_launch_npx_server() { + // NPM based MCP servers should launch successfully using `npx` + let transport = StdioTransport::create_with_server_launch( + "npx", + vec!["-y".into(), NPX_SERVER_EVERYTHING.into()], + None, + TransportOptions::default(), + ) + .unwrap(); + + let client = client_runtime::create_client(test_client_info(), transport, TestClientHandler {}); + + client.clone().start().await.unwrap(); + + let server_capabilities = client.server_capabilities().unwrap(); + let server_info = client.server_info().unwrap(); + + assert!(server_info.server_info.name.len() > 0); + assert!(server_info.server_info.version.len() > 0); + assert!(server_capabilities.tools.is_some()); +} + +#[cfg(unix)] +#[tokio::test] +async fn tets_client_launch_uvx_server() { + // The Python-based MCP server should launch successfully + // provided that `uvx` is installed and accessible in the system's PATH + let transport = StdioTransport::create_with_server_launch( + "uvx", + vec![UVX_SERVER_GIT.into()], + None, + TransportOptions::default(), + ) + .unwrap(); + + let client = client_runtime::create_client(test_client_info(), transport, TestClientHandler {}); + + client.clone().start().await.unwrap(); + + let server_capabilities = client.server_capabilities().unwrap(); + let server_info = client.server_info().unwrap(); + + assert!(server_info.server_info.name.len() > 0); + assert!(server_info.server_info.version.len() > 0); + assert!(server_capabilities.tools.is_some()); +}