Skip to main content

MCP Agent

A multi-agent MCP (Model Context Protocol) tool-calling pipeline that coordinates an mcp-runner (filesystem reads and codebase search via @waxell.tool(tool_type="mcp")) and an mcp-evaluator (retrieval, reasoning, decision, LLM synthesis). Built with OpenAI and waxell-observe decorator patterns.

Environment variables

This example runs in dry-run mode by default (no API key needed). For live mode, set OPENAI_API_KEY, WAXELL_API_KEY, and WAXELL_API_URL.

Architecture

Key Code

MCP tool decorators

The tool_type="mcp" value marks these as MCP protocol tool calls in the trace, distinguishing them from regular function or API tools.

@waxell.tool(tool_type="mcp")
def mcp_read_file(path: str) -> dict:
"""Call MCP filesystem.read_file tool to read a file."""
return {
"path": "src/main.py",
"content": "import asyncio\nfrom agent import Agent\n...",
"size_bytes": 204,
"encoding": "utf-8",
}

@waxell.tool(tool_type="mcp")
def mcp_search_query(query: str, max_results: int = 10) -> dict:
"""Call MCP search.query tool to search the codebase."""
return {
"matches": [
{"file": "src/agent.py", "line": 12, "text": "class Agent:", "score": 0.95},
{"file": "src/tools.py", "line": 8, "text": "def analyze_code(source):", "score": 0.88},
],
"total_matches": 3,
}

MCP runner child agent

The runner wraps MCP tool calls with waxell metadata for full observability of which MCP tools were invoked and what they returned.

@waxell.observe(agent_name="mcp-runner", workflow_name="mcp-tool-execution")
async def run_mcp_runner(query: str, waxell_ctx=None):
waxell.tag("agent_role", "runner")
waxell.tag("protocol", "model-context-protocol")
waxell.metadata("available_tools", ["filesystem.read_file", "search.query"])

file_result = mcp_read_file(path="src/main.py") # @tool(mcp) auto-recorded
search_result = mcp_search_query(query="agent impl") # @tool(mcp) auto-recorded

waxell.metadata("files_read", 1)
waxell.metadata("search_matches", search_result["total_matches"])
return {"file_result": file_result, "search_result": search_result}

Evaluator with reasoning and decision

The evaluator retrieves ranked results, reasons about codebase structure, decides analysis depth, and synthesizes via an auto-instrumented OpenAI call.

@waxell.observe(agent_name="mcp-evaluator", workflow_name="mcp-analysis")
async def run_mcp_evaluator(query, runner_results, openai_client, waxell_ctx=None):
waxell.tag("agent_role", "evaluator")

ranked = retrieve_search_results(query="agent implementation") # @retrieval
assessment = await assess_codebase_structure(file_result, search_result) # @reasoning
depth = await choose_analysis_depth(search_result=search_result) # @decision

response = await openai_client.chat.completions.create(...) # auto-instrumented

waxell.score("code_structure", 0.88, comment="Well-organized")
waxell.score("test_presence", 1.0, data_type="boolean")
waxell.score("analysis_completeness", 0.82)
return {"analysis": response.choices[0].message.content}

What this demonstrates

  • @waxell.tool(tool_type="mcp") -- MCP protocol tool calls recorded with the mcp tool type, distinguishing them from regular API or function tools in the trace view.
  • @waxell.retrieval(source="mcp_search") -- search result retrieval recorded with source attribution.
  • @waxell.reasoning_dec -- codebase structure assessment with thought, evidence, and conclusion.
  • @waxell.decision -- analysis depth decision (shallow/standard/deep) based on codebase complexity.
  • Auto-instrumented LLM calls -- OpenAI synthesis call captured automatically via waxell.init().
  • waxell.score() with mixed types -- numeric scores (code_structure, completeness) and boolean scores (test_presence).
  • waxell.metadata() -- MCP-specific metadata (available tools, files read, search matches) attached to the runner span.
  • Nested @waxell.observe -- orchestrator is parent; mcp-runner and mcp-evaluator are child agents with automatic lineage.

Run it

# Dry-run (no API key needed)
python -m app.demos.mcp_agent --dry-run

# Live mode with OpenAI
OPENAI_API_KEY=sk-... python -m app.demos.mcp_agent

Source

dev/waxell-dev/app/demos/mcp_agent.py