Creación de una redacción de agentes LLM con protocolo A2A y MCP en Elasticsearch: Parte II
Descubre cómo construir una sala de redacción híbrida especializada para agentes LLM empleando el protocolo A2A para la colaboración de agentes y MCP para el acceso a herramientas en Elasticsearch.
A2A y MCP: el código en acción
Este es el artículo complementario al artículo "¡Creando una redacción para agentes LLM con protocolo A2A y MCP en Elasticsearch!", que explicaba los beneficios de implementar tanto las arquitecturas A2A como MCP dentro del mismo agente para aprovechar realmente los beneficios únicos de ambos frameworks. Hay un repositorio disponible si deseas ejecutar la demo por tu cuenta.

Vamos a repasar cómo colaboran nuestros agentes de redacción empleando tanto A2A como MCP para producir un artículo de noticias. El repositorio adjunto para ver a los agentes en acción se puede encontrar aquí.
Paso 1: Asignación de la historia
El Jefe de Noticias (actuando como cliente) asigna una noticia:
{
"message_type": "task_request",
"sender": "news_chief",
"receiver": "reporter_agent",
"payload": {
"task_id": "story_renewable_energy_2024",
"assignment": {
"topic": "Renewable Energy Adoption in Europe",
"angle": "Policy changes driving solar and wind expansion",
"target_length": 1200,
"deadline": "2025-09-30T18:00:00Z"
}
}
}Paso 2: El reportero aplicar investigación
El Agente Reportero reconoce que necesita información de fondo y delega al Agente Investigador mediante A2A:
{
"message_type": "task_request",
"sender": "reporter_agent",
"receiver": "researcher_agent",
"payload": {
"task_id": "research_eu_renewable_2024",
"parent_task_id": "story_renewable_energy_2024",
"capability": "fact_gathering",
"parameters": {
"queries": [
"EU renewable energy capacity 2024",
"Solar installations growth Europe",
"Wind energy policy changes 2024"
],
"depth": "comprehensive"
}
}
}Paso 3: El reportero aplicar contexto histórico al Agente de Archivo
El Reporter Agent reconoce que el contexto histórico fortalecería la historia. Delega al Agente de Archivo (impulsado por el Agente A2A de Elastic) a través de A2A para buscar en el archivo de artículos impulsado por Elasticsearch de la redacción:
{
"message_type": "task_request",
"sender": "reporter_agent",
"receiver": "archive_agent",
"payload": {
"task_id": "archive_search_renewable_2024",
"parent_task_id": "story_renewable_energy_2024",
"capability": "search_archive",
"parameters": {
"query": "European renewable energy policy changes and adoption trends over past 5 years",
"focus_areas": ["solar", "wind", "policy", "Germany", "France"],
"time_range": "2019-2024",
"result_count": 10
}
}
}Paso 4: El Agente de Archivo emplea el Agente Elastic A2A con MCP
El Archive Agent emplea el A2A Agent de Elastic, que a su vez emplea MCP para acceder a las herramientas de Elasticsearch. Esto demuestra la arquitectura híbrida donde A2A permite la colaboración entre agentes mientras que MCP proporciona acceso a herramientas:
# Archive Agent using Elastic A2A Agent
async def search_historical_articles(self, query_params):
# The Archive Agent sends a request to Elastic's A2A Agent
elastic_response = await self.a2a_client.send_request(
agent="elastic_agent",
capability="search_and_analyze",
parameters={
"natural_language_query": query_params["query"],
"index_pattern": "newsroom-articles-*",
"filters": {
"topics": query_params["focus_areas"],
"date_range": query_params["time_range"]
},
"analysis_type": "trend_analysis"
}
)
# Elastic's A2A Agent internally uses MCP tools:
# - platform.core.search (to find relevant articles)
# - platform.core.generate_esql (to analyze trends)
# - platform.core.index_explorer (to identify relevant indices)
return elastic_responseEl Agente de Archivo recibe datos históricos completos del Agente A2A de Elastic y los devuelve al Reportero:
{
"message_type": "task_response",
"sender": "archive_agent",
"receiver": "reporter_agent",
"payload": {
"task_id": "archive_search_renewable_2024",
"status": "completed",
"archive_data": {
"historical_articles": [
{
"title": "Germany's Energiewende: Five Years of Solar Growth",
"published": "2022-06-15",
"key_points": [
"Germany added 7 GW annually 2020-2022",
"Policy subsidies drove 60% of growth"
],
"relevance_score": 0.94
},
{
"title": "France Balances Nuclear and Renewables",
"published": "2023-03-20",
"key_points": [
"France increased renewable target to 40% by 2030",
"Solar capacity doubled 2021-2023"
],
"relevance_score": 0.89
}
],
"trend_analysis": {
"coverage_frequency": "EU renewable stories increased 150% since 2019",
"emerging_themes": ["policy incentives", "grid modernization", "battery storage"],
"coverage_gaps": ["Small member states", "offshore wind permitting"]
},
"total_articles_found": 47,
"search_confidence": 0.91
}
}
}Este paso demuestra cómo el Agente A2A de Elastic se integra en el flujo de trabajo de la redacción. El Agente de Archivo (un agente específico de redacción) coordina con el Agente A2A de Elastic (un especialista externo) para aprovechar las poderosas capacidades de búsqueda y análisis de Elasticsearch. El agente de Elastic emplea MCP internamente para acceder a las herramientas de Elasticsearch, mostrando la separación limpia entre la coordinación del agente (A2A) y el acceso a la herramienta (MCP).
Paso 5: El investigador emplea servidores MCP
El Agente Investigador accede a múltiples servidores MCP para recopilar información:
# Researcher Agent using MCP to access tools
async def gather_facts(self, queries):
results = []
# Use News API MCP Server
news_data = await self.mcp_client.invoke_tool(
server="news_api_mcp",
tool="search_articles",
parameters={
"query": queries[0],
"date_range": "2024-01-01 to 2024-09-30",
"sources": ["reuters", "bloomberg", "euractiv"]
}
)
# Use Statistics MCP Server
stats_data = await self.mcp_client.invoke_tool(
server="eurostat_mcp",
tool="get_energy_statistics",
parameters={
"metrics": ["solar_capacity_mw", "wind_capacity_mw"],
"countries": ["EU27"],
"year": 2024
}
)
# Use Fact-Checking MCP Server
verified_facts = await self.mcp_client.invoke_tool(
server="factcheck_mcp",
tool="verify_claims",
parameters={
"claims": [
"EU solar capacity increased 40% in 2024",
"Germany leads in wind installations"
]
}
)
return self._synthesize_research(news_data, stats_data, verified_facts)Paso 6: El investigador devuelve los datos al Reportero
El Agente Investigador envía una investigación exhaustiva de vuelta a través de A2A:
{
"message_type": "task_response",
"sender": "researcher_agent",
"receiver": "reporter_agent",
"payload": {
"task_id": "research_eu_renewable_2024",
"status": "completed",
"research_data": {
"key_facts": [
{
"claim": "EU solar capacity reached 260 GW in 2024",
"source": "Eurostat Q3 2024 Report",
"confidence": 0.95,
"verified": true
},
{
"claim": "Germany installed 12 GW of wind capacity in 2024",
"source": "German Federal Network Agency",
"confidence": 0.92,
"verified": true
}
],
"statistics": {
"solar_growth_rate": "35%",
"wind_growth_rate": "28%"
},
"sources_count": 15
}
}
}Paso 7: Reportero escribe artículo
El Reporter Agent emplea los datos de investigación y sus propias capacidades de LLM para redactar el artículo. Durante la redacción, el Reportero emplea los servidores MCP para el estilo y las plantillas:
# Reporter Agent writing with MCP assistance
async def write_article(self, research_data, assignment):
# Get style guidelines via MCP
style_guide = await self.mcp_client.get_resource(
server="newsroom_mcp",
resource="style://editorial/ap_style_guide"
)
# Get article template via MCP
template = await self.mcp_client.get_resource(
server="newsroom_mcp",
resource="template://articles/news_story"
)
# Generate article using LLM + research + style
draft = await self.llm.generate(
prompt=f"""
Write a news article following these guidelines:
{style_guide}
Using this template:
{template}
Based on this research:
{research_data}
Assignment: {assignment}
"""
)
# Self-evaluate confidence in claims
confidence_check = await self._evaluate_confidence(draft)
return draft, confidence_checkPaso 8: la baja confianza desencadena la re-investigación
El Agente Reportero evalúa su borrador y encuentra que una afirmación tiene baja confianza. Envía otra solicitud al Agente Investigador:
{
"message_type": "collaboration_request",
"sender": "reporter_agent",
"receiver": "researcher_agent",
"payload": {
"request_type": "fact_verification",
"claims": [
{
"text": "France's nuclear phase-down contributed to 15% increase in renewable capacity",
"context": "Discussing policy drivers for renewable growth",
"current_confidence": 0.45,
"required_confidence": 0.80
}
],
"urgency": "high"
}
}El investigador verifica la afirmación empleando servidores MCP de verificación de hechos y devuelve información actualizada:
{
"message_type": "collaboration_response",
"sender": "researcher_agent",
"receiver": "reporter_agent",
"payload": {
"verified_claims": [
{
"original_claim": "France's nuclear phase-down contributed to 15% increase...",
"verified_claim": "France's renewable capacity increased 18% in 2024, partially offsetting reduced nuclear output",
"confidence": 0.88,
"corrections": "Percentage was 18%, not 15%; nuclear phase-down is gradual, not primary driver",
"sources": ["RTE France", "French Energy Ministry Report 2024"]
}
]
}
}Paso 9: El reportero revisa y envía al editor
El Reportero incorpora los hechos verificados y envía el borrador completo al Agente Editor a través de A2A:
{
"message_type": "task_request",
"sender": "reporter_agent",
"receiver": "editor_agent",
"payload": {
"task_id": "edit_renewable_story",
"parent_task_id": "story_renewable_energy_2024",
"content": {
"headline": "Europe's Renewable Revolution: Solar and Wind Surge 30% in 2024",
"body": "[Full article text...]",
"word_count": 1185,
"sources": [/* array of sources */]
},
"editing_requirements": {
"check_style": true,
"check_facts": true,
"check_seo": true
}
}
}Paso 10: Revisiones del editor usando herramientas MCP
El Editor Agent emplea múltiples servidores MCP para revisar el artículo:
# Editor Agent using MCP for quality checks
async def review_article(self, content):
# Grammar and style check
grammar_issues = await self.mcp_client.invoke_tool(
server="grammarly_mcp",
tool="check_document",
parameters={"text": content["body"]}
)
# SEO optimization check
seo_analysis = await self.mcp_client.invoke_tool(
server="seo_mcp",
tool="analyze_content",
parameters={
"headline": content["headline"],
"body": content["body"],
"target_keywords": ["renewable energy", "Europe", "solar", "wind"]
}
)
# Plagiarism check
originality = await self.mcp_client.invoke_tool(
server="plagiarism_mcp",
tool="check_originality",
parameters={"text": content["body"]}
)
# Generate editorial feedback
feedback = await self._generate_feedback(
grammar_issues,
seo_analysis,
originality
)
return feedbackEl editor aprueba el artículo y lo envía hacia adelante:
{
"message_type": "task_response",
"sender": "editor_agent",
"receiver": "reporter_agent",
"payload": {
"status": "approved",
"quality_score": 9.2,
"minor_edits": [
"Changed 'surge' to 'increased' in paragraph 3 for AP style consistency",
"Added Oxford comma in list of countries"
],
"approved_content": "[Final edited article]"
}
}Paso 11: El editor publica vía CI/CD
Finalmente, el Agente de Impresora publica el artículo aprobado empleando los servidores MCP para la tubería CMS y CI/CD:
# Publisher Agent publishing via MCP
async def publish_article(self, content, metadata):
# Upload to CMS via MCP
cms_result = await self.mcp_client.invoke_tool(
server="wordpress_mcp",
tool="create_post",
parameters={
"title": content["headline"],
"body": content["body"],
"status": "draft",
"categories": metadata["categories"],
"tags": metadata["tags"],
"featured_image_url": metadata["image_url"]
}
)
post_id = cms_result["post_id"]
# Trigger CI/CD deployment via MCP
deploy_result = await self.mcp_client.invoke_tool(
server="cicd_mcp",
tool="trigger_deployment",
parameters={
"pipeline": "publish_article",
"environment": "production",
"post_id": post_id,
"schedule": "immediate"
}
)
# Track analytics
await self.mcp_client.invoke_tool(
server="analytics_mcp",
tool="register_publication",
parameters={
"post_id": post_id,
"publish_time": datetime.now().isoformat(),
"story_id": metadata["story_id"]
}
)
return {
"status": "published",
"post_id": post_id,
"url": f"https://newsroom.example.com/articles/{post_id}",
"deployment_id": deploy_result["deployment_id"]
}El editor confirma la publicación a través de A2A:
{
"message_type": "task_complete",
"sender": "printer_agent",
"receiver": "news_chief",
"payload": {
"task_id": "story_renewable_energy_2024",
"status": "published",
"publication": {
"url": "https://newsroom.example.com/articles/renewable-europe-2024",
"published_at": "2025-09-30T17:45:00Z",
"post_id": "12345"
},
"workflow_metrics": {
"total_time_minutes": 45,
"agents_involved": ["reporter", "researcher", "archive", "editor", "printer"],
"iterations": 2,
"mcp_calls": 12
}
}
}Aquí está la secuencia completa del flujo de trabajo A2A en el repositorio adjunto usando los mismos Agentes descritos anteriormente.
# | De | Para | Acción | Protocolo | Descripción |
|---|---|---|---|---|---|
1 | Usuario | Jefe de Noticias | Asignar historia | HTTP POST | El usuario presenta la historia, tema y ángulo |
2 | Jefe de Noticias | Interno | Crear historia | - | Crea un registro de historia con un ID único |
3 | Jefe de Noticias | Reportero | Asignación de Delegados | A2A | Envía asignación de historia mediante el protocolo A2A |
4 | Reportero | Interno | Aceptar Asignación | - | Asignación interna de almacenes |
5 | Reportero | Servidor MCP | Generar esquema | MCP/HTTP | Crea un esquema de artículos y preguntas de investigación |
6A | Reportero | Investigador | Solicitud de investigación | A2A | Envía preguntas (paralelo con 6b) |
6b | Reportero | Archivero | Archivo de búsqueda | A2A JSONRPC | Busca en artículos históricos (paralelo con 6a) |
7 | Investigador | Servidor MCP | Preguntas de investigación | MCP/HTTP | Emplea Anthropic vía MCP para responder preguntas |
8 | Investigador | Reportero | Investigación de Retorno | A2A | Respuestas de investigación de retornos |
9 | Archivero | Elasticsearch | Índice de búsqueda | ES REST API | Consultas news_archive índice |
10 | Archivero | Reportero | Archivo de retorno | A2A JSONRPC | Devuelve resultados históricos de búsqueda |
11 | Reportero | Servidor MCP | Generar artículo | MCP/HTTP | Crea un artículo con contexto de investigación/archivo |
12 | Reportero | Interno | Tiro de la tienda | - | Salvamentos en el draft interno |
13 | Reportero | Jefe de Noticias | Enviar Borrador | A2A | Entrega borrador completado |
14 | Jefe de Noticias | Interno | Actualización de la historia | - | Borrador de la tienda, actualiza el estado a "draft_submitted" |
15 | Jefe de Noticias | Editor | Borrador de revisión | A2A | Rutas automáticas al Editor para su revisión |
16 | Editor | Servidor MCP | Artículo de revisión | MCP/HTTP | Analiza contenido usando Anthropic a través de MCP |
17 | Editor | Jefe de Noticias | Reseña de Retorno | A2A | Envía comentarios y sugerencias editoriales |
18 | Jefe de Noticias | Interno | Reseña de la tienda | - | Comentarios sobre el editor de tiendas |
19 | Jefe de Noticias | Reportero | Aplicar ediciones | A2A | Comentarios de la revisión de rutas al Reportero |
20 | Reportero | Servidor MCP | Aplicar ediciones | MCP/HTTP | Revisa el artículo basar en los comentarios |
21 | Reportero | Interno | Actualización del borrador | - | Borrador de actualizaciones con revisiones |
22 | Reportero | Jefe de Noticias | Devolución revisada | A2A | Artículo revisado de declaraciones |
23 | Jefe de Noticias | Interno | Actualización de la historia | - | Borrador revisado de las tiendas, estado a "revisado" |
24 | Jefe de Noticias | Editor | Publicar artículo | A2A | Rutas automáticas hacia el editor |
25 | Editor | Servidor MCP | Generar etiquetas | MCP/HTTP | Crea etiquetas y categorías |
26 | Editor | Elasticsearch | Artículo índice | ES REST API | Índice de artículos a índice news_archive |
27 | Editor | Sistema de archivos | Almacenar Markdown | E/S de archivos | Almacena el artículo como .md archivo en /articles |
28 | Editor | Jefe de Noticias | Confirmar publicación | A2A | Resultados de éxito |
29 | Jefe de Noticias | Interno | Actualización de la historia | - | Actualiza el estado de la noticia a "publicado" |
Conclusión
Tanto A2A como MCP tienen papeles importantes en el paradigma moderno de infraestructura de LLM aumentado. A2A ofrece flexibilidad para sistemas complejos multiagente, pero potencialmente menos portabilidad y mayor complejidad operativa. MCP ofrece un enfoque estandarizado para la integración de herramientas que es más sencillo de implementar y mantener, aunque no está diseñado para manejar orquestación multiagente.
La elección no es binaria. Como demuestra nuestro ejemplo de redacción, los sistemas respaldados por LLM más sofisticados y efectivos suelen combinar ambos enfoques: los agentes coordinan y se especializan mediante protocolos A2A mientras acceden a sus herramientas y recursos a través de servidores MCP. Esta arquitectura híbrida ofrece los beneficios organizacionales de los sistemas multiagente junto con los beneficios de estandarización y ecosistema del MCP. Esto sugiere que puede que no sea necesario elegir en absoluto: simplemente usar ambos como un enfoque estándar
Depende de ti, como desarrollador o arquitecto, probar y determinar la mejor combinación de ambas soluciones para crear el resultado adecuado para tu caso de uso específico. Comprender las fortalezas, limitaciones y aplicaciones adecuadas de cada enfoque te permitirá construir sistemas de IA más eficaces, mantenibles y escalables.
Ya sea que estés construyendo una redacción digital, una plataforma de atención al cliente, un asistente de investigación o cualquier otra aplicación impulsada por LLM, considerar cuidadosamente tus necesidades de coordinación (A2A) y requisitos de acceso a herramientas (MCP) te pondrá en el camino hacia el éxito.
Recursos adicionales
Constructor de agentes Elasticsearch: https://www.elastic.co/docs/solutions/search/elastic-agent-builder
Especificación A2A: https://a2a-protocol.org/latest/specification/
Integración de A2A y MCP: https://a2a-protocol.org/latest/topics/a2a-and-mcp/
Protocolo de Contexto del Modelo: https://modelcontextprotocol.io




