|
|
""" |
|
|
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 |
|
|
|
|
|
|
|
|
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 = [] |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
if not validate_config(): |
|
|
print("\nβ Test aborted due to configuration errors") |
|
|
return |
|
|
|
|
|
|
|
|
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'}") |
|
|
|
|
|
|
|
|
print("\n[1] Creating MCP client...") |
|
|
client = await MCPClientManager.create_multi_server_client() |
|
|
|
|
|
|
|
|
print("\n[2] Loading tools...") |
|
|
tools = await MCPClientManager.get_tools(client) |
|
|
|
|
|
print(f"\n[3] Total tools available: {len(tools)}") |
|
|
|
|
|
|
|
|
classifier_tools = [t for t in tools if 'classify' in t.name.lower()] |
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
print(f"\n[4] Testing search_birds tool...") |
|
|
|
|
|
try: |
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
AgentConfig.NUTHATCH_USE_STDIO = original_stdio_mode |
|
|
|
|
|
print("\n" + "="*70) |
|
|
print("Both transport tests complete!") |
|
|
print("="*70) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
import sys |
|
|
|
|
|
|
|
|
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") |
|
|
|