Skip to content

Commit 0f33ad1

Browse files
committed
Fix MCP integration and operational issues for successful assessments
Resolves critical bugs preventing agent-to-agent shopping workflow: - Fix MCP 421 errors: Disable DNS rebinding protection in FastMCP for trusted Docker network communication (green_agent/src/webshop_mcp/server.py) - Fix WebShop data access: Add WEBSHOP_DIR environment variable support and mount full webshop directory for templates (docker-compose.yml, green_agent/src/webshop_patched/engine.py) - Fix LLM connectivity: Support LLM_MODEL and LLM_API_BASE environment variables for Docker deployment (purple_agent/src/shopping_agent.py) - Fix actions_taken tracking: Add set_action_count() method to sync MCP session action counts with SessionState for accurate evaluation (green_agent/src/models.py, green_agent/src/agent.py) All fixes verified with successful 3-task assessment showing proper tool execution (search, click, checkout) and accurate action tracking.
1 parent ba268da commit 0f33ad1

6 files changed

Lines changed: 105 additions & 11 deletions

File tree

docker-compose.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,21 @@ services:
2121
dockerfile: Dockerfile
2222
image: ghcr.io/mpnikhil/webshop-plus-green:latest
2323
container_name: webshop-plus-green
24+
command: [".venv/bin/python", "src/server.py", "--host", "0.0.0.0", "--port", "8000", "--advertise-host", "green", "--card-url", "http://localhost:8000"]
2425
ports:
2526
- "8000:8000"
2627
environment:
2728
- LLM_MODEL=${LLM_MODEL:-nebius/Qwen/Qwen3-32B}
2829
- LLM_API_KEY=${LLM_API_KEY:-}
30+
- LLM_API_BASE=${LLM_API_BASE:-}
31+
- LLM_TEMPERATURE=${LLM_TEMPERATURE:-0.2}
32+
- LLM_MAX_TOKENS=${LLM_MAX_TOKENS:-8192}
2933
- WEBSHOP_MODE=${WEBSHOP_MODE:-preview}
34+
- WEBSHOP_DIR=/app/webshop
3035
- LOG_LEVEL=${LOG_LEVEL:-INFO}
3136
- PURPLE_AGENT_URL=http://purple:8001/a2a
3237
volumes:
33-
- ./webshop/data:/app/webshop/data:ro
38+
- ./webshop:/app/webshop:ro
3439
depends_on:
3540
purple:
3641
condition: service_healthy
@@ -51,11 +56,15 @@ services:
5156
dockerfile: Dockerfile
5257
image: ghcr.io/mpnikhil/webshop-plus-purple:latest
5358
container_name: webshop-plus-purple
59+
command: [".venv/bin/python", "src/server.py", "--host", "0.0.0.0", "--port", "8001", "--card-url", "http://purple:8001"]
5460
ports:
5561
- "8001:8001"
5662
environment:
5763
- LLM_MODEL=${LLM_MODEL:-nebius/Qwen/Qwen3-32B}
5864
- LLM_API_KEY=${LLM_API_KEY:-}
65+
- LLM_API_BASE=${LLM_API_BASE:-}
66+
- LLM_TEMPERATURE=${LLM_TEMPERATURE:-0.2}
67+
- LLM_MAX_TOKENS=${LLM_MAX_TOKENS:-8192}
5968
- LOG_LEVEL=${LOG_LEVEL:-INFO}
6069
healthcheck:
6170
test: ["CMD", "curl", "-f", "http://localhost:8001/health"]

