test_calculator_debug.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. #!/usr/bin/env python3
  2. """
  3. Debug script for calculator-server to trace JSON-RPC request handling
  4. """
  5. import subprocess
  6. import time
  7. import json
  8. import sys
  9. import os
  10. import threading
  11. import queue
  12. class MCPServerTester:
  13. def __init__(self, server_path):
  14. self.server_path = server_path
  15. self.server_process = None
  16. self.message_queue = queue.Queue()
  17. def start_server(self):
  18. """Start MCP server process"""
  19. print(f"Starting MCP server: {self.server_path}")
  20. self.server_process = subprocess.Popen(
  21. [self.server_path],
  22. stdin=subprocess.PIPE,
  23. stdout=subprocess.PIPE,
  24. stderr=subprocess.PIPE,
  25. text=False,
  26. bufsize=0
  27. )
  28. # Start thread to read stderr
  29. stderr_thread = threading.Thread(target=self._read_stderr)
  30. stderr_thread.daemon = True
  31. stderr_thread.start()
  32. # Start thread to read stdout
  33. stdout_thread = threading.Thread(target=self._read_stdout)
  34. stdout_thread.daemon = True
  35. stdout_thread.start()
  36. time.sleep(1)
  37. def _read_stderr(self):
  38. """Read stderr output from server"""
  39. while self.server_process and self.server_process.poll() is None:
  40. try:
  41. line = self.server_process.stderr.readline()
  42. if line:
  43. print(f"SERVER STDERR: {line.decode().strip()}")
  44. except:
  45. break
  46. def _read_stdout(self):
  47. """Read stdout output from server with proper Content-Length parsing"""
  48. buffer = b""
  49. while self.server_process and self.server_process.poll() is None:
  50. try:
  51. # Read line by line
  52. line = self.server_process.stdout.readline()
  53. if not line:
  54. break
  55. buffer += line
  56. # Check if we have a complete message
  57. if b'\n' in buffer:
  58. # Split on newline to get individual messages
  59. lines = buffer.split(b'\n')
  60. for i, msg_line in enumerate(lines):
  61. if msg_line.strip():
  62. try:
  63. # Try to parse as JSON
  64. message = json.loads(msg_line.decode())
  65. self.message_queue.put(message)
  66. print(f"RECEIVED: {json.dumps(message, indent=2)}")
  67. except json.JSONDecodeError as e:
  68. print(f"JSON DECODE ERROR: {e}")
  69. print(f"RAW: {msg_line}")
  70. # Keep the last incomplete line if any
  71. if lines and not lines[-1].strip():
  72. buffer = lines[-1]
  73. else:
  74. buffer = b""
  75. except:
  76. break
  77. def send_jsonrpc_request(self, method, params=None, id=1):
  78. """Send a JSON-RPC request with Content-Length header"""
  79. request = {
  80. "jsonrpc": "2.0",
  81. "id": id,
  82. "method": method,
  83. "params": params or {}
  84. }
  85. request_str = json.dumps(request)
  86. # Use the format from test_resources_fixed.py which works
  87. message = f"{request_str}\n"
  88. print(f"SENDING: {json.dumps(request, indent=2)}")
  89. print(f"RAW: {message.encode()}")
  90. self.server_process.stdin.write(message.encode())
  91. self.server_process.stdin.flush()
  92. try:
  93. response = self.message_queue.get(timeout=10)
  94. return response
  95. except queue.Empty:
  96. print("TIMEOUT: No response received")
  97. return None
  98. def test_calculator_with_debug(self):
  99. """Test calculator with the exact request that's failing"""
  100. print("\n=== Testing Calculator with Debug ===")
  101. # Test with the exact request from the issue
  102. test_request = {
  103. "jsonrpc": "2.0",
  104. "id": 1,
  105. "method": "tools/call",
  106. "params": {
  107. "name": "basic_calculator",
  108. "arguments": {
  109. "operation": "add",
  110. "operand1": 2,
  111. "operand2": 3
  112. },
  113. "_meta": {
  114. "progressToken": 0
  115. }
  116. }
  117. }
  118. print(f"\nTesting with request:")
  119. print(json.dumps(test_request, indent=2))
  120. response = self.send_jsonrpc_request("tools/call", test_request["params"], id=1)
  121. if response:
  122. print(f"\nResponse received:")
  123. print(json.dumps(response, indent=2))
  124. if "result" in response:
  125. result = response["result"]
  126. if "content" in result and len(result["content"]) > 0:
  127. content = result["content"][0]
  128. if "text" in content:
  129. print(f"\nResult text: {content['text']}")
  130. if "structured_content" in result:
  131. print(f"Structured content: {result['structured_content']}")
  132. # Check if operands are correctly parsed
  133. if isinstance(result['structured_content'], dict):
  134. if 'operand1' in result['structured_content']:
  135. print(f"operand1 in result: {result['structured_content']['operand1']}")
  136. if 'operand2' in result['structured_content']:
  137. print(f"operand2 in result: {result['structured_content']['operand2']}")
  138. if 'result' in result['structured_content']:
  139. print(f"calculation result: {result['structured_content']['result']}")
  140. else:
  141. print("No content in response")
  142. elif "error" in response:
  143. print(f"Error in response: {response['error']}")
  144. else:
  145. print("No response received")
  146. def test_simple_request(self):
  147. """Test with a simpler request"""
  148. print("\n=== Testing Simple Request ===")
  149. # Test tools/list first
  150. print("\nTesting tools/list...")
  151. list_response = self.send_jsonrpc_request("tools/list", id=2)
  152. if list_response:
  153. print(f"Tools list response:")
  154. print(json.dumps(list_response, indent=2))
  155. else:
  156. print("No response for tools/list")
  157. def stop_server(self):
  158. """Stop MCP server process"""
  159. if self.server_process:
  160. print("\nStopping server...")
  161. self.server_process.terminate()
  162. try:
  163. self.server_process.wait(timeout=5)
  164. except subprocess.TimeoutExpired:
  165. self.server_process.kill()
  166. self.server_process = None
  167. def main():
  168. if len(sys.argv) != 2:
  169. print("Usage: python3 test_calculator_debug.py <server-executable>")
  170. sys.exit(1)
  171. server_path = sys.argv[1]
  172. if not os.path.exists(server_path):
  173. print(f"Error: Server executable '{server_path}' not found")
  174. sys.exit(1)
  175. tester = MCPServerTester(server_path)
  176. try:
  177. tester.start_server()
  178. time.sleep(2)
  179. # Initialize first
  180. print("\nInitializing server...")
  181. init_params = {
  182. "protocolVersion": "2025-11-25",
  183. "capabilities": {},
  184. "clientInfo": {
  185. "name": "test-client",
  186. "version": "1.0.0"
  187. }
  188. }
  189. init_response = tester.send_jsonrpc_request("initialize", init_params, id=0)
  190. if init_response:
  191. print("✓ Server initialized successfully")
  192. # Test simple request first
  193. tester.test_simple_request()
  194. # Test calculator with debug
  195. tester.test_calculator_with_debug()
  196. else:
  197. print("✗ Failed to initialize server")
  198. except KeyboardInterrupt:
  199. print("\nTest interrupted by user")
  200. except Exception as e:
  201. print(f"\nError during test: {e}")
  202. import traceback
  203. traceback.print_exc()
  204. finally:
  205. tester.stop_server()
  206. if __name__ == "__main__":
  207. main()