Skip to content

Releases: EmilLindfors/a2a-rs

a2a-web-client v0.4.0

05 Jun 06:17
00444d9

Choose a tag to compare

Added

  • (a2a-agents) MCP server over Streamable HTTP transport

Documentation

  • Doc-comment audit, add ROADMAP, retire stale planning docs

Feat

  • (a2a-rs) Client Transport port + JSON-RPC 2.0 client + card negotiation

Refactor

  • (a2a-rs) Split streaming & push out of storage adapters (Phase 4 final)

a2a-rs v0.4.0

05 Jun 06:15
00444d9

Choose a tag to compare

Added

  • (a2a-rs) Add runnable jsonrpc_client example
  • (a2a-agents) MCP server over Streamable HTTP transport
  • (0.4) Typed error details, task versioning, call interceptors, streaming wiring + doc audit

Changed

  • (a2a-rs) Remove unused synchronous port traits

Documentation

  • Doc-comment audit, add ROADMAP, retire stale planning docs

Fixed

  • Fix json rpc

Feat

  • (a2a-rs) Client Transport port + JSON-RPC 2.0 client + card negotiation

Refactor

  • (a2a-rs) Split streaming & push out of storage adapters (Phase 4 final)

Added

  • impl AsyncStreamingHandler for Arc<dyn AsyncStreamingHandler> — a
    forwarding blanket impl so a type-erased, shared streaming backend can be
    passed wherever an impl AsyncStreamingHandler is expected (e.g.
    TaskService::with_streaming_handler). This lets one streaming instance be
    injected into both a message handler and a transport adapter without naming
    its concrete type, so handler broadcasts and SSE subscribers share a registry.

Breaking Changes — Port capability decomposition

The server-side AsyncTaskManager port trait carried 17 methods spanning four
distinct capabilities. It has been removed and split into focused capability
traits. All consumers are in-workspace; there is no deprecation shim.

Task ports

  • Removed AsyncTaskManager.
  • Added AsyncTaskLifecycle — per-task CRUD: create, get, update_status,
    cancel, exists.
  • Added AsyncTaskQuery — cross-task listing: list.
  • Added AsyncTaskLifecycleExt (blanket-implemented) — validation
    conveniences: get_validated, cancel_validated.
  • Express requirements at the use site (e.g. T: AsyncTaskLifecycle + AsyncTaskQuery);
    there is no umbrella trait.

Method renames (the noun-prefix is redundant once the trait carries it):

Old (AsyncTaskManager) New
create_task(id, ctx) AsyncTaskLifecycle::create
get_task(id, hist) AsyncTaskLifecycle::get
update_task_status(...) AsyncTaskLifecycle::update_status
cancel_task(id) AsyncTaskLifecycle::cancel
task_exists(id) AsyncTaskLifecycle::exists
list_tasks_v3(params) AsyncTaskQuery::list
  • Removed the dead get_task_metadata and legacy list_tasks(context, limit)
    methods (never called).

Push-notification ports

  • The four v1.0.0 push-config methods moved off AsyncTaskManager and were
    reconciled into AsyncNotificationManager, now expressed in terms of the
    richer multi-config model: set_config, get_config, list_configs,
    delete_config.
  • Added AsyncNotificationManagerExt (blanket-implemented): validate_config,
    set_validated.
  • Removed the drifting single-config methods (set_task_notification,
    get_task_notification, remove_task_notification, has_task_notification,
    send_test_notification) and the unused notify_task_status_update /
    notify_task_artifact_update stubs from the async trait. The synchronous
    NotificationManager trait is unchanged.

Strongly-typed identifiers

  • Added TaskId, ContextId, PushConfigId newtypes (domain::ids,
    re-exported from the crate root). Each validates non-emptiness on construction
    (FromStr/TryFrom), making argument-order mix-ups a compile error. They
    appear in the new port signatures; conversion from wire strings happens once at
    the RPC boundary. #[serde(transparent)] deserialization bypasses validation
    by design (validated at the boundary).

Dispatch — ports held as Arc<dyn …> at the composition edge

The composition-edge structs no longer carry viral generic parameters; they hold
their ports as Arc<dyn …> trait objects. Dispatch goes through the vtable —
one indirect call per RPC, negligible on the I/O-bound port boundary — and the
generic noise disappears from every type that holds a processor or handler.

  • DefaultRequestProcessor lost its five generic parameters
    (<M, T, N, A, S>). It is now a plain non-generic struct with
    Arc<dyn AsyncMessageHandler>, Arc<dyn AsyncTaskLifecycle>,
    Arc<dyn AsyncTaskQuery>, Arc<dyn AsyncNotificationManager>,
    Arc<dyn AgentInfoProvider>, and Arc<dyn AsyncStreamingHandler> fields.
    Constructors (new, with_handler, with_streaming_handler) now take
    impl Trait arguments, so call sites are unchanged.
  • DefaultMessageHandler lost its <T> parameter; it holds
    Arc<dyn AsyncTaskLifecycle> and its constructor takes
    impl AsyncTaskLifecycle + 'static.
  • ReimbursementHandler (in a2a-agents) lost its <T> parameter; it holds
    Arc<dyn AsyncTaskLifecycle> + Arc<dyn AsyncStreamingHandler>. The Clone
    bound it forced on storage is gone (cloning an Arc<dyn …> is a refcount bump).

Migration

  • Construction is source-compatible: the de-generic'd constructors accept the
    same arguments via impl Trait, so existing DefaultRequestProcessor::new(…)
    / ReimbursementHandler::new(…) call sites compile unchanged.
  • Code that named the processor's generic parameters
    (DefaultRequestProcessor<M, T, N, A, S>) must drop the type arguments — the
    type is now non-generic.
  • The HTTP client API (HttpClient::get_task, cancel_task, etc.) is
    unaffected — those names belong to the client surface, not the server port.

Added — cross-port TaskStatusBroadcast mixin

