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.
Files changed (105) hide show
  1. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/CHANGELOG.md +19 -0
  2. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/PKG-INFO +31 -7
  3. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/README.md +30 -6
  4. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/README.zh-CN.md +15 -3
  5. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/__init__.py +1 -1
  6. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/render.py +2 -2
  7. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/cli.py +77 -7
  8. agenteval_toolkit-0.2.0/agenteval/collectors/a2a.py +176 -0
  9. agenteval_toolkit-0.2.0/agenteval/collectors/mcp.py +169 -0
  10. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/adapters.py +177 -2
  11. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/messages.py +9 -2
  12. agenteval_toolkit-0.2.0/agenteval/mcp_server.py +384 -0
  13. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/reports/templates/certificate.html.j2 +1 -1
  14. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schema.py +1 -1
  15. agenteval_toolkit-0.2.0/agenteval/schemas/agent.schema.json +358 -0
  16. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/pack.schema.json +3 -2
  17. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/PKG-INFO +31 -7
  18. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/SOURCES.txt +16 -0
  19. agenteval_toolkit-0.2.0/docs/a2a.md +84 -0
  20. agenteval_toolkit-0.2.0/docs/mcp.md +81 -0
  21. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/docs/rule-schema.md +42 -3
  22. agenteval_toolkit-0.2.0/examples/a2a/legacy-helper.agent-card.json +15 -0
  23. agenteval_toolkit-0.2.0/examples/a2a/northwind-support.agent-card.json +26 -0
  24. agenteval_toolkit-0.2.0/examples/agents/a2a.example.yaml +15 -0
  25. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/agents/demo-good.yaml +1 -0
  26. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/agents/demo-weak.yaml +1 -0
  27. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/agents/openai-compatible.example.yaml +1 -0
  28. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/agents/webhook.example.yaml +1 -0
  29. agenteval_toolkit-0.2.0/examples/mcp/mcp.json +35 -0
  30. agenteval_toolkit-0.2.0/rules/agent-protocols/a2a.yaml +104 -0
  31. agenteval_toolkit-0.2.0/rules/agent-protocols/i18n/zh.yaml +63 -0
  32. agenteval_toolkit-0.2.0/rules/agent-protocols/mcp.yaml +112 -0
  33. agenteval_toolkit-0.2.0/rules/agent-protocols/pack.yaml +8 -0
  34. agenteval_toolkit-0.2.0/tests/test_a2a.py +176 -0
  35. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_cert.py +1 -1
  36. agenteval_toolkit-0.2.0/tests/test_mcp.py +168 -0
  37. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_schemas.py +2 -0
  38. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/LICENSE +0 -0
  39. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/MANIFEST.in +0 -0
  40. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/NOTICE +0 -0
  41. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/__init__.py +0 -0
  42. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/assess.py +0 -0
  43. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/issuer.py +0 -0
  44. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/models.py +0 -0
  45. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/certification/signing.py +0 -0
  46. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/collectors/__init__.py +0 -0
  47. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/collectors/kubernetes.py +0 -0
  48. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/collectors/normalize.py +0 -0
  49. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/config.py +0 -0
  50. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/engine/__init__.py +0 -0
  51. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/engine/conditions.py +0 -0
  52. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/engine/evaluator.py +0 -0
  53. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/engine/loader.py +0 -0
  54. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/__init__.py +0 -0
  55. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/assertions.py +0 -0
  56. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/judges.py +0 -0
  57. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/models.py +0 -0
  58. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/papers.py +0 -0
  59. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/report.py +0 -0
  60. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/evaluation/runner.py +0 -0
  61. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/i18n.py +0 -0
  62. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/models.py +0 -0
  63. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/reports/__init__.py +0 -0
  64. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/reports/templates/eval_report.html.j2 +0 -0
  65. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/reports/templates/report.html.j2 +0 -0
  66. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/attestations.schema.json +0 -0
  67. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/exam.schema.json +0 -0
  68. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/policy.schema.json +0 -0
  69. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/rule.schema.json +0 -0
  70. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/system.schema.json +0 -0
  71. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval/schemas/translations.schema.json +0 -0
  72. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/dependency_links.txt +0 -0
  73. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/entry_points.txt +0 -0
  74. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/requires.txt +0 -0
  75. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/agenteval_toolkit.egg-info/top_level.txt +0 -0
  76. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/certification/policy.yaml +0 -0
  77. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/certification/systems/northwind-cs-staging.yaml +0 -0
  78. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/certification/systems/northwind-cs.yaml +0 -0
  79. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/docs/certification.md +0 -0
  80. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/docs/exams.md +0 -0
  81. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/attestations-certification.yaml +0 -0
  82. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/attestations.yaml +0 -0
  83. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/examples/snapshots/sample-cluster.json +0 -0
  84. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/exams/customer-service/cs-ecommerce-v1.yaml +0 -0
  85. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/pyproject.toml +0 -0
  86. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/industry-pci-dss/i18n/zh.yaml +0 -0
  87. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/industry-pci-dss/pack.yaml +0 -0
  88. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/industry-pci-dss/rules.yaml +0 -0
  89. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-ca/i18n/zh.yaml +0 -0
  90. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-ca/pack.yaml +0 -0
  91. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-ca/rules.yaml +0 -0
  92. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-federal/governance.yaml +0 -0
  93. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-federal/i18n/zh.yaml +0 -0
  94. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-federal/pack.yaml +0 -0
  95. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/rules/us-federal/security.yaml +0 -0
  96. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/setup.cfg +0 -0
  97. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/setup.py +0 -0
  98. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/conftest.py +0 -0
  99. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_cli.py +0 -0
  100. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_conditions.py +0 -0
  101. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_eval.py +0 -0
  102. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_i18n.py +0 -0
  103. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_init.py +0 -0
  104. {agenteval_toolkit-0.1.0 → agenteval_toolkit-0.2.0}/tests/test_normalize_and_scan.py +0 -0
  105. {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.1.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.1.0).** The rule packs are starter mappings, and every one is marked `verified: false`.
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 agent configs
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.1.0).** The rule packs are starter mappings, and every one is marked `verified: false`.
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 agent configs
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 基础设施,用标准试卷**评测** AI Agent,两项都通过时**签发数字签名证书**。
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.1.0)。** 内置规则包为入门级映射,全部标记为 `verified: false`,供贵司法务/合规团队审核的起点,不构成法律意见。
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 兼容接口、通用 Webhook、演示 Agent;离线启发式 / Anthropic / 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
 
@@ -2,4 +2,4 @@
2
2
  # Copyright 2026 The AgentEval Authors
3
3
  """AgentEval: scan AI infrastructure, evaluate AI agents, and certify systems that pass both."""
4
4
 
5
- __version__ = "0.1.0"
5
+ __version__ = "0.2.0"
@@ -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 is required when --source=snapshot",
83
- "zh": "source=snapshot 时必须提供 --snapshot"},
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 evaluate it against the rule packs."""
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 not snapshot:
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
- source_label = f"kubernetes:{context or ('in-cluster' if in_cluster else 'current-context')}"
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]