Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e96b7e2
chore: snapshot in-flight 0.4 refactor work
EmilLindfors May 30, 2026
5bbf3f9
refactor(a2a-rs)!: remove unused synchronous port traits
EmilLindfors May 30, 2026
f592806
fix(a2a-rs): make port layer compile with no features
EmilLindfors May 30, 2026
11ce5c5
fix json rpc
EmilLindfors May 31, 2026
e5dbfef
feat(a2a-rs)!: client Transport port + JSON-RPC 2.0 client + card neg…
EmilLindfors May 31, 2026
6e72c78
refactor(a2a-rs)!: split streaming & push out of storage adapters (Ph…
EmilLindfors Jun 1, 2026
7b6589e
docs: retire REFACTORING_PLAN.md (complete) and refresh TODO.md
EmilLindfors Jun 1, 2026
32f69ff
feat(0.4): typed error details, task versioning, call interceptors, s…
EmilLindfors Jun 3, 2026
5162881
feat(a2a-agents): MCP server over Streamable HTTP transport
EmilLindfors Jun 4, 2026
ee500e0
feat(0.4): finish mcp-client framework integration + a2a-mcp edition …
EmilLindfors Jun 4, 2026
fab71f9
refactor(a2a-agents)!: drop the stale ws_port config field
EmilLindfors Jun 4, 2026
116458c
build: consolidate common deps into [workspace.dependencies]
EmilLindfors Jun 4, 2026
9e751c2
feat(a2a-rs): add runnable jsonrpc_client example
EmilLindfors Jun 4, 2026
e4b44f9
test: cover SSE tool-call metadata wiring and proto vendor sync
EmilLindfors Jun 4, 2026
1ff4e01
ci: add release-plz.toml (per-crate tags) and bump actions off Node 20
EmilLindfors Jun 4, 2026
2ef3515
docs: doc-comment audit, add ROADMAP, retire stale planning docs
EmilLindfors Jun 4, 2026
2c5161f
fmt
EmilLindfors Jun 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/release-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ on:
permissions:
contents: write

env:
# Force Node 20-pinned JavaScript actions onto Node 24 (silences the
# deprecation annotation until the actions update).
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
upload-assets:
name: Build and upload binaries
Expand All @@ -37,7 +42,7 @@ jobs:
use_cross: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
ref: ${{ inputs.tag || github.ref }}

Expand Down
49 changes: 42 additions & 7 deletions .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,66 @@ permissions:

on:
push:
tags:
- "v*"
branches:
- master
workflow_dispatch:

env:
# Force Node 20-pinned JavaScript actions onto Node 24 (silences the
# deprecation annotation until the actions update).
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

# Standard release-plz flow with per-crate tags (see release-plz.toml):
# - `release-plz-pr` keeps an open "release" PR with the pending version bumps
# and changelog. Merging it lands the new versions on master.
# - `release-plz-release` then publishes each crate whose version is ahead of
# crates.io and pushes its `{package}-v{version}` tag.
jobs:
release-plz:
name: Release-plz
release-plz-release:
name: Release-plz release
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
ref: master

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

- name: Run release-plz
- name: Run release-plz (release)
uses: release-plz/action@v0.5
with:
command: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

release-plz-pr:
name: Release-plz PR
runs-on: ubuntu-latest
concurrency:
group: release-plz-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

- name: Run release-plz (release-pr)
uses: release-plz/action@v0.5
with:
command: release-pr
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
217 changes: 110 additions & 107 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -1,107 +1,110 @@
name: Rust CI

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0

jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

- name: Build workspace
run: cargo build --workspace --verbose --locked

- name: Run tests
run: cargo test --verbose --workspace --locked --all-targets

clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

# Run clippy on a2a-rs with all features (the core crate supports --all-features)
- name: Run clippy (a2a-rs, all features)
run: cargo clippy -p a2a-rs --all-targets --all-features -- -D warnings

# Run clippy on other workspace crates that support --all-features
- name: Run clippy (a2a-ap2, a2a-web-client, a2a-agents-common)
run: cargo clippy -p a2a-ap2 -p a2a-web-client -p a2a-agents-common --all-targets --all-features -- -D warnings

# Run clippy on a2a-agents with specific features (mcp-server/mcp-client
# depend on a local path that is not available in CI)
- name: Run clippy (a2a-agents)
run: cargo clippy -p a2a-agents --all-targets --features "reimbursement-agent,sqlx,ap2,auth" -- -D warnings

rustfmt:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt

- name: Check formatting
run: cargo fmt --all -- --check

docs:
name: Doc Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

# Check docs per-crate to avoid mcp-related path dep issues
- name: Check documentation (a2a-rs)
run: cargo doc --no-deps --all-features -p a2a-rs

- name: Check documentation (other crates)
run: cargo doc --no-deps -p a2a-ap2 -p a2a-web-client -p a2a-agents-common

- name: Check documentation (a2a-agents)
run: cargo doc --no-deps -p a2a-agents --features "reimbursement-agent,sqlx,ap2,auth"

audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-audit
uses: taiki-e/install-action@cargo-audit
- name: Run cargo-audit
run: cargo audit --ignore RUSTSEC-2023-0071

name: Rust CI

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
# Run JavaScript actions still pinned to the deprecated Node 20 runtime on
# Node 24, silencing the per-run deprecation annotation until they update.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

- name: Build workspace
run: cargo build --workspace --verbose --locked

- name: Run tests
run: cargo test --verbose --workspace --locked --all-targets

clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

# Run clippy on a2a-rs with all features (the core crate supports --all-features)
- name: Run clippy (a2a-rs, all features)
run: cargo clippy -p a2a-rs --all-targets --all-features -- -D warnings

# Run clippy on other workspace crates that support --all-features
- name: Run clippy (a2a-ap2, a2a-web-client, a2a-agents-common)
run: cargo clippy -p a2a-ap2 -p a2a-web-client -p a2a-agents-common --all-targets --all-features -- -D warnings

# Run clippy on a2a-agents with specific features (mcp-server/mcp-client
# depend on a local path that is not available in CI)
- name: Run clippy (a2a-agents)
run: cargo clippy -p a2a-agents --all-targets --features "reimbursement-agent,sqlx,ap2,auth" -- -D warnings

rustfmt:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt

- name: Check formatting
run: cargo fmt --all -- --check

docs:
name: Doc Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

# Check docs per-crate to avoid mcp-related path dep issues
- name: Check documentation (a2a-rs)
run: cargo doc --no-deps --all-features -p a2a-rs

- name: Check documentation (other crates)
run: cargo doc --no-deps -p a2a-ap2 -p a2a-web-client -p a2a-agents-common

- name: Check documentation (a2a-agents)
run: cargo doc --no-deps -p a2a-agents --features "reimbursement-agent,sqlx,ap2,auth"

audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-audit
uses: taiki-e/install-action@cargo-audit
- name: Run cargo-audit
run: cargo audit --ignore RUSTSEC-2023-0071

29 changes: 16 additions & 13 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
/target
*/target
**/.claude/settings.local.json
CLAUDE.md
a2a-client/leptos/
# Environment files
.env
.env.*
*.env
# Database files
*.db
/target
*/target
**/.claude/settings.local.json
CLAUDE.md
a2a-client/leptos/

# Environment files
.env
.env.*
*.env

# Database files
*.db
/target
*/target
**/.claude/settings.local.json
Expand All @@ -30,6 +30,9 @@ a2a-client/leptos/
# Embedded git repos (a2a-mcp uses a local clone of the MCP Rust SDK)
a2a-mcp/rust-sdk/

# Local reference clone of the official upstream A2A Rust SDK
a2aproject/

.vscode/*
!.vscode/mcp.json
.claude/
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - 2026-05-24

### Added
- **Client-side `Transport` port + JSON-RPC 2.0 client + card-driven negotiation (`a2a-rs`)**: The client gained a hexagonal transport abstraction mirroring the server side, plus a wire-compatible JSON-RPC 2.0 client so it can talk to any standard A2A agent.
- `port::client::Transport` (re-exported as `a2a_rs::Transport`) is the outbound client port — the renamed, relocated `AsyncA2AClient` with an added `protocol()` discriminator. `HttpClient` (ConnectRPC) reports `"CONNECTRPC"`.
- `JsonRpcClient` (new `jsonrpc-client` feature) implements `Transport` over the spec JSON-RPC 2.0 wire format (single `POST`, SSE for streaming), reusing the generated ProtoJSON request/response types. Its method names, error codes, and envelopes come from a shared `adapter::transport::jsonrpc_wire` module extracted from the server adapter, so the two directions are byte-compatible (proven by `tests/jsonrpc_client_interop_test.rs`, an in-process client↔server round-trip over a real socket: send/get/list/cancel, push-config CRUD, SSE subscribe, typed error mapping).
- `TransportFactory` + `TransportNegotiator` + `connect(base_url, &negotiator)` select a transport from an agent card's `supported_interfaces`, ranked by client preference (factory registration order). `default_registry()` prefers CONNECTRPC then JSON-RPC. Unit tests in `tests/transport_negotiation_test.rs`.
- `a2a-web-client`'s `WebA2AClient` now holds a `Box<dyn Transport>` (field `transport`, was `http`); `auto_connect` performs real card-driven negotiation, falling back to a direct ConnectRPC client.
- **Wire-compatible JSON-RPC 2.0 + HTTP+JSON transport (`a2a-rs`)**: Added `JsonRpcAdapter`, a sibling of `ConnectRpcAdapter` that speaks the spec-mandated JSON-RPC 2.0 and HTTP+JSON (REST) bindings for interop with the canonical `a2aproject` SDK (and the Go/C#/Python SDKs). Behind the new `jsonrpc-server` feature.
- Wraps the same inner `TaskService`; mounted at the composition edge via the `jsonrpc_router` / `rest_router` free functions (see `examples/jsonrpc_server.rs`).
- JSON-RPC: single `POST /` with all 11 methods (`SendMessage`, `GetTask`, `ListTasks`, `CancelTask`, push-config CRUD, `GetExtendedAgentCard`), `A2AError` → spec error codes (`-32001`…, `-32700`/`-32601`/`-32602`), and SSE for the two streaming methods.
- REST: official-SDK paths (no `/v1` prefix) — `POST /message:send`, `GET /tasks/{id}`, `GET /extendedAgentCard`, push-config routes — with HTTP status mapped from `A2AError`. Task custom-verbs use slash-form aliases (`/tasks/{id}/cancel`) since axum's matchit router rejects a path-param + `:`-suffix in one segment.
- The wire body reuses the `buffa`-generated proto request/response types directly: verified ProtoJSON-clean (camelCase, SCREAMING_SNAKE enums, RFC3339 timestamps, base64 `bytes`, bare `Struct` metadata, tag-free field-presence unions), so no hand-written wire DTOs are needed. Golden + behavioral tests in `tests/jsonrpc_wire_test.rs` and `tests/jsonrpc_dispatch_test.rs`.
- End-to-end router tests in `tests/jsonrpc_router_test.rs` drive the real `jsonrpc_router`/`rest_router` via `tower::ServiceExt::oneshot`: REST round-trip, the `/tasks/{id}/cancel` slash alias, 404/error-status mapping, list-via-query, the JSON-RPC envelope + version rejection, and both SSE framings (JSON-RPC wraps each event in a response envelope; REST emits the bare ProtoJSON `StreamResponse`).
- **Agent-card transport negotiation (`a2a-rs`)**: `SimpleAgentInfo` gained `with_preferred_transport` and `add_interface` so a card can advertise multiple `supportedInterfaces` (e.g. `JSONRPC` + `HTTP+JSON`) — the metadata an off-the-shelf A2A client reads to negotiate a transport. `examples/jsonrpc_server.rs` advertises both bindings it mounts.
- **Native LLM Tool Calling**: Added `LlmProvider` primitives (`ToolDefinition`, `ToolCall`) to `a2a-agents-common` for standardizing function calling across models (OpenAI, Gemini).
- **LLM Streaming Support (SSE)**: Added `chat_completion_stream` to stream content and fully formed tool calls in real time.
- **AI/LLM Integration (Phase 3)**: Integrated `McpClientManager` into `AgentBuilder` via the `mcp-client` feature.
Expand All @@ -28,6 +40,12 @@ All notable changes to this project will be documented in this file.
- Documented the metadata tool-call envelope used by `McpToA2ABridge` in `lib.rs` crate-level rustdoc and the bridge's struct docs.
- Added an architecture diagram to the `a2a-mcp` README.
- Converted rustdoc examples in `a2a-mcp`'s `lib.rs` from `rust,ignore` to real compile-checked `no_run` doctests.
- **Kitchen-sink complex agent example (`a2a-agents`)**: `examples/complex_agent.rs` (+ `complex_agent.toml`, behind `--features mcp-server`) — a "Research Assistant" wiring every major building block in one binary: declarative TOML config, optional LLM tool-calling via `LlmProvider` (with a keyless rule-based fallback), MCP tool consumption through `McpToA2ABridge` against an in-process tool server, live SSE streaming of progress artifacts, and native A2A task lifecycle via the `TaskStatusBroadcast` mixin.
- **Builder-level streaming wiring (`a2a-agents` + `a2a-rs`)**: `AgentBuilder::with_streaming` / `AgentRuntime::with_streaming` attach a shared streaming backend that the runtime injects into the transport (`ConnectRpcAdapter::with_streaming_handler`), so `tasks/subscribe` SSE streams finally observe the broadcasts a handler emits. Backed by a new forwarding blanket `impl AsyncStreamingHandler for Arc<dyn AsyncStreamingHandler>` in `a2a-rs`. **Fixes** a gap where the builder path defaulted to a no-op streaming handler and silently dropped handler broadcasts before they reached SSE clients.

### Changed
- **`a2a-rs` transport**: Extracted the ConnectRPC adapter's request-decoding helpers (`decode_send_config`, `list_request_to_params`, `map_update_event`) to `pub(super)` so the new JSON-RPC adapter reuses them — both transports now share a single decode/encode path against the generated proto types.
- **BREAKING — client port renamed and relocated (`a2a-rs`)**: The client trait `services::client::AsyncA2AClient` is now `port::client::Transport` (re-exported as `a2a_rs::Transport`), with a new required `fn protocol(&self) -> &str` method. `StreamItem` moved alongside it (`a2a_rs::StreamItem`). The `services::client` module and the `services::{AsyncA2AClient, StreamItem}` re-exports are gone. Call sites import `a2a_rs::Transport` / `a2a_rs::StreamItem`; method names are unchanged. `a2a-web-client`'s `WebA2AClient.http: HttpClient` field became `transport: Box<dyn Transport>`.

### Removed
- Removed the printf-only `examples/minimal_example.rs` in `a2a-mcp`.
Loading