diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd883ef6..c9ad1768 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,5 +41,15 @@ jobs: with: go-version: "1.25" + # 3.4 is the oldest series still supported: 3.2 reached end of life in + # March 2026, so the anthropic gem's own `>= 3.2.0` floor is a statement + # about what it tolerates, not a version to install here. Gems are + # installed by the smoke test rather than by `bundler-cache`, which wants + # a Gemfile at the checkout root; the two Ruby examples keep their own. + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4" + - name: Run smoke tests run: ./tests/smoke-test.sh diff --git a/mcp-client-ruby/Gemfile b/mcp-client-ruby/Gemfile index d9f3d541..dfb9b637 100644 --- a/mcp-client-ruby/Gemfile +++ b/mcp-client-ruby/Gemfile @@ -2,7 +2,6 @@ source "https://rubygems.org" -gem "anthropic" -gem "base64" +gem "anthropic", '>= 1.60.0' gem "dotenv" gem "mcp", '>= 0.15.0' diff --git a/tests/README.md b/tests/README.md index 1a769729..2f0fa396 100644 --- a/tests/README.md +++ b/tests/README.md @@ -7,9 +7,11 @@ This directory contains smoke tests for the MCP quickstart examples. These tests The smoke tests verify: - **Servers**: Each weather server (Python, TypeScript, Rust, Go) can start, respond to MCP protocol requests, and honour the output schemas it advertises -- **Clients**: The Python and TypeScript MCP clients can connect to a mock server and list tools +- **Clients**: The Python, TypeScript and Ruby MCP clients can connect to a mock server and list tools -The Go and Rust clients are not covered here: on `main` both abort when no `.env` file is present, so they cannot be driven without credentials. Making them start credential-free is a change in their own directories, so their coverage lands with those changes rather than here. The Ruby examples are not covered either — the `mcp` gem cannot negotiate protocol revision `2026-07-28`. +The Go and Rust clients are not covered here: on `main` both abort when no `.env` file is present, so they cannot be driven without credentials. Making them start credential-free is a change in their own directories, so their coverage lands with those changes rather than here. + +The Ruby **weather server** is not covered, for an upstream reason rather than a local one. The `mcp` gem negotiates `2026-07-28`, but its server never emits the `resultType` field that revision makes mandatory, so the test client rejects its responses — including `tools/list`. That is a fix for the gem, not something an example can work around. The Ruby **client** is covered: there it is the gem's client code that runs, and the server it is pointed at is the mock. ## Structured content @@ -37,6 +39,8 @@ Tool calls reach the live NWS API. When it is unreachable the tools return an er - **Rust** stable - **Cargo** (for Rust builds) - **Go** 1.25+ +- **Ruby** 3.4+ (3.2 and 3.3 satisfy the gems, but 3.2 is past end of life) +- **Bundler** (ships with Ruby 3.x) ## How It Works @@ -111,6 +115,9 @@ nvm install 24 # Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# Ruby (via rbenv) +rbenv install 3.4 ``` ## Adding New Tests diff --git a/tests/smoke-test.sh b/tests/smoke-test.sh index acb039b5..7e34192c 100755 --- a/tests/smoke-test.sh +++ b/tests/smoke-test.sh @@ -100,12 +100,31 @@ test_mcp_client_typescript() { node "${client_dir}/build/index.js" "${MOCK_SERVER}" >/dev/null 2>&1 } +# Test: Ruby MCP client +# +# The client connects to the server before it looks for ANTHROPIC_API_KEY and +# exits 0 when there is none, so the connection and tool listing are exercised +# without credentials -- the same shape as the Python client above. +test_mcp_client_ruby() { + check_dependency ruby || return 1 + check_dependency bundle || return 1 + local client_dir="${PROJECT_ROOT}/mcp-client-ruby" + ensure_bundled "${client_dir}" || return 1 + (cd "${client_dir}" && bundle exec ruby client.rb "${MOCK_SERVER}") >/dev/null 2>&1 +} + # Run all tests # -# All four servers are covered. The Go and Rust clients are not: on main both -# abort when no .env file is present, so they cannot be driven without -# credentials. Making them start credential-free is a change in their own -# directories, so their coverage lands with those changes rather than here. +# The Go and Rust clients are not covered: on main both abort when no .env file +# is present, so they cannot be driven without credentials. Making them start +# credential-free is a change in their own directories, so their coverage lands +# with those changes rather than here. +# +# The Ruby weather server is not covered either, for an upstream reason rather +# than a local one: the mcp gem's server does not emit the `resultType` field +# that protocol revision 2026-07-28 makes mandatory, so the test client rejects +# its responses. The Ruby client is covered, because it is the gem's client +# code that runs there and the server it is pointed at is the mock. print_header "Running smoke tests" run_test "weather-server-python" test_weather_server_python run_test "weather-server-typescript" test_weather_server_typescript @@ -113,6 +132,7 @@ run_test "weather-server-rust" test_weather_server_rust run_test "weather-server-go" test_weather_server_go run_test "mcp-client-python" test_mcp_client_python run_test "mcp-client-typescript" test_mcp_client_typescript +run_test "mcp-client-ruby" test_mcp_client_ruby # Print summary echo "" diff --git a/tests/utils.sh b/tests/utils.sh index 7423eae7..200faed2 100644 --- a/tests/utils.sh +++ b/tests/utils.sh @@ -138,3 +138,22 @@ ensure_built() { run_build "go build in ${dir}" go build -o "server$(exe_suffix)" . || return 1 fi } + +# Ensure a Ruby project directory has its gems installed. +# +# Separate from ensure_built because there is nothing on disk to test for: a +# Ruby example has no build step and Gemfile.lock is gitignored, so the checks +# ensure_built makes -- does this artefact exist yet -- have no equivalent. +# `bundle check` asks Bundler itself whether the Gemfile is already satisfied, +# which is the only reliable form of the question. +# +# Returns rather than exits, as ensure_built does, so a failure to install +# fails only the test that needed the gems. +ensure_bundled() { + local dir=$1 + cd "${dir}" || return 1 + + if ! bundle check >/dev/null 2>&1; then + run_build "bundle install in ${dir}" bundle install || return 1 + fi +}