CI installed no Ruby toolchain and the suite ran no Ruby example, so nothing under mcp-client-ruby or weather-server-ruby was exercised by a green build. A dependency change there -- #177 removes the base64 gem now that the anthropic gem declares it -- merges on the strength of a check that never resolved its Gemfile. The client is drivable without credentials: it connects before it looks for ANTHROPIC_API_KEY and exits 0 when there is none, the same shape the Python and TypeScript clients already rely on for their coverage. The Ruby weather server stays uncovered. 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; that is a fix for the gem rather than something an example can work around. The README said the gem could not negotiate the revision at all, which stopped being true at 1.1.0 -- it negotiates, then answers incompletely. Ruby 3.4 rather than the 3.2.0 floor the anthropic gem declares: 3.2 reached end of life in March 2026, and a gem's floor says what it tolerates, not what an example should tell people to run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
153 lines
4.2 KiB
Shell
153 lines
4.2 KiB
Shell
#!/bin/bash
|
|
# Shared utilities for test scripts
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Print functions
|
|
print_header() {
|
|
echo -e "${YELLOW}==== $1 ====${NC}"
|
|
}
|
|
|
|
print_success() {
|
|
echo -e "${GREEN}✓ $1${NC}"
|
|
}
|
|
|
|
print_error() {
|
|
echo -e "${RED}✗ $1${NC}"
|
|
}
|
|
|
|
# Get project root directory
|
|
get_project_root() {
|
|
cd "$(dirname "$0")/.." && pwd
|
|
}
|
|
|
|
# Check if command exists
|
|
command_exists() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
# Check dependency and fail the calling test if not found.
|
|
#
|
|
# Returns rather than exits: a missing toolchain should fail its own test and
|
|
# let the rest of the suite run, not abort the whole script before the summary
|
|
# is printed.
|
|
check_dependency() {
|
|
local cmd=$1
|
|
if ! command_exists "${cmd}"; then
|
|
print_error "${cmd} is not installed"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# The executable suffix for this platform: ".exe" under MSYS/Git Bash, empty
|
|
# elsewhere. Windows will not run a binary without it, whatever the file is
|
|
# actually named.
|
|
exe_suffix() {
|
|
case "$(uname -s)" in
|
|
MINGW* | MSYS* | CYGWIN*) echo ".exe" ;;
|
|
*) echo "" ;;
|
|
esac
|
|
}
|
|
|
|
# Echo the path to a built binary, accounting for the .exe suffix on Windows.
|
|
# Prints nothing and returns 1 when neither exists.
|
|
resolve_binary() {
|
|
local path=$1
|
|
if [ -f "${path}$(exe_suffix)" ]; then
|
|
echo "${path}$(exe_suffix)"
|
|
elif [ -f "${path}" ]; then
|
|
echo "${path}"
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Setup test environment
|
|
setup_test() {
|
|
local test_name=$1
|
|
print_header "Testing ${test_name}"
|
|
|
|
PROJECT_ROOT=$(get_project_root)
|
|
SERVER_DIR="${PROJECT_ROOT}/${test_name}"
|
|
CLIENT_DIR="${PROJECT_ROOT}/${test_name}"
|
|
TEST_CLIENT="${PROJECT_ROOT}/tests/helpers/build/mcp-test-client.js"
|
|
MOCK_SERVER="${PROJECT_ROOT}/tests/helpers/build/mock-mcp-server.js"
|
|
}
|
|
|
|
# Ensure test helpers are built
|
|
ensure_helpers_built() {
|
|
if [ ! -f "${TEST_CLIENT}" ] || [ ! -f "${MOCK_SERVER}" ]; then
|
|
print_error "Test helpers not built"
|
|
print_header "Building test helpers..."
|
|
cd "${PROJECT_ROOT}/tests/helpers" || return 1
|
|
npm install >/dev/null 2>&1
|
|
npm run build >/dev/null 2>&1
|
|
cd - >/dev/null || return 1
|
|
print_success "Test helpers built"
|
|
fi
|
|
}
|
|
|
|
# Run a build step, showing its output only if it fails.
|
|
#
|
|
# Builds used to be run with `>/dev/null 2>&1` unconditionally, which hid
|
|
# compile errors: a failed build left no binary behind and the smoke test
|
|
# reported the downstream `spawn ... ENOENT` instead of the actual error.
|
|
run_build() {
|
|
local what=$1
|
|
shift
|
|
local output
|
|
if ! output=$("$@" 2>&1); then
|
|
print_error "${what} failed"
|
|
echo "${output}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Ensure a project directory is built (TypeScript/Rust/Go)
|
|
#
|
|
# Returns rather than exits, for the same reason check_dependency does: one
|
|
# project failing to build should fail its own test, not abort the suite
|
|
# before the summary is printed.
|
|
ensure_built() {
|
|
local dir=$1
|
|
cd "${dir}" || return 1
|
|
|
|
# Install npm dependencies if needed
|
|
if [ -f "package.json" ] && [ ! -d "node_modules" ]; then
|
|
run_build "npm install in ${dir}" npm install || return 1
|
|
fi
|
|
|
|
# Build TypeScript if needed
|
|
if [ -f "tsconfig.json" ] && [ ! -f "build/index.js" ]; then
|
|
run_build "npm run build in ${dir}" npm run build || return 1
|
|
fi
|
|
|
|
# Build Rust if needed
|
|
if [ -f "Cargo.toml" ] \
|
|
&& ! resolve_binary "target/release/weather" >/dev/null \
|
|
&& ! resolve_binary "target/debug/weather" >/dev/null; then
|
|
run_build "cargo build in ${dir}" cargo build --release || return 1
|
|
fi
|
|
|
|
# Build Go if needed
|
|
if [ -f "go.mod" ] && ! resolve_binary "server" >/dev/null; then
|
|
run_build "go build in ${dir}" go build -o "server$(exe_suffix)" . || return 1
|
|
fi
|
|
}
|
|
|
|
# Ensure a Ruby project directory has its gems installed.
|
|
#
|
|
# Unlike ensure_built there is no artefact to test for -- a Ruby example has no
|
|
# build step and Gemfile.lock is gitignored -- so ask Bundler directly.
|
|
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
|
|
}
|