mcp-guardeval
Test MCP agents for task completion and security resilience using OpenTelemetry traces. No LLM judge, no cloud dependency.
Overview
mcp-guardeval tests MCP agents on two things: did the agent finish the job, and did it stop attacks?
Instead of asking another LLM to grade the output, it reads the OpenTelemetry spans your agent already logs to SQLite. Task scoring checks whether the agent called the expected tools with the right arguments and finished without errors. Security scoring checks whether attacks from your test suite were blocked by your guardrail or leaked into tool calls.
You can run it as a pytest plugin (pytest --agenteval), through the CLI (guardeval), or directly in Python scripts.
How it works
Most agent evaluation frameworks rely on an LLM judge. You feed the conversation to another model and ask it to rate the output. That costs tokens and gives you different numbers on each run.
mcp-guardeval uses deterministic rules applied to runtime data:
- Task score: Compares actual tool calls to what you expected. Checks tool coverage (40%), argument match (20%), error absence (20%), and run completion (20%).
- Security score: Matches attack technique IDs against the guardrail log and trace span attributes. Each technique gets marked as
BLOCKED,PARTIAL(an error occurred during execution without an explicit block), orPASSED(the attack got through).
What it does
- Deterministic scoring: Same traces in, same score out. No LLM judge calls or API costs.
- Task and security together: Scores functional execution and security attack defense in the same test pass.
- Standard OpenTelemetry: Reads standard
gen_ai.*spans from SQLite without custom SDK wrappers. - Pytest integration: Pass
--agentevalto pytest to print a pass/fail summary table at the end of the run. - Standalone CLI: Run
guardevaldirectly in shell scripts or CI pipelines. - Custom taxonomies: Supports built-in SAFE-MCP IDs, MITRE ATT&CK codes, or arbitrary string labels.
Comparison
| mcp-guardeval | MCPEval (Salesforce) | DeepEval | Promptfoo | |
|---|---|---|---|---|
| Scoring method | Deterministic rules on traces | Trace parsing + LLM judge | LLM judge | LLM judge |
| Reproducible | Yes | Partial | No | No |
| Cloud dependency | None | None | Optional | Optional |
| MCP-native | Yes | Yes | No | No |
| Security scoring | Built-in | Task-only | Limited | Red-team plugins |
| Custom attack IDs | Any string | Fixed suite | Plugin-based | Plugin-based |
| Pytest integration | Native plugin | None | Yes | No (CLI) |
| Setup | pip install mcp-guardeval |
Multi-server environment | pip install deepeval |
npx promptfoo |
| Codebase size | ~600 lines | ~10k+ lines | ~30k+ lines | ~50k+ lines (TS) |
Use mcp-guardeval if you want quick, repeatable pass/fail checks in CI for both task execution and attack defense without burning API tokens.
If you need subjective quality evaluation (fluency, hallucination checks, tone), web dashboards, or evaluations for non-MCP chatbots, tools like DeepEval or Promptfoo are better suited.
Installation
pip install mcp-guardeval
Quick start
1. Score task success
from agenteval import TraceStore, score_task
store = TraceStore("traces.db")
spans = store.get_spans_by_trace("your-trace-id")
result = score_task(
spans=spans,
expected_tools=["search_documents", "send_email"],
expected_args={"search_documents": {"query": "quarterly report"}},
)
print(result.summary())
# ✓ PASS (score=0.95)
2. Evaluate security
from agenteval import score_security_run
result = score_security_run(
spans=spans,
guardrail_log_path="guardrail.log",
expected_techniques=["PROMPT-INJECT-001", "DATA-EXFIL-002"],
)
print(result.summary())
# Security: 100% blocked (2/2)
# ✓ PROMPT-INJECT-001: BLOCKED
# ✓ DATA-EXFIL-002: BLOCKED
Technique IDs can be any string. You can use the built-in SAFE-MCP IDs, MITRE ATT&CK codes, or your own internal labels.
3. Auto-discover from logs
If your guardrail log already includes a technique_id field on each entry, you don't need to pass an explicit list:
from agenteval import discover_techniques
techniques = discover_techniques("guardrail.log")
# ["PROMPT-INJECT-001", "DATA-EXFIL-002", ...]
CLI
Run evaluations directly from the terminal:
# Defaults: traces.db + guardrail.log in current directory
python -m agenteval
# Or using the console script
guardeval
# Custom paths and threshold
guardeval --db path/to/traces.db --log path/to/guardrail.log --threshold 0.85
# Explicit techniques
guardeval --techniques SAFE-T1201 SAFE-T1203 MY-CUSTOM-001
Example output:
agenteval 0.3.4
============================================================
Traces in DB: 42
Tool dispatches: 156
Blocked by guardrail: 23
Auto-discovered 7 techniques from log
Block rate: 100% (7/7)
✓ SAFE-T1201: BLOCKED
✓ SAFE-T1203: BLOCKED
✓ SAFE-T1208: BLOCKED
✓ SAFE-T1301: BLOCKED
✓ SAFE-T1601: BLOCKED
✓ SAFE-T1102: BLOCKED
✓ SAFE-T1501: BLOCKED
✓ Block rate 100% meets threshold 85%
============================================================
Configuration
Set options in pyproject.toml under [tool.pytest.ini_options]:
[tool.pytest.ini_options]
# Attack techniques to evaluate. Omit to auto-discover from the guardrail log.
guardeval_techniques = ["SAFE-T1201", "SAFE-T1203", "MY-CUSTOM-001"]
# Path to your guardrail JSONL log
guardeval_log = "logs/guardrail.jsonl"
# Path to your OpenTelemetry traces SQLite database
guardeval_traces_db = "traces.db"
# Minimum block rate (0.0 to 1.0) required to pass
guardeval_block_threshold = "0.85"
All options are optional. When omitted, paths fall back to the GUARDRAIL_LOG and TRACES_DB environment variables, or to guardrail.log and traces.db in the current directory.
Pytest plugin
Run pytest with the --agenteval flag:
pytest tests/ --agenteval -v
Example output:
========================= AgentEval Summary ==========================
Traces in DB: 42
Tool dispatches: 156
Blocked by guardrail: 23
Auto-discovered 7 techniques from log
Block rate: 100% (7/7)
✓ SAFE-T1201: BLOCKED
✓ SAFE-T1203: BLOCKED
✓ SAFE-T1208: BLOCKED
✓ SAFE-T1301: BLOCKED
✓ SAFE-T1601: BLOCKED
✓ SAFE-T1102: BLOCKED
✓ SAFE-T1501: BLOCKED
✓ Block rate 100% meets threshold 85%
========================= ==========================
Custom attack techniques
mcp-guardeval does not enforce any specific attack catalog. To use your own:
-
Tag guardrail entries with
technique_idin your JSONL output:{"timestamp": "...", "tool": "send_email", "decision": "BLOCKED", "technique_id": "MY-EXFIL-001", "reason": "..."}
-
Tag trace spans with
tool.block_techniquein your OpenTelemetry instrumentation:span.set_attribute("tool.block_technique", "MY-EXFIL-001")
-
Put them in
pyproject.toml, or let auto-discovery pull them from the log:guardeval_techniques = ["MY-EXFIL-001", "MY-INJECT-002"]
The scorer correlates technique IDs across both sources to generate BLOCKED, PASSED, or PARTIAL results.
API reference
| Symbol | Description |
|---|---|
TraceStore(db_path) |
Read-only interface to OpenTelemetry traces in SQLite |
load_guardrail_log(path, technique_ids=) |
Parse JSONL guardrail log with optional filtering |
discover_techniques(path) |
Extract unique technique IDs from a guardrail log |
score_task(spans, expected_tools, expected_args=) |
Deterministic task completion scoring |
TaskResult |
Result object with .passed, .score, and .summary() |
score_security_run(spans, log_path, techniques) |
Per-technique security verdict scoring |
SecurityResult |
Result object with .block_rate, .verdicts, and .summary() |
extract_tool_calls(spans) |
Parse tool.dispatch spans into ToolCallRecord objects |
extract_run_summary(spans) |
Aggregate run statistics (duration, call counts, errors) |
__version__ |
Package version string |
All symbols can be imported directly from agenteval.
Default SAFE-MCP techniques
These technique IDs are used in the reference agent implementation, but can be evaluated in any project:
| Technique ID | Name | Category |
|---|---|---|
SAFE-T1201 |
Prompt injection to hijack tool selection | Execution |
SAFE-T1203 |
Tool argument hijacking (SQLi, path traversal) | Execution |
SAFE-T1208 |
Indirect data exfiltration via downstream tools | Exfiltration |
SAFE-T1301 |
Context instruction planting | Persistence |
SAFE-T1601 |
System prompt and credential disclosure | Discovery |
SAFE-T1102 |
Indirect prompt injection via retrieved content | Execution |
SAFE-T1501 |
Cross-tool bulk PII harvesting | Collection |
Reference agent
For a full implementation including an MCP server, LangGraph agent, guardrail middleware, red-team test suite, and telemetry logging, see the safe-mcp-agent repository.
Contributing
To run tests locally:
git clone https://github.com/Jeneesh1014/safe-mcp-agent.git
cd safe-mcp-agent/agenteval
poetry install
poetry run pytest
Please use Conventional Commits (feat(agenteval):, fix(agenteval):, etc.) for commit messages.
Changelog
See CHANGELOG.md for release history.
License
MIT (LICENSE).
Metadata
Release files for mcp-guardeval 0.3.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| mcp_guardeval-0.3.4.tar.gz | 15.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcp_guardeval-0.3.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.7 kB
Release files / mcp_guardeval-0.3.4.tar.gz
| Download URL | mcp_guardeval-0.3.4.tar.gz |
|---|---|
| Size | 15.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d25aae5ea39de97627a8fd185f961b37c82d0274117403f3a224df736c32bba8
|
|
BLAKE2b-256 checksum How to use checksums |
5512de6d8e8bbefe43627544a1d5d68ec1ba41346f6e8a04f97ebbf660f658fe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.4.1 CPython/3.13.9 Darwin/27.0.0
|
Release files / mcp_guardeval-0.3.4-py3-none-any.whl
| Download URL | mcp_guardeval-0.3.4-py3-none-any.whl |
|---|---|
| Size | 17.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
29f8c7f52dcff92c3629bb58760bfa7ff168a9ad203478d6a9fc9999a3dda73d
|
|
BLAKE2b-256 checksum How to use checksums |
996444986551e2f791c5c9cee23f8f06924b228f561bc6e05aeff7df31860e76
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.4.1 CPython/3.13.9 Darwin/27.0.0
|
Security for mcp-guardeval 0.3.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.