DEV Community

vishalmysore
vishalmysore

Posted on

Building AI Agents with A2A and MCP Protocol: A Hands-on Implementation Guide

This article will focus on building AI agents hands-on with A2A and MCP protocol in 4 different languages, deploying them, setting them up with A2A and MCP clients as well as Java-based clients. This will focus more on coding and hands-on implementation rather than theoretical knowledge as it's already available abundantly.

Understanding A2A and MCP

A2A is primarily for agent-to-agent communication and coordination, while MCP is focused on agent-to-tool integration. In other words, "A2A enables agents to talk to each other; MCP lets agents trigger tools." For a detailed comparison including schema differences, read more here: MCP vs A2A: Similarities and Differences

Chapter 1: Server Implementation in Multiple Languages

We will build servers that cater to both A2A and MCP protocols. This chapter demonstrates how to implement the same functionality across different JVM languages, showcasing the flexibility and interoperability of the A2A/MCP approach.

Java + Spring Boot Implementation

This section demonstrates how to rapidly build a Spring Boot application, expose its services using both A2A and MCP protocols, and connect it to various clients. We’ll follow familiar Spring conventions to ensure a seamless developer experience and quick adoption.

Quick Links

Create a Spring Boot application with the EnableAgent annotation:
This annotation transforms your standard Spring application into an agentic application, enabling it to participate in A2A and MCP-based workflows without changing your existing service logic.

package io.github.vishalmysore;

import io.github.vishalmysore.tools4ai.EnableAgent;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableAgent
public class Application {
}
Enter fullscreen mode Exit fullscreen mode

Create a service as a normal Spring bean:


import com.t4a.annotations.Action;
import com.t4a.annotations.Agent;

import com.t4a.api.JavaMethodAction;

import com.t4a.detect.ActionCallback;
import com.t4a.processor.AIProcessor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Agent(groupName ="food preferences", groupDescription = "Provide persons name and then find out what does that person like")
@Slf4j
public class SimpleService {

    /**
     * Each action has access to AIProcessor and ActionCallback which are autowired by tools4ai
     */
    private ActionCallback callback;

    /**
     * Each action has access to AIProcessor and ActionCallback which are autowired by tools4ai
     */
    private AIProcessor processor;
    public SimpleService(){
      log.info(" Created Simple Service");
    }

    @Action(description = "Get the favourite food of a person")
    public String whatThisPersonFavFood(String name) {
        if("vishal".equalsIgnoreCase(name))
        return "Paneer Butter Masala";
        else if ("vinod".equalsIgnoreCase(name)) {
            return "aloo kofta";
        }else
            return "something yummy";
    }

}

Enter fullscreen mode Exit fullscreen mode

✅ Note: Any method annotated with @Action becomes automatically exposed as an A2A or MCP tool.
🧠 Tip: Group related actions across classes using the same @agent group name to keep your logic modular and cohesive.

When you start the server, it exposes the method whatThisPersonFavFood in both A2A and MCP protocols.
View the agent card at: https://localhost:7860/.well-known/agent.json

Detailed instructions: A2A MCP Spring Guide

Implementations in Other JVM Languages

Each implementation showcases how the A2A and MCP protocols can be leveraged in different JVM languages, each bringing its own strengths to the table. These implementations demonstrate the protocol's language-agnostic nature and the ability to create a polyglot agent ecosystem.

Scala Implementation

The Scala implementation demonstrates how to leverage Scala's powerful type system and functional programming features while maintaining protocol compatibility:

@Agent(groupName = "IceCream", groupDescription = "An agent that provides ice cream related information")
class IceCreamAgent {

  @Action(description = "get template before ordering icecream")
  def getTemperature(city: String): String = {
    s"The current temperature in $city is 25°C "
  }

}
Enter fullscreen mode Exit fullscreen mode

View Scala Implementation

Kotlin

@Agent(
    groupName = "GeoSpatial Route Planner Agent",
    groupDescription = "Provides advanced route planning, traffic analysis, and custom map generation services"
)
class RoutePlannerAgent {
    @Action(description = "Generates custom map with points of interest")
    fun generateCustomMap(points: List<String>, radius: Double): String {
        return "Custom map generated with ${points.size} points of interest within $radius mile radius"
    }

