BirdScopeAI / tests /test_nuthatch.py
facemelter's picture
Initial commit to hf space for hackathon
ff0e97f verified
"""
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=<your-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=<optional-auth-key>
# Also required (Modal classifier)
MODAL_MCP_URL=<your-modal-url>
BIRD_CLASSIFIER_API_KEY=<your-modal-key>
HTTP Mode Setup:
# Start Nuthatch HTTP server in separate terminal:
NUTHATCH_API_KEY=<your-key> python nuthatch_tools.py --http --port 8001
# With auth protection:
NUTHATCH_API_KEY=<your-key> NUTHATCH_MCP_AUTH_KEY=<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")