Testing Mcp Servers
Validate protocol compliance, exercise every capability, and catch regressions — all from the command line.
Why CLI-First Testing?
| Approach | Pros | Cons |
|---|---|---|
| Custom test client code | Full control | High effort, maintains its own MCP client |
| GUI-based MCP inspector | Visual | Manual, can’t automate |
| mcp2cli | Zero code, automatable, JSON output | Requires mcp2cli installed |
mcp2cli gives you a ready-made MCP client that speaks the full protocol. Point it at your server and validate everything.
Quick Smoke Test
The fastest way to validate a new MCP server:
# Test HTTP servermcp2cli --url http://localhost:3001/mcp doctor
# Test stdio servermcp2cli --stdio "./target/debug/my-server" doctordoctor runs a comprehensive health check:
- ✅ Transport connection
- ✅ Server info (name, version)
- ✅ Auth state
- ✅ Cached capabilities
Protocol Compliance Testing
Step 1: Initialize & Inspect
# Full server capabilities dumpmcp2cli --url http://localhost:3001/mcp --json inspectVerify:
- Protocol version matches (2025-11-25)
- Expected capabilities are advertised
- Server name and version are correct
# Extract specific capabilitiesmcp2cli --url http://localhost:3001/mcp --json inspect | \ jq '.data.capabilities'Step 2: Discovery
# List all toolsmcp2cli --url http://localhost:3001/mcp --json ls --tools | \ jq '.data.items[].id'
# List all resourcesmcp2cli --url http://localhost:3001/mcp --json ls --resources | \ jq '.data.items[].id'
# List all promptsmcp2cli --url http://localhost:3001/mcp --json ls --prompts | \ jq '.data.items[].id'Step 3: Tool Invocation
# Call each tool and verify it returns without errorfor tool in $(mcp2cli --url http://localhost:3001/mcp --json ls --tools | jq -r '.data.items[].id'); do echo "Testing: $tool" if mcp2cli --url http://localhost:3001/mcp --json --timeout 10 "$tool" 2>/dev/null; then echo " ✅ $tool OK" else echo " ❌ $tool FAILED (exit code: $?)" fidoneStep 4: Resource Reading
# Test every concrete resourcemcp2cli --url http://localhost:3001/mcp --json ls --resources | \ jq -r '.data.items[] | select(.kind == "resource") | .id' | \ while read uri; do echo "Reading: $uri" mcp2cli --url http://localhost:3001/mcp --json get "$uri" | jq '.summary' doneStep 5: Ping & Latency
# Liveness checkmcp2cli --url http://localhost:3001/mcp --json ping | jq '.data'Automated Test Script
Save this as test-mcp-server.sh:
#!/bin/bashset -euo pipefail
ENDPOINT="${1:?Usage: $0 <endpoint-or-stdio-command>}"PASS=0FAIL=0
# Determine connection modeif [[ "$ENDPOINT" == http* ]]; then BASE="mcp2cli --url $ENDPOINT --json --timeout 15"else BASE="mcp2cli --stdio \"$ENDPOINT\" --json --timeout 15"fi
run_test() { local name="$1" shift if eval "$BASE $*" >/dev/null 2>&1; then echo " ✅ $name" ((PASS++)) else echo " ❌ $name (exit $?)" ((FAIL++)) fi}
echo "=== MCP Server Test Suite ==="echo "Target: $ENDPOINT"echo ""
echo "--- Connectivity ---"run_test "ping" "ping"run_test "doctor" "doctor"run_test "inspect" "inspect"
echo ""echo "--- Discovery ---"run_test "discover all" "ls"run_test "discover tools" "ls --tools"run_test "discover resources" "ls --resources"run_test "discover prompts" "ls --prompts"
echo ""echo "--- Tool Calls ---"TOOLS=$(eval "$BASE ls --tools" 2>/dev/null | jq -r '.data.items[].id' 2>/dev/null || echo "")for tool in $TOOLS; do run_test "tool: $tool" "$tool" 2>/dev/null || truedone
echo ""echo "=== Results: $PASS passed, $FAIL failed ==="exit $FAILUsage:
chmod +x test-mcp-server.sh
# Test HTTP server./test-mcp-server.sh http://localhost:3001/mcp
# Test stdio server./test-mcp-server.sh "npx @modelcontextprotocol/server-everything"Regression Testing in CI/CD
GitHub Actions
name: MCP Server Testson: [push, pull_request]
jobs: test-mcp: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Build server run: cargo build --release
- name: Install mcp2cli run: cargo install --path .
- name: Start server run: ./target/release/my-mcp-server &
- name: Wait for server run: | for i in $(seq 1 30); do mcp2cli --url http://localhost:3001/mcp --timeout 5 ping && break sleep 1 done
- name: Run protocol tests run: | # Discovery TOOLS=$(mcp2cli --url http://localhost:3001/mcp --json ls --tools | jq '.data.items | length') echo "Server exposes $TOOLS tools" [ "$TOOLS" -gt 0 ] || exit 1
# Smoke test each tool mcp2cli --url http://localhost:3001/mcp --json ls --tools | \ jq -r '.data.items[].id' | while read tool; do echo "Testing $tool..." mcp2cli --url http://localhost:3001/mcp --json --timeout 10 "$tool" || \ echo "WARNING: $tool failed" done
# Health check mcp2cli --url http://localhost:3001/mcp doctorTesting Specific Scenarios
Required vs. Optional Arguments
# Should fail — missing required argmcp2cli --url http://localhost:3001/mcp echo 2>&1 && echo "BAD: should have failed"
# Should succeed — with required argmcp2cli --url http://localhost:3001/mcp echo --message hello || echo "BAD: should have passed"Argument Type Validation
# Integer argumentmcp2cli --url http://localhost:3001/mcp add --a 5 --b 3
# Boolean flagmcp2cli --url http://localhost:3001/mcp process --include-metadata
# JSON argumentmcp2cli --url http://localhost:3001/mcp deploy --config '{"replicas": 3}'
# Array argumentmcp2cli --url http://localhost:3001/mcp tag --labels bug,critical,p0Error Handling
# Call a non-existent tool (should fail gracefully)mcp2cli --url http://localhost:3001/mcp nonexistent-tool 2>&1
# Pass invalid argument types (should fail with validation error)mcp2cli --url http://localhost:3001/mcp add --a "not-a-number" --b 3 2>&1Timeout Behavior
# Server should respond within 5 secondsmcp2cli --url http://localhost:3001/mcp --timeout 5 ping
# Test slow operationsmcp2cli --url http://localhost:3001/mcp --timeout 2 slow-operation 2>&1 | \ grep -q "timed out" && echo "Timeout works correctly"Comparing Server Outputs
Capture baseline output and diff against future runs:
# Create baselinemcp2cli --url http://localhost:3001/mcp --json ls > baseline.json
# After changes, comparemcp2cli --url http://localhost:3001/mcp --json ls > current.jsondiff <(jq -S '.data.items[].id' baseline.json) <(jq -S '.data.items[].id' current.json)Demo Mode for Fixtures
Use the built-in demo backend for deterministic testing when you need a stable baseline:
mcp2cli config init --name fixture --transport streamable_http --endpoint https://demo.invalid/mcpmcp2cli use fixturemcp2cli --json ls | jq '.data.items | length' # Always returns same countSee Also
- Ad-Hoc Connections —
--url/--stdiofor stateless testing - Output Formats — JSON output for assertions
- Request Timeouts — timeout enforcement
- Shell Scripting with MCP — full scripting patterns