A2A Provider
The a2a provider allows you to use agents that implement the
Agent2Agent protocol directly as providers in
promptfoo. This is useful for testing agentic applications that expose an A2A interface for
message-based interaction, streaming responses, and asynchronous task execution.
Promptfoo sends each test prompt as an A2A message, waits for the agent to respond or complete a task, and extracts the final text from the A2A response. When an Agent Card is available, promptfoo can also use it to discover the agent endpoint and add advertised skills to red team generation context.
Setup
To use the A2A provider, you need an A2A server that supports the HTTP+JSON REST binding. The current provider supports:
POST /message:sendPOST /message:streamGET /tasks/{id}polling- Agent Card discovery from
/.well-known/agent-card.json
JSON-RPC, gRPC, and push-notification webhooks are not supported in this provider yet.
Basic Configuration
The simplest configuration points promptfoo at the base URL for the A2A HTTP+JSON interface:
providers:
- id: a2a:https://agent.example.com/a2a/v1
config:
auth:
type: bearer
token: '{{ env.A2A_API_KEY }}'
The a2a:<url> shorthand sets config.url. Promptfoo appends the operation paths, such as
/message:send, /message:stream, and /tasks/{id}, to this base URL.
Agent Card Discovery
If your agent publishes an Agent Card, you can configure agentCardUrl instead of hardcoding the
A2A endpoint:
providers:
- id: a2a
config:
agentCardUrl: https://agent.example.com/.well-known/agent-card.json
auth:
type: bearer
token: '{{ env.A2A_API_KEY }}'
mode: auto
When an Agent Card is configured, promptfoo selects the first supported interface with
protocolBinding: HTTP+JSON and uses its URL, tenant, protocol version, and streaming capability
unless you explicitly override them in config.
During red team generation, promptfoo also extracts useful Agent Card metadata such as the agent name, description, capabilities, and skills. This gives the attack generator more target-specific context, similar to how the MCP provider uses discovered tools.
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
url | string | - | Base URL for the A2A HTTP+JSON interface |
agentCardUrl | string | - | URL of the Agent Card used for endpoint and capability discovery |
auth | object | - | Bearer, basic, API key, or OAuth authentication configuration |
headers | Record<string, string> | {} | Headers sent to the Agent Card endpoint and A2A operation requests |
mode | auto | send | stream | auto | Whether to call message:send, message:stream, or choose automatically |
tenant | string | - | Tenant override. Defaults to the selected Agent Card interface tenant |
protocolVersion | string | 1.0 | Value for the A2A-Version header |
polling.enabled | boolean | true | Poll non-terminal tasks returned by message:send |
polling.intervalMs | number | 1000 | Delay between GET /tasks/{id} polls |
polling.timeoutMs | number | 300000 | Maximum time to wait for task completion |
message | object | - | Custom A2A message template |
configuration | object | - | A2A message configuration sent with each request |
transformResponse | string | Function | - | JavaScript transform for reshaping the final provider response |
timeoutMs | number | - | Per-request HTTP timeout. Defaults to promptfoo's provider request timeout. |
Authentication
Use auth for common authentication schemes. Promptfoo applies it to both Agent Card discovery
requests and A2A operation requests. Values support Nunjucks variables, so use the env global for
environment variables, such as {{ env.A2A_API_KEY }}.
Bearer Token
providers:
- id: a2a:https://agent.example.com/a2a/v1
config:
auth:
type: bearer
token: '{{ env.A2A_API_KEY }}'
Basic Auth
providers:
- id: a2a:https://agent.example.com/a2a/v1
config:
auth:
type: basic
username: '{{ env.A2A_USERNAME }}'
password: '{{ env.A2A_PASSWORD }}'
API Key
API keys default to the X-API-Key header. Set placement: query when the server expects a query
parameter instead.
providers:
- id: a2a:https://agent.example.com/a2a/v1
config:
auth:
type: api_key
keyName: X-API-Key
value: '{{ env.A2A_API_KEY }}'
providers:
- id: a2a:https://agent.example.com/a2a/v1
config:
auth:
type: api_key
placement: query
keyName: api_key
value: '{{ env.A2A_API_KEY }}'
OAuth 2.0
OAuth supports the client credentials and password grants. If tokenUrl is omitted, promptfoo tries
OAuth authorization-server metadata discovery from the A2A server URL.
providers:
- id: a2a:https://agent.example.com/a2a/v1
config:
auth:
type: oauth
grantType: client_credentials
tokenUrl: https://auth.example.com/oauth/token
clientId: '{{ env.A2A_CLIENT_ID }}'
clientSecret: '{{ env.A2A_CLIENT_SECRET }}'
scopes:
- a2a.send
You can still use headers for custom headers that are not authentication-specific. If both
headers.Authorization and auth produce an Authorization header, the auth header wins.
Request Modes
Auto Mode
mode: auto chooses streaming when the Agent Card advertises streaming support. Otherwise it uses
message:send.
providers:
- id: a2a
config:
agentCardUrl: https://agent.example.com/.well-known/agent-card.json
mode: auto
If you configure only url and no Agent Card, auto uses message:send because promptfoo has no
capability metadata to indicate that streaming is supported.
Send and Poll
Use mode: send to call POST /message:send. If the response returns a non-terminal task,
promptfoo polls GET /tasks/{id} until the task completes, fails, is canceled, is rejected, or
requires more input/authentication.
providers:
- id: a2a:https://agent.example.com/a2a/v1
config:
mode: send
polling:
enabled: true
intervalMs: 1000
timeoutMs: 300000
Streaming
Use mode: stream to call POST /message:stream and consume Server-Sent Events (SSE). Promptfoo
returns one final ProviderResponse when the stream closes or a terminal task state is reached.
providers:
- id: a2a:https://agent.example.com/a2a/v1
config:
mode: stream
The provider supports stream events containing message, task, statusUpdate, and
artifactUpdate payloads.
Custom Messages
By default, promptfoo sends a ROLE_USER message with a single text part containing {{prompt}}.
You can provide a custom message template:
providers:
- id: a2a:https://agent.example.com/a2a/v1
config:
message:
role: ROLE_USER
parts:
- text: '{{prompt}}'
configuration:
returnImmediately: false
Promptfoo renders Nunjucks variables in message, auth, headers, agentCardUrl, url, and
configuration. It also adds a stable messageId and uses sessionId as the A2A contextId when
available.
Response Transforms
Use transformResponse when the A2A response needs to be reshaped before promptfoo evaluates it.
This is useful when your agent returns multiple artifacts, structured data, or metadata that should
be promoted into the final provider response.
providers:
- id: a2a:https://agent.example.com/a2a/v1
config:
transformResponse: |
{
output: text,
metadata: { taskId: context.task?.id, mode: context.mode }
}
The transform receives three arguments, matching the HTTP provider convention:
json: The normalized A2A result{ message, task, events, raw }text: Promptfoo's default extracted outputcontext: A2A metadata{ message, task, events, raw, mode }
You can provide the transform as a JavaScript expression, a function, or a file reference:
transformResponse: 'file://path/to/parser.js'
module.exports = (json, text, context) => ({
output: json.task?.artifacts?.[0]?.parts?.[0]?.text ?? text,
metadata: { mode: context.mode },
});
Return a primitive value to set output, or return a full ProviderResponse object when you need
fields such as metadata, guardrails, or sessionId. Function and file-based transforms may be
async; promptfoo awaits them before evaluating the response.
For inline JavaScript expressions, result is also available as an alias for json.
Output Extraction
If you do not provide transformResponse, promptfoo extracts output in this order:
- Direct A2A
messagetext parts - Completed task artifact text parts
- Task status message text
- Task history text
- Raw JSON fallback
Text parts are joined with newlines. Structured data remains available through raw,
metadata.a2a, and transformResponse.
Direct message response
If the agent returns a direct message:
{
"message": {
"role": "ROLE_AGENT",
"parts": [{ "text": "I can help book that flight." }]
}
}
Promptfoo output is:
I can help book that flight.
Completed task artifact
If message:send returns a task and polling later returns a completed task with artifacts:
{
"id": "task-123",
"status": {
"state": "TASK_STATE_COMPLETED",
"message": {
"role": "ROLE_AGENT",
"parts": [{ "text": "Completed" }]
}
},
"artifacts": [
{
"artifactId": "final-answer",
"parts": [{ "text": "The best itinerary is SFO to JFK at 9:00 AM." }]
}
]
}
Promptfoo output is the artifact text, not the lifecycle status message:
The best itinerary is SFO to JFK at 9:00 AM.
Status message fallback
If there is no direct message and no artifact, promptfoo falls back to task status text:
{
"id": "task-123",
"status": {
"state": "TASK_STATE_COMPLETED",
"message": {
"role": "ROLE_AGENT",
"parts": [{ "text": "Completed with no artifact." }]
}
}
}
Promptfoo output is:
Completed with no artifact.
Structured output
For agents that return useful non-text fields, use transformResponse to shape the output:
providers:
- id: a2a:https://agent.example.com/a2a/v1
config:
transformResponse: |
{
output: text,
metadata: {
taskId: json.task?.id,
state: json.task?.status?.state,
eventCount: json.events?.length ?? 0
}
}
Red Team Testing with A2A
A2A targets work with normal promptfoo red team configuration. When agentCardUrl is configured,
the provider can add Agent Card skills and capabilities to the generated attack context, helping
promptfoo produce probes that are specific to the target agent.
description: A2A travel agent red team
providers:
- id: a2a
config:
agentCardUrl: https://travel-agent.example.com/.well-known/agent-card.json
auth:
type: bearer
token: '{{ env.A2A_API_KEY }}'
redteam:
purpose: |
The system is a travel booking agent that helps users search for flights,
compare itineraries, and manage reservations.
plugins:
- pii
- bola
- bfla
- excessive-agency
strategies:
- basic
Error Handling
The A2A provider returns provider errors for common failure cases:
- Agent Card or operation requests return non-2xx HTTP responses
- Responses are not valid JSON
- Streaming responses contain malformed SSE frames
- Tasks reach
TASK_STATE_FAILED,TASK_STATE_CANCELED, orTASK_STATE_REJECTED - Tasks require additional input or authentication
- Polling exceeds
polling.timeoutMs
Limitations
- Only the A2A HTTP+JSON REST binding is supported
- JSON-RPC and gRPC bindings are not supported yet
- Push-notification webhooks are not supported because promptfoo evals require a synchronous result
- Streaming is consumed into a single final
ProviderResponse; token-by-token UI streaming is not exposed - Agent Card discovery currently selects the first
HTTP+JSONsupported interface