Elasticsearch에서 A2A 프로토콜과 MCP를 사용하여 LLM 에이전트 뉴스룸 만들기: 2부
에이전트 협업을 위한 A2A 프로토콜과 Elasticsearch의 도구 액세스를 위한 MCP를 사용해 특화된 하이브리드 LLM 에이전트 뉴스룸을 구축하는 방법을 알아보세요.
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_check8단계: 낮은 신뢰도로 인한 재조사 트리거
리포터 에이전트가 초안을 평가한 결과 한 클레임의 신뢰도가 낮다는 것을 발견했습니다. 연구원 에이전트에게 또 다른 요청을 보냅니다:
{
"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)을 신중하게 고려하면 성공의 길로 들어설 수 있습니다.
추가 리소스
Elasticsearch 에이전트 빌더: https://www.elastic.co/docs/solutions/search/elastic-agent-builder
A2A 및 MCP 통합: https://a2a-protocol.org/latest/topics/a2a-and-mcp/
모델 컨텍스트 프로토콜: https://modelcontextprotocol.io




