Skip to content

Commit b1bd0fe

Browse files
authored
Merge branch 'main' into fix/remove-duplicate-setup-database
2 parents 925e877 + d016cd6 commit b1bd0fe

9 files changed

Lines changed: 436 additions & 268 deletions

File tree

pipeline.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
import argparse
11+
import sys
1112
from datetime import datetime
1213
from pathlib import Path
1314
from dotenv import load_dotenv
@@ -19,6 +20,27 @@
1920
from src.model_config import ModelConfig
2021

2122

23+
# Suppress httpcore/anyio cleanup exceptions that don't affect functionality.
24+
# These "Exception ignored" messages are caused by MCP library's streamablehttp_client
25+
# timing issues during cleanup, but don't impact actual task execution.
26+
def _suppress_cleanup_exceptions(unraisable):
27+
"""Suppress known cleanup exceptions from httpcore/anyio."""
28+
msg = str(unraisable.exc_value)
29+
if any(
30+
pattern in msg
31+
for pattern in [
32+
"async generator ignored GeneratorExit",
33+
"cancel scope in a different task",
34+
"no running event loop",
35+
]
36+
):
37+
return # Silently ignore
38+
# Use default handler for other exceptions
39+
sys.__unraisablehook__(unraisable)
40+
41+
42+
sys.unraisablehook = _suppress_cleanup_exceptions
43+
2244
# Initialize logger
2345
logger = get_logger(__name__)
2446

@@ -74,7 +96,10 @@ def main():
7496

7597
# Execution configuration
7698
parser.add_argument(
77-
"--timeout", type=int, default=3600, help="Timeout in seconds for agent execution"
99+
"--timeout",
100+
type=int,
101+
default=3600,
102+
help="Timeout in seconds for agent execution",
78103
)
79104
parser.add_argument(
80105
"--compaction-token",
@@ -125,7 +150,9 @@ def main():
125150
)
126151

127152
logger.info("MCPMark Evaluation")
128-
logger.info(f"Experiment: {args.exp_name} | {len(model_list)} Model(s): {', '.join(model_list)}")
153+
logger.info(
154+
f"Experiment: {args.exp_name} | {len(model_list)} Model(s): {', '.join(model_list)}"
155+
)
129156
logger.info(f"Task suite: {args.task_suite}")
130157
if args.k > 1:
131158
logger.info(f"Running {args.k} evaluation runs for pass@k metrics")
@@ -136,7 +163,7 @@ def main():
136163
logger.info(f"\n{'=' * 80}")
137164
logger.info(f"Starting Run {run_idx}/{args.k}")
138165
logger.info(f"{'=' * 80}\n")
139-
166+
140167
# For k-runs, results/{exp}/{mcp}__{model}/run-N
141168
run_exp_name = f"run-{run_idx}"
142169
run_output_dir = args.output_dir / args.exp_name
@@ -149,7 +176,9 @@ def main():
149176
for i, model in enumerate(model_list, 1):
150177
logger.info(f"\n{'=' * 60}")
151178
if args.k > 1:
152-
logger.info(f"Run {run_idx}/{args.k} | Model {i}/{len(model_list)}: {model}")
179+
logger.info(
180+
f"Run {run_idx}/{args.k} | Model {i}/{len(model_list)}: {model}"
181+
)
153182
else:
154183
logger.info(f"Starting evaluation {i}/{len(model_list)}: {model}")
155184
logger.info(f"{'=' * 60}\n")
@@ -168,14 +197,14 @@ def main():
168197
)
169198

170199
pipeline.run_evaluation(args.tasks)
171-
logger.info(
172-
f"📁 Results: {pipeline.base_experiment_dir}"
173-
)
200+
logger.info(f"📁 Results: {pipeline.base_experiment_dir}")
174201

