블로그

Elasticsearch에서 A2A 프로토콜과 MCP를 사용하여 LLM 에이전트 뉴스룸 만들기: 2부

에이전트 협업을 위한 A2A 프로토콜과 Elasticsearch의 도구 액세스를 위한 MCP를 사용해 특화된 하이브리드 LLM 에이전트 뉴스룸을 구축하는 방법을 알아보세요.

Agent Builder is available now GA. Get started with an Elastic Cloud Trial, and check out the documentation for Agent Builder here.

A2A 및 MCP: 작동 중인 코드

이 글은 동일한 에이전트 내에서 A2A와 MCP 아키텍처를 모두 구현하여 두 프레임워크의 고유한 이점을 제대로 활용할 수 있는 이점을 설명한 "Elasticsearch에서 A2A 프로토콜과 MCP를 사용하여 LLM 에이전트 뉴스룸 만들기!" 글의 후속 글입니다. 직접 데모를 실행하려는 경우 저장소를 사용할 수 있습니다.

뉴스룸 에이전트가 A2A와 MCP를 사용하여 뉴스 기사를 제작하기 위해 어떻게 협업하는지 살펴보겠습니다. 에이전트가 작동하는 모습을 볼 수 있는 리포지토리는 여기에서 확인할 수 있습니다.

1단계: 스토리 과제

뉴스 책임자 (클라이언트 역할)가 스토리를 할당합니다:

{
  "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"
    }
  }
}

2단계: 리포터가 리서치 요청

리포터 에이전트는 배경 정보가 필요하다는 것을 인식하고 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"
    }
  }
}

3단계: 리포터가 아카이브 에이전트에게 기록 컨텍스트를 요청합니다.

리포터 에이전트는 역사적 맥락이 스토리를 강화할 수 있다는 것을 알고 있습니다. A2A를 통해 (Elastic의 A2A 에이전트로 구동되는) 아카이브 에이전트에 위임하여 뉴스룸의 Elasticsearch 기반 기사 아카이브를 검색합니다:

{
  "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
    }
  }
}

4단계: 아카이브 에이전트가 MCP와 함께 Elastic A2A 에이전트 사용

아카이브 에이전트는 Elastic의 A2A 에이전트를 사용하며, 이 에이전트는 다시 MCP를 사용해 Elasticsearch 도구에 액세스합니다. 이는 A2A가 에이전트 협업을 지원하는 동시에 MCP가 툴 액세스를 제공하는 하이브리드 아키텍처를 보여줍니다:

# 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_response

아카이브 에이전트는 Elastic의 A2A 에이전트로부터 포괄적인 기록 데이터를 받아 리포터에게 반환합니다:

{
  "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
    }
  }
}

이 단계에서는 Elastic의 A2A 에이전트가 뉴스룸 워크플로우에 어떻게 통합되는지 보여드립니다. Archive 에이전트(뉴스룸 전용 에이전트)는 Elastic의 A2A 에이전트(타사 전문가)와 협력하여 Elasticsearch의 강력한 검색 및 분석 기능을 활용합니다. Elastic의 에이전트는 내부적으로 MCP를 사용하여 Elasticsearch 도구에 액세스하므로 에이전트 조정(A2A)과 도구 액세스(MCP)가 깔끔하게 분리되어 있습니다.

5단계: 연구자가 MCP 서버 사용

연구원 에이전트는 여러 MCP 서버에 액세스하여 정보를 수집합니다:

# 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)

6단계: 연구원이 리포터에게 데이터를 반환합니다.

리서처 에이전트는 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
    }
  }
}

7단계: 기자가 기사 작성

리포터 에이전트는 리서치 데이터와 자체 LLM 기능을 사용하여 기사를 작성합니다. 글을 작성하는 동안 리포터는 스타일과 템플릿을 위해 MCP 서버를 사용합니다:

# 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_check

8단계: 낮은 신뢰도로 인한 재조사 트리거

리포터 에이전트가 초안을 평가한 결과 한 클레임의 신뢰도가 낮다는 것을 발견했습니다. 연구원 에이전트에게 또 다른 요청을 보냅니다:

{
  "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"
  }
}

연구원은 사실 확인 MCP 서버를 사용하여 클레임을 확인하고 업데이트된 정보를 반환합니다:

{
  "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"]
      }
    ]
  }
}

9단계: 리포터가 수정하여 편집자에게 제출합니다.

리포터는 확인된 사실을 통합하고 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
    }
  }
}

10단계: MCP 도구를 사용한 편집자 리뷰

편집자 에이전트는 여러 MCP 서버를 사용하여 문서를 검토합니다:

# 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 feedback

편집자가 글을 승인하고 앞으로 보냅니다:

{
  "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]"
  }
}

11단계: CI/CD를 통해 게시자 게시

마지막으로 프린터 에이전트는 CMS 및 CI/CD 파이프라인을 위한 MCP 서버를 사용하여 승인된 문서를 게시합니다:

# 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"]
    }

게시자는 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
    }
  }
}

다음은 위에서 설명한 것과 동일한 에이전트를 사용하여 첨부된 리포지토리에 있는 A2A 워크플로우의 전체 시퀀스입니다.

#

에서

To

액션

프로토콜

설명

1

사용자

뉴스 수석

스토리 할당

HTTP POST

사용자가 스토리 주제와 각도를 제출합니다.

2

뉴스 수석

내부

스토리 만들기

-

고유 ID로 스토리 레코드 생성

3

뉴스 수석

리포터

위임자 할당

A2A

