mirror of
https://github.com/sstent/AICycling_mcp.git
synced 2026-01-26 00:52:35 +00:00
sync - getting json resp now
This commit is contained in:
274
test_workaround.py
Executable file
274
test_workaround.py
Executable file
@@ -0,0 +1,274 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script using the workaround for hanging MCP tools
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import asyncio
|
||||
import shutil
|
||||
import logging
|
||||
import yaml
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
# MCP Protocol imports
|
||||
try:
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
MCP_AVAILABLE = True
|
||||
except ImportError:
|
||||
MCP_AVAILABLE = False
|
||||
print("MCP not available. Install with: pip install mcp")
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GarthMCPConnectorWorkaround:
|
||||
"""MCP Connector with workaround for hanging tools issue"""
|
||||
|
||||
def __init__(self, garth_token: str, server_path: str):
|
||||
self.garth_token = garth_token
|
||||
self.server_path = server_path
|
||||
self.server_available = False
|
||||
self.cached_tools = []
|
||||
self._session: Optional[ClientSession] = None
|
||||
self._client_context = None
|
||||
self._read_stream = None
|
||||
self._write_stream = None
|
||||
|
||||
async def _get_server_params(self):
|
||||
"""Get server parameters for MCP connection"""
|
||||
env = os.environ.copy()
|
||||
env['GARTH_TOKEN'] = self.garth_token
|
||||
|
||||
server_command = shutil.which("garth-mcp-server")
|
||||
if not server_command:
|
||||
raise FileNotFoundError("garth-mcp-server not found")
|
||||
|
||||
return StdioServerParameters(
|
||||
command="/bin/bash",
|
||||
args=["-c", f"exec {server_command} \"$@\" 1>&2"],
|
||||
capture_stderr=True,
|
||||
env=env,
|
||||
)
|
||||
|
||||
async def connect(self):
|
||||
"""Start the MCP server and establish a persistent session."""
|
||||
if self._session and self.server_available:
|
||||
return True
|
||||
|
||||
if not MCP_AVAILABLE:
|
||||
logger.error("MCP library not available")
|
||||
return False
|
||||
|
||||
try:
|
||||
logger.info("Connecting to Garth MCP server...")
|
||||
server_params = await self._get_server_params()
|
||||
|
||||
self._client_context = stdio_client(server_params)
|
||||
streams = await self._client_context.__aenter__()
|
||||
|
||||
if len(streams) == 3:
|
||||
self._read_stream, self._write_stream, stderr_stream = streams
|
||||
# Start stderr logging in background
|
||||
asyncio.create_task(self._log_stderr(stderr_stream))
|
||||
else:
|
||||
self._read_stream, self._write_stream = streams
|
||||
|
||||
await asyncio.sleep(1.0) # Wait for server to start
|
||||
|
||||
self._session = ClientSession(self._read_stream, self._write_stream)
|
||||
|
||||
# Initialize with timeout
|
||||
try:
|
||||
await asyncio.wait_for(self._session.initialize(), timeout=30)
|
||||
logger.info("✓ MCP session initialized successfully")
|
||||
self.server_available = True
|
||||
return True
|
||||
except asyncio.TimeoutError:
|
||||
logger.error("MCP session initialization timed out")
|
||||
await self.disconnect()
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect to MCP server: {e}")
|
||||
await self.disconnect()
|
||||
return False
|
||||
|
||||
async def _log_stderr(self, stderr_stream):
|
||||
"""Log stderr from server in background"""
|
||||
try:
|
||||
async for line in stderr_stream:
|
||||
logger.debug(f"[server] {line.decode().strip()}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def disconnect(self):
|
||||
"""Disconnect from MCP server"""
|
||||
if self._client_context:
|
||||
try:
|
||||
await self._client_context.__aexit__(None, None, None)
|
||||
except Exception as e:
|
||||
logger.error(f"Disconnect error: {e}")
|
||||
|
||||
self._session = None
|
||||
self.server_available = False
|
||||
self.cached_tools = []
|
||||
self._client_context = None
|
||||
|
||||
async def get_available_tools_info(self) -> List[Dict[str, str]]:
|
||||
"""Get tools info using workaround - bypasses hanging list_tools()"""
|
||||
if not self.cached_tools:
|
||||
logger.info("Using known Garth MCP tools (workaround for hanging list_tools)")
|
||||
self.cached_tools = [
|
||||
{"name": "user_profile", "description": "Get user profile information"},
|
||||
{"name": "user_settings", "description": "Get user settings and preferences"},
|
||||
{"name": "daily_sleep", "description": "Get daily sleep summary data"},
|
||||
{"name": "daily_steps", "description": "Get daily steps data"},
|
||||
{"name": "daily_hrv", "description": "Get heart rate variability data"},
|
||||
{"name": "get_activities", "description": "Get list of activities"},
|
||||
{"name": "get_activity_details", "description": "Get detailed activity information"},
|
||||
{"name": "get_body_composition", "description": "Get body composition data"},
|
||||
{"name": "get_respiration_data", "description": "Get respiration data"},
|
||||
{"name": "get_blood_pressure", "description": "Get blood pressure readings"}
|
||||
]
|
||||
return self.cached_tools
|
||||
|
||||
async def call_tool(self, tool_name: str, arguments: Dict[str, Any] = None) -> Any:
|
||||
"""Call a tool with timeout"""
|
||||
if not self.server_available or not self._session:
|
||||
raise Exception("MCP server not available")
|
||||
|
||||
try:
|
||||
logger.info(f"Calling tool: {tool_name}")
|
||||
result = await asyncio.wait_for(
|
||||
self._session.call_tool(tool_name, arguments or {}),
|
||||
timeout=30
|
||||
)
|
||||
logger.info(f"✓ Tool call '{tool_name}' successful")
|
||||
return result
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"Tool call '{tool_name}' timed out")
|
||||
raise Exception(f"Tool call '{tool_name}' timed out")
|
||||
except Exception as e:
|
||||
logger.error(f"Tool call '{tool_name}' failed: {e}")
|
||||
raise
|
||||
|
||||
async def test_real_tool_call(self, tool_name: str = "user_profile"):
|
||||
"""Test if we can actually call a real MCP tool"""
|
||||
if not self.server_available:
|
||||
return False, "Server not connected"
|
||||
|
||||
try:
|
||||
result = await self.call_tool(tool_name)
|
||||
return True, result
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
async def run_tests():
|
||||
"""Run comprehensive tests with the workaround"""
|
||||
print("="*60)
|
||||
print("TESTING MCP CONNECTOR WITH WORKAROUND")
|
||||
print("="*60)
|
||||
|
||||
# Load config
|
||||
try:
|
||||
with open("config.yaml") as f:
|
||||
config = yaml.safe_load(f)
|
||||
except Exception as e:
|
||||
print(f"✗ Could not load config: {e}")
|
||||
return
|
||||
|
||||
connector = GarthMCPConnectorWorkaround(
|
||||
config['garth_token'],
|
||||
config['garth_mcp_server_path']
|
||||
)
|
||||
|
||||
# Test 1: Connection
|
||||
print("\n1. Testing MCP server connection...")
|
||||
success = await connector.connect()
|
||||
if success:
|
||||
print("✓ MCP server connected successfully")
|
||||
else:
|
||||
print("✗ MCP server connection failed")
|
||||
return
|
||||
|
||||
# Test 2: Tools listing (with workaround)
|
||||
print("\n2. Testing tools listing (using workaround)...")
|
||||
try:
|
||||
tools = await connector.get_available_tools_info()
|
||||
print(f"✓ Retrieved {len(tools)} tools using workaround")
|
||||
|
||||
print("\nAvailable tools:")
|
||||
for tool in tools:
|
||||
print(f" - {tool['name']}: {tool['description']}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Tools listing failed: {e}")
|
||||
|
||||
# Test 3: Real tool call
|
||||
print("\n3. Testing actual tool call...")
|
||||
success, result = await connector.test_real_tool_call("user_profile")
|
||||
if success:
|
||||
print("✓ Real tool call successful!")
|
||||
print("Sample result:")
|
||||
if hasattr(result, 'content'):
|
||||
for content in result.content[:1]: # Show first result only
|
||||
if hasattr(content, 'text'):
|
||||
# Try to parse and show nicely
|
||||
try:
|
||||
data = json.loads(content.text)
|
||||
print(f" Profile data: {type(data)} with keys: {list(data.keys()) if isinstance(data, dict) else 'N/A'}")
|
||||
except:
|
||||
print(f" Raw text: {content.text[:100]}...")
|
||||
else:
|
||||
print(f" Result type: {type(result)}")
|
||||
else:
|
||||
print(f"✗ Real tool call failed: {result}")
|
||||
|
||||
# Test 4: Alternative tool call
|
||||
print("\n4. Testing alternative tool call...")
|
||||
success, result = await connector.test_real_tool_call("get_activities")
|
||||
if success:
|
||||
print("✓ Activities tool call successful!")
|
||||
else:
|
||||
print(f"✗ Activities tool call failed: {result}")
|
||||
|
||||
# Test 5: Show that app would work
|
||||
print("\n5. Simulating main app behavior...")
|
||||
try:
|
||||
# This simulates what your main app does
|
||||
available_tools = await connector.get_available_tools_info()
|
||||
print(f"✓ Main app would see {len(available_tools)} available tools")
|
||||
|
||||
# Show tool info like your app does
|
||||
tool_info = "\n\nAvailable Garmin data tools:\n"
|
||||
for tool in available_tools:
|
||||
tool_info += f"- {tool['name']}: {tool.get('description', 'No description')}\n"
|
||||
|
||||
print("Tool info that would be sent to AI:")
|
||||
print(tool_info)
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Main app simulation failed: {e}")
|
||||
|
||||
# Cleanup
|
||||
print("\n6. Cleaning up...")
|
||||
await connector.disconnect()
|
||||
print("✓ Cleanup complete")
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("TEST SUMMARY:")
|
||||
print("- MCP connection: Working")
|
||||
print("- Tools listing: Working (with workaround)")
|
||||
print("- Your main app should now run without hanging!")
|
||||
print("="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(run_tests())
|
||||
except KeyboardInterrupt:
|
||||
print("\n✗ Tests interrupted by user")
|
||||
except Exception as e:
|
||||
print(f"\n✗ Test script failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
Reference in New Issue
Block a user