    @Action(description = "Shows nearby locations of specified type")
    fun findNearbyPlaces(location: String, placeType: String, radius: Double): String {
        return "Found nearby $placeType locations around $location within $radius miles - MTR Coffee, NR Colony Coffee Shop, Cafe Coffee House"
    }
}
Enter fullscreen mode Exit fullscreen mode

View Kotlin Implementation

Groovy

@Agent(groupName = "Property Maintenance", groupDescription = "Provides lawn mowing and snow cleaning services")
class MaintenanceAgent {
    @Action(description = "Schedule a lawn mowing service")
    String scheduleLawnMowing(String address, String preferredDate) {
        return "Lawn mowing service scheduled for ${address} on ${preferredDate}"
    }

    @Action(description = "Get lawn mowing service price estimation")
    String getLawnMowingPrice(String lawnSize) {
        return "Estimated price for ${lawnSize} lawn is \$50"
    }

    @Action(description = "Schedule snow removal service")
    String scheduleSnowRemoval(String address, String urgency) {
        return "Snow removal service scheduled for ${address} with ${urgency} priority"
    }

    @Action(description = "Get snow removal price estimation")
    String getSnowRemovalPrice(String drivewaySize, String snowDepth) {
        return "Estimated price for ${drivewaySize} driveway with ${snowDepth} snow is \$75"
    }
}
Enter fullscreen mode Exit fullscreen mode

View Groovy Implementation

Language Selection Guide

Complex data processing: Use Scala with Spark – ideal for building large-scale distributed data pipelines, e.g., analyzing IoT sensor data from smart homes for anomaly detection.

🧠 Agentic Action Example: @Action("Detect abnormal temperature patterns") — processes time-series data to flag overheating rooms.

Clean async operations: Use Kotlin's coroutines – great for writing non-blocking REST or gRPC clients, e.g., combining multiple weather APIs for a unified forecast.

🧠 Agentic Action Example: @Action("Get consolidated weather report") — asynchronously queries different sources and merges the results.

Enterprise integrations: Use Java's ecosystem – best suited for backend systems requiring integration with legacy software, e.g., syncing employee records between HR and payroll systems.

🧠 Agentic Action Example: @Action("Sync HR data with payroll") — reads HR updates and pushes them to an external payroll API.

Rapid prototyping: Use Groovy – perfect for mocking agents and tools quickly, and integrating with tools like SoapUI, e.g., simulating an AI assistant for medical form validation.

🧠 Agentic Action Example: @Action("Validate patient intake form") — checks for required fields and alerts if any are missing

Chapter 2: Adding Security

This chapter demonstrates how to add Spring-based security to agents. Code available at A2A MCP Security Demo.

@Log
@Service
@Agent(groupName = "car booking", groupDescription = "actions related to car booking")
public class CarBookingAgent {
    @PreAuthorize("hasRole('USER')")
    @Action(description = "Book a car for the given details")
    public String bookCar(String carType, String pickupLocation, String dropLocation) {
        return "Car of type " + carType + " has been booked from " + pickupLocation + " to " + dropLocation;
    }

    @PreAuthorize("hasRole('ADMIN')")
    @Action(description = "Cancel a car booking")
    public String cancelCarBooking(String bookingId) {
        return "Car booking with ID " + bookingId + " has been cancelled";
    }

    @Action(description = "Get booking status of a car")
    public String getBookingStatus(String bookingId) {
        return "The status of booking ID " + bookingId + " is confirmed";
    }

    // Additional methods...
}
Enter fullscreen mode Exit fullscreen mode

Detailed security configuration guide: RBAC Security Integration
Live demo: A2A MCP Security Demo

