agenteval-toolkit 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/CHANGELOG.md +19 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/PKG-INFO +31 -7
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/README.md +30 -6
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/README.zh-CN.md +15 -3
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/__init__.py +1 -1
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/render.py +2 -2
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/cli.py +77 -7
- agenteval_toolkit-0.2.0/agenteval/collectors/a2a.py +176 -0
- agenteval_toolkit-0.2.0/agenteval/collectors/mcp.py +169 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/adapters.py +177 -2
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/messages.py +9 -2
- agenteval_toolkit-0.2.0/agenteval/mcp_server.py +384 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/reports/templates/certificate.html.j2 +1 -1
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schema.py +1 -1
- agenteval_toolkit-0.2.0/agenteval/schemas/agent.schema.json +358 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/pack.schema.json +3 -2
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/PKG-INFO +31 -7
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/SOURCES.txt +16 -0
- agenteval_toolkit-0.2.0/docs/a2a.md +84 -0
- agenteval_toolkit-0.2.0/docs/mcp.md +81 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/docs/rule-schema.md +42 -3
- agenteval_toolkit-0.2.0/examples/a2a/legacy-helper.agent-card.json +15 -0
- agenteval_toolkit-0.2.0/examples/a2a/northwind-support.agent-card.json +26 -0
- agenteval_toolkit-0.2.0/examples/agents/a2a.example.yaml +15 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/agents/demo-good.yaml +1 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/agents/demo-weak.yaml +1 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/agents/openai-compatible.example.yaml +1 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/agents/webhook.example.yaml +1 -0
- agenteval_toolkit-0.2.0/examples/mcp/mcp.json +35 -0
- agenteval_toolkit-0.2.0/rules/agent-protocols/a2a.yaml +104 -0
- agenteval_toolkit-0.2.0/rules/agent-protocols/i18n/zh.yaml +63 -0
- agenteval_toolkit-0.2.0/rules/agent-protocols/mcp.yaml +112 -0
- agenteval_toolkit-0.2.0/rules/agent-protocols/pack.yaml +8 -0
- agenteval_toolkit-0.2.0/tests/test_a2a.py +176 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_cert.py +1 -1
- agenteval_toolkit-0.2.0/tests/test_mcp.py +168 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_schemas.py +2 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/LICENSE +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/MANIFEST.in +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/NOTICE +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/__init__.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/assess.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/issuer.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/models.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/signing.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/collectors/__init__.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/collectors/kubernetes.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/collectors/normalize.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/config.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/engine/__init__.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/engine/conditions.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/engine/evaluator.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/engine/loader.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/__init__.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/assertions.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/judges.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/models.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/papers.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/report.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/runner.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/i18n.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/models.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/reports/__init__.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/reports/templates/eval_report.html.j2 +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/reports/templates/report.html.j2 +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/attestations.schema.json +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/exam.schema.json +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/policy.schema.json +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/rule.schema.json +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/system.schema.json +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/translations.schema.json +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/dependency_links.txt +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/entry_points.txt +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/requires.txt +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/top_level.txt +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/certification/policy.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/certification/systems/northwind-cs-staging.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/certification/systems/northwind-cs.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/docs/certification.md +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/docs/exams.md +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/attestations-certification.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/attestations.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/snapshots/sample-cluster.json +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/exams/customer-service/cs-ecommerce-v1.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/pyproject.toml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/industry-pci-dss/i18n/zh.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/industry-pci-dss/pack.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/industry-pci-dss/rules.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-ca/i18n/zh.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-ca/pack.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-ca/rules.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-federal/governance.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-federal/i18n/zh.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-federal/pack.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-federal/security.yaml +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/setup.cfg +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/setup.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/conftest.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_cli.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_conditions.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_eval.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_i18n.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_init.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_normalize_and_scan.py +0 -0
- {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_rules.py +0 -0
|
@@ -6,6 +6,25 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.0] - 2026-10-12
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **A2A (Agent2Agent) evaluation adapter** (`adapter: a2a`): discovers the Agent Card, prefers a v1.0 JSON-RPC
|
|
13
|
+
interface and falls back to v0.3; one A2A conversation per exam case (`contextId`), `taskId` continuation for
|
|
14
|
+
`input-required` / `auth-required` tasks, `GetTask` polling for long-running tasks, tool calls read from
|
|
15
|
+
`metadata.tool_calls` or DataParts. Verified against the official `a2a-sdk` 1.2 server (v1.0 and v0.3 compat).
|
|
16
|
+
- **A2A Agent Card scanning**: `agenteval a2a card` and `agenteval scan --a2a-card` (new `a2a_agent` asset type).
|
|
17
|
+
- **MCP client-config scanning**: `agenteval scan --mcp-config` for `mcpServers` / `servers` JSON (new `mcp_server` asset type).
|
|
18
|
+
- **`agent-protocols` rule pack** (new `baseline` tier): 6 A2A and 6 MCP rules, with Chinese translations.
|
|
19
|
+
- **`agenteval mcp serve`**: dependency-free MCP stdio server with 8 tools (scan, inspect_a2a_card, run_eval,
|
|
20
|
+
check_certification, verify_certificate, …), confined to `--root`, network-gated by `--allow-network`.
|
|
21
|
+
Verified with the official MCP Python SDK 2.3 client.
|
|
22
|
+
- `agent.schema.json` for agent-under-test configs; editor modelines on the example agents.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- `scan` no longer requires `--snapshot` when `--a2a-card` or `--mcp-config` is given.
|
|
26
|
+
- Adapter error messages default to English.
|
|
27
|
+
|
|
9
28
|
## [0.1.0] - 2026-10-11
|
|
10
29
|
|
|
11
30
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agenteval-toolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Scan AI infrastructure against policy-as-code rule packs, evaluate AI agents with standard exams, and issue signed certificates when both pass.
|
|
5
5
|
Author: The AgentEval Authors
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -47,13 +47,13 @@ Dynamic: license-file
|
|
|
47
47
|
|
|
48
48
|
**Scan, evaluate, certify.** AgentEval is an Apache-2.0 command-line toolkit with three jobs:
|
|
49
49
|
|
|
50
|
-
- it **scans** self-hosted AI infrastructure against policy-as-code rule packs,
|
|
50
|
+
- it **scans** self-hosted AI infrastructure, A2A Agent Cards and MCP configurations against policy-as-code rule packs,
|
|
51
51
|
- it **evaluates** AI agents with standard exam papers, and
|
|
52
52
|
- it **issues signed certificates** when a system passes both.
|
|
53
53
|
|
|
54
54
|
[English](https://github.com/pony360/AgentEval/blob/main/README.md) · [简体中文](https://github.com/pony360/AgentEval/blob/main/README.zh-CN.md)
|
|
55
55
|
|
|
56
|
-
> **Status: alpha (0.
|
|
56
|
+
> **Status: alpha (0.2.0).** The rule packs are starter mappings, and every one is marked `verified: false`.
|
|
57
57
|
> They are a starting point for your own legal and compliance review. They are not legal advice.
|
|
58
58
|
|
|
59
59
|
---
|
|
@@ -68,6 +68,7 @@ Dynamic: license-file
|
|
|
68
68
|
- [Rule packs](#rule-packs)
|
|
69
69
|
- [Writing rules](#writing-rules)
|
|
70
70
|
- [Evaluating agents](#evaluating-agents)
|
|
71
|
+
- [Agent protocols: A2A and MCP](#agent-protocols-a2a-and-mcp)
|
|
71
72
|
- [Certification](#certification)
|
|
72
73
|
- [Configuration](#configuration)
|
|
73
74
|
- [Security model](#security-model)
|
|
@@ -95,7 +96,9 @@ combined result into a certificate that anyone can verify offline.
|
|
|
95
96
|
| **Tiered rule packs** | National (US federal guidance), regional (California) and industry (PCI DSS) starter packs |
|
|
96
97
|
| **Reports** | JSON, SARIF (GitHub code scanning) and HTML, in English or Simplified Chinese |
|
|
97
98
|
| **Agent evaluation** | Scripted multi-turn exams with tool mocks, deterministic assertions plus rubric judging, and critical-failure vetoes |
|
|
98
|
-
| **Pluggable adapters and judges** | OpenAI-compatible endpoints, generic webhooks and demo agents. Heuristic (offline), Anthropic or OpenAI-compatible judges |
|
|
99
|
+
| **Pluggable adapters and judges** | OpenAI-compatible endpoints, A2A agents (v0.3 and v1.0), generic webhooks and demo agents. Heuristic (offline), Anthropic or OpenAI-compatible judges |
|
|
100
|
+
| **Agent protocols** | Checks A2A Agent Cards (TLS, auth, OAuth flows, signing) and MCP client configs (plaintext secrets, unpinned packages, download-and-execute, privileged containers) |
|
|
101
|
+
| **MCP server** | `agenteval mcp serve` exposes scanning, evaluation and certificate checks as MCP tools, confined to a root directory and offline by default |
|
|
99
102
|
| **Certification** | Strict gates, Ed25519-signed JSON, HTML/PDF certificates, optional QR verification URL and revocation status check |
|
|
100
103
|
| **CI-friendly** | Stable exit codes. No database or server required |
|
|
101
104
|
|
|
@@ -172,6 +175,8 @@ Every command accepts `--lang zh` for Chinese output.
|
|
|
172
175
|
| `agenteval schemas -o DIR` | Export the JSON Schemas for editors |
|
|
173
176
|
| `agenteval eval papers \| validate \| run` | List or validate exam papers. Run an exam against an agent |
|
|
174
177
|
| `agenteval cert check \| issue \| verify \| keygen` | Certification |
|
|
178
|
+
| `agenteval a2a card TARGET` | Fetch and check an A2A Agent Card |
|
|
179
|
+
| `agenteval mcp serve` | Run AgentEval as an MCP server over stdio |
|
|
175
180
|
|
|
176
181
|
| Exit code | Meaning |
|
|
177
182
|
|---|---|
|
|
@@ -185,6 +190,7 @@ Every command accepts `--lang zh` for Chinese output.
|
|
|
185
190
|
|---|---|---|---|
|
|
186
191
|
| `us-federal` | national | 16 | NIST AI RMF 1.0, AI 600-1, SP 800-53, SP 800-190, SP 800-218A and OMB M-25-21: authentication, TLS, network policy, image pinning, secrets, provenance, governance attestations |
|
|
187
192
|
| `us-ca` | regional | 3 | Bot disclosure (B&P §17940), CCPA data minimization, AB 2013 training-data transparency |
|
|
193
|
+
| `agent-protocols` | baseline | 12 | A2A Agent Cards and MCP client configs: TLS, authentication, OAuth flows, card signing, plaintext secrets, version pinning, download-and-execute, container isolation |
|
|
188
194
|
| `pci-dss` | industry | 4 | PCI DSS v4.0.1 (Req. 3, 4, 8, 10): no full card numbers in logs, strong cryptography in transit, authentication, and audit logging for AI that handles payment conversations |
|
|
189
195
|
|
|
190
196
|
Rule text is in English, and Simplified Chinese translations are in `rules/*/i18n/zh.yaml`.
|
|
@@ -228,6 +234,22 @@ agenteval eval run -p cs-ecommerce-v1 -a my-agent.yaml --judge anthropic --judge
|
|
|
228
234
|
|
|
229
235
|
See **[docs/exams.md](https://github.com/pony360/AgentEval/blob/main/docs/exams.md)** for the paper format, scoring, adapters and judges.
|
|
230
236
|
|
|
237
|
+
## Agent protocols: A2A and MCP
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# Evaluate an A2A agent (v1.0 or v0.3): each exam case runs as one A2A conversation
|
|
241
|
+
agenteval eval run -p cs-ecommerce-v1 -a examples/agents/a2a.example.yaml
|
|
242
|
+
|
|
243
|
+
# Check an Agent Card, and the MCP servers your tools launch
|
|
244
|
+
agenteval a2a card https://agents.example.com
|
|
245
|
+
agenteval scan --a2a-card ./agent-card.json --mcp-config ~/.cursor/mcp.json -f html -o protocols.html
|
|
246
|
+
|
|
247
|
+
# Use AgentEval from Claude, an IDE or any MCP client
|
|
248
|
+
agenteval mcp serve --root .
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
See **[docs/a2a.md](https://github.com/pony360/AgentEval/blob/main/docs/a2a.md)** and **[docs/mcp.md](https://github.com/pony360/AgentEval/blob/main/docs/mcp.md)**.
|
|
252
|
+
|
|
231
253
|
## Certification
|
|
232
254
|
|
|
233
255
|
`cert issue` signs a certificate only if all of the following hold:
|
|
@@ -271,17 +293,19 @@ To report a vulnerability, see [SECURITY.md](https://github.com/pony360/AgentEva
|
|
|
271
293
|
|
|
272
294
|
```
|
|
273
295
|
agenteval/ Python package (CLI, engine, collectors, evaluation, certification, schemas)
|
|
274
|
-
rules/ Rule packs (national / regional / industry)
|
|
296
|
+
rules/ Rule packs (national / regional / industry / baseline)
|
|
275
297
|
exams/ Exam papers
|
|
276
298
|
certification/ Certification policy and registered systems
|
|
277
|
-
examples/ Sample snapshot, attestations and
|
|
299
|
+
examples/ Sample snapshot, attestations, agent configs, A2A cards and MCP configs
|
|
278
300
|
docs/ Reference documentation
|
|
279
301
|
tests/ Test suite (pytest)
|
|
280
302
|
```
|
|
281
303
|
|
|
282
304
|
## Roadmap
|
|
283
305
|
|
|
284
|
-
- More collectors (cloud-managed endpoints, model registries, API gateways)
|
|
306
|
+
- More collectors (cloud-managed endpoints, model registries, API gateways, live MCP server introspection)
|
|
307
|
+
- A2A streaming (`SendStreamingMessage`) and gRPC / HTTP+JSON bindings; Agent Card signature verification
|
|
308
|
+
- An MCP mock-tool server so agents that consume MCP can be scored on their tool calls
|
|
285
309
|
- More rule packs (EU AI Act, ISO/IEC 42001, HIPAA), contributed with reviewer sign-off
|
|
286
310
|
- More exam papers (IT helpdesk, sales assistant, RAG question answering)
|
|
287
311
|
- A hosted verification and status service reference implementation
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
**Scan, evaluate, certify.** AgentEval is an Apache-2.0 command-line toolkit with three jobs:
|
|
4
4
|
|
|
5
|
-
- it **scans** self-hosted AI infrastructure against policy-as-code rule packs,
|
|
5
|
+
- it **scans** self-hosted AI infrastructure, A2A Agent Cards and MCP configurations against policy-as-code rule packs,
|
|
6
6
|
- it **evaluates** AI agents with standard exam papers, and
|
|
7
7
|
- it **issues signed certificates** when a system passes both.
|
|
8
8
|
|
|
9
9
|
[English](https://github.com/pony360/AgentEval/blob/main/README.md) · [简体中文](https://github.com/pony360/AgentEval/blob/main/README.zh-CN.md)
|
|
10
10
|
|
|
11
|
-
> **Status: alpha (0.
|
|
11
|
+
> **Status: alpha (0.2.0).** The rule packs are starter mappings, and every one is marked `verified: false`.
|
|
12
12
|
> They are a starting point for your own legal and compliance review. They are not legal advice.
|
|
13
13
|
|
|
14
14
|
---
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
- [Rule packs](#rule-packs)
|
|
24
24
|
- [Writing rules](#writing-rules)
|
|
25
25
|
- [Evaluating agents](#evaluating-agents)
|
|
26
|
+
- [Agent protocols: A2A and MCP](#agent-protocols-a2a-and-mcp)
|
|
26
27
|
- [Certification](#certification)
|
|
27
28
|
- [Configuration](#configuration)
|
|
28
29
|
- [Security model](#security-model)
|
|
@@ -50,7 +51,9 @@ combined result into a certificate that anyone can verify offline.
|
|
|
50
51
|
| **Tiered rule packs** | National (US federal guidance), regional (California) and industry (PCI DSS) starter packs |
|
|
51
52
|
| **Reports** | JSON, SARIF (GitHub code scanning) and HTML, in English or Simplified Chinese |
|
|
52
53
|
| **Agent evaluation** | Scripted multi-turn exams with tool mocks, deterministic assertions plus rubric judging, and critical-failure vetoes |
|
|
53
|
-
| **Pluggable adapters and judges** | OpenAI-compatible endpoints, generic webhooks and demo agents. Heuristic (offline), Anthropic or OpenAI-compatible judges |
|
|
54
|
+
| **Pluggable adapters and judges** | OpenAI-compatible endpoints, A2A agents (v0.3 and v1.0), generic webhooks and demo agents. Heuristic (offline), Anthropic or OpenAI-compatible judges |
|
|
55
|
+
| **Agent protocols** | Checks A2A Agent Cards (TLS, auth, OAuth flows, signing) and MCP client configs (plaintext secrets, unpinned packages, download-and-execute, privileged containers) |
|
|
56
|
+
| **MCP server** | `agenteval mcp serve` exposes scanning, evaluation and certificate checks as MCP tools, confined to a root directory and offline by default |
|
|
54
57
|
| **Certification** | Strict gates, Ed25519-signed JSON, HTML/PDF certificates, optional QR verification URL and revocation status check |
|
|
55
58
|
| **CI-friendly** | Stable exit codes. No database or server required |
|
|
56
59
|
|
|
@@ -127,6 +130,8 @@ Every command accepts `--lang zh` for Chinese output.
|
|
|
127
130
|
| `agenteval schemas -o DIR` | Export the JSON Schemas for editors |
|
|
128
131
|
| `agenteval eval papers \| validate \| run` | List or validate exam papers. Run an exam against an agent |
|
|
129
132
|
| `agenteval cert check \| issue \| verify \| keygen` | Certification |
|
|
133
|
+
| `agenteval a2a card TARGET` | Fetch and check an A2A Agent Card |
|
|
134
|
+
| `agenteval mcp serve` | Run AgentEval as an MCP server over stdio |
|
|
130
135
|
|
|
131
136
|
| Exit code | Meaning |
|
|
132
137
|
|---|---|
|
|
@@ -140,6 +145,7 @@ Every command accepts `--lang zh` for Chinese output.
|
|
|
140
145
|
|---|---|---|---|
|
|
141
146
|
| `us-federal` | national | 16 | NIST AI RMF 1.0, AI 600-1, SP 800-53, SP 800-190, SP 800-218A and OMB M-25-21: authentication, TLS, network policy, image pinning, secrets, provenance, governance attestations |
|
|
142
147
|
| `us-ca` | regional | 3 | Bot disclosure (B&P §17940), CCPA data minimization, AB 2013 training-data transparency |
|
|
148
|
+
| `agent-protocols` | baseline | 12 | A2A Agent Cards and MCP client configs: TLS, authentication, OAuth flows, card signing, plaintext secrets, version pinning, download-and-execute, container isolation |
|
|
143
149
|
| `pci-dss` | industry | 4 | PCI DSS v4.0.1 (Req. 3, 4, 8, 10): no full card numbers in logs, strong cryptography in transit, authentication, and audit logging for AI that handles payment conversations |
|
|
144
150
|
|
|
145
151
|
Rule text is in English, and Simplified Chinese translations are in `rules/*/i18n/zh.yaml`.
|
|
@@ -183,6 +189,22 @@ agenteval eval run -p cs-ecommerce-v1 -a my-agent.yaml --judge anthropic --judge
|
|
|
183
189
|
|
|
184
190
|
See **[docs/exams.md](https://github.com/pony360/AgentEval/blob/main/docs/exams.md)** for the paper format, scoring, adapters and judges.
|
|
185
191
|
|
|
192
|
+
## Agent protocols: A2A and MCP
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Evaluate an A2A agent (v1.0 or v0.3): each exam case runs as one A2A conversation
|
|
196
|
+
agenteval eval run -p cs-ecommerce-v1 -a examples/agents/a2a.example.yaml
|
|
197
|
+
|
|
198
|
+
# Check an Agent Card, and the MCP servers your tools launch
|
|
199
|
+
agenteval a2a card https://agents.example.com
|
|
200
|
+
agenteval scan --a2a-card ./agent-card.json --mcp-config ~/.cursor/mcp.json -f html -o protocols.html
|
|
201
|
+
|
|
202
|
+
# Use AgentEval from Claude, an IDE or any MCP client
|
|
203
|
+
agenteval mcp serve --root .
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
See **[docs/a2a.md](https://github.com/pony360/AgentEval/blob/main/docs/a2a.md)** and **[docs/mcp.md](https://github.com/pony360/AgentEval/blob/main/docs/mcp.md)**.
|
|
207
|
+
|
|
186
208
|
## Certification
|
|
187
209
|
|
|
188
210
|
`cert issue` signs a certificate only if all of the following hold:
|
|
@@ -226,17 +248,19 @@ To report a vulnerability, see [SECURITY.md](https://github.com/pony360/AgentEva
|
|
|
226
248
|
|
|
227
249
|
```
|
|
228
250
|
agenteval/ Python package (CLI, engine, collectors, evaluation, certification, schemas)
|
|
229
|
-
rules/ Rule packs (national / regional / industry)
|
|
251
|
+
rules/ Rule packs (national / regional / industry / baseline)
|
|
230
252
|
exams/ Exam papers
|
|
231
253
|
certification/ Certification policy and registered systems
|
|
232
|
-
examples/ Sample snapshot, attestations and
|
|
254
|
+
examples/ Sample snapshot, attestations, agent configs, A2A cards and MCP configs
|
|
233
255
|
docs/ Reference documentation
|
|
234
256
|
tests/ Test suite (pytest)
|
|
235
257
|
```
|
|
236
258
|
|
|
237
259
|
## Roadmap
|
|
238
260
|
|
|
239
|
-
- More collectors (cloud-managed endpoints, model registries, API gateways)
|
|
261
|
+
- More collectors (cloud-managed endpoints, model registries, API gateways, live MCP server introspection)
|
|
262
|
+
- A2A streaming (`SendStreamingMessage`) and gRPC / HTTP+JSON bindings; Agent Card signature verification
|
|
263
|
+
- An MCP mock-tool server so agents that consume MCP can be scored on their tool calls
|
|
240
264
|
- More rule packs (EU AI Act, ISO/IEC 42001, HIPAA), contributed with reviewer sign-off
|
|
241
265
|
- More exam papers (IT helpdesk, sales assistant, RAG question answering)
|
|
242
266
|
- A hosted verification and status service reference implementation
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# AgentEval
|
|
2
2
|
|
|
3
|
-
**扫描 · 评测 · 认证。** AgentEval 是一个 Apache-2.0 开源命令行工具:按策略即代码(policy-as-code)规则包**扫描**自托管 AI
|
|
3
|
+
**扫描 · 评测 · 认证。** AgentEval 是一个 Apache-2.0 开源命令行工具:按策略即代码(policy-as-code)规则包**扫描**自托管 AI 基础设施、A2A Agent Card 与 MCP 配置,用标准试卷**评测** AI Agent,两项都通过时**签发数字签名证书**。
|
|
4
4
|
|
|
5
5
|
[English](https://github.com/pony360/AgentEval/blob/main/README.md) · [简体中文](https://github.com/pony360/AgentEval/blob/main/README.zh-CN.md)
|
|
6
6
|
|
|
7
|
-
> **状态:Alpha(0.
|
|
7
|
+
> **状态:Alpha(0.2.0)。** 内置规则包为入门级映射,全部标记为 `verified: false`,供贵司法务/合规团队审核的起点,不构成法律意见。
|
|
8
8
|
|
|
9
9
|
## 目录
|
|
10
10
|
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
| **分层规则包** | 国家(美国联邦指引)、区域(加州)、行业(PCI DSS)入门规则包 |
|
|
30
30
|
| **报告** | JSON、SARIF(GitHub 代码扫描)、HTML;中英文输出 |
|
|
31
31
|
| **Agent 评测** | 脚本化多轮对话 + 工具模拟;确定性断言 + 评分细则(rubric)裁判;关键项一票否决 |
|
|
32
|
-
| **可插拔** | OpenAI
|
|
32
|
+
| **可插拔** | OpenAI 兼容接口、A2A Agent(v0.3 与 v1.0)、通用 Webhook、演示 Agent;离线启发式 / Anthropic / OpenAI 兼容裁判 |
|
|
33
|
+
| **智能体协议** | 检查 A2A Agent Card(TLS、认证、OAuth 模式、签名)与 MCP 客户端配置(明文密钥、未固定版本、下载即执行、特权容器) |
|
|
34
|
+
| **MCP 服务器** | `agenteval mcp serve` 把扫描、评测与证书校验作为 MCP 工具提供;文件访问限定在根目录,默认不联网 |
|
|
33
35
|
| **认证** | 严格门槛、Ed25519 签名 JSON、HTML/PDF 证书、可选二维码验证地址与吊销状态查询 |
|
|
34
36
|
| **适合 CI** | 稳定退出码;无需数据库或服务端 |
|
|
35
37
|
|
|
@@ -84,6 +86,7 @@ agenteval cert verify certificates/AEV-*.json --public-key issuer.pem --lang zh
|
|
|
84
86
|
| `us-federal` | 国家 | 16 | NIST AI RMF 1.0、AI 600-1、SP 800-53、SP 800-190、SP 800-218A、OMB M-25-21 |
|
|
85
87
|
| `us-ca` | 区域 | 3 | 机器人身份披露(B&P §17940)、CCPA 数据最小化、AB 2013 训练数据透明度 |
|
|
86
88
|
| `pci-dss` | 行业 | 4 | PCI DSS v4.0.1(要求 3、4、8、10) |
|
|
89
|
+
| `agent-protocols` | 基线 | 12 | A2A Agent Card 与 MCP 客户端配置:TLS、认证、OAuth 模式、卡片签名、明文密钥、版本固定、下载即执行、容器隔离 |
|
|
87
90
|
|
|
88
91
|
规则以英文为源语言,简体中文译文位于 `rules/*/i18n/zh.yaml`。
|
|
89
92
|
|
|
@@ -95,6 +98,15 @@ agenteval cert verify certificates/AEV-*.json --public-key issuer.pem --lang zh
|
|
|
95
98
|
|
|
96
99
|
- 试卷格式、评分与适配器:[docs/exams.md](https://github.com/pony360/AgentEval/blob/main/docs/exams.md)
|
|
97
100
|
- 认证门槛、密钥、验证与吊销:[docs/certification.md](https://github.com/pony360/AgentEval/blob/main/docs/certification.md)
|
|
101
|
+
- A2A 评测适配器与 Agent Card 检查:[docs/a2a.md](https://github.com/pony360/AgentEval/blob/main/docs/a2a.md)
|
|
102
|
+
- MCP 服务器与 MCP 配置扫描:[docs/mcp.md](https://github.com/pony360/AgentEval/blob/main/docs/mcp.md)
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
agenteval eval run -p cs-ecommerce-v1 -a examples/agents/a2a.example.yaml # 评测 A2A Agent
|
|
106
|
+
agenteval a2a card https://agents.example.com # 检查 Agent Card
|
|
107
|
+
agenteval scan --mcp-config ~/.cursor/mcp.json --lang zh # 扫描 MCP 配置
|
|
108
|
+
agenteval mcp serve --root . # 作为 MCP 服务器运行
|
|
109
|
+
```
|
|
98
110
|
|
|
99
111
|
颁证条件:安全得分 **> 90** 且质量得分 **> 90**;无未修复的高危/严重项;无待证明项;评测无关键项失败与会话错误;证据不超过 30 天。离线启发式裁判只能获得 **DEMO** 证书。证书有效期 6 个月。
|
|
100
112
|
|
|
@@ -17,7 +17,7 @@ UI = {
|
|
|
17
17
|
"certifies": "兹证明", "statement": "已通过 AgentEval 工具的安全合规扫描与标准化质量评测,满足本认证全部颁证条件。",
|
|
18
18
|
"security": "安全", "security_sub": "国家、区域及行业安全规范", "quality": "质量", "quality_sub": "标准化评测指标",
|
|
19
19
|
"score": "得分", "threshold": "阈值", "tier": "层级", "pack": "规则包", "checks": "通过 / 适用", "status": "结论",
|
|
20
|
-
"national": "国家", "regional": "区域", "industry": "行业", "unknown": "其他",
|
|
20
|
+
"national": "国家", "regional": "区域", "industry": "行业", "baseline": "基线", "unknown": "其他",
|
|
21
21
|
"st_passed": "通过", "st_failed": "未通过", "st_not_applicable": "不适用", "st_not_assessed": "未评估",
|
|
22
22
|
"exam": "试卷", "grade": "等级", "judge": "裁判", "cases": "样本通过", "dimension": "维度",
|
|
23
23
|
"issued": "颁发日期", "expires": "有效期至", "evidence": "证据", "scan": "扫描", "rules": "规则集", "run": "评测",
|
|
@@ -38,7 +38,7 @@ UI = {
|
|
|
38
38
|
"security": "Security", "security_sub": "National, regional and industry security requirements",
|
|
39
39
|
"quality": "Quality", "quality_sub": "Standardized evaluation metrics",
|
|
40
40
|
"score": "Score", "threshold": "Threshold", "tier": "Tier", "pack": "Rule pack", "checks": "Passed / applicable",
|
|
41
|
-
"status": "Result", "national": "National", "regional": "Regional", "industry": "Industry", "unknown": "Other",
|
|
41
|
+
"status": "Result", "national": "National", "regional": "Regional", "industry": "Industry", "baseline": "Baseline", "unknown": "Other",
|
|
42
42
|
"st_passed": "Passed", "st_failed": "Failed", "st_not_applicable": "Not applicable", "st_not_assessed": "Not assessed",
|
|
43
43
|
"exam": "Exam", "grade": "Grade", "judge": "Judge", "cases": "Cases passed", "dimension": "Dimension",
|
|
44
44
|
"issued": "Issued", "expires": "Valid until", "evidence": "Evidence", "scan": "Scan", "rules": "Rule set", "run": "Eval run",
|
|
@@ -79,8 +79,8 @@ CLI_MESSAGES = {
|
|
|
79
79
|
"attest_skipped": {"en": "Evaluation did not pass; no pre-deployment testing attestation written",
|
|
80
80
|
"zh": "评测未通过,未生成部署前测试证明"},
|
|
81
81
|
"snapshot_written": {"en": "Exported {n} workloads to {path}", "zh": "已导出 {n} 个工作负载到 {path}"},
|
|
82
|
-
"snapshot_required": {"en": "--snapshot
|
|
83
|
-
"zh": "source
|
|
82
|
+
"snapshot_required": {"en": "Nothing to scan: pass --snapshot, --source kubernetes, --a2a-card or --mcp-config",
|
|
83
|
+
"zh": "没有可扫描的对象:请提供 --snapshot、--source kubernetes、--a2a-card 或 --mcp-config"},
|
|
84
84
|
"collect_failed": {"en": "Collection failed: {e}", "zh": "采集失败: {e}"},
|
|
85
85
|
"unknown_system": {"en": "Unknown AI system: {ref} (registered: {ids})", "zh": "未登记的 AI 系统: {ref}(可用: {ids})"},
|
|
86
86
|
"bad_inputs": {"en": "Cannot read scan/eval JSON (produce them with -f json): {e}",
|
|
@@ -141,6 +141,8 @@ def scan(
|
|
|
141
141
|
context: Optional[str] = typer.Option(None, help="kubeconfig context."),
|
|
142
142
|
namespace: list[str] = typer.Option([], "--namespace", "-n", help="Limit to a namespace; repeatable."),
|
|
143
143
|
in_cluster: bool = typer.Option(False, help="Use the in-cluster ServiceAccount."),
|
|
144
|
+
a2a_card: list[str] = typer.Option([], "--a2a-card", help="A2A Agent Card URL, agent base URL or JSON file; repeatable."),
|
|
145
|
+
mcp_config: list[Path] = typer.Option([], "--mcp-config", help="MCP client config (mcp.json / claude_desktop_config.json); repeatable."),
|
|
144
146
|
rules: Path = typer.Option(None, help="Rules directory or file (default ./rules or $AGENTEVAL_RULES_DIR)."),
|
|
145
147
|
attestations: list[Path] = typer.Option([], help="Attestation YAML; repeatable, files are merged."),
|
|
146
148
|
fmt: OutFormat = typer.Option(OutFormat.json, "--format", "-f", help="Report format."),
|
|
@@ -148,19 +150,32 @@ def scan(
|
|
|
148
150
|
lang: Lang = LANG_OPT,
|
|
149
151
|
fail_on: FailOn = typer.Option(FailOn.high, help="Exit 1 if any failed check is at this severity or above."),
|
|
150
152
|
) -> None:
|
|
151
|
-
"""Scan a cluster (or snapshot) and
|
|
153
|
+
"""Scan a cluster (or snapshot), A2A Agent Cards and MCP configs against the rule packs."""
|
|
154
|
+
from agenteval.collectors.a2a import collect_a2a_cards
|
|
155
|
+
from agenteval.collectors.mcp import collect_mcp_configs
|
|
156
|
+
|
|
152
157
|
rule_set = _load_rules_or_exit(rules or default_rules_dir())
|
|
153
158
|
try:
|
|
154
159
|
atts = load_attestations(list(attestations))
|
|
160
|
+
labels: list[str] = []
|
|
161
|
+
assets = []
|
|
155
162
|
if source == SourceKind.snapshot:
|
|
156
|
-
if
|
|
163
|
+
if snapshot:
|
|
164
|
+
assets, _ = collect_from_snapshot_file(snapshot)
|
|
165
|
+
labels.append(f"snapshot:{snapshot.name}")
|
|
166
|
+
elif not (a2a_card or mcp_config):
|
|
157
167
|
_err(_m("snapshot_required", lang.value))
|
|
158
168
|
raise typer.Exit(2)
|
|
159
|
-
assets, _ = collect_from_snapshot_file(snapshot)
|
|
160
|
-
source_label = f"snapshot:{snapshot.name}"
|
|
161
169
|
else:
|
|
162
170
|
assets, _ = collect_from_kubernetes(context=context, namespaces=namespace or None, in_cluster=in_cluster)
|
|
163
|
-
|
|
171
|
+
labels.append(f"kubernetes:{context or ('in-cluster' if in_cluster else 'current-context')}")
|
|
172
|
+
if a2a_card:
|
|
173
|
+
assets += collect_a2a_cards(list(a2a_card))
|
|
174
|
+
labels.append(f"a2a:{len(a2a_card)}")
|
|
175
|
+
if mcp_config:
|
|
176
|
+
assets += collect_mcp_configs(list(mcp_config))
|
|
177
|
+
labels.append("mcp:" + ",".join(p.name for p in mcp_config))
|
|
178
|
+
source_label = " + ".join(labels)
|
|
164
179
|
except typer.Exit:
|
|
165
180
|
raise
|
|
166
181
|
except Exception as e:
|
|
@@ -529,5 +544,60 @@ def cert_keygen(
|
|
|
529
544
|
typer.echo(public_pem(key) + f"key_id: {key_id(key)}")
|
|
530
545
|
|
|
531
546
|
|
|
547
|
+
# ======================= Agent protocols (A2A / MCP) =======================
|
|
548
|
+
|
|
549
|
+
a2a_app = typer.Typer(help="Agent2Agent (A2A) protocol helpers.", no_args_is_help=True)
|
|
550
|
+
mcp_app = typer.Typer(help="Model Context Protocol (MCP): run AgentEval as an MCP server.", no_args_is_help=True)
|
|
551
|
+
app.add_typer(a2a_app, name="a2a")
|
|
552
|
+
app.add_typer(mcp_app, name="mcp")
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
@a2a_app.command("card")
|
|
556
|
+
def a2a_card_cmd(
|
|
557
|
+
target: str = typer.Argument(..., help="Agent Card file, card URL, or agent base URL."),
|
|
558
|
+
rules: Path = typer.Option(None, help="Rules directory (default ./rules or $AGENTEVAL_RULES_DIR)."),
|
|
559
|
+
lang: Lang = LANG_OPT,
|
|
560
|
+
) -> None:
|
|
561
|
+
"""Fetch and check an A2A Agent Card: endpoints, protocol versions, auth and signing. Exit 1 on high findings."""
|
|
562
|
+
from agenteval.collectors.a2a import CardError, fetch_card, normalize_card
|
|
563
|
+
from agenteval.i18n import localize_result
|
|
564
|
+
|
|
565
|
+
try:
|
|
566
|
+
asset = normalize_card(*fetch_card(target))
|
|
567
|
+
except CardError as e:
|
|
568
|
+
_err(str(e))
|
|
569
|
+
raise typer.Exit(2)
|
|
570
|
+
rule_set = [r for r in _load_rules_or_exit(rules or default_rules_dir()) if "a2a_agent" in r.applies_to.asset_types]
|
|
571
|
+
result = run_scan(rule_set, [asset], source=f"a2a:{target}")
|
|
572
|
+
f = asset.facts
|
|
573
|
+
typer.echo(f"{asset.name} ({asset.id})")
|
|
574
|
+
typer.echo(f" protocol: {', '.join(f['protocol_versions']) or '-'} bindings: {', '.join(f['bindings']) or '-'}")
|
|
575
|
+
typer.echo(f" endpoints: {', '.join(f['endpoint']['urls']) or '-'}")
|
|
576
|
+
typer.echo(f" auth: {', '.join(f['auth']['schemes']) or 'none'} (required: {f['auth']['required']}) "
|
|
577
|
+
f"signed: {f['card']['signed']} skills: {f['skills']['count']}")
|
|
578
|
+
localized = localize_result(result, lang.value)
|
|
579
|
+
worst = 0
|
|
580
|
+
for x in localized.findings:
|
|
581
|
+
if x.status == Status.not_applicable:
|
|
582
|
+
continue
|
|
583
|
+
mark = "✓" if x.status == Status.passed else "✕"
|
|
584
|
+
typer.secho(f" {mark} {x.rule_id:<14} {x.severity.value:<8} {x.rule_title}",
|
|
585
|
+
fg="green" if x.status == Status.passed else "red")
|
|
586
|
+
if x.status == Status.failed:
|
|
587
|
+
worst = max(worst, x.severity.rank)
|
|
588
|
+
raise typer.Exit(1 if worst >= Severity.high.rank else 0)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
@mcp_app.command("serve")
|
|
592
|
+
def mcp_serve_cmd(
|
|
593
|
+
root: Path = typer.Option(Path("."), help="Only files under this directory can be read by tools."),
|
|
594
|
+
allow_network: bool = typer.Option(False, help="Allow tools that fetch remote Agent Cards or call remote agents."),
|
|
595
|
+
) -> None:
|
|
596
|
+
"""Run AgentEval as an MCP server over stdio (for Claude, IDEs and other MCP clients)."""
|
|
597
|
+
from agenteval.mcp_server import serve
|
|
598
|
+
|
|
599
|
+
serve(root, allow_network)
|
|
600
|
+
|
|
601
|
+
|
|
532
602
|
if __name__ == "__main__": # pragma: no cover
|
|
533
603
|
app()
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright 2026 The AgentEval Authors
|
|
3
|
+
"""Normalize A2A (Agent2Agent) Agent Cards into `a2a_agent` assets.
|
|
4
|
+
|
|
5
|
+
Supports both the v0.3 card shape (top-level `url`, `preferredTransport`,
|
|
6
|
+
`additionalInterfaces`, `security`) and the v1.0 shape (`supportedInterfaces[]`
|
|
7
|
+
with `protocolBinding` / `protocolVersion`, `capabilities.extendedAgentCard`).
|
|
8
|
+
Only metadata is recorded; nothing is sent to the agent.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
from urllib.parse import parse_qs, urlparse
|
|
18
|
+
|
|
19
|
+
import httpx
|
|
20
|
+
|
|
21
|
+
from agenteval.models import Asset
|
|
22
|
+
|
|
23
|
+
WELL_KNOWN = "/.well-known/agent-card.json"
|
|
24
|
+
LOCAL_HOSTS = {"localhost", "127.0.0.1", "::1"}
|
|
25
|
+
|
|
26
|
+
# v1.0 proto-JSON wraps each scheme in a oneof; map wrapper names to the classic `type` values.
|
|
27
|
+
_SCHEME_WRAPPERS = {
|
|
28
|
+
"apiKeySecurityScheme": "apiKey",
|
|
29
|
+
"httpAuthSecurityScheme": "http",
|
|
30
|
+
"oauth2SecurityScheme": "oauth2",
|
|
31
|
+
"openIdConnectSecurityScheme": "openIdConnect",
|
|
32
|
+
"mtlsSecurityScheme": "mutualTLS",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CardError(ValueError):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _slug(text: str) -> str:
|
|
41
|
+
return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-") or "agent"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _is_local(url: str) -> bool:
|
|
45
|
+
return (urlparse(url).hostname or "") in LOCAL_HOSTS
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def card_url_for(target: str) -> str:
|
|
49
|
+
"""Accept a card URL or an agent base URL; return the URL to fetch."""
|
|
50
|
+
if target.endswith(".json"):
|
|
51
|
+
return target
|
|
52
|
+
return target.rstrip("/") + WELL_KNOWN
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def fetch_card(target: str, client: httpx.Client | None = None, timeout: float = 15.0,
|
|
56
|
+
headers: dict[str, str] | None = None) -> tuple[dict, str]:
|
|
57
|
+
"""Load a card from a local file or over HTTPS (plain HTTP only for localhost)."""
|
|
58
|
+
p = Path(target)
|
|
59
|
+
if not target.startswith(("http://", "https://")):
|
|
60
|
+
if not p.exists():
|
|
61
|
+
raise CardError(f"Agent Card not found: {target}")
|
|
62
|
+
return json.loads(p.read_text(encoding="utf-8")), str(p)
|
|
63
|
+
url = card_url_for(target)
|
|
64
|
+
if url.startswith("http://") and not _is_local(url):
|
|
65
|
+
raise CardError(f"Refusing to fetch an Agent Card over plain HTTP: {url}")
|
|
66
|
+
own = client is None
|
|
67
|
+
client = client or httpx.Client(follow_redirects=False)
|
|
68
|
+
try:
|
|
69
|
+
r = client.get(url, headers=headers or {}, timeout=timeout)
|
|
70
|
+
r.raise_for_status()
|
|
71
|
+
return r.json(), url
|
|
72
|
+
except (httpx.HTTPError, ValueError) as e:
|
|
73
|
+
raise CardError(f"Could not fetch Agent Card {url}: {e}") from e
|
|
74
|
+
finally:
|
|
75
|
+
if own:
|
|
76
|
+
client.close()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def interfaces(card: dict) -> list[dict[str, str]]:
|
|
80
|
+
"""All declared endpoints as {url, binding, version}, newest card shape first."""
|
|
81
|
+
out: list[dict[str, str]] = []
|
|
82
|
+
for i in card.get("supportedInterfaces") or []:
|
|
83
|
+
if isinstance(i, dict) and i.get("url"):
|
|
84
|
+
out.append({"url": i["url"], "binding": str(i.get("protocolBinding") or "JSONRPC").upper(),
|
|
85
|
+
"version": str(i.get("protocolVersion") or "1.0")})
|
|
86
|
+
if card.get("url"):
|
|
87
|
+
out.append({"url": card["url"], "binding": str(card.get("preferredTransport") or "JSONRPC").upper(),
|
|
88
|
+
"version": str(card.get("protocolVersion") or "0.3")})
|
|
89
|
+
for i in card.get("additionalInterfaces") or []:
|
|
90
|
+
if isinstance(i, dict) and i.get("url"):
|
|
91
|
+
out.append({"url": i["url"], "binding": str(i.get("transport") or "JSONRPC").upper(),
|
|
92
|
+
"version": str(card.get("protocolVersion") or "0.3")})
|
|
93
|
+
seen, uniq = set(), []
|
|
94
|
+
for i in out:
|
|
95
|
+
key = (i["url"], i["binding"], i["version"])
|
|
96
|
+
if key not in seen:
|
|
97
|
+
seen.add(key)
|
|
98
|
+
uniq.append(i)
|
|
99
|
+
return uniq
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _schemes(card: dict) -> dict[str, dict]:
|
|
103
|
+
out: dict[str, dict] = {}
|
|
104
|
+
for name, raw in (card.get("securitySchemes") or {}).items():
|
|
105
|
+
if not isinstance(raw, dict):
|
|
106
|
+
continue
|
|
107
|
+
if "type" in raw:
|
|
108
|
+
out[name] = raw
|
|
109
|
+
continue
|
|
110
|
+
for wrapper, typ in _SCHEME_WRAPPERS.items():
|
|
111
|
+
if isinstance(raw.get(wrapper), dict):
|
|
112
|
+
out[name] = {"type": typ, **raw[wrapper]}
|
|
113
|
+
break
|
|
114
|
+
return out
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _requirements(card: dict) -> list | None:
|
|
118
|
+
for key in ("security", "securityRequirements"):
|
|
119
|
+
if key in card:
|
|
120
|
+
return card.get(key) or []
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def normalize_card(card: dict, source: str = "") -> Asset:
|
|
125
|
+
if not isinstance(card, dict) or not card.get("name"):
|
|
126
|
+
raise CardError(f"Not an A2A Agent Card (missing name): {source}")
|
|
127
|
+
ifaces = interfaces(card)
|
|
128
|
+
urls = [i["url"] for i in ifaces]
|
|
129
|
+
schemes = _schemes(card)
|
|
130
|
+
types = sorted({str(s.get("type")) for s in schemes.values()})
|
|
131
|
+
reqs = _requirements(card)
|
|
132
|
+
flows = {f for s in schemes.values() if s.get("type") == "oauth2" for f in (s.get("flows") or {})}
|
|
133
|
+
api_key_in_query = any(s.get("type") == "apiKey" and str(s.get("in", s.get("location", ""))).lower() == "query"
|
|
134
|
+
for s in schemes.values())
|
|
135
|
+
query_secrets = [u for u in urls if any(k.lower() in ("key", "api_key", "apikey", "token", "access_token")
|
|
136
|
+
for k in parse_qs(urlparse(u).query))]
|
|
137
|
+
caps = card.get("capabilities") or {}
|
|
138
|
+
provider = card.get("provider") or {}
|
|
139
|
+
host = (urlparse(urls[0]).hostname if urls else None) or "local"
|
|
140
|
+
facts: dict[str, Any] = {
|
|
141
|
+
"name": card.get("name"),
|
|
142
|
+
"version": card.get("version"),
|
|
143
|
+
"protocol_versions": sorted({i["version"] for i in ifaces}),
|
|
144
|
+
"bindings": sorted({i["binding"] for i in ifaces}),
|
|
145
|
+
"endpoint": {
|
|
146
|
+
"urls": urls,
|
|
147
|
+
"all_https": bool(urls) and all(u.startswith("https://") or _is_local(u) for u in urls),
|
|
148
|
+
"any_plain_http": any(u.startswith("http://") and not _is_local(u) for u in urls),
|
|
149
|
+
"secrets_in_url": bool(query_secrets),
|
|
150
|
+
},
|
|
151
|
+
"auth": {
|
|
152
|
+
"schemes": types,
|
|
153
|
+
"declared": bool(schemes),
|
|
154
|
+
# No explicit requirement list: treat declared schemes as required (common in practice).
|
|
155
|
+
"required": bool(reqs) if reqs is not None else bool(schemes),
|
|
156
|
+
"api_key_in_query": api_key_in_query,
|
|
157
|
+
"mutual_tls": "mutualTLS" in types,
|
|
158
|
+
},
|
|
159
|
+
"oauth": {"flows": sorted(flows), "deprecated_flows": bool(flows & {"implicit", "password"})},
|
|
160
|
+
"card": {"signed": bool(card.get("signatures")), "source": source},
|
|
161
|
+
"capabilities": {
|
|
162
|
+
"streaming": bool(caps.get("streaming")),
|
|
163
|
+
"push_notifications": bool(caps.get("pushNotifications")),
|
|
164
|
+
"extended_card": bool(caps.get("extendedAgentCard") or card.get("supportsAuthenticatedExtendedCard")),
|
|
165
|
+
},
|
|
166
|
+
"provider": {"organization": provider.get("organization"), "url": provider.get("url")},
|
|
167
|
+
"skills": {"count": len(card.get("skills") or []),
|
|
168
|
+
"ids": [s.get("id") for s in card.get("skills") or [] if isinstance(s, dict)]},
|
|
169
|
+
"documentation_url": card.get("documentationUrl"),
|
|
170
|
+
}
|
|
171
|
+
return Asset(id=f"a2a/{host}/{_slug(card['name'])}", type="a2a_agent", name=card["name"],
|
|
172
|
+
namespace=None, facts=facts)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def collect_a2a_cards(targets: list[str], client: httpx.Client | None = None) -> list[Asset]:
|
|
176
|
+
return [normalize_card(*fetch_card(t, client=client)) for t in targets]
|