#!/usr/bin/env bash # ═══════════════════════════════════════════════════════════ # GooseFactory MCP Server — Tool Verification Script # # Tests all 12 MCP tools via JSON-RPC over stdio. # Prerequisites: # - API running at http://localhost:4000 # - MCP server compiled (packages/mcp-server/dist/index.js) # # Usage: ./scripts/test-mcp-tools.sh # ═══════════════════════════════════════════════════════════ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" MCP_SERVER="$PROJECT_DIR/packages/mcp-server/dist/index.js" API_URL="${FACTORY_API_URL:-http://localhost:4000}" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color PASS=0 FAIL=0 ERRORS=() # ─── Preflight checks ──────────────────────────────────── echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" echo -e "${BLUE} GooseFactory MCP Server — Tool Verification${NC}" echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" echo "" # Check API health echo -n "Checking API health... " HEALTH=$(curl -s "$API_URL/health" 2>/dev/null || echo "FAIL") if echo "$HEALTH" | grep -q '"status":"ok"'; then echo -e "${GREEN}✅ API healthy${NC}" else echo -e "${RED}❌ API not reachable at $API_URL${NC}" echo " Start it with: cd packages/api && DEV_MODE=true npx tsx src/index.ts" exit 1 fi # Check MCP server exists if [ ! -f "$MCP_SERVER" ]; then echo -e "${RED}❌ MCP server not compiled at $MCP_SERVER${NC}" echo " Build it with: cd packages/mcp-server && npx tsc" exit 1 fi echo -e "MCP server: ${GREEN}$MCP_SERVER${NC}" echo "" # ─── Helper: call MCP tool via stdio ───────────────────── call_mcp_tool() { local tool_name="$1" local arguments="$2" printf '%s\n%s\n%s\n' \ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"mcp-test","version":"1.0"}}}' \ '{"jsonrpc":"2.0","method":"notifications/initialized"}' \ "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"$tool_name\",\"arguments\":$arguments}}" \ | node "$MCP_SERVER" 2>/dev/null | tail -1 } # ─── Helper: run test ──────────────────────────────────── run_test() { local num="$1" local name="$2" local args="$3" local desc="$4" echo -n " $num. $name... " local result result=$(call_mcp_tool "$name" "$args" 2>/dev/null) if [ -z "$result" ]; then echo -e "${RED}❌ NO RESPONSE${NC}" FAIL=$((FAIL + 1)) ERRORS+=("$name: No response from MCP server") return fi # Check for JSON-RPC error if echo "$result" | grep -q '"error"'; then local err_msg err_msg=$(echo "$result" | sed 's/.*"message":"\([^"]*\)".*/\1/' | head -c 100) echo -e "${RED}❌ RPC ERROR: $err_msg${NC}" FAIL=$((FAIL + 1)) ERRORS+=("$name: $err_msg") return fi # Check for tool-level isError if echo "$result" | grep -q '"isError":true'; then local err_text err_text=$(echo "$result" | sed 's/.*"text":"\([^"]*\).*/\1/' | head -c 120) echo -e "${RED}❌ $err_text${NC}" FAIL=$((FAIL + 1)) ERRORS+=("$name: $err_text") return fi # Extract first 100 chars of text content for display local preview preview=$(echo "$result" | sed 's/.*"text":"\([^"]*\).*/\1/' | head -c 100) echo -e "${GREEN}✅${NC} ${preview}" PASS=$((PASS + 1)) } # ─── Get test data from API ────────────────────────────── echo -e "${YELLOW}Fetching test data from API...${NC}" # Get a pipeline ID PIPELINE_ID=$(curl -s "$API_URL/v1/pipelines?limit=1" 2>/dev/null | sed 's/.*"id":"\([^"]*\)".*/\1/' | head -c 36) echo " Pipeline ID: $PIPELINE_ID" # Create a test pipeline for advance/deploy/test operations NEW_PIPELINE=$(curl -s -X POST "$API_URL/v1/pipelines" \ -H 'Content-Type: application/json' \ -d '{"name":"mcp-test-run-'"$(date +%s)"'","platform":"test","priority":"low"}' 2>/dev/null) TEST_PIPELINE_ID=$(echo "$NEW_PIPELINE" | sed 's/.*"id":"\([^"]*\)".*/\1/' | head -c 36) echo " Test Pipeline ID: $TEST_PIPELINE_ID" # Advance test pipeline to review (creates approval task with real UUID) # intake -> scaffolding -> building -> testing (4 advances to get to review) for i in 1 2 3; do curl -s -X POST "$API_URL/v1/pipelines/$TEST_PIPELINE_ID/stages/advance" \ -H 'Content-Type: application/json' -d '{"skipValidation":true}' > /dev/null 2>&1 done # This advance goes to review (requires approval) -> creates task ADVANCE_RESULT=$(curl -s -X POST "$API_URL/v1/pipelines/$TEST_PIPELINE_ID/stages/advance" \ -H 'Content-Type: application/json' -d '{"skipValidation":true}' 2>/dev/null) # Extract task ID from tasksCreated array - get the id after "tasksCreated" TASK_ID=$(echo "$ADVANCE_RESULT" | node -e " let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>{ try { const j=JSON.parse(d); const tasks = j.data?.tasksCreated || []; if (tasks.length > 0) { console.log(tasks[0].id); } else { console.log(''); } } catch(e) { console.log(''); } });" 2>/dev/null) if [ -z "$TASK_ID" ] || [ ${#TASK_ID} -lt 36 ]; then echo " (No task from advance, checking pending tasks...)" TASK_ID=$(curl -s "$API_URL/v1/tasks?status=pending&limit=5" | node -e " let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>{ try { const j=JSON.parse(d); // Find a task with a valid RFC4122 UUID (variant byte 8-b) const t = j.data?.find(t => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(t.id)); if (t) console.log(t.id); else console.log(''); } catch(e) { console.log(''); } });" 2>/dev/null) fi echo " Task ID: $TASK_ID" # Create a second pipeline for the reject task NEW_PIPELINE2=$(curl -s -X POST "$API_URL/v1/pipelines" \ -H 'Content-Type: application/json' \ -d '{"name":"mcp-reject-test-'"$(date +%s)"'","platform":"test","priority":"low"}' 2>/dev/null) TEST_PIPELINE2_ID=$(echo "$NEW_PIPELINE2" | node -e " let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>{ try { console.log(JSON.parse(d).data.id); } catch(e) { console.log(''); } });" 2>/dev/null) # Advance pipeline2 to review for i in 1 2 3; do curl -s -X POST "$API_URL/v1/pipelines/$TEST_PIPELINE2_ID/stages/advance" \ -H 'Content-Type: application/json' -d '{"skipValidation":true}' > /dev/null 2>&1 done REJECT_RESULT=$(curl -s -X POST "$API_URL/v1/pipelines/$TEST_PIPELINE2_ID/stages/advance" \ -H 'Content-Type: application/json' -d '{"skipValidation":true}' 2>/dev/null) REJECT_TASK_ID=$(echo "$REJECT_RESULT" | node -e " let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>{ try { const j=JSON.parse(d); const tasks = j.data?.tasksCreated || []; if (tasks.length > 0) { console.log(tasks[0].id); } else { console.log(''); } } catch(e) { console.log(''); } });" 2>/dev/null) echo " Reject Task ID: $REJECT_TASK_ID" echo "" # ─── Run all 12 tests ──────────────────────────────────── echo -e "${BLUE}Running 12 tool tests...${NC}" echo "" # === Task Tools (3) === echo -e "${YELLOW}Task Tools:${NC}" run_test "01" "factory_get_pending_tasks" \ '{"limit":5}' \ "Get pending tasks" run_test "02" "factory_approve_task" \ "{\"task_id\":\"$TASK_ID\",\"notes\":\"Test approval via script\"}" \ "Approve a task" run_test "03" "factory_reject_task" \ "{\"task_id\":\"${REJECT_TASK_ID:-$TASK_ID}\",\"reason\":\"Test rejection via script\",\"severity\":\"minor\"}" \ "Reject a task" echo "" # === Pipeline Tools (4) === echo -e "${YELLOW}Pipeline Tools:${NC}" run_test "04" "factory_get_pipeline_status" \ '{"status":"active"}' \ "Get pipeline status" run_test "05" "factory_advance_stage" \ "{\"pipeline_id\":\"$PIPELINE_ID\",\"skip_validation\":true,\"notes\":\"Test script advance\"}" \ "Advance pipeline stage" run_test "06" "factory_create_pipeline" \ "{\"name\":\"test-script-pipeline-$(date +%s)\",\"platform\":\"test\",\"priority\":\"low\"}" \ "Create a new pipeline" run_test "07" "factory_search" \ '{"query":"mcp","limit":5}' \ "Search across entities" echo "" # === Operations Tools (4) === echo -e "${YELLOW}Operations Tools:${NC}" run_test "08" "factory_assign_priority" \ "{\"entity_type\":\"pipeline\",\"entity_id\":\"$PIPELINE_ID\",\"priority\":\"high\",\"reason\":\"Test script priority change\"}" \ "Assign priority" run_test "09" "factory_get_blockers" \ '{}' \ "Get blockers" run_test "10" "factory_run_tests" \ "{\"pipeline_id\":\"$PIPELINE_ID\",\"test_type\":\"all\"}" \ "Run tests" run_test "11" "factory_deploy" \ "{\"pipeline_id\":\"$PIPELINE_ID\",\"target\":\"staging\",\"dry_run\":true}" \ "Deploy (dry run)" echo "" # === Review Tools (1) === echo -e "${YELLOW}Review Tools:${NC}" run_test "12" "factory_request_review" \ "{\"pipeline_id\":\"$PIPELINE_ID\",\"modal_type\":\"traffic-light\"}" \ "Request review modal" echo "" # ─── Summary ───────────────────────────────────────────── echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" echo -e " Results: ${GREEN}$PASS passed${NC} / ${RED}$FAIL failed${NC} / 12 total" echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" if [ ${#ERRORS[@]} -gt 0 ]; then echo "" echo -e "${RED}Failures:${NC}" for err in "${ERRORS[@]}"; do echo -e " ${RED}•${NC} $err" done fi echo "" if [ $FAIL -eq 0 ]; then echo -e "${GREEN}🎉 All 12 MCP tools are working!${NC}" exit 0 else echo -e "${YELLOW}⚠️ $FAIL tool(s) need attention${NC}" exit 1 fi