test_mcp.sh 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. #!/usr/bin/env bash
  2. # MCP Server Integration Test Script
  3. # Requires: the app running with MCP server toggled ON
  4. # Setup: adb forward tcp:8734 tcp:8734
  5. set -e
  6. BASE="${1:-http://127.0.0.1:8734}"
  7. PASS=0
  8. FAIL=0
  9. green() { echo -e "\033[32m✓ $*\033[0m"; }
  10. red() { echo -e "\033[31m✗ $*\033[0m"; ((FAIL++)); }
  11. header() { echo -e "\n\033[1;36m━━━ $* ━━━\033[0m"; }
  12. assert_ok() {
  13. local code=$(echo "$1" | jq -r 'if .result then "ok" else "err:" + .error.message end')
  14. if [ "$code" = "ok" ]; then
  15. green "$2"
  16. else
  17. red "$2 — $code"
  18. fi
  19. }
  20. assert_eq() {
  21. local actual=$(echo "$1" | jq -r "$2")
  22. if [ "$actual" = "$3" ]; then
  23. green "$4"
  24. else
  25. red "$4 — expected '$3', got '$actual'"
  26. fi
  27. }
  28. rpc() {
  29. # $1 = method, $2 = params JSON (optional), $3 = id (optional, default: 1)
  30. local method="$1"
  31. local params="${2:-{}}"
  32. local id="${3:-1}"
  33. curl -s -X POST "$BASE/mcp" \
  34. -H "Content-Type: application/json" \
  35. -d "{\"jsonrpc\":\"2.0\",\"id\":$id,\"method\":\"$method\",\"params\":$params}"
  36. }
  37. # ── 1. Health Check ─────────────────────────────────────────────
  38. header "1. Health Check"
  39. HEALTH=$(curl -s "$BASE/health")
  40. assert_eq "$HEALTH" ".status" "ok" "Health endpoint returns ok"
  41. # ── 2. Initialize Handshake ─────────────────────────────────────
  42. header "2. Initialize Handshake"
  43. INIT=$(rpc "initialize" '{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}')
  44. assert_ok "$INIT" "Initialize succeeds"
  45. assert_eq "$INIT" ".result.protocolVersion" "2024-11-05" "Protocol version"
  46. assert_eq "$INIT" ".result.serverInfo.name" "ijs-mcp" "Server name"
  47. # Send initialized notification
  48. INITD=$(rpc "notifications/initialized" '{}' "null")
  49. 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"}')"
  50. # ── 3. List Tools ───────────────────────────────────────────────
  51. header "3. List Tools"
  52. TOOLS=$(rpc "tools/list")
  53. assert_ok "$TOOLS" "tools/list succeeds"
  54. TOOL_COUNT=$(echo "$TOOLS" | jq '.result.tools | length')
  55. echo " Tools available: $TOOL_COUNT"
  56. for name in evaluate differentiate integrate solve simplify numeric to_latex list_plugins get_plugin create_plugin delete_plugin execute_plugin; do
  57. if echo "$TOOLS" | jq -e ".result.tools[] | select(.name == \"$name\")" > /dev/null 2>&1; then
  58. green " Tool: $name"
  59. else
  60. red " Tool: $name — MISSING"
  61. fi
  62. done
  63. # ── 4. Call Tools — Core Math ───────────────────────────────────
  64. header "4. Call Tools — Core Math"
  65. # 4a. evaluate: basic arithmetic
  66. R=$(rpc "tools/call" '{"name":"evaluate","arguments":{"expression":"2 + 2"}}')
  67. assert_ok "$R" "evaluate: 2+2"
  68. assert_eq "$R" '.result.content[0].text' "4" "evaluate: result = 4"
  69. # 4b. evaluate: variable assignment (shared session)
  70. R=$(rpc "tools/call" '{"name":"evaluate","arguments":{"expression":"myTestVar = 42"}}' 3)
  71. assert_ok "$R" "evaluate: myTestVar = 42"
  72. # 4c. evaluate: use assigned variable
  73. R=$(rpc "tools/call" '{"name":"evaluate","arguments":{"expression":"myTestVar * 2"}}' 4)
  74. assert_eq "$R" '.result.content[0].text' "84" "evaluate: myTestVar * 2 = 84 (shared session)"
  75. # 4d. differentiate (use clean variable 'y' to avoid collision with myTestVar)
  76. R=$(rpc "tools/call" '{"name":"differentiate","arguments":{"expression":"y^3","variable":"y"}}' 5)
  77. assert_eq "$R" '.result.content[0].text' "3*y^2" "differentiate: D[y^3, y] = 3*y^2"
  78. # 4e. integrate (indefinite) — use variable 't' to avoid collisions
  79. R=$(rpc "tools/call" '{"name":"integrate","arguments":{"expression":"t^2","variable":"t"}}' 6)
  80. assert_ok "$R" "integrate: indefinite ∫t²dt"
  81. echo " Result: $(echo "$R" | jq -r '.result.content[0].text')"
  82. # 4f. integrate (definite)
  83. R=$(rpc "tools/call" '{"name":"integrate","arguments":{"expression":"t^2","variable":"t","lower":"0","upper":"1"}}' 7)
  84. assert_eq "$R" '.result.content[0].text' "1/3" "integrate: definite ∫₀¹t²dt = 1/3"
  85. # 4g. solve
  86. R=$(rpc "tools/call" '{"name":"solve","arguments":{"equation":"u^2 - 5*u + 6 == 0","variable":"u"}}' 8)
  87. assert_ok "$R" "solve: u²-5u+6=0"
  88. echo " Result: $(echo "$R" | jq -r '.result.content[0].text')"
  89. # 4h. simplify
  90. R=$(rpc "tools/call" '{"name":"simplify","arguments":{"expression":"Sin[a]^2 + Cos[a]^2"}}' 9)
  91. assert_eq "$R" '.result.content[0].text' "1" "simplify: sin²a+cos²a = 1"
  92. # 4i. numeric
  93. R=$(rpc "tools/call" '{"name":"numeric","arguments":{"expression":"Pi","precision":"50"}}' 10)
  94. assert_ok "$R" "numeric: N[Pi, 50]"
  95. echo " Result: $(echo "$R" | jq -r '.result.content[0].text' | head -c 60)..."
  96. # 4j. to_latex
  97. R=$(rpc "tools/call" '{"name":"to_latex","arguments":{"expression":"z^2 + 2*z + 1"}}' 11)
  98. assert_ok "$R" "to_latex: TeXForm[z²+2z+1]"
  99. echo " LaTeX: $(echo "$R" | jq -r '.result.content[0].text')"
  100. # 4k. evaluate: Symja error handling
  101. R=$(rpc "tools/call" '{"name":"evaluate","arguments":{"expression":"1/0"}}' 12)
  102. assert_ok "$R" "evaluate: 1/0 (graceful error)"
  103. echo " Error msg: $(echo "$R" | jq -r '.result.content[0].text' | head -c 80)"
  104. # ── 5. List Resources ───────────────────────────────────────────
  105. header "5. List Resources"
  106. RESOURCES=$(rpc "resources/list")
  107. assert_ok "$RESOURCES" "resources/list succeeds"
  108. echo " Resources:"
  109. echo "$RESOURCES" | jq -r '.result.resources[] | " \(.uri) — \(.name)"'
  110. # ── 6. Read Resources ───────────────────────────────────────────
  111. header "6. Read Resources"
  112. R=$(rpc "resources/read" '{"uri":"variables://current"}' 13)
  113. assert_ok "$R" "resources/read: variables://current"
  114. echo " Variables: $(echo "$R" | jq -r '.result.contents[0].text')"
  115. R=$(rpc "resources/read" '{"uri":"history://recent"}' 14)
  116. assert_ok "$R" "resources/read: history://recent"
  117. # ── 7. Plugin CRUD ──────────────────────────────────────────────
  118. header "7. Plugin CRUD"
  119. R=$(rpc "tools/call" '{"name":"list_plugins","arguments":{}}' 15)
  120. assert_ok "$R" "list_plugins"
  121. 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)
  122. assert_ok "$R" "create_plugin: test_hello"
  123. echo " $R" | jq -r '.result.content[0].text'
  124. R=$(rpc "tools/call" '{"name":"get_plugin","arguments":{"plugin_id":"test_hello"}}' 17)
  125. assert_ok "$R" "get_plugin: test_hello"
  126. R=$(rpc "tools/call" '{"name":"execute_plugin","arguments":{"plugin_id":"test_hello","arguments":{"radius":"5"}}}' 18)
  127. assert_ok "$R" "execute_plugin: hello[radius=5]"
  128. echo " Plugin output:"
  129. echo "$R" | jq -r '.result.content[0].text'
  130. R=$(rpc "tools/call" '{"name":"delete_plugin","arguments":{"plugin_id":"test_hello"}}' 19)
  131. assert_ok "$R" "delete_plugin: test_hello"
  132. # ── 8. JSON-RPC Error Handling ──────────────────────────────────
  133. header "8. JSON-RPC Error Handling"
  134. R=$(curl -s -X POST "$BASE/mcp" -H "Content-Type: application/json" -d '{"jsonrpc":"1.0","id":1,"method":"evaluate"}')
  135. assert_eq "$R" '.error.message' "Invalid Request: jsonrpc must be 2.0" "Rejects non-2.0 jsonrpc"
  136. R=$(curl -s -X POST "$BASE/mcp" -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1}')
  137. assert_eq "$R" '.error.message' "Invalid Request: missing method" "Rejects missing method"
  138. R=$(rpc "nonexistent_method" '{}' 20)
  139. assert_eq "$R" '.error.code' "-32603" "Unknown method returns proper JSON-RPC error"
  140. assert_eq "$R" '.id' "20" "Error preserves request id"
  141. R=$(curl -s -X POST "$BASE/mcp" -H "Content-Type: application/json" -d 'not json')
  142. assert_eq "$R" '.error.message' "Parse error: invalid JSON" "Rejects invalid JSON"
  143. # ── Summary ─────────────────────────────────────────────────────
  144. header "Results"
  145. echo " Passed: $PASS"
  146. echo " Failed: $FAIL"
  147. if [ $FAIL -eq 0 ]; then
  148. green "All tests passed!"
  149. else
  150. red "$FAIL test(s) failed"
  151. exit 1
  152. fi