Use JACS with A2A
Use native JACS to publish signed Agent Cards and sign artifacts. Discovery export does not implement a messaging endpoint: your application must serve the skills and URL it advertises. The portable MCP server has no A2A transport.
Build this checkout
The binding build uses Python 3.11+, Rust 1.97 through rustup, a C compiler/linker, Make and Perl. On Linux, the default keychain build also needs pkg-config and D-Bus development headers (libdbus-1-dev on Debian/Ubuntu). The official JavaScript SDK smoke requires Node.js 24.7+ and npm.
These instructions use current source, including direct ML-DSA-87 card signing, which first shipped in the published 0.16.0 packages. From the repository root, with Rust 1.97+ and native build prerequisites:
cargo install --locked --path archive/native/jacs-cli
Provide the native key password through your host secret source or configured
keychain; keep it out of command arguments and committed files. Use a private
working directory for persistent keys and jacs.config.json.
Serve local discovery
The default direct-PQ walkthrough requires an ML-DSA-87 root. Loading an older Ed25519 identity does not migrate it; explicitly upgrade it before using this profile.
jacs-compat a2a quickstart --name example-agent --domain agent.example.com
This creates a persistent PQ identity, or loads the existing identity as-is and serves discovery on
http://127.0.0.1:8080. In another terminal:
curl --fail http://127.0.0.1:8080/.well-known/agent-card.json
curl --fail http://127.0.0.1:8080/.well-known/jwks.json
The CLI uses the legacy 0.4.0 card model with compact signatures[].jws.
Default source discovery publishes five documents and signs with the persisted
PQ root; its JWK has kty: "AKP" and alg: "ML-DSA-87". Fetching these documents
only checks that the server responds—it does not verify a trusted identity.
For an existing config, restart with jacs-compat a2a serve. Behind a TLS
reverse proxy, pass --origin https://agent.example.com; the signed public
origin must match the service clients use. A classical peer requires explicit
--a2a-algorithm ES256 and the scoped compatibility binding (a sixth document).
Selecting an Ed25519 root alone does not enable ES256 card signing.
Export an A2A 1.0 card
The public Node/Python APIs accept an explicit protocolVersion: "1.0", actual
messaging URL, nonempty skills and content modes. Omitting these options keeps
legacy discovery. Build the Node binding with make build-jacsnpm, then install
its source package in your consuming project:
npm install /absolute/path/to/JACS/archive/native/jacsnpm
Save this as discovery.cjs and run node discovery.cjs:
const { JacsClient } = require('@hai.ai/jacs/client');
const { buildWellKnownDocuments } = require('@hai.ai/jacs/a2a-server');
async function main() {
const client = await JacsClient.quickstart({
name: 'report-agent', domain: 'agent.example.com',
});
const documents = buildWellKnownDocuments(client, {
protocolVersion: '1.0',
messagingUrl: 'https://agent.example.com/a2a',
skills: [{ id: 'report', name: 'Report', description: 'Return a report', tags: ['report'] }],
defaultInputModes: ['text/plain'],
defaultOutputModes: ['application/json'],
});
console.log(JSON.stringify(documents['/.well-known/agent-card.json'], null, 2));
}
main().catch(error => { console.error(error.message); process.exitCode = 1; });
The resulting card uses supportedInterfaces with protocol version 1.0 and
detached signatures[].protected / signature fields. This example exports a
card; it does not start the advertised /a2a handler. Preserve the encrypted
identity and its host-managed password between runs.
Python exposes the same option names through
client.get_a2a().generate_well_known_documents(discovery_options=options);
see the binding examples.
Verify and exchange a message
Before choosing a transport, verify the card with an independently trusted key,
expected identity/version and algorithm. For direct PQ v1 cards, native Rust
provides jacs::a2a::v1::verify_root_card; official SDK verification also needs
trusted key selection. Legacy verified/strict policy helpers are not v1 parsers.
The official SDK examples start real SDK servers/clients, verify cards and exchange one signed report. For the JavaScript example, use Node 24.7+ with ML-DSA WebCrypto, then run from the repository root after building the binding:
npm ci --ignore-scripts --prefix archive/native/examples/a2a_v1_sdk
npm test --prefix archive/native/examples/a2a_v1_sdk
The pinned JavaScript SDK 1.3.0 supports direct ML-DSA-87 through jose 6.2.13.
The Python SDK 1.3.0 example uses explicit ES256 because its PyJWT backend lacks
ML-DSA verification; the native root remains PQ. The tests include modified
cards/signatures and untrusted-root rejection, not every A2A capability.
Next: signed artifacts, discovery trust policy and W3C DID export. Apply application authorization after verification; card discovery, cached freshness and signature validity do not grant it.