UNPKG

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
"use strict"; 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, }, };