diff --git a/actions/setup/sh/check_mcp_servers.sh b/actions/setup/sh/check_mcp_servers.sh new file mode 100755 index 00000000000..4bd4ad11c82 --- /dev/null +++ b/actions/setup/sh/check_mcp_servers.sh @@ -0,0 +1,289 @@ +#!/usr/bin/env bash +# Check MCP Server Functionality +# This script performs basic functionality checks on MCP servers configured by the MCP gateway +# It connects to each server, retrieves tools list, and displays available tools + +set -e + +# Usage: check_mcp_servers.sh GATEWAY_CONFIG_PATH GATEWAY_URL GATEWAY_API_KEY +# +# Arguments: +# GATEWAY_CONFIG_PATH : Path to the gateway output configuration file (gateway-output.json) +# GATEWAY_URL : The HTTP URL of the MCP gateway (e.g., http://localhost:8080) +# GATEWAY_API_KEY : API key for gateway authentication +# +# Exit codes: +# 0 - All server checks completed (warnings logged for failures) +# 1 - Invalid arguments or configuration file issues + +if [ "$#" -ne 3 ]; then + echo "Usage: $0 GATEWAY_CONFIG_PATH GATEWAY_URL GATEWAY_API_KEY" >&2 + exit 1 +fi + +GATEWAY_CONFIG_PATH="$1" +GATEWAY_URL="$2" +GATEWAY_API_KEY="$3" + +echo "==========================================" +echo "MCP Server Functionality Check" +echo "==========================================" +echo "" +echo "Configuration:" +echo " Gateway Config: $GATEWAY_CONFIG_PATH" +echo " Gateway URL: $GATEWAY_URL" +echo " API Key: ${GATEWAY_API_KEY:0:8}..." # Show only first 8 chars for security +echo "" + +# Validate configuration file exists +if [ ! -f "$GATEWAY_CONFIG_PATH" ]; then + echo "ERROR: Gateway configuration file not found: $GATEWAY_CONFIG_PATH" >&2 + exit 1 +fi + +echo "Reading gateway configuration..." +# Parse the mcpServers section from gateway-output.json +if ! MCP_SERVERS=$(jq -r '.mcpServers' "$GATEWAY_CONFIG_PATH" 2>/dev/null); then + echo "ERROR: Failed to parse mcpServers from configuration file" >&2 + echo "Configuration file content:" >&2 + cat "$GATEWAY_CONFIG_PATH" >&2 + exit 1 +fi + +# Check if mcpServers is null or empty +if [ "$MCP_SERVERS" = "null" ] || [ "$MCP_SERVERS" = "{}" ]; then + echo "No MCP servers configured in gateway output" + echo "Configuration appears to be empty or invalid" + exit 0 +fi + +echo "Gateway configuration loaded successfully" +echo "" + +# Get list of server names +SERVER_NAMES=$(echo "$MCP_SERVERS" | jq -r 'keys[]' 2>/dev/null) + +if [ -z "$SERVER_NAMES" ]; then + echo "No MCP servers found in configuration" + exit 0 +fi + +# Count servers +SERVER_COUNT=$(echo "$SERVER_NAMES" | wc -l | tr -d ' ') +echo "Found $SERVER_COUNT MCP server(s) to check" +echo "" + +# Track overall results +SERVERS_CHECKED=0 +SERVERS_SUCCEEDED=0 +SERVERS_FAILED=0 +SERVERS_SKIPPED=0 + +# Iterate through each server +while IFS= read -r SERVER_NAME; do + echo "==========================================" + echo "Checking server: $SERVER_NAME" + echo "==========================================" + + SERVERS_CHECKED=$((SERVERS_CHECKED + 1)) + + # Extract server configuration + echo "Extracting server configuration..." + SERVER_CONFIG=$(echo "$MCP_SERVERS" | jq -r ".\"$SERVER_NAME\"" 2>/dev/null) + + if [ "$SERVER_CONFIG" = "null" ]; then + echo "WARNING: Server configuration is null for: $SERVER_NAME" + SERVERS_FAILED=$((SERVERS_FAILED + 1)) + echo "" + continue + fi + + echo "Server configuration:" + echo "$SERVER_CONFIG" | jq '.' 2>/dev/null || echo "$SERVER_CONFIG" + echo "" + + # Extract server URL (should be HTTP URL pointing to gateway) + SERVER_URL=$(echo "$SERVER_CONFIG" | jq -r '.url // empty' 2>/dev/null) + + if [ -z "$SERVER_URL" ] || [ "$SERVER_URL" = "null" ]; then + echo "WARNING: Server does not have HTTP URL (not gatewayed): $SERVER_NAME" + echo "Skipping functionality check..." + SERVERS_SKIPPED=$((SERVERS_SKIPPED + 1)) + echo "" + continue + fi + + echo "Server URL: $SERVER_URL" + + # Extract authentication headers if present + AUTH_HEADER="" + if echo "$SERVER_CONFIG" | jq -e '.headers.Authorization' >/dev/null 2>&1; then + AUTH_HEADER=$(echo "$SERVER_CONFIG" | jq -r '.headers.Authorization' 2>/dev/null) + echo "Authentication: Configured (${AUTH_HEADER:0:20}...)" + else + echo "Authentication: None" + fi + echo "" + + # Step 1: Send MCP initialize request + echo "Step 1: Sending MCP initialize request..." + INIT_PAYLOAD='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"gh-aw-check","version":"1.0.0"}}}' + + echo "Request payload:" + echo "$INIT_PAYLOAD" | jq '.' 2>/dev/null || echo "$INIT_PAYLOAD" + echo "" + + # Make the request with proper headers (5 second timeout) + echo "Sending HTTP POST to: $SERVER_URL" + if [ -n "$AUTH_HEADER" ]; then + INIT_RESPONSE=$(curl -s -w "\n%{http_code}" --max-time 5 -X POST "$SERVER_URL" \ + -H "Content-Type: application/json" \ + -H "Authorization: $AUTH_HEADER" \ + -d "$INIT_PAYLOAD" 2>&1 || echo -e "\n000") + else + INIT_RESPONSE=$(curl -s -w "\n%{http_code}" --max-time 5 -X POST "$SERVER_URL" \ + -H "Content-Type: application/json" \ + -d "$INIT_PAYLOAD" 2>&1 || echo -e "\n000") + fi + + INIT_HTTP_CODE=$(echo "$INIT_RESPONSE" | tail -n 1) + INIT_BODY=$(echo "$INIT_RESPONSE" | head -n -1) + + echo "HTTP Status: $INIT_HTTP_CODE" + echo "Response:" + echo "$INIT_BODY" | jq '.' 2>/dev/null || echo "$INIT_BODY" + echo "" + + # Check if initialize succeeded + if [ "$INIT_HTTP_CODE" != "200" ]; then + echo "WARNING: Initialize request failed with HTTP $INIT_HTTP_CODE" + echo "Server may require different authentication or be unavailable" + SERVERS_FAILED=$((SERVERS_FAILED + 1)) + echo "" + continue + fi + + # Check for JSON-RPC error in response + if echo "$INIT_BODY" | jq -e '.error' >/dev/null 2>&1; then + echo "WARNING: Initialize request returned JSON-RPC error:" + echo "$INIT_BODY" | jq '.error' 2>/dev/null + SERVERS_FAILED=$((SERVERS_FAILED + 1)) + echo "" + continue + fi + + echo "✓ Initialize request succeeded" + echo "" + + # Step 2: Send tools/list request + echo "Step 2: Sending tools/list request..." + TOOLS_PAYLOAD='{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' + + echo "Request payload:" + echo "$TOOLS_PAYLOAD" | jq '.' 2>/dev/null || echo "$TOOLS_PAYLOAD" + echo "" + + echo "Sending HTTP POST to: $SERVER_URL" + if [ -n "$AUTH_HEADER" ]; then + TOOLS_RESPONSE=$(curl -s -w "\n%{http_code}" --max-time 5 -X POST "$SERVER_URL" \ + -H "Content-Type: application/json" \ + -H "Authorization: $AUTH_HEADER" \ + -d "$TOOLS_PAYLOAD" 2>&1 || echo -e "\n000") + else + TOOLS_RESPONSE=$(curl -s -w "\n%{http_code}" --max-time 5 -X POST "$SERVER_URL" \ + -H "Content-Type: application/json" \ + -d "$TOOLS_PAYLOAD" 2>&1 || echo -e "\n000") + fi + + TOOLS_HTTP_CODE=$(echo "$TOOLS_RESPONSE" | tail -n 1) + TOOLS_BODY=$(echo "$TOOLS_RESPONSE" | head -n -1) + + echo "HTTP Status: $TOOLS_HTTP_CODE" + echo "Response:" + echo "$TOOLS_BODY" | jq '.' 2>/dev/null || echo "$TOOLS_BODY" + echo "" + + # Check if tools/list succeeded + if [ "$TOOLS_HTTP_CODE" != "200" ]; then + echo "WARNING: tools/list request failed with HTTP $TOOLS_HTTP_CODE" + SERVERS_FAILED=$((SERVERS_FAILED + 1)) + echo "" + continue + fi + + # Check for JSON-RPC error in response + if echo "$TOOLS_BODY" | jq -e '.error' >/dev/null 2>&1; then + echo "WARNING: tools/list request returned JSON-RPC error:" + echo "$TOOLS_BODY" | jq '.error' 2>/dev/null + SERVERS_FAILED=$((SERVERS_FAILED + 1)) + echo "" + continue + fi + + echo "✓ tools/list request succeeded" + echo "" + + # Step 3: Display available tools + echo "Available tools from $SERVER_NAME:" + echo "---" + + # Extract tools array and display + if TOOLS_ARRAY=$(echo "$TOOLS_BODY" | jq -r '.result.tools[]?' 2>/dev/null); then + if [ -n "$TOOLS_ARRAY" ]; then + TOOL_COUNT=0 + while IFS= read -r TOOL; do + TOOL_COUNT=$((TOOL_COUNT + 1)) + TOOL_NAME=$(echo "$TOOL" | jq -r '.name // "unknown"' 2>/dev/null) + TOOL_DESC=$(echo "$TOOL" | jq -r '.description // "No description"' 2>/dev/null) + + echo " [$TOOL_COUNT] $TOOL_NAME" + echo " Description: $TOOL_DESC" + + # Show input schema if available + if echo "$TOOL" | jq -e '.inputSchema' >/dev/null 2>&1; then + echo " Input schema: $(echo "$TOOL" | jq -c '.inputSchema' 2>/dev/null)" + fi + echo "" + done <<< "$(echo "$TOOLS_BODY" | jq -c '.result.tools[]' 2>/dev/null)" + + echo "Total tools available: $TOOL_COUNT" + else + echo " No tools available from this server" + fi + else + echo " Could not parse tools array from response" + SERVERS_FAILED=$((SERVERS_FAILED + 1)) + echo "" + continue + fi + + echo "---" + echo "" + + SERVERS_SUCCEEDED=$((SERVERS_SUCCEEDED + 1)) + echo "✓ Server check completed successfully: $SERVER_NAME" + echo "" + +done <<< "$SERVER_NAMES" + +# Print summary +echo "==========================================" +echo "MCP Server Check Summary" +echo "==========================================" +echo "Servers checked: $SERVERS_CHECKED" +echo "Servers succeeded: $SERVERS_SUCCEEDED" +echo "Servers failed: $SERVERS_FAILED" +echo "Servers skipped: $SERVERS_SKIPPED" +echo "" + +if [ $SERVERS_FAILED -gt 0 ]; then + echo "ERROR: One or more MCP servers failed to respond" + echo "Failed servers: $SERVERS_FAILED" + exit 1 +elif [ $SERVERS_SUCCEEDED -eq 0 ]; then + echo "ERROR: No HTTP servers were successfully checked" + exit 1 +else + echo "✓ All HTTP server checks succeeded ($SERVERS_SUCCEEDED succeeded, $SERVERS_SKIPPED skipped)" + exit 0 +fi diff --git a/actions/setup/sh/check_mcp_servers_test.sh b/actions/setup/sh/check_mcp_servers_test.sh new file mode 100755 index 00000000000..f7c25a7c029 --- /dev/null +++ b/actions/setup/sh/check_mcp_servers_test.sh @@ -0,0 +1,358 @@ +#!/bin/bash +# Test script for check_mcp_servers.sh +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRIPT_PATH="$SCRIPT_DIR/check_mcp_servers.sh" + +# Color codes for output +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +# Test counters +TESTS_RUN=0 +TESTS_PASSED=0 +TESTS_FAILED=0 + +# Print test result +print_result() { + local test_name="$1" + local result="$2" + + TESTS_RUN=$((TESTS_RUN + 1)) + + if [ "$result" = "PASS" ]; then + echo -e "${GREEN}✓ PASS${NC}: $test_name" + TESTS_PASSED=$((TESTS_PASSED + 1)) + else + echo -e "${RED}✗ FAIL${NC}: $test_name" + TESTS_FAILED=$((TESTS_FAILED + 1)) + fi +} + +# Test 1: Script syntax is valid +test_script_syntax() { + echo "" + echo "Test 1: Verify script syntax" + + if bash -n "$SCRIPT_PATH" 2>/dev/null; then + print_result "Script syntax is valid" "PASS" + else + print_result "Script has syntax errors" "FAIL" + fi +} + +# Test 2: Script requires 3 arguments +test_argument_validation() { + echo "" + echo "Test 2: Argument validation" + + # Test with no arguments + if ! bash "$SCRIPT_PATH" 2>/dev/null; then + print_result "Script rejects no arguments" "PASS" + else + print_result "Script should reject no arguments" "FAIL" + fi + + # Test with 1 argument + if ! bash "$SCRIPT_PATH" "config.json" 2>/dev/null; then + print_result "Script rejects 1 argument" "PASS" + else + print_result "Script should reject 1 argument" "FAIL" + fi + + # Test with 2 arguments + if ! bash "$SCRIPT_PATH" "config.json" "http://localhost:8080" 2>/dev/null; then + print_result "Script rejects 2 arguments" "PASS" + else + print_result "Script should reject 2 arguments" "FAIL" + fi +} + +# Test 3: Config file not found +test_config_not_found() { + echo "" + echo "Test 3: Config file not found" + + local tmpdir=$(mktemp -d) + local nonexistent_config="$tmpdir/nonexistent.json" + + if ! bash "$SCRIPT_PATH" "$nonexistent_config" "http://localhost:8080" "test-key" 2>/dev/null; then + print_result "Script rejects non-existent config file" "PASS" + else + print_result "Script should reject non-existent config file" "FAIL" + fi + + rm -rf "$tmpdir" +} + +# Test 4: Invalid JSON configuration +test_invalid_json_config() { + echo "" + echo "Test 4: Invalid JSON configuration" + + local tmpdir=$(mktemp -d) + local config_file="$tmpdir/config.json" + + # Create invalid JSON + echo "{ invalid json" > "$config_file" + + if ! bash "$SCRIPT_PATH" "$config_file" "http://localhost:8080" "test-key" 2>/dev/null; then + print_result "Script rejects invalid JSON" "PASS" + else + print_result "Script should reject invalid JSON" "FAIL" + fi + + rm -rf "$tmpdir" +} + +# Test 5: Empty mcpServers object +test_empty_servers() { + echo "" + echo "Test 5: Empty mcpServers object" + + local tmpdir=$(mktemp -d) + local config_file="$tmpdir/config.json" + + # Create config with empty mcpServers + cat > "$config_file" <<'EOF' +{ + "mcpServers": {}, + "gateway": { + "port": 8080, + "domain": "localhost", + "apiKey": "test-key" + } +} +EOF + + # Should exit 0 but indicate no servers + if bash "$SCRIPT_PATH" "$config_file" "http://localhost:8080" "test-key" >/dev/null 2>&1; then + print_result "Script handles empty mcpServers gracefully" "PASS" + else + print_result "Script should handle empty mcpServers gracefully" "FAIL" + fi + + rm -rf "$tmpdir" +} + +# Test 6: Configuration with null mcpServers +test_null_servers() { + echo "" + echo "Test 6: Null mcpServers" + + local tmpdir=$(mktemp -d) + local config_file="$tmpdir/config.json" + + # Create config with null mcpServers + cat > "$config_file" <<'EOF' +{ + "mcpServers": null, + "gateway": { + "port": 8080, + "domain": "localhost", + "apiKey": "test-key" + } +} +EOF + + # Should exit 0 but indicate no servers + if bash "$SCRIPT_PATH" "$config_file" "http://localhost:8080" "test-key" >/dev/null 2>&1; then + print_result "Script handles null mcpServers gracefully" "PASS" + else + print_result "Script should handle null mcpServers gracefully" "FAIL" + fi + + rm -rf "$tmpdir" +} + +# Test 7: Valid configuration with HTTP server +test_valid_http_server() { + echo "" + echo "Test 7: Valid configuration with HTTP server" + + local tmpdir=$(mktemp -d) + local config_file="$tmpdir/config.json" + + # Create valid config with HTTP server + cat > "$config_file" <<'EOF' +{ + "mcpServers": { + "github": { + "type": "http", + "url": "http://localhost:8080/mcp/github", + "headers": { + "Authorization": "Bearer test-token" + } + } + }, + "gateway": { + "port": 8080, + "domain": "localhost", + "apiKey": "test-key" + } +} +EOF + + # Script should fail because no servers can be connected (no gateway running) + if ! bash "$SCRIPT_PATH" "$config_file" "http://localhost:8080" "test-key" >/dev/null 2>&1; then + print_result "Script fails when no servers can connect" "PASS" + else + print_result "Script should fail when no servers can connect" "FAIL" + fi + + rm -rf "$tmpdir" +} + +# Test 8: Server without URL (stdio server) +test_server_without_url() { + echo "" + echo "Test 8: Server without URL (stdio server)" + + local tmpdir=$(mktemp -d) + local config_file="$tmpdir/config.json" + + # Create config with stdio server (no URL) + cat > "$config_file" <<'EOF' +{ + "mcpServers": { + "safeinputs": { + "type": "stdio", + "command": "gh", + "args": ["aw", "mcp-server", "--mode", "safe-inputs"] + } + }, + "gateway": { + "port": 8080, + "domain": "localhost", + "apiKey": "test-key" + } +} +EOF + + # Should fail because only stdio servers (which are skipped) + if ! bash "$SCRIPT_PATH" "$config_file" "http://localhost:8080" "test-key" >/dev/null 2>&1; then + print_result "Script fails when only stdio servers (no HTTP servers)" "PASS" + else + print_result "Script should fail when only stdio servers" "FAIL" + fi + + rm -rf "$tmpdir" +} + +# Test 9: Multiple servers with mixed types +test_mixed_servers() { + echo "" + echo "Test 9: Multiple servers with mixed types" + + local tmpdir=$(mktemp -d) + local config_file="$tmpdir/config.json" + + # Create config with multiple servers + cat > "$config_file" <<'EOF' +{ + "mcpServers": { + "safeinputs": { + "type": "stdio", + "command": "gh", + "args": ["aw", "mcp-server", "--mode", "safe-inputs"] + }, + "github": { + "type": "http", + "url": "http://localhost:8080/mcp/github", + "headers": { + "Authorization": "Bearer github-token" + } + }, + "playwright": { + "type": "http", + "url": "http://localhost:8080/mcp/playwright" + } + }, + "gateway": { + "port": 8080, + "domain": "localhost", + "apiKey": "test-key" + } +} +EOF + + # Should fail because HTTP servers cannot connect (no gateway running) + if ! bash "$SCRIPT_PATH" "$config_file" "http://localhost:8080" "test-key" >/dev/null 2>&1; then + print_result "Script fails when HTTP servers cannot connect" "PASS" + else + print_result "Script should fail when HTTP servers cannot connect" "FAIL" + fi + + rm -rf "$tmpdir" +} + +# Test 10: Key validation functions exist +test_validation_functions_exist() { + echo "" + echo "Test 10: Verify key validation logic exists" + + # Check for configuration file validation + if grep -q "Gateway configuration file not found" "$SCRIPT_PATH"; then + print_result "Config file validation exists" "PASS" + else + print_result "Config file validation missing" "FAIL" + fi + + # Check for mcpServers parsing + if grep -q "Failed to parse mcpServers" "$SCRIPT_PATH"; then + print_result "mcpServers parsing validation exists" "PASS" + else + print_result "mcpServers parsing validation missing" "FAIL" + fi + + # Check for initialize request + if grep -q "method.*initialize" "$SCRIPT_PATH"; then + print_result "Initialize request logic exists" "PASS" + else + print_result "Initialize request logic missing" "FAIL" + fi + + # Check for tools/list request + if grep -q "tools/list" "$SCRIPT_PATH"; then + print_result "tools/list request logic exists" "PASS" + else + print_result "tools/list request logic missing" "FAIL" + fi + + # Check for tool display + if grep -q "Available tools" "$SCRIPT_PATH"; then + print_result "Tool display logic exists" "PASS" + else + print_result "Tool display logic missing" "FAIL" + fi +} + +# Run all tests +echo "=== Testing check_mcp_servers.sh ===" +echo "Script: $SCRIPT_PATH" + +test_script_syntax +test_argument_validation +test_config_not_found +test_invalid_json_config +test_empty_servers +test_null_servers +test_valid_http_server +test_server_without_url +test_mixed_servers +test_validation_functions_exist + +# Print summary +echo "" +echo "=== Test Summary ===" +echo "Tests run: $TESTS_RUN" +echo -e "${GREEN}Tests passed: $TESTS_PASSED${NC}" +if [ $TESTS_FAILED -gt 0 ]; then + echo -e "${RED}Tests failed: $TESTS_FAILED${NC}" + exit 1 +else + echo -e "${GREEN}All tests passed!${NC}" + exit 0 +fi diff --git a/actions/setup/sh/start_mcp_gateway.sh b/actions/setup/sh/start_mcp_gateway.sh index d7b70a6b7d2..df3974af546 100755 --- a/actions/setup/sh/start_mcp_gateway.sh +++ b/actions/setup/sh/start_mcp_gateway.sh @@ -278,6 +278,25 @@ case "$ENGINE_TYPE" in esac echo "" +# Check MCP server functionality +echo "Checking MCP server functionality..." +if [ -f /opt/gh-aw/actions/check_mcp_servers.sh ]; then + echo "Running MCP server checks..." + if ! bash /opt/gh-aw/actions/check_mcp_servers.sh \ + /tmp/gh-aw/mcp-config/gateway-output.json \ + "http://localhost:${MCP_GATEWAY_PORT}" \ + "${MCP_GATEWAY_API_KEY}"; then + echo "ERROR: MCP server checks failed - no servers could be connected" + echo "Gateway process will be terminated" + kill $GATEWAY_PID 2>/dev/null || true + exit 1 + fi +else + echo "WARNING: MCP server check script not found at /opt/gh-aw/actions/check_mcp_servers.sh" + echo "Skipping MCP server functionality checks" +fi +echo "" + echo "MCP gateway is running:" echo " - From host: http://localhost:${MCP_GATEWAY_PORT}" echo " - From containers: http://${MCP_GATEWAY_DOMAIN}:${MCP_GATEWAY_PORT}"