""" Nuthatch MCP Integration Test Tests the Nuthatch species database MCP server with both STDIO and HTTP transports. Usage: # Test with current .env configuration (STDIO or HTTP) python tests/test_nuthatch.py # Test both STDIO and HTTP modes sequentially python tests/test_nuthatch.py --both Configuration (.env): # For STDIO mode (default - subprocess integration) NUTHATCH_USE_STDIO=true NUTHATCH_API_KEY= NUTHATCH_BASE_URL=https://nuthatch.lastelm.software/v2 # For HTTP mode (external server) NUTHATCH_USE_STDIO=false NUTHATCH_MCP_URL=http://localhost:8001/mcp NUTHATCH_MCP_AUTH_KEY= # Also required (Modal classifier) MODAL_MCP_URL= BIRD_CLASSIFIER_API_KEY= HTTP Mode Setup: # Start Nuthatch HTTP server in separate terminal: NUTHATCH_API_KEY= python nuthatch_tools.py --http --port 8001 # With auth protection: NUTHATCH_API_KEY= NUTHATCH_MCP_AUTH_KEY= python nuthatch_tools.py --http --port 8001 """ import asyncio import os import sys from pathlib import Path from dotenv import load_dotenv # Add parent directory to sys.path so we can import modules parent_dir = Path(__file__).parent.parent sys.path.insert(0, str(parent_dir)) from langgraph_agent import AgentFactory from agent_cache import get_or_create_agent, cleanup_old_agents, get_cache_stats from langgraph_agent.mcp_clients import MCPClientManager from langgraph_agent.config import AgentConfig load_dotenv() def validate_config(): """Validate required configuration is present.""" errors = [] # Critical checks if not AgentConfig.MODAL_MCP_URL: errors.append("MODAL_MCP_URL not set") if not AgentConfig.BIRD_CLASSIFIER_API_KEY: errors.append("BIRD_CLASSIFIER_API_KEY not set") if not AgentConfig.NUTHATCH_API_KEY: errors.append("NUTHATCH_API_KEY not set") # HTTP mode specific checks if not AgentConfig.NUTHATCH_USE_STDIO: if not AgentConfig.NUTHATCH_MCP_URL: errors.append("NUTHATCH_MCP_URL required for HTTP mode") if AgentConfig.NUTHATCH_MCP_URL == "http://localhost:8001/mcp": print("\nāš ļø WARNING: Using default NUTHATCH_MCP_URL (localhost:8001)") print(" Make sure nuthatch_tools.py is running with: python nuthatch_tools.py --http --port 8001") if errors: print("\nāŒ Configuration Errors:") for error in errors: print(f" • {error}") print("\nšŸ’” Check your .env file or environment variables") return False return True async def test_nuthatch(): print("="*70) print("Testing Nuthatch MCP Integration") print("="*70) # Validate configuration if not validate_config(): print("\nāŒ Test aborted due to configuration errors") return # Show configuration print("\n[CONFIG] Transport Configuration:") print(f" • NUTHATCH_USE_STDIO: {AgentConfig.NUTHATCH_USE_STDIO}") print(f" • NUTHATCH_API_KEY: {'āœ… Set' if AgentConfig.NUTHATCH_API_KEY else 'āŒ Not set'}") print(f" • NUTHATCH_BASE_URL: {AgentConfig.NUTHATCH_BASE_URL}") if AgentConfig.NUTHATCH_USE_STDIO: print(f" • Transport Mode: STDIO (subprocess)") print(f" • Command: python nuthatch_tools.py") else: print(f" • Transport Mode: HTTP (external server)") print(f" • MCP URL: {AgentConfig.NUTHATCH_MCP_URL}") print(f" • Auth: {'šŸ”’ Protected' if AgentConfig.NUTHATCH_MCP_AUTH_KEY else 'šŸ”“ No auth'}") # Create multi-server client (Modal + Nuthatch) print("\n[1] Creating MCP client...") client = await MCPClientManager.create_multi_server_client() # Get tools print("\n[2] Loading tools...") tools = await MCPClientManager.get_tools(client) print(f"\n[3] Total tools available: {len(tools)}") # Separate tools by source classifier_tools = [t for t in tools if 'classify' in t.name.lower()] # Nuthatch tools = everything except classifier tools nuthatch_tools = [t for t in tools if 'classify' not in t.name.lower()] print(f"\nšŸ“Š Classifier tools ({len(classifier_tools)}):") for tool in classifier_tools: print(f" - {tool.name}") print(f"\n🐦 Nuthatch tools ({len(nuthatch_tools)}):") for tool in nuthatch_tools: print(f" - {tool.name}") # Test a Nuthatch tool print(f"\n[4] Testing search_birds tool...") try: # Find the search_birds tool (it's a LangChain Tool object) search_tool = None for tool in tools: if tool.name == "search_birds": search_tool = tool break if not search_tool: print(f"\nāŒ search_birds tool not found!") else: # Invoke the tool directly with arguments result = await search_tool.ainvoke({"name": "Blue Jay", "page_size": 3}) print(f"\nāœ… Tool call successful!") print(f"Result preview:\n{result[:400]}...") except Exception as e: print(f"\nāŒ Tool call failed: {e}") import traceback traceback.print_exc() print("\n"+"="*70) print("Integration test complete!") print("="*70) async def test_both_transports(): """Test both STDIO and HTTP transports sequentially.""" print("\n" + "="*70) print("Testing BOTH Transport Modes (STDIO + HTTP)") print("="*70) original_stdio_mode = AgentConfig.NUTHATCH_USE_STDIO # Test 1: STDIO mode print("\n\n### TEST 1: STDIO Transport ###") AgentConfig.NUTHATCH_USE_STDIO = True try: await test_nuthatch() except Exception as e: print(f"\nāŒ STDIO test failed: {e}") import traceback traceback.print_exc() # Test 2: HTTP mode (only if server is configured) if AgentConfig.NUTHATCH_MCP_URL and AgentConfig.NUTHATCH_MCP_URL != "http://localhost:8001/mcp": print("\n\n### TEST 2: HTTP Transport ###") AgentConfig.NUTHATCH_USE_STDIO = False try: await test_nuthatch() except Exception as e: print(f"\nāŒ HTTP test failed: {e}") import traceback traceback.print_exc() else: print("\n\n### TEST 2: HTTP Transport (SKIPPED) ###") print(" Reason: NUTHATCH_MCP_URL not configured or using default") print(" Tip: To test HTTP mode, set NUTHATCH_MCP_URL and start nuthatch_tools.py with --http") # Restore original mode AgentConfig.NUTHATCH_USE_STDIO = original_stdio_mode print("\n" + "="*70) print("Both transport tests complete!") print("="*70) if __name__ == "__main__": import sys # Check if user wants to test both transports if "--both" in sys.argv: asyncio.run(test_both_transports()) else: asyncio.run(test_nuthatch()) print("\nšŸ’” Tip: Run with '--both' to test both STDIO and HTTP transports")