apcore-a2a is the A2A (Agent-to-Agent) protocol adapter for the apcore ecosystem.
It solves a common problem: you've built AI capabilities with apcore modules, but you need them to talk to other AI agents over a standard protocol. apcore-a2a bridges that gap — it reads your existing module metadata (schemas, descriptions, examples) and automatically exposes them as a standards-compliant A2A server. No hand-written Agent Cards, no JSON-RPC boilerplate, no manual task lifecycle management.
In short: apcore modules + apcore-a2a = a fully functional A2A agent, ready to be discovered and invoked by any A2A-compatible client.
Also available in: Python | TypeScript
- One-call server — launch a compliant A2A server with
serve(source, config) - Automatic Agent Card —
/.well-known/agent-card.json(A2A 1.0;/.well-known/agent.jsonkept as a 0.3 alias) generated from module metadata - Skill mapping — apcore modules become A2A Skills with names, descriptions, tags, and examples;
metadata.display.a2aoverrides surface-facing fields (§5.13) - Full task lifecycle — submitted, working, completed, failed, canceled, input-required
- JWT authentication — tokens bridged to apcore's Identity context
- Built-in client —
A2AClientfor calling remote A2A agents - SSE streaming —
message/streamserver-sent events;A2AClient::stream_messageon the client - Push notifications —
tasks/pushNotificationConfig/{set,get,delete}with webhook delivery - CLI support —
apcore-a2a --extensions-dir ./extensionsfor zero-code startup - Pluggable storage —
TaskStore/PushConfigStoretraits for custom backends, owner-scoped per call - Observability —
/healthendpoint - Config Bus — registers
apcore-a2anamespace withAPCORE_A2Aenv prefix (apcore 0.22) - Error Formatter Registry — registers A2A error formatter with apcore ecosystem (§8.8)
Note: Metrics (
/metrics) are Python/TypeScript-only — the Rust crate does not serve a/metricsendpoint. SSE streaming (message/stream) and push notifications (tasks/pushNotificationConfig/*) are fully supported.
- Rust edition 2021
apcore0.22apcore-toolkit0.8
[dependencies]
apcore-a2a = "0.4"use apcore_a2a::{APCoreA2A, APCoreA2AConfig, BackendSource};
use std::path::PathBuf;
#[tokio::main]
async fn main() {
let source = BackendSource::ExtensionsDir(PathBuf::from("./extensions"));
let config = APCoreA2AConfig::default();
apcore_a2a::serve(source, config).await.unwrap();
}use apcore_a2a::A2AClient;
use serde_json::json;
#[tokio::main]
async fn main() {
let client = A2AClient::new("http://remote-agent:8000");
// `message` is an A2A message object; `metadata.skillId` selects the module.
let task = client
.send_message(
json!({
"messageId": "m1",
"role": "ROLE_USER",
"parts": [{ "data": { "name": "Tercel" } }]
}),
Some(json!({ "skillId": "demo.greet" })),
None, // optional contextId
)
.await
.unwrap();
println!("Result: {}", task);
}use apcore_a2a::{JWTAuthenticator, ClaimMapping};
let auth = JWTAuthenticator::new("your-secret-key")
.with_claim_mapping(ClaimMapping {
id_claim: "sub".into(),
roles_claim: "roles".into(),
..Default::default()
});| A2A Concept | apcore Mapping |
|---|---|
| Agent Card | Derived from Registry configuration |
| Skill id | module_id |
| Skill name | metadata.display.a2a.alias or humanized module_id |
| Skill desc | metadata.display.a2a.description or module.description |
| Skill tags | metadata.display.tags or module.tags |
| Task | Managed execution via ApCoreAgentExecutor::call / stream_channel |
| Security | Bridged to apcore's Identity context |
src/
adapters/ AgentCardBuilder, SkillMapper, SchemaConverter, ErrorMapper, PartConverter
auth/ JWTAuthenticator, AuthMiddleware, Authenticator trait
server/ A2AServerFactory, ApCoreAgentExecutor
client/ A2AClient, AgentCardFetcher
storage/ TaskStore + PushConfigStore traits, in-memory implementations, CallContext
explorer/ Explorer UI HTML + card handler
apcore_a2a.rs APCoreA2A builder, serve(), async_serve()
cli.rs CLI entrypoint
A self-contained example lives in examples/run/main.rs. It
registers a tiny in-code demo.greet module and serves it as an A2A agent — no
extensions directory or deployed modules required:
# Serve the demo module on port 8000
cargo run --example run
# Bind to a different port if 8000 is taken (e.g. by a Docker container)
A2A_URL=http://localhost:8001 cargo run --example runPort 8000 already in use? If
curl http://localhost:8000/...returns a non-apcore response such as{"detail":"Not Found"}with aserver: uvicornheader, another process (often a Docker container) owns the port. Find it withlsof -nP -iTCP:8000 -sTCP:LISTEN, then either stop it or setA2A_URLto a free port as shown above.
Once it's running, probe the agent from another terminal:
curl http://localhost:8000/.well-known/agent-card.json # Agent Card (A2A 1.0; lists demo.greet)
# (0.3 alias) curl http://localhost:8000/.well-known/agent.json
curl http://localhost:8000/health # Health check
open http://localhost:8000/explorer # Explorer UI (browser)
# Invoke the skill — inputs go in a `data` part; metadata.skillId picks the module:
curl -X POST http://localhost:8000/ -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":"1","method":"message/send",
"params":{"message":{"messageId":"m1","role":"ROLE_USER",
"parts":[{"data":{"name":"Tercel"}}]},
"metadata":{"skillId":"demo.greet"}}}'
# => artifacts[0].parts[0].data == {"greeting":"Hello, Tercel!"}To serve your own modules instead, build an apcore::registry::Registry,
register your modules, and pass BackendSource::Registry(Arc::new(registry)) to
serve — or point a BackendSource::ExtensionsDir at a directory of deployed
modules. See the serve entry point for all backend sources.
To verify the example compiles as part of a check, build all examples:
cargo build --examplesgit clone https://github.com/aiperceivable/apcore-a2a-rust.git
cd apcore-a2a-rust
cargo test # run the test suite
cargo build --examples # ensure examples still compileApache 2.0 — see LICENSE.