Skip to content

jamjet-labs/jamjet-a2a

Repository files navigation

jamjet-a2a

Crates.io docs.rs License

Standalone Rust SDK for the A2A (Agent-to-Agent) protocol v1.0 — client, server, coordinator, MCP bridge.

Feature Flags

Feature Default Description
client yes A2aClient with all v1.0 operations
server yes A2aServer, TaskHandler, TaskStore
federation no Bearer token auth, mTLS, FederationPolicy
coordinator no Multi-dimensional agent scoring and selection
mcp-bridge no Bidirectional A2A ↔ MCP tool mapping

Quick Start — Client

use jamjet_a2a::client::A2aClient;
use jamjet_a2a_types::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = A2aClient::new();

    // Discover an agent's card
    let card = client.discover("https://agent.example.com").await?;
    println!("Agent: {} v{}", card.name, card.version);

    // Send a message
    let resp = client
        .send_message(
            "https://agent.example.com",
            SendMessageRequest {
                tenant: None,
                message: Message {
                    message_id: "msg-1".into(),
                    role: Role::User,
                    parts: vec![Part {
                        content: PartContent::Text("Hello".into()),
                        metadata: None,
                        filename: None,
                        media_type: None,
                    }],
                    context_id: None,
                    task_id: None,
                    metadata: None,
                    extensions: vec![],
                    reference_task_ids: vec![],
                },
                configuration: None,
                metadata: None,
            },
        )
        .await?;

    println!("{resp:?}");
    Ok(())
}

Quick Start — Server

use jamjet_a2a::server::{A2aServer, TaskHandler};
use jamjet_a2a::store::TaskStore;
use jamjet_a2a_types::*;
use std::sync::Arc;

struct Echo;

#[async_trait::async_trait]
impl TaskHandler for Echo {
    async fn handle(
        &self,
        task_id: String,
        message: Message,
        store: Arc<dyn TaskStore>,
    ) -> Result<(), A2aError> {
        // Echo the first text part back as a completed status message
        let text = message.parts.iter().find_map(|p| match &p.content {
            PartContent::Text(t) => Some(t.clone()),
            _ => None,
        }).unwrap_or_default();

        store.update_status(&task_id, TaskStatus {
            state: TaskState::Completed,
            message: Some(Message {
                message_id: "reply-1".into(),
                role: Role::Agent,
                parts: vec![Part {
                    content: PartContent::Text(format!("Echo: {text}")),
                    metadata: None,
                    filename: None,
                    media_type: None,
                }],
                context_id: None,
                task_id: Some(task_id.clone()),
                metadata: None,
                extensions: vec![],
                reference_task_ids: vec![],
            }),
            timestamp: None,
        }).await?;

        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<(), A2aError> {
    let card = AgentCard {
        name: "echo".into(),
        description: "Echo agent".into(),
        version: "1.0".into(),
        supported_interfaces: vec![],
        capabilities: AgentCapabilities {
            streaming: Some(true),
            push_notifications: None,
            extensions: vec![],
            extended_agent_card: None,
        },
        default_input_modes: vec!["text/plain".into()],
        default_output_modes: vec!["text/plain".into()],
        skills: vec![AgentSkill {
            id: "echo".into(),
            name: "echo".into(),
            description: "Echoes input back".into(),
            ..Default::default()
        }],
        provider: None,
        security_schemes: Default::default(),
        security_requirements: vec![],
        signatures: vec![],
        icon_url: None,
    };

    A2aServer::new(card)
        .with_handler(Echo)
        .with_port(3000)
        .start()
        .await
}

A2A TCK Conformance

Tested against the official A2A Technology Compatibility Kit (mandatory category):

Category Passed Failed Skipped
Mandatory 75 1* 40

* The single failure is a bug in the TCK itself, not in this SDK.

The server accepts both v1.0 method names (SendMessage, GetTask) and v0.3 aliases (message/send, tasks/get) for backward compatibility.

Two Crates

This workspace ships two crates:

Crate Purpose
jamjet-a2a-types Pure A2A v1.0 types (Task, Message, Part, AgentCard, etc.). Zero I/O dependencies — use it when you only need the data model.
jamjet-a2a Full SDK — client, server, task store, federation auth, coordinator, and MCP bridge. Depends on jamjet-a2a-types.

Add only the types crate if you are building your own transport layer:

[dependencies]
jamjet-a2a-types = "0.1"

Or use the full SDK with the features you need:

[dependencies]
jamjet-a2a = { version = "0.1", features = ["client", "server", "coordinator"] }

License

Licensed under either of

at your option.

About

Standalone Rust SDK for the A2A (Agent-to-Agent) protocol v1.0 — client, server, coordinator, MCP bridge

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages