@clduab11/gemini-flow
Version:
Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.
849 lines (755 loc) • 25.7 kB
text/typescript
/**
* A2A MCP Bridge Tests
*
* Comprehensive test suite for A2AMCPBridge with MCP↔A2A translation
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import {
A2AMessage,
A2AResponse,
MCPToA2AMapping,
ParameterMapping,
ResponseMapping,
TransformFunction
} from '../../../src/types/a2a';
import {
MCPRequest,
MCPResponse,
MCPTool
} from '../../../src/types/mcp';
// Mock implementation will be created later
class A2AMCPBridge {
constructor() {}
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
async translateMCPToA2A(mcpRequest: MCPRequest): Promise<A2AMessage> { return {} as A2AMessage; }
async translateA2AToMCP(a2aMessage: A2AMessage): Promise<MCPRequest> { return {} as MCPRequest; }
async translateMCPResponseToA2A(mcpResponse: MCPResponse): Promise<A2AResponse> { return {} as A2AResponse; }
async translateA2AResponseToMCP(a2aResponse: A2AResponse): Promise<MCPResponse> { return {} as MCPResponse; }
registerMapping(mapping: MCPToA2AMapping): void {}
unregisterMapping(mcpMethod: string): void {}
getMappings(): Map<string, MCPToA2AMapping> { return new Map(); }
getBridgeMetrics(): any { return {}; }
}
describe('A2AMCPBridge', () => {
let mcpBridge: A2AMCPBridge;
let mockMCPRequest: MCPRequest;
let mockA2AMessage: A2AMessage;
let mockMappings: MCPToA2AMapping[];
beforeEach(async () => {
mcpBridge = new A2AMCPBridge();
// Create mock MCP request
mockMCPRequest = {
id: 'mcp-req-001',
prompt: 'Process this data using advanced analysis capabilities',
tools: [
{
name: 'mcp__claude-flow__neural_status',
description: 'Get neural network status',
parameters: {
type: 'object',
properties: {
modelId: { type: 'string' }
},
required: ['modelId']
}
},
{
name: 'mcp__claude-flow__task_orchestrate',
description: 'Orchestrate complex tasks',
parameters: {
type: 'object',
properties: {
task: { type: 'string' },
priority: { type: 'string' },
strategy: { type: 'string' }
},
required: ['task']
}
}
],
temperature: 0.7,
topP: 0.9,
topK: 40,
maxTokens: 4096,
history: [
{ role: 'user', content: 'Initialize the analysis system' },
{ role: 'assistant', content: 'System initialized successfully' }
],
cacheTTL: 3600
};
// Create mock A2A message
mockA2AMessage = {
jsonrpc: '2.0',
method: 'neural.status',
params: {
modelId: 'gemini-2.5-flash',
includeMetrics: true
},
id: 'a2a-001',
from: 'mcp-bridge-agent',
to: 'neural-agent-001',
timestamp: Date.now(),
messageType: 'request',
priority: 'normal'
};
// Create mock mappings
mockMappings = [
{
mcpMethod: 'mcp__claude-flow__neural_status',
a2aMethod: 'neural.status',
parameterMapping: [
{
mcpParam: 'modelId',
a2aParam: 'modelId'
}
],
responseMapping: [
{
mcpField: 'status',
a2aField: 'result.status'
},
{
mcpField: 'metrics',
a2aField: 'result.metrics'
}
]
},
{
mcpMethod: 'mcp__claude-flow__task_orchestrate',
a2aMethod: 'task.orchestrate',
parameterMapping: [
{
mcpParam: 'task',
a2aParam: 'taskDescription'
},
{
mcpParam: 'priority',
a2aParam: 'priority',
transform: (value: string) => value.toLowerCase()
},
{
mcpParam: 'strategy',
a2aParam: 'executionStrategy',
transform: (value: string) => {
const strategyMap: { [key: string]: string } = {
'parallel': 'concurrent',
'sequential': 'ordered',
'adaptive': 'dynamic'
};
return strategyMap[value] || value;
}
}
],
responseMapping: [
{
mcpField: 'taskId',
a2aField: 'result.taskId'
},
{
mcpField: 'status',
a2aField: 'result.status'
},
{
mcpField: 'agents',
a2aField: 'result.assignedAgents',
transform: (agents: any[]) => agents.map(a => a.id)
}
]
},
{
mcpMethod: 'mcp__ruv-swarm__swarm_init',
a2aMethod: 'swarm.initialize',
parameterMapping: [
{
mcpParam: 'topology',
a2aParam: 'networkTopology'
},
{
mcpParam: 'maxAgents',
a2aParam: 'maxAgentCount'
},
{
mcpParam: 'strategy',
a2aParam: 'distributionStrategy'
}
],
responseMapping: [
{
mcpField: 'swarmId',
a2aField: 'result.swarmId'
},
{
mcpField: 'agents',
a2aField: 'result.initialAgents'
}
]
}
];
await mcpBridge.initialize();
// Register all mock mappings
mockMappings.forEach(mapping => {
mcpBridge.registerMapping(mapping);
});
});
afterEach(async () => {
await mcpBridge.shutdown();
jest.clearAllMocks();
});
describe('Initialization and Configuration', () => {
it('should initialize bridge successfully', async () => {
await expect(mcpBridge.initialize()).resolves.not.toThrow();
});
it('should register method mappings', () => {
const testMapping: MCPToA2AMapping = {
mcpMethod: 'test.method',
a2aMethod: 'test.a2a.method',
parameterMapping: [],
responseMapping: []
};
expect(() => mcpBridge.registerMapping(testMapping)).not.toThrow();
const mappings = mcpBridge.getMappings();
expect(mappings.has('test.method')).toBe(true);
});
it('should unregister method mappings', () => {
mcpBridge.unregisterMapping('mcp__claude-flow__neural_status');
const mappings = mcpBridge.getMappings();
expect(mappings.has('mcp__claude-flow__neural_status')).toBe(false);
});
it('should handle duplicate mapping registration', () => {
const duplicateMapping = { ...mockMappings[0] };
expect(() => mcpBridge.registerMapping(duplicateMapping))
.toThrow('Mapping already exists for method: mcp__claude-flow__neural_status');
});
});
describe('MCP to A2A Translation', () => {
it('should translate simple MCP request to A2A message', async () => {
const simpleMcpRequest: MCPRequest = {
id: 'simple-001',
prompt: 'Get neural network status',
tools: [
{
name: 'mcp__claude-flow__neural_status',
description: 'Get neural status',
parameters: {
type: 'object',
properties: {
modelId: { type: 'string' }
}
}
}
]
};
const a2aMessage = await mcpBridge.translateMCPToA2A(simpleMcpRequest);
expect(a2aMessage.jsonrpc).toBe('2.0');
expect(a2aMessage.method).toBe('neural.status');
expect(a2aMessage.id).toBe('simple-001');
expect(a2aMessage.messageType).toBe('request');
});
it('should translate MCP request with parameter transformation', async () => {
const mcpWithTransform: MCPRequest = {
id: 'transform-001',
prompt: 'Orchestrate a task with custom strategy',
tools: [
{
name: 'mcp__claude-flow__task_orchestrate',
description: 'Orchestrate tasks',
parameters: {
type: 'object',
properties: {
task: { type: 'string' },
strategy: { type: 'string' }
}
}
}
]
};
// Mock tool parameters
(mcpWithTransform as any).toolParams = {
task: 'Process dataset',
strategy: 'parallel'
};
const a2aMessage = await mcpBridge.translateMCPToA2A(mcpWithTransform);
expect(a2aMessage.method).toBe('task.orchestrate');
expect(a2aMessage.params).toHaveProperty('taskDescription', 'Process dataset');
expect(a2aMessage.params).toHaveProperty('executionStrategy', 'concurrent'); // Transformed from 'parallel'
});
it('should handle MCP request with multiple tools', async () => {
const multiToolRequest = { ...mockMCPRequest };
const a2aMessage = await mcpBridge.translateMCPToA2A(multiToolRequest);
expect(a2aMessage.jsonrpc).toBe('2.0');
expect(a2aMessage.id).toBe('mcp-req-001');
// Should pick the first available mapped tool
expect(['neural.status', 'task.orchestrate']).toContain(a2aMessage.method);
});
it('should preserve MCP context in A2A message', async () => {
const contextMcpRequest: MCPRequest = {
...mockMCPRequest,
temperature: 0.8,
maxTokens: 2048,
cacheTTL: 1800
};
const a2aMessage = await mcpBridge.translateMCPToA2A(contextMcpRequest);
expect(a2aMessage.context).toBeDefined();
expect(a2aMessage.context?.timeout).toBeDefined();
// MCP cache TTL should be preserved in A2A context
});
it('should handle MCP request without mapped tools', async () => {
const unmappedRequest: MCPRequest = {
id: 'unmapped-001',
prompt: 'Use an unmapped tool',
tools: [
{
name: 'unknown__tool__method',
description: 'Unknown tool'
}
]
};
await expect(mcpBridge.translateMCPToA2A(unmappedRequest))
.rejects.toThrow('No A2A mapping found for MCP method: unknown__tool__method');
});
});
describe('A2A to MCP Translation', () => {
it('should translate A2A message to MCP request', async () => {
const a2aMessage: A2AMessage = {
jsonrpc: '2.0',
method: 'neural.status',
params: { modelId: 'test-model' },
id: 'a2a-to-mcp-001',
from: 'test-agent',
to: 'neural-agent',
timestamp: Date.now(),
messageType: 'request'
};
const mcpRequest = await mcpBridge.translateA2AToMCP(a2aMessage);
expect(mcpRequest.id).toBe('a2a-to-mcp-001');
expect(mcpRequest.tools).toBeDefined();
expect(mcpRequest.tools?.[0].name).toBe('mcp__claude-flow__neural_status');
});
it('should handle reverse parameter transformation', async () => {
const orchestrateMessage: A2AMessage = {
jsonrpc: '2.0',
method: 'task.orchestrate',
params: {
taskDescription: 'Analyze data',
executionStrategy: 'concurrent', // Should transform back to 'parallel'
priority: 'HIGH' // Should transform to lowercase
},
id: 'reverse-001',
from: 'coordinator',
to: 'task-agent',
timestamp: Date.now(),
messageType: 'request'
};
const mcpRequest = await mcpBridge.translateA2AToMCP(orchestrateMessage);
expect(mcpRequest.tools?.[0].name).toBe('mcp__claude-flow__task_orchestrate');
// Parameters should be reverse-transformed
const toolParams = (mcpRequest as any).toolParams;
expect(toolParams.task).toBe('Analyze data');
expect(toolParams.strategy).toBe('parallel'); // Reverse-transformed from 'concurrent'
expect(toolParams.priority).toBe('high'); // Lowercased
});
it('should handle A2A message without reverse mapping', async () => {
const unmappedA2AMessage: A2AMessage = {
jsonrpc: '2.0',
method: 'unknown.method',
params: {},
id: 'unmapped-a2a-001',
from: 'test-agent',
to: 'target-agent',
timestamp: Date.now(),
messageType: 'request'
};
await expect(mcpBridge.translateA2AToMCP(unmappedA2AMessage))
.rejects.toThrow('No MCP mapping found for A2A method: unknown.method');
});
});
describe('Response Translation', () => {
it('should translate MCP response to A2A response', async () => {
const mcpResponse: MCPResponse = {
id: 'resp-001',
model: 'gemini-2.5-flash',
content: 'Neural network status retrieved successfully',
functionCalls: [
{
name: 'mcp__claude-flow__neural_status',
arguments: {
status: 'active',
metrics: {
accuracy: 0.95,
latency: 45
}
}
}
],
usage: {
promptTokens: 150,
completionTokens: 75,
totalTokens: 225
},
metadata: {
finishReason: 'stop',
cached: false
}
};
const a2aResponse = await mcpBridge.translateMCPResponseToA2A(mcpResponse);
expect(a2aResponse.jsonrpc).toBe('2.0');
expect(a2aResponse.id).toBe('resp-001');
expect(a2aResponse.result).toBeDefined();
expect(a2aResponse.result.status).toBe('active');
expect(a2aResponse.result.metrics).toEqual({
accuracy: 0.95,
latency: 45
});
});
it('should translate A2A response to MCP response', async () => {
const a2aResponse: A2AResponse = {
jsonrpc: '2.0',
result: {
taskId: 'task-12345',
status: 'initiated',
assignedAgents: ['agent-001', 'agent-002']
},
id: 'a2a-resp-001',
from: 'task-coordinator',
to: 'requester-agent',
timestamp: Date.now(),
messageType: 'response'
};
const mcpResponse = await mcpBridge.translateA2AResponseToMCP(a2aResponse);
expect(mcpResponse.id).toBe('a2a-resp-001');
expect(mcpResponse.functionCalls).toBeDefined();
expect(mcpResponse.functionCalls?.[0].arguments.taskId).toBe('task-12345');
expect(mcpResponse.functionCalls?.[0].arguments.agents).toEqual([
{ id: 'agent-001' },
{ id: 'agent-002' }
]); // Reverse-transformed from agent IDs
});
it('should handle response transformation errors', async () => {
const invalidMcpResponse: MCPResponse = {
id: 'invalid-001',
model: 'test-model',
content: 'Error occurred',
functionCalls: [
{
name: 'unmapped__function',
arguments: {}
}
],
usage: {
promptTokens: 0,
completionTokens: 0,
totalTokens: 0
},
metadata: {
cached: false
}
};
await expect(mcpBridge.translateMCPResponseToA2A(invalidMcpResponse))
.rejects.toThrow('No mapping found for function: unmapped__function');
});
});
describe('Advanced Parameter Transformations', () => {
it('should handle complex nested parameter transformations', async () => {
const complexMapping: MCPToA2AMapping = {
mcpMethod: 'mcp__complex__transform',
a2aMethod: 'complex.transform',
parameterMapping: [
{
mcpParam: 'config.settings.advanced',
a2aParam: 'advancedConfig',
transform: (value: any) => {
return {
enabled: value.enable,
options: value.opts,
version: value.ver || '1.0.0'
};
}
}
],
responseMapping: []
};
mcpBridge.registerMapping(complexMapping);
const complexMcpRequest: MCPRequest = {
id: 'complex-001',
prompt: 'Complex transformation test',
tools: [
{
name: 'mcp__complex__transform',
description: 'Complex transform test'
}
]
};
// Mock complex parameters
(complexMcpRequest as any).toolParams = {
config: {
settings: {
advanced: {
enable: true,
opts: ['option1', 'option2'],
ver: '2.1.0'
}
}
}
};
const a2aMessage = await mcpBridge.translateMCPToA2A(complexMcpRequest);
expect(a2aMessage.params.advancedConfig).toEqual({
enabled: true,
options: ['option1', 'option2'],
version: '2.1.0'
});
});
it('should handle array parameter transformations', async () => {
const arrayMapping: MCPToA2AMapping = {
mcpMethod: 'mcp__array__transform',
a2aMethod: 'array.transform',
parameterMapping: [
{
mcpParam: 'items',
a2aParam: 'processedItems',
transform: (items: any[]) => {
return items.map(item => ({
id: item.identifier,
name: item.displayName,
active: item.status === 'enabled'
}));
}
}
],
responseMapping: []
};
mcpBridge.registerMapping(arrayMapping);
const arrayMcpRequest: MCPRequest = {
id: 'array-001',
prompt: 'Array transformation test',
tools: [
{
name: 'mcp__array__transform',
description: 'Array transform test'
}
]
};
// Mock array parameters
(arrayMcpRequest as any).toolParams = {
items: [
{ identifier: '001', displayName: 'Item One', status: 'enabled' },
{ identifier: '002', displayName: 'Item Two', status: 'disabled' }
]
};
const a2aMessage = await mcpBridge.translateMCPToA2A(arrayMcpRequest);
expect(a2aMessage.params.processedItems).toHaveLength(2);
expect(a2aMessage.params.processedItems[0]).toEqual({
id: '001',
name: 'Item One',
active: true
});
expect(a2aMessage.params.processedItems[1]).toEqual({
id: '002',
name: 'Item Two',
active: false
});
});
it('should handle conditional transformations', async () => {
const conditionalTransform: TransformFunction = (value: any, context?: any) => {
if (context?.type === 'priority') {
const priorityMap: { [key: string]: number } = {
'low': 1,
'normal': 2,
'high': 3,
'critical': 4
};
return priorityMap[value.toLowerCase()] || 2;
}
return value;
};
const conditionalMapping: MCPToA2AMapping = {
mcpMethod: 'mcp__conditional__transform',
a2aMethod: 'conditional.transform',
parameterMapping: [
{
mcpParam: 'priority',
a2aParam: 'priorityLevel',
transform: conditionalTransform
}
],
responseMapping: []
};
mcpBridge.registerMapping(conditionalMapping);
const conditionalRequest: MCPRequest = {
id: 'conditional-001',
prompt: 'Conditional transformation test',
tools: [
{
name: 'mcp__conditional__transform',
description: 'Conditional test'
}
]
};
// Mock conditional parameters
(conditionalRequest as any).toolParams = {
priority: 'HIGH'
};
(conditionalRequest as any).context = { type: 'priority' };
const a2aMessage = await mcpBridge.translateMCPToA2A(conditionalRequest);
expect(a2aMessage.params.priorityLevel).toBe(3); // 'HIGH' -> 3
});
});
describe('Error Handling and Validation', () => {
it('should validate parameter types during translation', async () => {
const invalidParams: MCPRequest = {
id: 'invalid-params-001',
prompt: 'Test with invalid parameters',
tools: [
{
name: 'mcp__claude-flow__neural_status',
description: 'Neural status',
parameters: {
type: 'object',
properties: {
modelId: { type: 'string' }
},
required: ['modelId']
}
}
]
};
// Mock invalid parameters (missing required modelId)
(invalidParams as any).toolParams = {
wrongParam: 'value'
};
await expect(mcpBridge.translateMCPToA2A(invalidParams))
.rejects.toThrow('Required parameter missing: modelId');
});
it('should handle transformation function errors', async () => {
const errorMapping: MCPToA2AMapping = {
mcpMethod: 'mcp__error__transform',
a2aMethod: 'error.transform',
parameterMapping: [
{
mcpParam: 'data',
a2aParam: 'processedData',
transform: (value: any) => {
throw new Error('Transformation failed');
}
}
],
responseMapping: []
};
mcpBridge.registerMapping(errorMapping);
const errorRequest: MCPRequest = {
id: 'error-001',
prompt: 'Error transformation test',
tools: [
{
name: 'mcp__error__transform',
description: 'Error test'
}
]
};
(errorRequest as any).toolParams = { data: 'test-data' };
await expect(mcpBridge.translateMCPToA2A(errorRequest))
.rejects.toThrow('Parameter transformation failed for data: Transformation failed');
});
it('should handle malformed JSON-RPC messages', async () => {
const malformedA2A = {
// Missing jsonrpc field
method: 'test.method',
id: 'malformed-001'
} as A2AMessage;
await expect(mcpBridge.translateA2AToMCP(malformedA2A))
.rejects.toThrow('Invalid JSON-RPC 2.0 message format');
});
});
describe('Performance and Metrics', () => {
it('should track bridge performance metrics', async () => {
// Perform several translations to generate metrics
for (let i = 0; i < 5; i++) {
const testRequest: MCPRequest = {
id: `perf-${i}`,
prompt: 'Performance test',
tools: [mockMCPRequest.tools![0]]
};
await mcpBridge.translateMCPToA2A(testRequest);
}
const metrics = mcpBridge.getBridgeMetrics();
expect(metrics.totalTranslations).toBeGreaterThanOrEqual(5);
expect(metrics.avgTranslationTime).toBeGreaterThan(0);
expect(metrics.successRate).toBeGreaterThan(0);
});
it('should track translation errors', async () => {
// Attempt translation that will fail
const failingRequest: MCPRequest = {
id: 'fail-001',
prompt: 'Failing test',
tools: [
{
name: 'unknown__method',
description: 'Unknown method'
}
]
};
try {
await mcpBridge.translateMCPToA2A(failingRequest);
} catch (error) {
// Expected to fail
}
const metrics = mcpBridge.getBridgeMetrics();
expect(metrics.errorRate).toBeGreaterThan(0);
expect(metrics.errorsByType).toHaveProperty('mapping_not_found');
});
it('should provide detailed bridge statistics', () => {
const metrics = mcpBridge.getBridgeMetrics();
expect(metrics).toHaveProperty('totalTranslations');
expect(metrics).toHaveProperty('mcpToA2ATranslations');
expect(metrics).toHaveProperty('a2aToMCPTranslations');
expect(metrics).toHaveProperty('avgTranslationTime');
expect(metrics).toHaveProperty('successRate');
expect(metrics).toHaveProperty('errorRate');
expect(metrics).toHaveProperty('mappingsCount');
expect(metrics).toHaveProperty('transformationCacheHits');
});
});
describe('Bidirectional Translation Consistency', () => {
it('should maintain consistency in bidirectional translation', async () => {
// Start with MCP request
const originalMcp: MCPRequest = {
id: 'bidirectional-001',
prompt: 'Bidirectional test',
tools: [
{
name: 'mcp__claude-flow__neural_status',
description: 'Neural status check'
}
]
};
(originalMcp as any).toolParams = { modelId: 'test-model-123' };
// MCP -> A2A -> MCP
const a2aMessage = await mcpBridge.translateMCPToA2A(originalMcp);
const backToMcp = await mcpBridge.translateA2AToMCP(a2aMessage);
expect(backToMcp.id).toBe(originalMcp.id);
expect(backToMcp.tools?.[0].name).toBe(originalMcp.tools![0].name);
expect((backToMcp as any).toolParams.modelId).toBe('test-model-123');
});
it('should handle response consistency', async () => {
// Start with A2A response
const originalA2A: A2AResponse = {
jsonrpc: '2.0',
result: {
status: 'active',
metrics: { accuracy: 0.98 }
},
id: 'resp-consistency-001',
from: 'neural-agent',
to: 'requester',
timestamp: Date.now(),
messageType: 'response'
};
// A2A -> MCP -> A2A
const mcpResponse = await mcpBridge.translateA2AResponseToMCP(originalA2A);
const backToA2A = await mcpBridge.translateMCPResponseToA2A(mcpResponse);
expect(backToA2A.id).toBe(originalA2A.id);
expect(backToA2A.result.status).toBe('active');
expect(backToA2A.result.metrics.accuracy).toBe(0.98);
});
});
});