This class elegantly combines agentic design with Spring Security to create a secure and modular AI-triggerable service. By annotating the class with @agent and individual methods with @Action, it exposes business logic (like booking or canceling a car) as structured agent tools—ready for invocation via A2A or MCP protocols. The use of @PreAuthorize from Spring Security adds robust role-based access control, ensuring that only authorized users can trigger sensitive actions (e.g., cancellations restricted to admins). This fusion provides a massively advantageous pattern for building scalable, secure, and AI-integrated APIs—where LLMs or agents can safely execute backend logic while respecting enterprise-grade authorization.
To secure your CarBookingAgent with JWT and OAuth2, you can integrate Spring Security by adding the spring-boot-starter-oauth2-resource-server and spring-boot-starter-security dependencies. Configure your application.yml with the issuer-uri from your OAuth provider (e.g., Auth0, Keycloak, Azure AD), enabling JWT validation. Then, create a SecurityFilterChain bean to set up HTTP security and enable method-level security with @EnableMethodSecurity, allowing you to use @PreAuthorize annotations for role-based access. This ensures that agentic actions like bookCar, cancelCarBooking, and getBookingStatus are protected based on user roles present in the JWT token

Chapter 3: Building Integrated Servers and Clients

In this chapter, we'll build a complete server and client implementation in Java that works with both A2A and MCP protocols. This demonstrates how a single codebase can serve both protocols, maximizing interoperability and reducing development overhead. The complete source code is available at: MCP Server Client Repository

Server Implementation

@Service
@Agent(groupName = "libraryOperations", groupDescription = "Manage library operations like holding books and getting books")
@Slf4j
public class LibraryService {
    private ActionCallback callback;
    private AIProcessor processor;

    @Action(description = "Hold a book for a user")
    public String holdBook(String bookName, String userName) {
        return String.format("Book '%s' has been held for user '%s'", bookName, userName);
    }

    @Action(description = "Get a book's availability status")
    public String getBookStatus(String bookName) {
        if ("Effective Java".equalsIgnoreCase(bookName)) {
            return "Available";
        } else if ("Clean Code".equalsIgnoreCase(bookName)) {
            return "Checked out";
        }
        return "Not in catalog";
    }

    // Additional methods...
}
Enter fullscreen mode Exit fullscreen mode

A2A Client Implementation

public class A2AClient {
    public static void main(String[] args) {
        A2AAgent a2aagent = new A2AAgent();
        a2aagent.connect("http://localhost:7860/");
        Object task = a2aagent.remoteMethodCall("get me list of books");
    }
}
Enter fullscreen mode Exit fullscreen mode

MCP Client Implementation

public class MCPClient {
    public static void main(String[] args) {
        MCPAgent mcpAgent = new MCPAgent();
        mcpAgent.connect("https://vishalmysore-a2amcpspring.hf.space/");
        AgentInfo mcpCard = mcpAgent.getInfo();
        CallToolResult result = (CallToolResult) mcpAgent.remoteMethodCall("holdBook", 
            "vishal needs to read harry potter book 1");
    }
}
Enter fullscreen mode Exit fullscreen mode

By keeping the client-side interface consistent across both A2A and MCP protocols, this architecture delivers a massive advantage in developer experience, maintainability, and scalability. Both A2AClient and MCPClient follow nearly identical patterns—initialize the agent, connect to the server, and invoke remoteMethodCall()—making it seamless for developers to switch or integrate protocols without rewriting logic or learning new APIs. This uniformity allows organizations to abstract away protocol differences, enabling agentic systems to scale across different communication models (agent-to-agent or agent-to-tool) with minimal friction. It also simplifies onboarding, testing, and debugging, empowering teams to build secure, intelligent systems faster with reduced cognitive overhead.

Chapter 4: Multi-Server Setup

Source code: MCP A2A Multi-Agent

Server Configuration

  • Movies Server (Port 7861)
  • Library Server (Port 7862)

Starting Servers

Movies Server:

mvn spring-boot:run -Pmovies
Enter fullscreen mode Exit fullscreen mode

Library Server:

mvn spring-boot:run -Plibrary
Enter fullscreen mode Exit fullscreen mode

Configuration example:

spring.application.name=spring-boot
server.port=7862
a2a.persistence=cache
tools4ai.properties.path=tools4ai_library.properties
Enter fullscreen mode Exit fullscreen mode

Using application-specific action scanning with action.packages.to.scan is highly beneficial in a multi-server MCP and A2A setup, as it allows each server to expose only the relevant agentic actions, maintaining clean separation of responsibilities and enabling modular deployments. For example, the Movies Server (port 7861) and Library Server (port 7862) each start with their own Spring Boot main class, load their own application properties, and register only the actions in their respective packages—example.movies.server and example.library.server. This design pattern allows you to scale horizontally by spinning up multiple specialized agentic services, each with their own action sets, configuration, and logic, while still participating seamlessly in a unified A2A/MCP network

Chapter 5: Agentic Mesh

An Agentic Mesh is a distributed architecture where multiple AI agents—each specialized for specific tasks—collaborate through structured protocols like A2A (Agent-to-Agent) and MCP (Model Context Protocol). Unlike traditional monolithic or API-centric systems, an agentic mesh enables decentralized intelligence, where each node (agent) can independently reason, act, and communicate with others in the network.

🔍 Key Characteristics:
Decentralized AI agents: Each agent performs focused tasks (e.g., booking, translation, data processing).

Multi-language support: Agents can be implemented in different programming languages (Java, Python, Go, etc.).

Protocol-driven communication: A2A handles peer agent communication, while MCP allows agents to invoke external tools.

Scalable and modular: New agents or tools can be added without changing the rest of the mesh.

Live coordination: Agents can coordinate decisions dynamically using voting, negotiation, or fallback mechanisms.

🧠 Example Use Case:
In a smart city system, different agents could handle traffic prediction, weather analysis, public transport coordination, and emergency response. These agents form a mesh—working autonomously but also collaboratively—to optimize outcomes in real time.

Think of it as microservices for intelligence, where instead of just exposing endpoints, each component contributes knowledge and decision-making to a shared, adaptive ecosystem.

Source code: MCP Agentic Mesh

Server Configuration

  • Dessert Server (Port 7863)
  • Gym Server (Port 7864)
  • Restaurant Server (Port 7865)
  • Yoga Server (Port 7866)

Agent Catalog Implementation

AgentCatalog agentCatalog = new AgentCatalog();
agentCatalog.addAgent("http://localhost:7862/");
agentCatalog.addAgent("http://localhost:7863/");
String answer = agentCatalog.processQuery("what is the recipe for Gulab Jamun").getTextResult();
Enter fullscreen mode Exit fullscreen mode

Testing with cURL

# Get tools list for Dessert Server
curl -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":9}' \
http://localhost:7863/
Enter fullscreen mode Exit fullscreen mode

Claude Desktop Integration

Configure in C:\Users\<yourusername>\AppData\Roaming\Claude\claude_desktop_config.json:

{
    "mcpServers": {
        "yogaserver": {
            "command": "java",
            "args": [
                "-jar",
                "PATH_TO_YOUR_JAR/mcp-connector-full.jar",
                "http://localhost:7866"
            ],
            "timeout": 30000
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Chapters 4 and 5 showcase how to scale an agentic system from individual task-specific servers to a fully coordinated multi-agent mesh. In Chapter 4, the setup demonstrates how to spin up multiple dedicated MCP+A2A servers—such as a Movies Server and Library Server—each running independently on separate ports, making it easy to modularize capabilities. Chapter 5 then takes this further by introducing the Agentic Mesh pattern, where multiple specialized servers (e.g., Dessert, Gym, Yoga) collaborate through a centralized AgentCatalog. This allows any client or LLM to route queries intelligently across agents—creating a distributed, scalable, and loosely coupled AI ecosystem. With support for cURL testing, Claude Desktop integration, and cross-agent orchestration, these chapters offer a practical blueprint for building robust AI-powered systems using open-source protocols like A2A and MCP.

Chapter 6: Agentic Mesh Patterns

Patterns are essential in an Agentic Mesh because they provide structured coordination strategies that enable agents to collaborate effectively, reliably, and intelligently. Without patterns, an agentic mesh can become chaotic, inefficient, and hard to scale.

Different mesh patterns are implemented in A2A Java Mesh Implementation

Interactive Pattern Demos

🔧 Why Patterns Matter in Agentic Mesh:
Standardized Collaboration:
Patterns define how agents should interact—whether through sequential chains, voting, fallback, or concurrent calls—making it easier to build predictable behaviors across agents.

Decoupling Logic from Strategy:
Instead of hardcoding communication flows, patterns abstract the decision-making and orchestration logic. Agents focus on what they do, while patterns control how and when they're used.

Improved Reusability and Scalability:
A well-defined pattern (like a voting pattern or retry-fallback pattern) can be reused across domains—education, health, finance—making it easy to plug in new agents without rewriting coordination logic.

Resilience and Fault Tolerance:
Patterns like fallback, timeout, or quorum ensure the mesh remains functional even if individual agents fail or underperform.

Intent-Driven Execution:
High-level patterns allow agents to work together based on intent (e.g., "optimize delivery route" or "validate a document") rather than procedural steps. This aligns more closely with how humans delegate complex tasks.

Easier Debugging and Optimization:
When agents follow well-defined patterns, it's easier to trace failures, optimize performance, or adjust workflows without breaking the entire system.

🎯 Example:
A Voting Pattern can be used when multiple agents provide conflicting answers (e.g., translation suggestions or eligibility checks), allowing the mesh to choose the best answer based on consensus or confidence scores

Chapter 7: Implementation Patterns and Advanced Integration

This chapter explores the various architectural patterns for implementing sophisticated agent networks. Each pattern is designed to address specific use cases and requirements in distributed AI systems. The implementation patterns we'll discuss have been battle-tested in production environments and offer different trade-offs in terms of scalability, resilience, and complexity.

Actor-Based Mesh Pattern: Foundation for Distributed Intelligence

public class IntelligentAgent extends AbstractBehavior<AgentMessage> {
    private final ActorRef<AgentMessage> supervisor;
    private final Map<String, ActorRef<AgentMessage>> knownAgents;

    public Behavior<AgentMessage> onMessage(AgentMessage msg) {
        return switch (msg) {
            case DiscoveryRequest req -> handleDiscovery(req);
            case TaskRequest task -> processTask(task);
            case CollaborationInvite invite -> joinCollaboration(invite);
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

Reactive Streams Mesh Pattern

public class ReactiveAgent {
    private final Flux<TaskRequest> incomingTasks;
    private final Sink<TaskResponse> outgoingResponses;

    public Flux<TaskResponse> processTaskStream() {
        return incomingTasks
            .flatMap(this::analyzeTask)
            .filter(this::canHandle)
            .switchIfEmpty(this::delegateToMesh)
            .map(this::executeTask);
    }
}
Enter fullscreen mode Exit fullscreen mode

Microservices-Based Agent Pattern

@RestController
@Component
public class AgentController {
    @Autowired
    private AgentCapabilityService capabilities;

    @PostMapping("/collaborate")
    public ResponseEntity<CollaborationResponse> collaborate(
            @RequestBody CollaborationRequest request) {
        List<Agent> peers = discoveryService.findCapableAgents(request.getRequiredSkills());
        return coalitionService.formTemporaryAlliance(peers, request);
    }
}
Enter fullscreen mode Exit fullscreen mode

Event-Driven Mesh Pattern

@KafkaListener(topics = "agent-collaboration")
public class EventDrivenAgent {
    @EventHandler
    public void handleTaskDistribution(TaskDistributionEvent event) {
        if (canHandleTask(event.getTask())) {
            TaskResult result = processTask(event.getTask());
            publishEvent(new TaskCompletionEvent(result));
        } else {
            publishEvent(new TaskRedirectionEvent(event, findBetterAgent()));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Graph-Based Mesh Pattern

@Node
public class GraphAgent {
    @Id private String agentId;
    @Property private Set<String> capabilities;

    @Relationship(type = "CAN_COLLABORATE_WITH")
    private Set<GraphAgent> collaborators;

    @Relationship(type = "DEPENDS_ON")
    private Set<GraphAgent> dependencies;

    public Optional<CollaborationPath> findOptimalCollaborationPath(TaskRequirement requirement) {
        return graphTraversalService.findShortestCapabilityPath(this, requirement);
    }
}
Enter fullscreen mode Exit fullscreen mode

Chapters 6 and 7 dive deep into the design patterns that power large-scale agentic systems, starting with interactive mesh patterns and culminating in advanced implementation architectures. Chapter 6 introduces real-world Agentic Mesh Patterns—such as Supply Chain, Education, Healthcare, and more—brought to life via interactive demos that showcase how specialized agents can collaborate to solve complex workflows. Chapter 7 then transitions into battle-tested implementation patterns that developers can adopt depending on their infrastructure needs. Whether it's an Actor-Based Mesh for asynchronous decision-making, Reactive Streams for backpressure-aware processing, Microservices Agents for REST-based interoperability, Event-Driven Meshes using Kafka for decoupled collaboration, or a Graph-Based Mesh that models agents and dependencies as queryable graphs—these patterns provide a toolkit for building resilient, scalable, and intelligent multi-agent systems. Together, these chapters serve as both a practical guide and architectural reference for building the next generation of distributed AI ecosystems.

Implementation Considerations

Service Discovery and Registry

Use technologies like:

  • Consul
  • Eureka
  • Kubernetes service discovery

Communication Protocols and Interoperability

The choice of communication protocol is crucial for building a robust agentic mesh. Each protocol offers different advantages:

  • gRPC: Ideal for high-performance, low-latency communication between agents. Its binary protocol and HTTP/2 foundation make it perfect for microservices architectures.
  • Apache Thrift: Excellent for polyglot environments where agents are implemented in different languages. Offers strong type safety and efficient serialization.
  • Custom protocols over HTTP/2: When you need specialized behavior or optimizations for your specific use case. Built on HTTP/2's multiplexing and streaming capabilities.

These protocols should be selected based on your specific requirements for performance, language support, and ecosystem compatibility.

Security and Trust Architecture

Security in an agentic mesh requires a comprehensive approach that addresses authentication, authorization, and secure communication:

Authentication and Authorization

  • OAuth 2.0: Provides robust token-based authentication and authorization flows. Perfect for scenarios where agents need to access resources on behalf of users.
  • JWT tokens: Enables stateless authentication with rich claim support. Particularly useful for carrying agent capabilities and permissions.
  • Custom claims can encode agent capabilities, trust levels, and access permissions.

Transport Security

  • Mutual TLS: Ensures bidirectional trust between agents through certificate-based authentication.
  • Certificate rotation and management strategies for long-running agents.
  • Network policy enforcement for agent-to-agent communication.

Security Monitoring

  • Real-time threat detection for unusual agent behavior
  • Audit logging of all inter-agent communications
  • Automated response to security events

Monitoring and Observability in Agentic Mesh

Maintaining visibility into a distributed agent network requires comprehensive monitoring and observability solutions. Here's a detailed approach to implementing effective monitoring:

Metrics Collection and Analysis

  • Micrometer: Provides a vendor-neutral metrics facade that supports multiple monitoring systems.
    • Custom metrics for agent performance
    • Task execution timing
    • Communication latency measurements
    • Resource utilization tracking

Time-Series Monitoring

  • Prometheus: Ideal for collecting and analyzing time-series data from agents
    • Query language for complex metric analysis
    • Alert management for agent issues
    • Performance trending and capacity planning
    • Custom dashboards for agent health

Distributed Tracing

  • Jaeger or Zipkin: Essential for understanding request flow across multiple agents
    • End-to-end request visualization
    • Performance bottleneck identification
    • Error chain analysis
    • Service dependency mapping

Advanced Monitoring Patterns

  • Real-time agent health monitoring
  • Automated performance optimization
  • Predictive maintenance using collected metrics
  • AI-driven anomaly detection

Benefits and Applications

Enhanced Scalability

  • Horizontal scaling through specialized agents
  • Reduced monolithic system dependencies

Improved Resilience

  • Localized system failures
  • Prevention of cascade failures

Specialized Expertise

  • Domain-optimized agents
  • Improved system performance

Dynamic Adaptation

  • Runtime reconfiguration of agent collaborations
  • Zero-downtime updates

Challenges and Future Directions

Current challenges include:

  • Coordination overhead
  • Consistency management
  • Distributed debugging complexity

Future developments focus on:

  • Autonomous coordination algorithms
  • Inter-agent learning mechanisms
  • Standardized collaboration protocols

The evolution of the JVM ecosystem with Project Loom and GraalVM will open new opportunities for agentic mesh implementations.

Screenshots

Image description

Image description

Image description

Image description

Image description

Image description

Image description

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.