This project contains a TypeScript client implementation for the Agent-to-Agent (A2A) communication protocol that is derived from Google's A2A sample code. This client is suitable for use in browsers or servers and is available as an easy-to-use NPM package.
For a demonstration of this library and examples of its implemention, please download the SDK
Additional support has been added for Agentic Profiles which scope agents by users and businesses, give those entities globally unique ids, and provide universal authentication. All these new capabilites are accomplished with standards (W3C DID documents, IETF JWK) and minimal glue code.
- Globally Unique Agent Ids: Scoped to users and businesses, to enable much easier discovery and powerful features like reputation.
- Universal Authentication: Leverages DID document authentication methods such as JSON Web Keys.
- JSON-RPC Communication: Handles sending requests and receiving responses (both standard and streaming via Server-Sent Events) according to the JSON-RPC 2.0 specification.
-
A2A Methods: Implements standard A2A methods like
sendTask
,sendTaskSubscribe
,getTask
,cancelTask
,setTaskPushNotification
,getTaskPushNotification
, andresubscribeTask
. - Error Handling: Provides basic error handling for network issues and JSON-RPC errors.
-
Streaming Support: Manages Server-Sent Events (SSE) for real-time task updates (
sendTaskSubscribe
,resubscribeTask
). -
Extensibility: Allows providing a custom
fetch
implementation for different environments (e.g., Node.js).
For a demonstration of this library and examples of its implemention, please download the SDK and follow the instructions there.
import {
A2AClient,
Task,
TaskQueryParams,
TaskSendParams
} from "@agentic-profile/a2a-client";
import { v4 as uuidv4 } from "uuid"; // Example for generating task IDs
const client = new A2AClient("http://localhost:41241"); // Replace with your server URL
async function run() {
try {
// Send a simple task (pass only params)
const taskId = uuidv4();
const sendParams: TaskSendParams = {
id: taskId,
message: { role: "user", parts: [{ text: "Hello, agent!", type: "text" }] },
};
// Method now returns Task | null directly
const taskResult: Task | null = await client.sendTask(sendParams);
console.log("Send Task Result:", taskResult);
// Get task status (pass only params)
const getParams: TaskQueryParams = { id: taskId };
// Method now returns Task | null directly
const getTaskResult: Task | null = await client.getTask(getParams);
console.log("Get Task Result:", getTaskResult);
} catch (error) {
console.error("A2A Client Error:", error);
}
}
run();
import {
A2AClient,
TaskStatusUpdateEvent,
TaskArtifactUpdateEvent,
TaskSendParams, // Use params type directly
} from "@agentic-profile/a2a-client";
import { v4 as uuidv4 } from "uuid";
const client = new A2AClient("http://localhost:41241");
async function streamTask() {
const streamingTaskId = uuidv4();
try {
console.log(`\n--- Starting streaming task ${streamingTaskId} ---`);
// Construct just the params
const streamParams: TaskSendParams = {
id: streamingTaskId,
message: { role: "user", parts: [{ text: "Stream me some updates!", type: "text" }] },
};
// Pass only params to the client method
const stream = client.sendTaskSubscribe(streamParams);
// Stream now yields the event payloads directly
for await (const event of stream) {
// Type guard to differentiate events based on structure
if ("status" in event) {
// It's a TaskStatusUpdateEvent
const statusEvent = event as TaskStatusUpdateEvent; // Cast for clarity
console.log(
`[${streamingTaskId}] Status Update: ${statusEvent.status.state} - ${
statusEvent.status.message?.parts[0]?.text ?? "No message"
}`
);
if (statusEvent.final) {
console.log(`[${streamingTaskId}] Stream marked as final.`);
break; // Exit loop when server signals completion
}
} else if ("artifact" in event) {
// It's a TaskArtifactUpdateEvent
const artifactEvent = event as TaskArtifactUpdateEvent; // Cast for clarity
console.log(
`[${streamingTaskId}] Artifact Update: ${
artifactEvent.artifact.name ??
`Index ${artifactEvent.artifact.index}`
} - Part Count: ${artifactEvent.artifact.parts.length}`
);
// Process artifact content (e.g., artifactEvent.artifact.parts[0].text)
} else {
console.warn("Received unknown event structure:", event);
}
}
console.log(`--- Streaming task ${streamingTaskId} finished ---`);
} catch (error) {
console.error(`Error during streaming task ${streamingTaskId}:`, error);
}
}
streamTask();
This client is designed to work with servers implementing the A2A protocol specification.
The Agentic Profile is a thin layer over A2A, MCP, and other HTTP protocols, and provides:
- Globally unique - user and business scoped - agent identity
- Universal authentication
The Agentic Profile is standards based:
- W3C DIDs and DID documents provide globally unique ids that are scoped to users and businesses
- IETF JSON Web Keys provide universal authentication
Identity is essential for digital communication between parties because it establishes trust, accountability, and context — without which meaningful, secure interaction is nearly impossible.
Current agent protocols focus on individual agent identity, which while accomplishing the communications goal, does not establish trust and accountability which derive from clear relationships with the people or business the agent represents.
For example, you trust an employee of a bank because they are in the bank building, behind the counter, and wearing a company nametag.
The Agentic Profile provides the digital equivalent of how we judge employees, by using a verifiable document provided by the person or business, and declaring all the agents that represent the person or business.
For example the business at the DNS domain matchwise.ai can have a "chat-agent", which combined becomes matchwise.ai#chat-agent. Concensys helped create the DID specification which has a URI format that results in did:web:matchwise.ai#chat-agent. DID documents (what you find using the did:web:matchwise.ai URI) provides a list of HTTP services, which are equivalent to agents. The Agentic Profile simply lists the agents in the DID document services.
With the Agentic Profile, the person or business is the first class citizen, and all the agents that represent them are clearly defined.
Very easily. For each DID document service/agent, we specify the "type" as "A2A" and use the serviceEndpoint to reference the agent.json file.
Most agent authentication is done using shared keys and HTTP Authorization headers. While this is easy to implement, it is very insecure.
Another popular option is OAuth, but that has another host of problems including dramatically increasing the attack surface and the challenges of making sure both agents agree on the same authentication service provider. OAuth is really good for human-to-service authentication, but not very good for dynamic environments where the agent might have to create a new account with a new authentication service or complete MFA.
Public key cryptography, which is used extensively for internet communication, is ideal for decentralized authentication. It is very easy to publish an agents public key via the Agentic Profile, and then the agent can use its secret key to authenticate. JSON Web Tokens + EdDSA are mature and widely used standards, and the ones Agentic Profile uses.
With great options like JWT+EdDSA, centralized authentication systems like OAuth are unecessary.