green_agent/src/agent.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,12 +597,19 @@ async def _dispatch_task_to_purple(
597597
if "actions" in task_result.result_data:
598598
result.actions_taken = len(task_result.result_data["actions"])
599599
if "turns_used" in task_result.result_data:
600+
logger.info(
601+
"Setting actions_taken from purple agent result_data",
602+
turns_used=task_result.result_data["turns_used"],
603+
task_id=task.task_id,
604+
)
600605
result.actions_taken = task_result.result_data["turns_used"]
601606

602607
logger.info(
603608
"MCP task completed successfully",
604609
task_id=task.task_id,
605610
result_data=task_result.result_data,
611+
actions_taken=result.actions_taken,
612+
mcp_session_id=mcp_session_id,
606613
)
607614
else:
608615
result.error = task_result.error or "Task failed"
@@ -614,16 +621,41 @@ async def _dispatch_task_to_purple(
614621

615622
# Get final result from MCP session if available
616623
if mcp_session_id and self._session_manager:
617-
if is_session_completed(mcp_session_id):
624+
session_completed = is_session_completed(mcp_session_id)
625+
logger.info(
626+
"Checking MCP session completion",
627+
mcp_session_id=mcp_session_id,
628+
session_completed=session_completed,
629+
task_id=task.task_id,
630+
)
631+
if session_completed:
618632
mcp_result = get_final_result(mcp_session_id)
633+
logger.info(
634+
"Retrieved MCP final result",
635+
mcp_session_id=mcp_session_id,
636+
has_result=mcp_result is not None,
637+
result_keys=list(mcp_result.keys()) if mcp_result else [],
638+
task_id=task.task_id,
639+
)
619640
if mcp_result:
620641
# Merge MCP result into task execution result
621642
if "turns_used" in mcp_result:
643+
logger.info(
644+
"Setting actions_taken from MCP result",
645+
turns_used=mcp_result["turns_used"],
646+
task_id=task.task_id,
647+
)
622648
result.actions_taken = mcp_result["turns_used"]
623649
if "success" in mcp_result:
624650
result.completed = mcp_result["success"]
625651
if "reward" in mcp_result:
626652
result.total_reward = mcp_result["reward"]
653+
else:
654+
logger.warning(
655+
"MCP session not marked as completed",
656+
mcp_session_id=mcp_session_id,
657+
task_id=task.task_id,
658+
)
627659

628660
# Clean up MCP session
629661
await self._session_manager.cleanup_session(mcp_session_id)
@@ -667,6 +699,10 @@ def _finalize_task(
667699
memory: Optional[AgentMemory],
668700
) -> TaskExecutionResult:
669701
"""Finalize task execution and evaluate."""
702+
# Update session with actions_taken from result
703+
# (result.actions_taken was set from MCP final result)
704+
session.set_action_count(result.actions_taken)
705+
670706
# Complete session
671707
session_summary = self.state_manager.complete_session(
672708
session.session_id,

green_agent/src/models.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,23 @@ def record_action(self, action: str, observation: str, reward: float = 0.0) -> N
389389
)
390390
self.current_observation = observation
391391

392+
def set_action_count(self, count: int) -> None:
393+
"""Set the action count (for MCP sessions where actions are tracked externally).
394+
395+
Args:
396+
count: Number of actions to set.
397+
"""
398+
# Clear existing actions and add placeholder records to match count
399+
self.actions.clear()
400+
for i in range(count):
401+
self.actions.append(
402+
ActionRecord(
403+
action=f"MCP action {i+1}",
404+
observation="Tracked by MCP session",
405+
reward=0.0,
406+
)
407+
)
408+
392409
def complete(self) -> None:
393410
"""Mark the session as completed."""
394411
self.completed = True

green_agent/src/webshop_mcp/server.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import structlog
2222
from bs4 import BeautifulSoup
2323
from mcp.server.fastmcp import FastMCP
24+
from mcp.server.transport_security import TransportSecuritySettings
2425

2526
from .session_state import SessionState
2627

@@ -190,7 +191,20 @@ def product_item_dict(self) -> dict[str, dict]:
190191
# Global FastMCP Server
191192
# =============================================================================
192193

193-
mcp = FastMCP("WebShop MCP Server")
194+
# Disable DNS rebinding protection for MCP in trusted environment
195+
# MCP sessions are already isolated via unique session IDs
196+
# Purple agents are authenticated via A2A protocol before receiving MCP URIs
197+
transport_security = TransportSecuritySettings(
198+
enable_dns_rebinding_protection=False
199+
)
200+
201+
mcp = FastMCP(
202+
"WebShop MCP Server",
203+
transport_security=transport_security,
204+
)
205+
206+
# Add logging for MCP server initialization
207+
logger.info("FastMCP server created", transport_security_enabled=transport_security.enable_dns_rebinding_protection)
194208

195209

196210
# =============================================================================
@@ -713,7 +727,15 @@ def search(query: str) -> dict[str, Any]:
713727
- turn: Current turn number
714728
- budget: Remaining budget info
715729
"""
716-
state = get_current_state()
730+
logger.info("search() tool called", query=query)
731+
732+
try:
733+
state = get_current_state()
734+
except Exception as e:
735+
logger.exception("Failed to get current state", error=str(e), query=query)
736+
raise
737+
738+
logger.info("Got session state", session_id=state.session_id)
717739

718740
# Check turn limit first
719741
if state.increment_turn():
@@ -727,8 +749,15 @@ def search(query: str) -> dict[str, Any]:
727749
})
728750

729751
# Execute search via WebShop
730-
webshop = _get_webshop(state.session_id)
731-
result = webshop.step(f"search[{query}]")
752+
try:
753+
logger.info("About to call _get_webshop", session_id=state.session_id)
754+
webshop = _get_webshop(state.session_id)
755+
logger.info("Got WebShop interface, calling step()")
756+
result = webshop.step(f"search[{query}]")
757+
logger.info("WebShop step() completed", observation_len=len(result.observation) if hasattr(result, 'observation') else 0)
758+
except Exception as e:
759+
logger.exception("Error during WebShop search", error=str(e), query=query, session_id=state.session_id)
760+
raise
732761

733762
# Update page state
734763
state.current_page = "search_results"

green_agent/src/webshop_patched/engine.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@
1919
from tqdm import tqdm
2020

2121

22-
# Paths - relative to this file
22+
# Paths - check environment variable first, then calculate relative path
2323
BASE_DIR = dirname(abspath(__file__))
24-
WEBSHOP_DIR = join(dirname(dirname(dirname(BASE_DIR))), "webshop")
24+
# In Docker: /app/webshop, otherwise calculate from BASE_DIR
25+
WEBSHOP_DIR = os.environ.get("WEBSHOP_DIR", join(dirname(dirname(dirname(BASE_DIR))), "webshop"))
2526
WEBSHOP_DATA_DIR = join(WEBSHOP_DIR, "data")
2627
TEMPLATE_DIR = join(WEBSHOP_DIR, "web_agent_site", "templates")
2728

purple_agent/src/shopping_agent.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,14 @@
4949
# Default model for the ADK agent using LiteLLM
5050
# Uses openai/ prefix for LM Studio's OpenAI-compatible API
5151
# Can be overridden via environment variable
52-
DEFAULT_MODEL = os.environ.get("ADK_MODEL", "openai/qwen3-coder-30b-a3b-instruct-mlx")
52+
# Use LLM_MODEL (from Docker) or ADK_MODEL (from local) or default model
53+
DEFAULT_MODEL = os.environ.get("LLM_MODEL") or os.environ.get("ADK_MODEL", "openai/qwen3-coder-30b-a3b-instruct-mlx")
5354

5455
# LM Studio API base URL (for local development)
5556
# LM Studio provides an OpenAI-compatible API
56-
OPENAI_API_BASE = os.environ.get("OPENAI_API_BASE", "http://localhost:1234/v1")
57-
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "lm-studio")
57+
# Use LLM_API_BASE (from Docker) or OPENAI_API_BASE (from local) or default to localhost
58+
OPENAI_API_BASE = os.environ.get("LLM_API_BASE") or os.environ.get("OPENAI_API_BASE", "http://localhost:1234/v1")
59+
OPENAI_API_KEY = os.environ.get("LLM_API_KEY") or os.environ.get("OPENAI_API_KEY", "lm-studio")
5860

5961
# Maximum number of turns the agent can take before timing out
6062
DEFAULT_MAX_TURNS = 30

0 commit comments

Comments
 (0)