n8n-nodes-a2a-protocol
Version:
Agent2Agent (A2A) Protocol nodes for n8n - Enable agent interoperability, communication, and MCP integration
450 lines (449 loc) • 20.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.toolDefinition = exports.toolDescription = exports.A2AClientTool = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const urlUtils_1 = require("../../utils/urlUtils");
class A2AClientTool {
constructor() {
this.description = {
displayName: 'A2A Client Tool',
name: 'a2aClientTool',
icon: 'file:a2a-client-tool.svg',
group: ['transform'],
version: 1,
description: 'Tool for sending data to A2A (Agent-to-Agent) servers for processing',
defaults: {
name: 'A2A Client Tool',
},
inputs: ["main" /* NodeConnectionType.Main */],
outputs: ["main" /* NodeConnectionType.Main */],
usableAsTool: true,
properties: [
{
displayName: 'A2A Registry URL',
name: 'registryUrl',
type: 'string',
default: (0, urlUtils_1.getDefaultRegistryUrl)(),
placeholder: (0, urlUtils_1.getRegistryPlaceholder)(),
description: 'URL of the A2A registry for agent discovery',
required: true,
},
{
displayName: 'A2A Server',
name: 'a2aServer',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getA2AServers',
},
default: '',
description: 'Select A2A server from registry or enter manually',
required: true,
},
{
displayName: 'Manual Server URL',
name: 'manualServerUrl',
type: 'string',
default: '',
placeholder: (0, urlUtils_1.getAgentPlaceholder)(),
description: 'Enter A2A server URL manually (when not using registry options)',
displayOptions: {
show: {
a2aServer: ['manual'],
},
},
},
{
displayName: 'Task Type',
name: 'taskType',
type: 'string',
default: 'data_processing',
placeholder: 'data_processing',
description: 'Type of task to execute on A2A agent',
required: true,
},
{
displayName: 'Task Description',
name: 'taskDescription',
type: 'string',
default: 'Process the provided data',
placeholder: 'Process the provided data',
description: 'Description of what the A2A agent should do with the data',
required: true,
},
{
displayName: 'Message',
name: 'message',
type: 'string',
default: 'Please process this data',
placeholder: 'Please process this data and return results',
description: 'Human-readable message/instruction to send to the A2A agent',
required: true,
typeOptions: {
rows: 3,
},
},
{
displayName: 'Processing Mode',
name: 'processingMode',
type: 'options',
options: [
{
name: 'Synchronous',
value: 'sync',
description: 'Wait for A2A task completion',
},
{
name: 'Asynchronous',
value: 'async',
description: 'Submit task and return immediately',
},
],
default: 'sync',
description: 'How to process the A2A task',
},
{
displayName: 'Timeout (seconds)',
name: 'timeout',
type: 'number',
default: 30,
description: 'Timeout for A2A requests in seconds',
},
],
};
this.methods = {
loadOptions: {
async getA2AServers() {
const registryUrl = this.getNodeParameter('registryUrl');
const options = [
{
name: '🔧 Manual Entry',
value: 'manual',
description: 'Manually enter A2A server URL',
},
];
if (!registryUrl) {
return options;
}
const cleanRegistryUrl = registryUrl.replace(/\/+$/, '');
const discoveryUrl = `${cleanRegistryUrl}/v1/agents/discover`;
try {
const response = await this.helpers.request({
method: 'GET',
url: discoveryUrl,
json: true,
timeout: 10000,
});
const agents = response.agents || [];
if (agents.length > 0) {
options.push({
name: '────────── Registry Servers ──────────',
value: 'separator',
description: 'Servers discovered from A2A registry',
});
}
const predefinedServers = urlUtils_1.PREDEFINED_AGENTS;
predefinedServers.forEach(server => {
const serverUrl = (0, urlUtils_1.getDefaultAgentUrl)(server.port);
options.push({
name: `🤖 ${server.name} (Port ${server.port})`,
value: serverUrl,
description: `${serverUrl} - ${server.id}`,
});
});
agents.forEach((agent) => {
var _a, _b;
const skillCount = ((_a = agent.skills) === null || _a === void 0 ? void 0 : _a.length) || 0;
const capabilityCount = agent.capabilities ? Object.keys(agent.capabilities).length : 0;
const statusIcon = agent.status === 'active' ? '🟢' : '🔴';
const skillsList = ((_b = agent.skills) === null || _b === void 0 ? void 0 : _b.map((skill) => skill.name || skill.id).join(', ')) || 'No skills';
options.push({
name: `${statusIcon} ${agent.name || agent.agent_id} (${skillCount} skills, ${capabilityCount} caps)`,
value: agent.endpoint,
description: `${agent.endpoint} - Skills: ${skillsList}`,
});
});
if (agents.length === 0) {
options.push({
name: '⚠️ No registry agents found',
value: 'none',
description: 'No active A2A agents found in registry',
});
}
return options;
}
catch (error) {
options.push({
name: '────────── Local Servers (Fallback) ──────────',
value: 'separator',
description: 'Predefined local A2A servers',
});
const fallbackServers = urlUtils_1.PREDEFINED_AGENTS;
fallbackServers.forEach(server => {
const serverUrl = (0, urlUtils_1.getDefaultAgentUrl)(server.port);
options.push({
name: `🤖 ${server.name} (Port ${server.port})`,
value: serverUrl,
description: `${serverUrl} - ${server.id}`,
});
});
options.push({
name: '❌ Registry Error',
value: 'registry_error',
description: `Cannot connect to registry: ${error.message}`,
});
return options;
}
},
},
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
for (let i = 0; i < items.length; i++) {
try {
const registryUrl = this.getNodeParameter('registryUrl', i);
const a2aServer = this.getNodeParameter('a2aServer', i);
const manualServerUrl = this.getNodeParameter('manualServerUrl', i, '');
const taskType = this.getNodeParameter('taskType', i);
const taskDescription = this.getNodeParameter('taskDescription', i);
const message = this.getNodeParameter('message', i);
const processingMode = this.getNodeParameter('processingMode', i);
const timeout = this.getNodeParameter('timeout', i) * 1000;
let serverUrl;
if (a2aServer === 'manual') {
if (!manualServerUrl) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Manual Server URL is required when using manual entry', { itemIndex: i });
}
serverUrl = manualServerUrl.replace(/\/+$/, '');
}
else if (a2aServer === 'none' || a2aServer === 'registry_error' || a2aServer === 'separator') {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Please select a valid A2A server or use manual entry', { itemIndex: i });
}
else {
serverUrl = a2aServer.replace(/\/+$/, '');
}
// Prepare the data payload - raw input data plus tool context
const inputData = items[i].json;
const toolContext = {
item_index: i,
total_items: items.length,
execution_time: new Date().toISOString(),
source: 'n8n_a2a_tool',
};
// Generate unique identifiers
const taskId = `a2a_tool_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
const sessionId = `a2a_session_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
const requestId = `a2a_req_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
// Enhanced JSON-RPC 2.0 payload with separate message and data fields
const a2aPayload = {
jsonrpc: "2.0",
method: "submitTask",
params: {
// Task metadata
type: taskType,
description: taskDescription,
// Human-readable instruction/message
message: message,
// Structured data payload
data: {
input: inputData,
message: message, // Include message value from textbox in data payload
tool_context: toolContext,
},
// A2A protocol context
context: {
task_id: taskId,
session_id: sessionId,
registry_url: registryUrl,
processing_mode: processingMode,
execution_time: new Date().toISOString(),
client_info: {
name: 'n8n-a2a-client-tool',
version: '1.0.0',
item_index: i,
total_items: items.length,
},
},
// Processing configuration
processing_mode: processingMode,
},
id: requestId,
};
const a2aUrl = `${serverUrl}/tasks`;
const response = await fetch(a2aUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-A2A-Task-ID': taskId,
'X-A2A-Session-ID': sessionId,
'X-A2A-Processing-Mode': processingMode,
'X-A2A-Registry-URL': registryUrl,
'User-Agent': 'n8n-a2a-tool/1.0',
},
body: JSON.stringify(a2aPayload),
signal: AbortSignal.timeout(timeout),
});
if (!response.ok) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `A2A server responded with status ${response.status}: ${response.statusText}`, { itemIndex: i });
}
const responseData = await response.json();
if (responseData.error) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `A2A server error: ${responseData.error.message}`, { itemIndex: i });
}
returnData.push({
json: Object.assign(Object.assign({}, items[i].json), {
// A2A-specific fields
a2a_result: responseData.result || responseData, a2a_task_id: taskId, a2a_server: serverUrl, a2a_registry: registryUrl, a2a_task_type: taskType, a2a_task_description: taskDescription, a2a_message: message, a2a_success: true, a2a_processed_at: new Date().toISOString(),
// Standard fields for compatibility
result: responseData.result || responseData, success: true, processed_at: new Date().toISOString() }),
});
}
catch (error) {
returnData.push({
json: Object.assign(Object.assign({}, items[i].json), { a2a_success: false, a2a_error: error.message, a2a_failed_at: new Date().toISOString(), success: false, error: error.message, failed_at: new Date().toISOString() }),
});
}
}
return this.prepareOutputData(returnData);
}
}
exports.A2AClientTool = A2AClientTool;
// ✅ Tool Description Export for MCP Discovery
exports.toolDescription = {
name: 'a2aClientTool',
icon: 'file:a2a-client-tool.svg',
displayName: 'A2A Client Tool',
group: ['transform'],
description: 'Tool for sending data to A2A (Agent-to-Agent) servers for processing',
properties: [
{
displayName: 'A2A Registry URL',
name: 'registryUrl',
type: 'string',
default: (0, urlUtils_1.getDefaultRegistryUrl)(),
description: 'URL of the A2A registry for agent discovery',
required: true,
},
{
displayName: 'A2A Server',
name: 'a2aServer',
type: 'string',
description: 'A2A server endpoint to send tasks to',
required: true,
},
{
displayName: 'Task Type',
name: 'taskType',
type: 'string',
default: 'data_processing',
description: 'Type of task to execute on A2A agent',
required: true,
},
{
displayName: 'Task Description',
name: 'taskDescription',
type: 'string',
default: 'Process the provided data',
description: 'Description of what the A2A agent should do with the data',
required: true,
},
{
displayName: 'Message',
name: 'message',
type: 'string',
default: 'Please process this data',
description: 'Human-readable message/instruction to send to the A2A agent',
required: true,
},
{
displayName: 'Processing Mode',
name: 'processingMode',
type: 'string',
default: 'sync',
description: 'How to process the A2A task (sync/async)',
},
],
};
// ✅ Tool Definition Export for MCP Agent Registry
exports.toolDefinition = {
name: 'a2aClientTool',
description: 'Executes an A2A task against a selected agent server with enhanced message integration',
icon: 'file:a2a-client-tool.svg',
useInAgent: true,
category: 'A2A Protocol',
actions: [
{
name: 'Execute A2A Task',
method: 'execute',
description: 'Send a task to an A2A agent server for processing',
parameters: {
registryUrl: {
type: 'string',
description: 'A2A registry URL for agent discovery',
default: (0, urlUtils_1.getDefaultRegistryUrl)(),
},
a2aServer: {
type: 'string',
description: 'A2A server endpoint URL',
required: true,
},
taskType: {
type: 'string',
description: 'Type of task to execute',
default: 'data_processing',
},
taskDescription: {
type: 'string',
description: 'What the A2A agent should do',
default: 'Process the provided data',
},
message: {
type: 'string',
description: 'Human-readable instruction for the A2A agent',
default: 'Please process this data',
},
processingMode: {
type: 'string',
description: 'Processing mode (sync or async)',
default: 'sync',
enum: ['sync', 'async'],
},
},
returns: {
type: 'object',
description: 'A2A task execution result with processed data',
properties: {
a2a_result: {
type: 'object',
description: 'Result from A2A agent processing',
},
a2a_task_id: {
type: 'string',
description: 'Unique task identifier',
},
a2a_server: {
type: 'string',
description: 'A2A server that processed the task',
},
a2a_message: {
type: 'string',
description: 'Original message sent to A2A agent',
},
success: {
type: 'boolean',
description: 'Whether the task completed successfully',
},
},
},
},
],
capabilities: {
agent_discovery: true,
registry_integration: true,
json_rpc_protocol: true,
message_integration: true,
dual_message_placement: true,
sync_async_processing: true,
},
};