Skip to content
Back to skills

A2a Bridge

ASecurity

The A2A JSON-RPC bridge - `sdk/src/a2a.rs`. The adapter (submit / task / cancel / Agent Card, riding the typed AGDX verbs) is behind the `a2a-bridge` feature (serde only, no HTTP). The ready-made axum `router()` is behind the additive `a2a-http` feature. Use when changing the A2A task lifecycle (TaskState), the JSON-RPC envelope, the adapter, the router, or the mapping from A2A methods onto agent topics. Sibling: the MCP bridge `McpBridge` in `sdk/src/mcp.rs` (`mcp-bridge` adapter, `mcp-http`...

  • 11 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 7, 2026
toolstypescriptpythonrustgoshelltesting

Works with

  • terminal
  • cli
  • mcp

Security analysis

A100/100

Scanned October 7, 2026

npx -y skills add laserdata/laser-sdk --skill a2a-bridge --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of A2a Bridge?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for A2a Bridge
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/laserdata-a2a-bridge/badge)](https://www.skillsdirectory.com/skills/laserdata-a2a-bridge)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: a2a-bridge
description: The A2A JSON-RPC bridge - `sdk/src/a2a.rs`. The adapter (submit / task / cancel / Agent Card, riding the typed AGDX verbs) is behind the `a2a-bridge` feature (serde only, no HTTP). The ready-made axum `router()` is behind the additive `a2a-http` feature. Use when changing the A2A task lifecycle (TaskState), the JSON-RPC envelope, the adapter, the router, or the mapping from A2A methods onto agent topics. Sibling: the MCP bridge `McpBridge` in `sdk/src/mcp.rs` (`mcp-bridge` adapter, `mcp-http` router).
---

# A2A bridge

The TypeScript A2A, MCP, AG-UI, and hop-guard peers live under `foreign/typescript/src/bridges` and share the cross-language bridge scenarios. Python binds the same bridges as classes: `A2aBridge(laser, source, request_topic, reply_topic, capabilities=, signing_key=)` with `signed_card(key)`, and `McpBridge(laser, source, tool_topic, reply_topic, server_name, memory_tools=False, timeout_secs=None)`, matching Rust `with_capabilities`, `with_signing_key`, `signed_card`, `with_memory_tools`, and `with_timeout` (default 30 seconds).

`a2a.rs` maps A2A requests to durable AGDX records. `a2a-bridge` enables the transport-independent `submit`, `task`, `cancel`, and `card` adapter. `handle_rpc` dispatches JSON-RPC requests without HTTP. `a2a-http` adds the axum `router()`. Neither feature is enabled by default. Load [laser-sdk-overview](../laser-sdk-overview/SKILL.md) first and follow [AGENTS.md](../../../AGENTS.md).

## STOP and ask the user before

- Changing the task-state dictionary or its kebab-case names: `TaskState` is the laser-wire agent dictionary (`wire/src/agent.rs`, pinned u8 codes), only re-exported here, and external A2A clients match the names. The JSON boundary (`task_state_json` in `a2a.rs`) maps codes to names via the dictionary's `Display`/`FromStr`, and an unknown inbound name is a protocol error.
- Changing the topic mapping (`SendMessage` -> request topic keyed by a fresh task conversation. `GetTask` -> replay of the reply topic).

## Key symbols

- `TaskState` (re-exported from `laser_wire::agent`, 9 states: submitted, working, input-required, completed, canceled, failed, rejected, auth-required, unknown, plus unknown-code passthrough), `Task`, `TaskStatus`, `Artifact`.
- `A2aMethod` (`SendMessage`, `SendStreamingMessage`, `GetTask`, `CancelTask` - the v1.0 PascalCase spellings. V1.0's `ListTasks` is not served, the bridge is stateless over the log and an unknown method answers the bridge's application error code `-32000`, not JSON-RPC's `-32601`) - the served methods as an enum with `Display`/`FromStr` (strum). Dispatch parses `request.method` into it, never match on bare method-name string literals. `SendStreamingMessage` maps to the same publish as `SendMessage` (streaming is consumed log-natively via `Laser::reassemble_channel`, not re-emitted as SSE).
- `JsonRpcRequest` / `JsonRpcResponse` / `JsonRpcError` - the 2.0 envelope. `JSONRPC_VERSION` and `APP_ERROR_CODE` are named consts, not literals.
- `AgentCard` / `AgentCardCapabilities` - the bridge's discovery doc (name = `source`, version, methods, `streaming`).
- `A2aBridge::new(laser, source, request_topic, reply_topic)` - rides the typed AGDX verbs (`Laser::agdx`), not raw `send_agent`:
  - `submit(params_json) -> Task` (`SendMessage`): publish a typed AGDX `command` tunneling the whole params JSON byte-identical (`agdx.ct = json`) on a fresh task conversation. The task identity rides `correlation`, derived from the conversation via `correlation_of` so lookup stays stateless. Returns `Submitted`.
  - `task(id) -> Task` (`GetTask`): read the reply topic (envelope-aware `ContextAssembler`), map the answering `response`/`error` envelope with the matching `correlation` via `task_from_envelope`, else `Working`.
  - `cancel(id) -> Task` (`CancelTask`): publish an AGDX `error` terminal (`Cancelled`, `task_state = Canceled`), returns `Canceled`.
  - `card() -> AgentCard`: served at `GET /.well-known/agent-card.json`.
  - `router() -> axum::Router` (requires `a2a-http`): the JSON-RPC endpoint at `POST /` plus the card route. The adapter above is usable without it (serve it over any transport, or call `submit` / `task` / `cancel` directly).
  - A worker behind the bridge reads `message.envelope` (the decoded command) and answers via `ctx.laser().agdx(reply_topic, source, conversation).respond(correlation, body)`.

## Rules specific to this area

- The bridge owns no state: truth is the log. `submit`/`task` are pure functions over `Laser`, so the HTTP layer (`router`) is a thin shell and is testable by calling `submit`/`task` directly against Apache Iggy.
- A bridge uses the stream selected by its `Laser` handle. Use `laser.with_default_stream(name)` to share one connection across stream-scoped views. Use separate connections for separate credentials. Reply reads use `AgentReplyReader`, `Laser::await_agdx_reply`, or `find_agdx_reply`. Avoid repeated full scans for an active reply wait.
- `authorize_edge` in `sdk/src/edge_auth.rs` checks token audience and required scopes. `EdgeDenial::StepUp` reports additional scope through `WWW-Authenticate`. Do not forward external tokens to the log. `sign::verify_delegation` binds `on_behalf_of` to the signed envelope. `laser_wire::authz::delegated_allow` intersects agent and invoking-user grants.
- Keep model calls and business logic out of the bridge. It only translates the protocol to topic sends and log replays.

## Testing

- Pure types (the `TaskStatus` JSON boundary, JSON-RPC parsing) are unit-tested in `a2a.rs`, and the dictionary itself is tested and fixtured in laser-wire.
- The Iggy-backed flow lives in `tests/integration/a2a.rs` (gated on the feature). Run with `cargo test -p laser-sdk --features "integration a2a-bridge query"`.

## Review smells

- Business logic or model calls leaking into the bridge handlers.
- A `TaskState` or `A2aMethod` wire name drifting from the A2A spelling.
- A bare method-name string literal in the dispatch instead of `A2aMethod`.
- The router holding task state instead of replaying the log.

Both `A2aBridge` and `McpBridge` expose `handle_rpc(JsonValue)` on their adapter features. Python binds it as `handle_rpc(request)` and TypeScript as `handleRpc(input)`. Python `A2aBridge.submit(params_json)` and `McpBridge.call_tool(name, params_json)` take raw JSON, as do TypeScript `submit` and `callTool(name, bytes)`. HTTP routes delegate to this dispatch. Every bridge stamps its own ID in `bridge_hops`, and `with_bridge_hops` continues a previous path while rejecting loops.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…