#!/usr/bin/env bash # MCP Server Integration Test Script # Requires: the app running with MCP server toggled ON # Setup: adb forward tcp:8734 tcp:8734 set -e BASE="${1:-http://127.0.0.1:8734}" PASS=0 FAIL=0 green() { echo -e "\033[32m✓ $*\033[0m"; } red() { echo -e "\033[31m✗ $*\033[0m"; ((FAIL++)); } header() { echo -e "\n\033[1;36m━━━ $* ━━━\033[0m"; } assert_ok() { local code=$(echo "$1" | jq -r 'if .result then "ok" else "err:" + .error.message end') if [ "$code" = "ok" ]; then green "$2" else red "$2 — $code" fi } assert_eq() { local actual=$(echo "$1" | jq -r "$2") if [ "$actual" = "$3" ]; then green "$4" else red "$4 — expected '$3', got '$actual'" fi } rpc() { # $1 = method, $2 = params JSON (optional), $3 = id (optional, default: 1) local method="$1" local params="${2:-{}}" local id="${3:-1}" curl -s -X POST "$BASE/mcp" \ -H "Content-Type: application/json" \ -d "{\"jsonrpc\":\"2.0\",\"id\":$id,\"method\":\"$method\",\"params\":$params}" } # ── 1. Health Check ───────────────────────────────────────────── header "1. Health Check" HEALTH=$(curl -s "$BASE/health") assert_eq "$HEALTH" ".status" "ok" "Health endpoint returns ok" # ── 2. Initialize Handshake ───────────────────────────────────── header "2. Initialize Handshake" INIT=$(rpc "initialize" '{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}') assert_ok "$INIT" "Initialize succeeds" assert_eq "$INIT" ".result.protocolVersion" "2024-11-05" "Protocol version" assert_eq "$INIT" ".result.serverInfo.name" "ijs-mcp" "Server name" # Send initialized notification INITD=$(rpc "notifications/initialized" '{}' "null") echo " Initialized notification → HTTP $(curl -s -o /dev/null -w '%{http_code}' -X POST "$BASE/mcp" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"notifications/initialized"}')" # ── 3. List Tools ─────────────────────────────────────────────── header "3. List Tools" TOOLS=$(rpc "tools/list") assert_ok "$TOOLS" "tools/list succeeds" TOOL_COUNT=$(echo "$TOOLS" | jq '.result.tools | length') echo " Tools available: $TOOL_COUNT" for name in evaluate differentiate integrate solve simplify numeric to_latex list_plugins get_plugin create_plugin delete_plugin execute_plugin; do if echo "$TOOLS" | jq -e ".result.tools[] | select(.name == \"$name\")" > /dev/null 2>&1; then green " Tool: $name" else red " Tool: $name — MISSING" fi done # ── 4. Call Tools — Core Math ─────────────────────────────────── header "4. Call Tools — Core Math" # 4a. evaluate: basic arithmetic R=$(rpc "tools/call" '{"name":"evaluate","arguments":{"expression":"2 + 2"}}') assert_ok "$R" "evaluate: 2+2" assert_eq "$R" '.result.content[0].text' "4" "evaluate: result = 4" # 4b. evaluate: variable assignment (shared session) R=$(rpc "tools/call" '{"name":"evaluate","arguments":{"expression":"myTestVar = 42"}}' 3) assert_ok "$R" "evaluate: myTestVar = 42" # 4c. evaluate: use assigned variable R=$(rpc "tools/call" '{"name":"evaluate","arguments":{"expression":"myTestVar * 2"}}' 4) assert_eq "$R" '.result.content[0].text' "84" "evaluate: myTestVar * 2 = 84 (shared session)" # 4d. differentiate (use clean variable 'y' to avoid collision with myTestVar) R=$(rpc "tools/call" '{"name":"differentiate","arguments":{"expression":"y^3","variable":"y"}}' 5) assert_eq "$R" '.result.content[0].text' "3*y^2" "differentiate: D[y^3, y] = 3*y^2" # 4e. integrate (indefinite) — use variable 't' to avoid collisions R=$(rpc "tools/call" '{"name":"integrate","arguments":{"expression":"t^2","variable":"t"}}' 6) assert_ok "$R" "integrate: indefinite ∫t²dt" echo " Result: $(echo "$R" | jq -r '.result.content[0].text')" # 4f. integrate (definite) R=$(rpc "tools/call" '{"name":"integrate","arguments":{"expression":"t^2","variable":"t","lower":"0","upper":"1"}}' 7) assert_eq "$R" '.result.content[0].text' "1/3" "integrate: definite ∫₀¹t²dt = 1/3" # 4g. solve R=$(rpc "tools/call" '{"name":"solve","arguments":{"equation":"u^2 - 5*u + 6 == 0","variable":"u"}}' 8) assert_ok "$R" "solve: u²-5u+6=0" echo " Result: $(echo "$R" | jq -r '.result.content[0].text')" # 4h. simplify R=$(rpc "tools/call" '{"name":"simplify","arguments":{"expression":"Sin[a]^2 + Cos[a]^2"}}' 9) assert_eq "$R" '.result.content[0].text' "1" "simplify: sin²a+cos²a = 1" # 4i. numeric R=$(rpc "tools/call" '{"name":"numeric","arguments":{"expression":"Pi","precision":"50"}}' 10) assert_ok "$R" "numeric: N[Pi, 50]" echo " Result: $(echo "$R" | jq -r '.result.content[0].text' | head -c 60)..." # 4j. to_latex R=$(rpc "tools/call" '{"name":"to_latex","arguments":{"expression":"z^2 + 2*z + 1"}}' 11) assert_ok "$R" "to_latex: TeXForm[z²+2z+1]" echo " LaTeX: $(echo "$R" | jq -r '.result.content[0].text')" # 4k. evaluate: Symja error handling R=$(rpc "tools/call" '{"name":"evaluate","arguments":{"expression":"1/0"}}' 12) assert_ok "$R" "evaluate: 1/0 (graceful error)" echo " Error msg: $(echo "$R" | jq -r '.result.content[0].text' | head -c 80)" # ── 5. List Resources ─────────────────────────────────────────── header "5. List Resources" RESOURCES=$(rpc "resources/list") assert_ok "$RESOURCES" "resources/list succeeds" echo " Resources:" echo "$RESOURCES" | jq -r '.result.resources[] | " \(.uri) — \(.name)"' # ── 6. Read Resources ─────────────────────────────────────────── header "6. Read Resources" R=$(rpc "resources/read" '{"uri":"variables://current"}' 13) assert_ok "$R" "resources/read: variables://current" echo " Variables: $(echo "$R" | jq -r '.result.contents[0].text')" R=$(rpc "resources/read" '{"uri":"history://recent"}' 14) assert_ok "$R" "resources/read: history://recent" # ── 7. Plugin CRUD ────────────────────────────────────────────── header "7. Plugin CRUD" R=$(rpc "tools/call" '{"name":"list_plugins","arguments":{}}' 15) assert_ok "$R" "list_plugins" R=$(rpc "tools/call" '{"name":"create_plugin","arguments":{"id":"test_hello","name":"Test Hello","icon":"👋","command":"hello","description":"A test plugin","js_code":"function calculate(inputs, Symja) {\n Symja.print(\"Hello from plugin!\");\n return \"Pi*\" + inputs.radius + \"^2\";\n}","inputs":[{"name":"radius","label":"Radius","defaultValue":"1"}]}}' 16) assert_ok "$R" "create_plugin: test_hello" echo " $R" | jq -r '.result.content[0].text' R=$(rpc "tools/call" '{"name":"get_plugin","arguments":{"plugin_id":"test_hello"}}' 17) assert_ok "$R" "get_plugin: test_hello" R=$(rpc "tools/call" '{"name":"execute_plugin","arguments":{"plugin_id":"test_hello","arguments":{"radius":"5"}}}' 18) assert_ok "$R" "execute_plugin: hello[radius=5]" echo " Plugin output:" echo "$R" | jq -r '.result.content[0].text' R=$(rpc "tools/call" '{"name":"delete_plugin","arguments":{"plugin_id":"test_hello"}}' 19) assert_ok "$R" "delete_plugin: test_hello" # ── 8. JSON-RPC Error Handling ────────────────────────────────── header "8. JSON-RPC Error Handling" R=$(curl -s -X POST "$BASE/mcp" -H "Content-Type: application/json" -d '{"jsonrpc":"1.0","id":1,"method":"evaluate"}') assert_eq "$R" '.error.message' "Invalid Request: jsonrpc must be 2.0" "Rejects non-2.0 jsonrpc" R=$(curl -s -X POST "$BASE/mcp" -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1}') assert_eq "$R" '.error.message' "Invalid Request: missing method" "Rejects missing method" R=$(rpc "nonexistent_method" '{}' 20) assert_eq "$R" '.error.code' "-32603" "Unknown method returns proper JSON-RPC error" assert_eq "$R" '.id' "20" "Error preserves request id" R=$(curl -s -X POST "$BASE/mcp" -H "Content-Type: application/json" -d 'not json') assert_eq "$R" '.error.message' "Parse error: invalid JSON" "Rejects invalid JSON" # ── Summary ───────────────────────────────────────────────────── header "Results" echo " Passed: $PASS" echo " Failed: $FAIL" if [ $FAIL -eq 0 ]; then green "All tests passed!" else red "$FAIL test(s) failed" exit 1 fi