A2A 프로토콜을 통해 스토리 할당 전송

4

리포터

내부

할당 수락

-

내부적으로 스토어 할당

5

리포터

MCP 서버

개요 생성

MCP/HTTP

기사 개요 및 연구 질문 생성

6a

리포터

연구원

연구 요청

A2A

질문 보내기(6b와 병행)

6b

리포터

아카이브

아카이브 검색

A2A JSONRPC

과거 기사 검색(6a와 병행)

7

연구원

MCP 서버

연구 질문

MCP/HTTP

MCP를 통해 Anthropic을 사용하여 질문에 답하기

8

연구원

리포터

리서치 반환

A2A

연구 답변 반환

9

아카이브

Elasticsearch

검색 색인

ES REST API

뉴스_아카이브 인덱스 쿼리

10

아카이브

리포터

아카이브 반환

A2A JSONRPC

과거 검색 결과 반환

11

리포터

MCP 서버

문서 생성

MCP/HTTP

리서치/아카이브 컨텍스트가 포함된 문서 생성

12

리포터

내부

저장 초안

-

내부적으로 초안 저장

13

리포터

뉴스 수석

초안 제출

A2A

완성된 초안 제출

14

뉴스 수석

내부

업데이트 스토리

-

초안 저장, "draft_submitted로 상태 업데이트"

15

뉴스 수석

편집기

초안 검토

A2A

검토를 위해 편집기로 자동 라우팅

16

편집기

MCP 서버

기사 검토

MCP/HTTP

MCP를 통해 Anthropic을 사용하여 콘텐츠 분석

17

편집기

뉴스 수석

반품 검토

A2A

편집 피드백 및 제안을 보냅니다.

18

뉴스 수석

내부

스토어 리뷰

-

편집자 피드백 저장

19

뉴스 수석

리포터

편집 적용

A2A

리포터에게 리뷰 피드백 전달

20

리포터

MCP 서버

편집 적용

MCP/HTTP

피드백에 따라 문서 수정

21

리포터

내부

업데이트 초안

-

수정 사항이 있는 초안 업데이트

22

리포터

뉴스 수석

수정된 반환

A2A

수정된 문서 반환

23

뉴스 수석

내부

업데이트 스토리

-

스토어 수정 초안, 상태 "수정됨"

24

뉴스 수석

게시자

기사 게시

A2A

게시자에게 자동 라우팅

25

게시자

MCP 서버

태그 생성

MCP/HTTP

태그 및 카테고리 만들기

26

게시자

Elasticsearch

색인 문서

ES REST API

기사를 뉴스_아카이브 인덱스에 색인화

27

게시자

파일 시스템

마크다운 저장

파일 I/O

.md로 문서 저장 아티클의 파일

28

게시자

뉴스 수석

게시 확인

A2A

성공 상태 반환

29

뉴스 수석

내부

업데이트 스토리

-

스토리 상태를 "게시됨으로 업데이트"

결론

A2A와 MCP는 모두 최신 증강-LLM 인프라 패러다임에서 중요한 역할을 담당하고 있습니다. A2A는 복잡한 멀티 에이전트 시스템을 위한 유연성을 제공하지만 잠재적으로 휴대성이 떨어지고 운영 복잡성이 높아질 수 있습니다. MCP는 멀티에이전트 오케스트레이션을 처리하도록 설계되지는 않았지만 구현 및 유지 관리가 더 간편한 도구 통합을 위한 표준화된 접근 방식을 제공합니다.

선택은 이분법적이지 않습니다. 뉴스룸의 예시를 통해 알 수 있듯이, 가장 정교하고 효과적인 LLM 지원 시스템은 에이전트가 A2A 프로토콜을 통해 조정하고 전문화하면서 MCP 서버를 통해 도구와 리소스에 액세스하는 두 가지 접근 방식을 결합하는 경우가 많습니다. 이 하이브리드 아키텍처는 MCP의 표준화 및 에코시스템의 장점과 함께 멀티 에이전트 시스템의 조직적 이점을 제공합니다. 이는 선택의 여지가 전혀 없을 수도 있음을 시사합니다. 두 가지를 모두 표준 접근 방식으로 사용하기만 하면 됩니다.

특정 사용 사례에 적합한 결과를 만들기 위해 두 솔루션을 가장 잘 조합하여 테스트하고 결정하는 것은 개발자 또는 아키텍트의 몫입니다. 각 접근 방식의 강점, 한계, 적절한 적용 사례를 이해하면 보다 효과적이고 유지 관리가 용이하며 확장 가능한 AI 시스템을 구축할 수 있습니다.

디지털 뉴스룸, 고객 서비스 플랫폼, 리서치 어시스턴트 또는 기타 LLM 기반 애플리케이션을 구축하는 경우, 조정 요구 사항(A2A)과 도구 액세스 요구 사항(MCP)을 신중하게 고려하면 성공의 길로 들어설 수 있습니다.

추가 리소스

관련 콘텐츠

Mastra와 Elasticsearch로 에이전틱 AI 애플리케이션을 구축하는 방법

Enrico Zimuel

TypeScript로 Elasticsearch MCP 서버 생성

Jeffrey Rengifo

셸 도구는 컨텍스트 엔지니어링을 위한 만능 해결책이 아닙니다

Leonie Monigatti

Elasticsearch 추론 API와 Hugging Face 모델 함께 사용하기

Jeffrey Rengifo

Elasticsearch용 Gemini CLI 확장 프로그램(도구 및 기술 포함)

Walter Rafelsberger