# A2AClient Stateless HTTP client for the A2A (Agent-to-Agent) protocol. Discovers remote agents and sends/gets/cancels tasks via JSON-RPC 2.0 over HTTP. Uses `Req` for transport with TLS verification disabled (self-signed cert support). ## Protocol All task operations POST to `/a2a` on the target agent as JSON-RPC 2.0 requests. Discovery uses `GET /.well-known/agent.json`. ``` Client Remote Agent | | |-- GET /.well-known/agent.json ------>| discover/2 |<------------ agent card -------------| | | |-- POST /a2a tasks/send ------------>| send_task/3 |<------------ completed task ---------| | | |-- POST /a2a tasks/get ------------->| get_task/3 |<------------ task status ------------| | | |-- POST /a2a tasks/cancel ---------->| cancel_task/3 |<------------ canceled task ----------| ``` ## Authentication All task operations require a `:cookie` option containing the encrypted user cookie value. Discovery is public (no cookie required). The cookie is passed as `Cookie: user=` header. ## Functions ### `discover(base_url, opts \\ [])` Fetches the agent card from `GET /.well-known/agent.json`. Returns `{:ok, card_map}` or `{:error, reason}`. ```elixir {:ok, card} = A2AClient.discover("https://localhost") card["name"] #=> "elixir-mcp-agent" card["skills"] #=> [%{"id" => "add_numbers", ...}, ...] ``` ### `send_task(base_url, text, opts \\ [])` Sends a `tasks/send` JSON-RPC request. Builds a message with a text part and optionally a data part specifying a tool and arguments. **Options:** - `:cookie` — authentication cookie (required) - `:tool` — specific tool name to invoke (optional) - `:arguments` — map of tool arguments (optional, used with `:tool`) Returns `{:ok, task_map}` or `{:error, reason}`. ```elixir # Simple text message (server picks default tool) {:ok, task} = A2AClient.send_task("https://localhost", "Hello!", cookie: cookie) # Invoke a specific tool with arguments {:ok, task} = A2AClient.send_task("https://localhost", "Add these numbers", cookie: cookie, tool: "add_numbers", arguments: %{"a" => 5, "b" => 12} ) task["status"]["state"] #=> "completed" task["artifacts"] #=> [%{"name" => "add_numbers-result", "parts" => [...]}] ``` ### `get_task(base_url, task_id, opts \\ [])` Retrieves a task by ID via `tasks/get`. ```elixir {:ok, task} = A2AClient.get_task("https://localhost", task_id, cookie: cookie) task["status"]["state"] #=> "completed" | "working" | "failed" | "canceled" task["status"]["timestamp"] #=> "2026-03-02T10:00:00.000Z" ``` ### `cancel_task(base_url, task_id, opts \\ [])` Cancels a task by ID via `tasks/cancel`. ```elixir {:ok, task} = A2AClient.cancel_task("https://localhost", task_id, cookie: cookie) task["status"]["state"] #=> "canceled" ``` ## JSON-RPC Wire Format Every task operation sends a POST to `/a2a` with this structure: ```json { "jsonrpc": "2.0", "id": "", "method": "tasks/send", "params": { "message": { "role": "user", "parts": [ {"type": "text", "text": "Add these numbers"}, {"type": "data", "data": {"tool": "add_numbers", "arguments": {"a": 5, "b": 12}}} ] } } } ``` Successful responses return `{"result": }`. Errors return `{"error": {"code": ..., "message": ...}}`. ## Source `lib/a2a_client.ex`