175202
logger.info(f"\n{'=' * 60}")
176203
if args.k > 1:
177204
logger.info(f"✓ All {args.k} runs completed for {len(model_list)} model(s)")
178-
logger.info(f"Run `python -m src.aggregators.aggregate_results --exp-name {args.exp_name}` to compute all metrics")
205+
logger.info(
206+
f"Run `python -m src.aggregators.aggregate_results --exp-name {args.exp_name}` to compute all metrics"
207+
)
179208
else:
180209
logger.info(f"✓ All evaluations completed for {len(model_list)} model(s)")
181210
logger.info(f"{'=' * 60}")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name = "MCPMark"
44
requires-python = ">= 3.11"
55
version = "0.0.1"
66
dependencies = [
7-
"notion-client>=2.4.0",
7+
"notion-client==2.4.0",
88
"playwright>=1.43.0",
99
"seaborn>=0.12.0",
1010
"matplotlib>=3.7.0",

requirements.txt

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/agents/base_agent.py

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@
1919
class BaseMCPAgent(ABC):
2020
"""Base class with shared functionality for MCPMark agents."""
2121

22-
STDIO_SERVICES = ["notion", "filesystem", "playwright", "playwright_webarena", "postgres", "insforge"]
22+
STDIO_SERVICES = [
23+
"notion",
24+
"filesystem",
25+
"playwright",
26+
"playwright_webarena",
27+
"postgres",
28+
"insforge",
29+
]
2330
HTTP_SERVICES = ["github", "supabase"]
2431
DEFAULT_TIMEOUT = 600
2532
COMPACTION_DISABLED_TOKEN = 999_999_999
@@ -70,7 +77,11 @@ def __init__(
7077
)
7178

7279
# Warn if Gemini 3 model uses unsupported reasoning_effort value
73-
if self._is_gemini_3_model() and self.reasoning_effort not in ["default", "low", "high"]:
80+
if self._is_gemini_3_model() and self.reasoning_effort not in [
81+
"default",
82+
"low",
83+
"high",
84+
]:
7485
logger.warning(
7586
"Gemini 3 models only support reasoning_effort 'low' or 'high', "
7687
"got '%s'. LiteLLM may map this to the nearest supported value.",
@@ -179,7 +190,11 @@ def _create_stdio_server(self) -> MCPStdioServer:
179190
raise ValueError("Test directory required for filesystem service")
180191
return MCPStdioServer(
181192
command="npx",
182-
args=["-y", "@modelcontextprotocol/server-filesystem", str(test_directory)],
193+
args=[
194+
"-y",
195+
"@modelcontextprotocol/server-filesystem",
196+
str(test_directory),
197+
],
183198
)
184199

185200
if self.mcp_service in ("playwright", "playwright_webarena"):
@@ -208,10 +223,14 @@ def _create_stdio_server(self) -> MCPStdioServer:
208223
port = self.service_config.get("port", 5432)
209224
username = self.service_config.get("username")
210225
password = self.service_config.get("password")
211-
database = self.service_config.get("current_database") or self.service_config.get("database")
226+
database = self.service_config.get(
227+
"current_database"
228+
) or self.service_config.get("database")
212229
if not all([username, password, database]):
213230
raise ValueError("PostgreSQL requires username, password, and database")
214-
database_url = f"postgresql://{username}:{password}@{host}:{port}/{database}"
231+
database_url = (
232+
f"postgresql://{username}:{password}@{host}:{port}/{database}"
233+
)
215234
return MCPStdioServer(
216235
command="pipx",
217236
args=["run", "postgres-mcp", "--access-mode=unrestricted"],
@@ -259,11 +278,16 @@ def _count_prompt_tokens_litellm(self, messages: List[Dict[str, Any]]) -> int:
259278
try:
260279
from litellm import token_counter
261280

262-
return int(token_counter(model=self.litellm_input_model_name, messages=messages) or 0)
281+
return int(
282+
token_counter(model=self.litellm_input_model_name, messages=messages)
283+
or 0
284+
)
263285
except Exception: # pragma: no cover - best effort
264286
return 0
265287

266-
def _convert_to_sdk_format(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
288+
def _convert_to_sdk_format(
289+
self, messages: List[Dict[str, Any]]
290+
) -> List[Dict[str, Any]]:
267291
sdk_format: List[Dict[str, Any]] = []
268292
function_call_map: Dict[str, str] = {}
269293

@@ -305,7 +329,9 @@ def _convert_to_sdk_format(self, messages: List[Dict[str, Any]]) -> List[Dict[st
305329
for item in user_content:
306330
if isinstance(item, dict) and item.get("type") == "text":
307331
text_parts.append(item.get("text", ""))
308-
sdk_format.append({"content": "\n".join(text_parts), "role": "user"})
332+
sdk_format.append(
333+
{"content": "\n".join(text_parts), "role": "user"}
334+
)
309335
else:
310336
sdk_format.append({"content": user_content, "role": "user"})
311337

@@ -324,7 +350,9 @@ def _convert_to_sdk_format(self, messages: List[Dict[str, Any]]) -> List[Dict[st
324350
elif block.get("type") == "thinking":
325351
thinking_text = block.get("thinking", "")
326352
if thinking_text:
327-
text_parts.append(f"<think>\n{thinking_text}\n</think>")
353+
text_parts.append(
354+
f"<think>\n{thinking_text}\n</think>"
355+
)
328356
elif block.get("type") == "tool_use":
329357
claude_tool_uses.append(block)
330358
content = "\n".join(text_parts)
@@ -364,7 +392,9 @@ def _convert_to_sdk_format(self, messages: List[Dict[str, Any]]) -> List[Dict[st
364392
func_name = tool_call.get("function", {}).get("name", "")
365393
sdk_format.append(
366394
{
367-
"arguments": tool_call.get("function", {}).get("arguments", "{}"),
395+
"arguments": tool_call.get("function", {}).get(
396+
"arguments", "{}"
397+
),
368398
"call_id": call_id,
369399
"name": func_name,
370400
"type": "function_call",
@@ -422,7 +452,9 @@ def _convert_to_sdk_format(self, messages: List[Dict[str, Any]]) -> List[Dict[st
422452

423453
return sdk_format
424454

425-
def _convert_to_anthropic_format(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
455+
def _convert_to_anthropic_format(
456+
self, tools: List[Dict[str, Any]]
457+
) -> List[Dict[str, Any]]:
426458
anthropic_tools = []
427459
for tool in tools:
428460
anthropic_tool = {
@@ -445,7 +477,9 @@ def _is_gemini_3_model(self) -> bool:
445477
model_lower = self.litellm_input_model_name.lower()
446478
return "gemini-3" in model_lower or "gemini/gemini-3" in model_lower
447479

448-
def _simplify_schema_for_gemini(self, schema: Optional[Dict[str, Any]]) -> Dict[str, Any]:
480+
def _simplify_schema_for_gemini(
481+
self, schema: Optional[Dict[str, Any]]
482+
) -> Dict[str, Any]:
449483
if not isinstance(schema, dict):
450484
return schema or {}
451485

@@ -464,14 +498,18 @@ def _simplify_schema_for_gemini(self, schema: Optional[Dict[str, Any]]) -> Dict[
464498
simplified[key] = self._simplify_schema_for_gemini(value)
465499
elif isinstance(value, list) and key not in ("required", "enum"):
466500
simplified[key] = [
467-
self._simplify_schema_for_gemini(item) if isinstance(item, dict) else item
501+
self._simplify_schema_for_gemini(item)
502+
if isinstance(item, dict)
503+
else item
468504
for item in value
469505
]
470506
else:
471507
simplified[key] = value
472508
return simplified
473509

474-
def _convert_to_openai_format(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
510+
def _convert_to_openai_format(
511+
self, tools: List[Dict[str, Any]]
512+
) -> List[Dict[str, Any]]:
475513
functions = []
476514
is_gemini = self._is_gemini_model()
477515

0 commit comments

Comments
 (0)