ブログ

Elasticsearch で A2A プロトコルと MCP を使用して LLM エージェント ニュースルームを作成する: パート II

エージェントのコラボレーションに 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:コードの動作

これは、記事「Elasticsearch で A2A プロトコルと MCP を使用して LLM エージェント ニュースルームを作成する」の補足記事です。この記事では、同じエージェント内に A2A と MCP の両方のアーキテクチャを実装して、両方のフレームワークの独自のメリットを最大限に活用するメリットについて説明しました。自分でデモを実行したい場合、リポジトリが利用可能です。

ニュースルームのエージェントが 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 エージェントを使用し、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 Agent(ニュースルーム固有のエージェント)は、Elastic の A2A Agent(サードパーティの専門家)と連携して、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:記者が記事を書く

Reporter Agent は、調査データと独自の LLM 機能を使用して記事を作成します。書き込み中、Reporter はスタイルとテンプレートに 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:自信が低い場合は再調査を促します

レポーター エージェントは下書きを評価し、1 つの主張の信頼性が低いことを発見しました。研究者エージェントに別のリクエストを送信します:

{
  "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 ワークフローの完全なシーケンスです。

#

から

アクション

プロトコル

説明

1

ユーザー

ニュースチーフ

ストーリーの割り当て

HTTP ポスト

ユーザーがストーリーのトピックと角度を提出する

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

news_archiveインデックスをクエリ

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

記事をnews_archiveインデックスにインデックスします

27

出版社

ファイルシステム

マークダウンを保存

ファイルI/O

記事を.mdとして保存します/articles内のファイル

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 Inference APIとHugging Faceモデルを組み合わせて使用

Jeffrey Rengifo

ElasticsearchのGemini CLI拡張機能(ツールとスキル付き)

Walter Rafelsberger