The capability-mixin pattern from .claude/rules/hexagonal_architecture.md §9,
applied at the port boundary (application::task_status_broadcast, behind the
server feature):

  • Added accessor ingredients HasTaskLifecycle and HasStreaming — each
    hands out a &dyn port, never a concrete adapter.
  • Added TaskStatusBroadcast, a blanket-implemented mixin giving any host
    that exposes both ingredients an update_and_broadcast ("commit the status
    through the lifecycle port, then announce it through the streaming port")
    method for free. A host exposing only one ingredient does not get the method —
    a compile_fail doc test pins that guarantee.
  • TaskService implements both accessors (see below), so it gains
    update_and_broadcast without coupling its lifecycle and streaming ports.

This is additive (no behavior change to existing call paths). Consuming it in
the request flow — and shedding the storage adapter's internal self-broadcast —
is deferred (REFACTORING_PLAN.md §4.0.2).

Added — application/transport split (REFACTORING_PLAN.md §4.2)

DefaultRequestProcessor previously did two jobs: orchestrating the ports and
serving as the ConnectRPC transport adapter. Those layers are now separated.

  • Added application::TaskService (behind the server feature) — the inner
    application service. It owns the six ports as Arc<dyn …> and holds all
    use-case orchestration (send_message, send_streaming_message, get,
    list, cancel, subscribe, push-config CRUD, extended_agent_card),
    speaking only domain types and A2AError. It hosts the HasTaskLifecycle /
    HasStreaming accessors, so it owns update_and_broadcast.
  • DefaultRequestProcessor is now a thin ConnectRPC transport adapter that
    decodes buffa wire views, delegates to a TaskService, and re-encodes the
    results. Its public constructors (new, with_handler,
    with_streaming_handler) are unchanged, so all call sites compile as before.
    map_* helpers and NoopStreamingHandler remain transport-side.

Changed — storage no longer self-broadcasts (REFACTORING_PLAN.md §4.0.2)

Persistence and streaming are now decoupled in the adapters; "commit then
announce" is owned by the orchestration layer via the TaskStatusBroadcast
mixin.

  • InMemoryTaskStorage / SqlxTaskStorage update_status and cancel are
    now persistence-only — they no longer call broadcast_status_update as a side
    effect. (Both structs still implement AsyncStreamingHandler; that is where
    streaming subscribers live. Shedding that role entirely is a later struct
    split, not done here.)
  • Added TaskStatusBroadcast::cancel_and_broadcast, the cancellation
    counterpart to update_and_broadcast. TaskService::cancel now routes through
    it, so cancellations still reach subscribers.
  • DefaultMessageHandler now hosts the broadcast mixin: it holds a streaming
    port in addition to the lifecycle port and routes every transition in
    process_message through update_and_broadcast. Breaking: its
    constructor takes a streaming port (and a responder — see below); use
    DefaultMessageHandler::echo(lifecycle, streaming) for the previous behavior.
  • ReimbursementHandler (in a2a-agents) implements HasTaskLifecycle /
    HasStreaming and broadcasts at all five transition sites, including the
    background AI worker — its updates and push notifications no longer depend on a
    storage side effect.
  • Behavioral note: an agent that drives update_status/cancel directly on
    storage no longer streams as a side effect. To announce transitions, host the
    TaskStatusBroadcast mixin (hold both ports) or use DefaultMessageHandler.

Breaking — storage/streaming/push struct-split (REFACTORING_PLAN.md §4.3, final)

The storage adapters shed their two non-persistence jobs. InMemoryTaskStorage
and SqlxTaskStorage previously implemented persistence and streaming
fan-out and fired push notifications inside their broadcast helpers. Each of
those is now its own adapter behind its own port, wired at the composition edge.

  • Removed the AsyncStreamingHandler impl (and the internal subscribers
    map) from InMemoryTaskStorage and SqlxTaskStorage. They now implement only
    AsyncTaskLifecycle + AsyncTaskQuery + AsyncNotificationManager
    (persistence and push-config CRUD).
  • Added `adapte...
Read more

a2a-mcp v0.4.0

05 Jun 06:19
00444d9

Choose a tag to compare

Added

  • (0.4) Finish mcp-client framework integration + a2a-mcp edition 2024
  • (a2a-agents) MCP server over Streamable HTTP transport

Documentation

  • Doc-comment audit, add ROADMAP, retire stale planning docs

Feat

  • (a2a-rs) Client Transport port + JSON-RPC 2.0 client + card negotiation

Refactor

  • (a2a-rs) Split streaming & push out of storage adapters (Phase 4 final)

a2a-ap2 v0.3.1

05 Jun 06:16
00444d9

Choose a tag to compare

Other

  • fmt,clippy
  • migrate to Connect-Rust, refactor project structure, update protobuf specs, and clean up temporary scripts
  • docs

a2a-agents v0.4.0

05 Jun 06:21
00444d9

Choose a tag to compare

Added

  • (0.4) Finish mcp-client framework integration + a2a-mcp edition 2024
  • (a2a-agents) MCP server over Streamable HTTP transport
  • (0.4) Typed error details, task versioning, call interceptors, streaming wiring + doc audit

Changed

  • (a2a-agents) Drop the stale ws_port config field

Documentation

  • Doc-comment audit, add ROADMAP, retire stale planning docs

Feat

  • (a2a-rs) Client Transport port + JSON-RPC 2.0 client + card negotiation

Refactor

  • (a2a-rs) Split streaming & push out of storage adapters (Phase 4 final)

Added

  • MCP server over Streamable HTTPrun_mcp_server can now serve a
    TOML-configured agent over MCP's Streamable HTTP transport (rmcp's
    StreamableHttpService on an axum router) in addition to stdio. Configure it
    via a new [features.mcp_server.http] section (McpHttpConfig: enabled,
    host, port, path); when http.enabled it takes precedence over stdio.
    DNS-rebinding protection defaults to loopback-only and is tunable via
    allowed_hosts / allowed_origins (empty allowed_hosts disables Host
    validation for proxy-fronted public binds). Enables the
    transport-streamable-http-server rmcp feature. New mcp_http_agent example
    (examples/mcp_http_agent.{rs,toml}) plus an end-to-end initialize-handshake
    and Host-allow-list integration test (tests/mcp_http_test.rs).
  • AgentBuilder::with_streaming / AgentRuntime::with_streaming — attach a
    shared streaming backend so tasks/subscribe SSE streams observe the
    broadcasts a handler emits (e.g. via the TaskStatusBroadcast mixin). Pass the
    same InMemoryStreamingHandler your handler broadcasts to (clones share
    their subscriber registry); the runtime injects it into the transport via
    ConnectRpcAdapter::with_streaming_handler and logs "📡 Streaming backend
    wired into transport" when active.
  • complex_agent example (examples/complex_agent.rs +
    examples/complex_agent.toml, behind --features mcp-server) — a kitchen-sink
    "Research Assistant" that wires declarative TOML config, optional LLM
    tool-calling (with a keyless, deterministic rule-based fallback), MCP tool
    consumption via McpToA2ABridge (against an in-process tool server over
    tokio::io::duplex), live SSE streaming of progress artifacts, and native A2A
    task lifecycle through the broadcast mixin.

Fixed

  • Streaming through the builder reached a no-op. AgentRuntime::start_http
    built its transport with ConnectRpcAdapter::new(...), which defaults to a
    NoopStreamingHandler — so broadcasts from a builder-constructed handler never
    reached tasks/subscribe SSE clients. They now do when the streaming backend
    is supplied via with_streaming (see Added).

a2a-agents-common v0.3.1

05 Jun 06:16
00444d9

Choose a tag to compare

Added

  • (a2a-agents) MCP server over Streamable HTTP transport

a2a-web-client-v0.3.0

27 May 11:59

Choose a tag to compare

Other

  • fmt,clippy
  • Fix clippy warnings and failing tests
  • migrate to Connect-Rust, refactor project structure, update protobuf specs, and clean up temporary scripts
  • docs

a2a-rs-v0.3.0

27 May 11:29

Choose a tag to compare

Fixed

  • allow clippy::result_large_err in request processor

Other

  • fmt,clippy
  • Fix clippy warnings and failing tests
  • migrate to Connect-Rust, refactor project structure, update protobuf specs, and clean up temporary scripts
  • docs

Changed

  • Demoted the ⚠️ No WebSocket subscribers found for task log in
    InMemoryTaskStorage::broadcast_status_update from WARN to DEBUG. The
    no-subscriber case is the steady state for message/send (non-streaming)
    flows and was previously flooding logs on every status broadcast.
    a2a-rs/src/adapter/storage/task_storage.rs:189.

Added - v1.0.0 Compliance

New API Methods

  • tasks/list - List tasks with comprehensive filtering and pagination
    • Filter by context_id, status, last_updated_after, and metadata
    • Offset-based pagination with page tokens
    • Configurable history length and artifact inclusion per request
    • Returns ListTasksResult with tasks, total_size, page_size, and next_page_token
  • tasks/pushNotificationConfig/list - List all push notification configs for a task
  • tasks/pushNotificationConfig/delete - Delete a specific push notification config
  • agent/getAuthenticatedExtendedCard - Get extended agent card for authenticated clients

Core Type Enhancements

  • Added TransportProtocol enum with JSONRPC, GRPC, and HTTP+JSON variants
  • Added AgentInterface type for additional transport protocol interfaces
  • Added AgentExtension type for protocol extension framework
    • URI-based extension identification
    • Optional description and required flags
    • Arbitrary parameters via JSON object
  • Added extensions field to Message and Artifact types
  • Added extensions field to AgentCapabilities

Agent Card Updates

  • Added protocol_version field (defaults to "0.3.0")
  • Added preferred_transport field (defaults to "JSONRPC")
  • Added additional_interfaces field for multi-transport support
  • Added icon_url field for agent branding
  • Changed signature to signatures (now supports multiple signatures)

Push Notification Enhancements

  • Added id field to PushNotificationConfig for unique identification
  • Added GetTaskPushNotificationConfigParams for retrieving specific configs
  • Added ListTaskPushNotificationConfigParams for listing all configs
  • Added DeleteTaskPushNotificationConfigParams for config deletion
  • Updated storage implementations with full CRUD operations for push notification configs

Task Management

  • Added ListTasksParams with comprehensive filtering options:
    • context_id: Filter by context
    • status: Filter by task state
    • page_size: Control pagination (1-100, default 50)
    • page_token: Offset-based pagination
    • history_length: Control message history depth
    • include_artifacts: Toggle artifact inclusion
    • last_updated_after: Filter by timestamp (ms since epoch)
    • metadata: Filter by metadata fields
  • Added ListTasksResult with pagination metadata
  • Added list_tasks_v3 to AsyncTaskManager trait
  • Added push notification config management methods to AsyncTaskManager:
    • get_push_notification_config
    • list_push_notification_configs
    • delete_push_notification_config

Protocol Migration & ConnectRPC

  • Completely migrated from legacy JSON-RPC and WebSocket transport to ConnectRPC (gRPC-compatible) over HTTP.
  • Replaced manual JSON-RPC routing with connectrpc-build and prost-generated models based on the official A2A v1.0.0 protocol buffers.
  • Deleted legacy WebSocket transport infrastructure across the workspace.
  • Updated a2a-client to utilize connectrpc stubs for client-server communication.
  • Built and verified a resilient in-process dispatch architecture for MCP standard I/O.

Storage Layer

  • Implemented all v1.0.0 methods in InMemoryTaskStorage:
    • Full filtering support for task listing
    • Timestamp-based filtering
    • Metadata filtering
    • Push notification config CRUD operations
  • All new trait methods have default implementations returning UnsupportedOperation error
  • Proper sorting of tasks by timestamp (most recent first)
  • Efficient pagination with configurable page sizes

Error Handling

  • Added AuthenticatedExtendedCardNotConfigured error variant
  • Moved DATABASE_ERROR code from -32007 to -32100 to avoid conflict with spec

Changed - Breaking Changes

Agent Card

  • BREAKING: signature field renamed to signatures and changed to Option<Vec<AgentCardSignature>>
    • Migration: Wrap single signature in a Vec: signature: Some(sig)signatures: Some(vec![sig])
  • BREAKING: Added required protocol_version field (has default: "0.3.0")
  • BREAKING: Added required preferred_transport field (has default: "JSONRPC")
  • New optional fields: additional_interfaces, icon_url

Message and Artifact

  • BREAKING: Added extensions field (optional, defaults to None)
    • Migration: Add extensions: None to all struct initializations

AgentCapabilities

  • BREAKING: Added extensions field (optional, defaults to None)
    • Migration: Add extensions: None to all struct initializations

PushNotificationConfig

  • BREAKING: Added id field (optional, used for multi-config support)
    • Migration: Add id: None to existing configs

MessageSendConfiguration

  • BREAKING: accepted_output_modes is now optional (was required)
    • Migration: Wrap existing values in Some(): vec![...]Some(vec![...])

Error Codes

  • BREAKING: DATABASE_ERROR moved from -32007 to -32100
  • New error code: -32007 now used for AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED

Migration Guide

Updating Agent Card Initializations

// Before (v0.2.x)
let card = AgentCard {
    signature: Some(my_signature),
    // ... other fields
};

// After (v1.0.0)
let card = AgentCard {
    protocol_version: "0.3.0".to_string(),  // NEW - required
    preferred_transport: "JSONRPC".to_string(),  // NEW - required
    additional_interfaces: None,  // NEW - optional
    icon_url: None,  // NEW - optional
    signatures: Some(vec![my_signature]),  // CHANGED - now plural, wrapped in Vec
    // ... other fields
};

Updating Message and Artifact Initializations

// Before (v0.2.x)
let message = Message {
    message_id: "msg-1".to_string(),
    // ... other fields
};

// After (v1.0.0)
let message = Message {
    message_id: "msg-1".to_string(),
    extensions: None,  // NEW - add this field
    // ... other fields
};

// Same for Artifact
let artifact = Artifact {
    artifact_id: "art-1".to_string(),
    extensions: None,  // NEW - add this field
    // ... other fields
};

Updating Capabilities

// Before (v0.2.x)
let caps = AgentCapabilities {
    streaming: true,
    push_notifications: false,
    state_transition_history: true,
};

// After (v1.0.0)
let caps = AgentCapabilities {
    streaming: true,
    push_notifications: false,
    state_transition_history: true,
    extensions: None,  // NEW - add this field
};

Updating Push Notification Configs

// Before (v0.2.x)
let config = PushNotificationConfig {
    url: "https://example.com/webhook".to_string(),
    token: Some("token".to_string()),
    authentication: None,
};

// After (v1.0.0)
let config = PushNotificationConfig {
    id: None,  // NEW - for multi-config support
    url: "https://example.com/webhook".to_string(),
    token: Some("token".to_string()),
    authentication: None,
};

Using New Task Listing API

use a2a_rs::domain::{ListTasksParams, TaskState};

// List tasks with filtering and pagination
let params = ListTasksParams {
    context_id: Some("ctx-123".to_string()),
    status: Some(TaskState::Working),
    page_size: Some(25),
    page_token: None,  // Start at beginning
    history_length: Some(10),
    include_artifacts: Some(true),
    last_updated_after: None,
    metadata: None,
};

let result = task_manager.list_tasks_v3(&params).await?;
println!("Found {} tasks, showing {}", result.total_size, result.tasks.len());

// Get next page if available
if !result.next_page_token.is_empty() {
    let next_params = ListTasksParams {
        page_token: Some(result.next_page_token),
        ..params
    };
    let next_result = task_manager.list_tasks_v3(&next_params).await?;
}

Managing Push Notification Configs

use a2a_rs::domain::{
    GetTaskPushNotificationConfigParams,
    ListTaskPushNotificationConfigParams,
    DeleteTaskPushNotificationConfigParams,
};

// List all configs for a task
let list_params = ListTaskPushNotificationConfigParams {
    id: "task-123".to_string(),
    metadata: None,
};
let configs = task_manager.list_push_notification_configs(&list_params).await?;

// Get a specific config
let get_params = GetTaskPushNotificationConfigParams {
    id: "task-123".to_string(),
    push_notification_config_id: Some("config-1".to_string()),
    metadata: None,
};
let config = task_manager.get_push_notification_config(&get_params).await?;

// Delete a config
let delete_params = DeleteTaskPushNotificationConfigParams {
    id: "task-123".to_string(),
    push_notification_config_id: "config-1".to_string(),
    metadata: None,
};
task_manager.delete_push_notification_config(&delete_params).await?;

Notes

  • All new trait methods have default implementations that return UnsupportedOperation error
  • Existing code will continue to work after adding required fields to struct initializations
  • The InMemoryTaskStorage implementation supports all new features
  • SQLx storage implementations need to be updated to support multi-config push notifications

a2a-mcp-v0.3.0

27 May 12:00

Choose a tag to compare

Changed - Breaking

  • McpToA2ABridge tool-call wire format replaced. The bridge no longer
    detects tool calls by scanning Message.parts for a Text part starting
    with TOOL_CALL: <name>. It now reads a typed [McpToolCall] envelope
    from Message.metadata under the key a2a_rs_tool_call (exported as
    MCP_TOOL_CALL_METADATA_KEY). The parts vector is no longer inspected
    for routing and is free for any display/logging payload.

    Migration: replace ad-hoc message construction with
    create_tool_call_message(name, args) or attach_tool_call(&mut msg, name, args).
    Messages built with the previous TOOL_CALL: text prefix are now treated as
    ordinary text and forwarded to the inner AsyncMessageHandler unchanged.

    // Before
    let msg = Message::builder()
        .role(Role::User)
        .parts(vec![
            Part::Text { text: "TOOL_CALL: add".into(), metadata: None },
            Part::Data { data: data_map, metadata: None },
        ])
        .message_id(id)
        .build();
    
    // After
    let msg = a2a_mcp::create_tool_call_message("add", json!({"a": 5, "b": 7}));
  • AgentToMcpBridge::new signature simplified. The redundant
    agent_url: String parameter has been removed; the bridge now derives its
    MCP tool-name namespace from agent_card.url. For the rare cases where the
    namespace should differ from the advertised URL (e.g. tunnels, reverse
    proxies), use the new AgentToMcpBridge::with_namespace(client, card, namespace)
    constructor.

    // Before
    let bridge = AgentToMcpBridge::new(client, card, "https://...".to_string());
    
    // After
    let bridge = AgentToMcpBridge::new(client, card);
    // Or, with explicit namespace:
    let bridge = AgentToMcpBridge::with_namespace(client, card, "internal-alias".into());

Added

  • MCP prompts ↔ A2A skills mapping for McpToA2ABridge:

    • Automatically query downstream MCP prompts via list_prompts and expose them as A2A skills.
    • Route prompt calls using a typed MCP_PROMPT_CALL_METADATA_KEY ("a2a_rs_prompt_call") envelope in Message.metadata to self.mcp_peer.get_prompt.
    • Convert PromptMessages back to A2A messages.
  • Dynamic Auth Bridging:

    • Parse self.agent_card.security_schemes in AgentToMcpBridge::get_info and advertise "io.modelcontextprotocol/oauth-client-credentials" extension capability if OAuth2 client credentials scheme is present.
  • Bidirectional Task Cancellation:

    • Added cancel_task to the BridgeBackend trait and implemented it for HTTP and WebSocket backends.
    • Implemented cancel_task on AgentToMcpBridge to support upstream cancellation.
    • Implemented RequestCancelGuard in McpToA2ABridge to cancel downstream MCP tool/prompt requests if the wrapping A2A task is canceled/dropped.
  • McpToA2ABridge Progress Streaming & Token Matching:

    • Dynamic matching of auto-generated client progress tokens returned by rmcp to correctly route downstream progress updates to A2A clients via streaming_handler.
  • Task streaming, progress reporting, and client sampling support for AgentToMcpBridge:

    • Support streaming task status, progress updates, and artifacts from the agent back to MCP clients.
    • Implement MCP progress notification support using progress_token (retrieved from RequestContext::meta).
    • Implement client-driven LLM sampling support: if the streaming task or polling loop transitions to InputRequired, the bridge suspends execution, converts the task history and current prompt into sampling messages, requests input from the client peer via peer.create_message, and resumes the task with the response.
    • Added polling fallback loop for backends that do not support status streams (e.g. HTTP backends).
  • Public BridgeBackend trait & WebSocket Backend:

    • Re-exported BridgeBackend as a public trait to allow custom backend implementations.
    • Implemented WebSocketBackend for communication with WebSocket-based agents (enabled via the "ws-client" feature).
    • Added AgentToMcpBridge::with_websocket and with_websocket_and_namespace constructors.
  • In-process backend for AgentToMcpBridge. Two new constructors —
    AgentToMcpBridge::with_handler(handler, card) and
    with_handler_and_namespace(handler, card, namespace) — call an
    in-process AsyncMessageHandler directly instead of dialing the agent
    over HTTP. When the bridge and the wrapped agent live in the same
    process, this skips the loopback HTTP server entirely.

    // HTTP-backed (unchanged) — for agents in another process/host:
    let bridge = AgentToMcpBridge::new(HttpClient::new(url), card);
    
    // In-process — for agents living in the same process:
    let bridge = AgentToMcpBridge::with_handler(my_handler, card);

    Covered by tests/agent_to_mcp_integration.rs::test_in_process_backend_dispatches_to_handler.

  • McpToolCall public struct ({ name, arguments }) representing the
    tool-call envelope carried in Message.metadata.

  • MCP_TOOL_CALL_METADATA_KEY constant ("a2a_rs_tool_call") — the metadata
    key the bridge looks at.

  • attach_tool_call(&mut Message, name, arguments) helper for adding a
    tool-call envelope to an existing message without losing its parts.

  • AgentToMcpBridge::with_namespace(...) constructor for explicit
    tool-name namespacing.

  • Examples: a2a_as_mcp_server.rs (A2A agent exposed as MCP tools),
    a2a_with_mcp_tools.rs (A2A handler augmented with MCP tools), and
    bidirectional_demo.rs (both bridges in one process: upstream MCP
    client → AgentToMcpBridge → A2A HTTP server → McpToA2ABridge
    downstream calculator MCP server).

Documentation

  • The metadata tool-call envelope is now documented in the crate-level
    rustdoc (lib.rs) and in McpToA2ABridge's struct docs, including a
    worked example of the on-wire JSON shape.
  • Crate-level rustdoc examples in lib.rs were promoted from
    rust,ignore to compile-checked no_run doctests, rewritten against
    the real public API (the previous snippets referenced a non-existent
    A2AClient). Doctests are now part of cargo test --doc -p a2a-mcp.

a2a-ap2-v0.3.0

27 May 11:58

Choose a tag to compare

Other

  • fmt,clippy
  • migrate to Connect-Rust, refactor project structure, update protobuf specs, and clean up temporary scripts
  • docs