🧰

C#でMCP入門(NEWS API連携編)- 書籍『MCP入門』のPythonコードを移植する(5)

に公開
2026/02/10

はじめに

シリーズ第5回目の本記事では、『MCP入門――生成AIアプリ本格開発』(技術評論社)の第7章に掲載されているプログラムnewsapi_server.pyを C# に移植します。(著者の小野哲さんからは、移植および掲載の許可をいただいています)

書籍『MCP入門』では、NewsAPIを利用して、ニュースを取得していましたが、本記事では、ニュース提供サービス NewsData.io を使って最新ニュース取得やキーワード検索を行う MCP サーバー(NewsServer)を C# で実装します。
NewsAPIでは、日本のニュースを取得できないようなので、日本のニュースを取得できるNewsData.ioを利用することにしました。


何をするMCPサーバーか(概要)

今回作成する NewsServer の役割は、外部のニュースAPI(NewsData.io)を呼び出して得られた JSON を MCPツールの戻り値としてそのまま返すことです。MCP クライアント(例: Claude Desktop)は返却された構造化データを利用してユーザーに自然言語で応答します。

具体的には次のツールを提供します:

  • SearchNewsByCategory: カテゴリ/国ごとの最新ニュース取得
  • SearchNewsByKeyword: キーワード検索によるニュース取得

NewsData.ioの の API キーを用意する

事前に NewsData.io のサイトから API キーを取得しておきます。

https://newsdata.io/

  1. サイトにアクセスし、Loginリンクをクリックします。

  2. Sign up here リンクをクリックします。

  3. サインアップが完了するとダッシュボードページが表示されますので、左メニューのAPI Key項目をクリックします。

  4. APIキーをメモしておきます。


プロジェクトの作成

以下のコマンドで、MCP サーバープロジェクトを作成します。プロジェクト名は、NewsServerとしました。

dotnet new mcpserver -n NewsServer

このプロジェクトでは、Microsoft.AspNetCore.WebUtilitiesパッケージを使いますので、インストールします。

dotnet add package Microsoft.AspNetCore.WebUtilities

DTOクラスの定義

まずは、MCP経由でJSONとして返却される公開DTOクラスを定義します。C#のレコード型を利用しています。
元のPythonのコードは型定義をしていませんが、C#の良さを出すために、できるだけ忠実にC#の型に移植しています。

Toolsフォルダに、Dtos.csファイルを作成し、Dtosクラスを定義します。

using System.Text.Json;

namespace NewsServer.Tools;

// newsdata.io の生レスポンスをそのまま扱うための DTO 群。
// SearchNewsByKeyword / SearchNewsByCategory は Dtos.ApiResponse を返します。
public static class Dtos
{
    // API 内の source オブジェクト({ id, name } の形)
    public record ApiSource(string? Id, string? Name);

    // API の articles / results に対応する型(API サンプルに合わせてプライマリーコンストラクタを使用)
    public record ApiArticle(
        string? ArticleId = null,
        string? Link = null,
        string? Title = null,
        string? Description = null,
        string? Content = null,
        List<string>? Keywords = null,
        // creator は文字列/配列/null が来るため JsonElement? のまま保持
        JsonElement? Creator = null,
        string? Language = null,
        List<string>? Country = null,
        List<string>? Category = null,
        string? Datatype = null,
        string? PubDate = null,
        string? PubDateTZ = null,
        string? ImageUrl = null,
        string? VideoUrl = null,
        string? SourceId = null,
        string? SourceName = null,
        long? SourcePriority = null,
        string? SourceUrl = null,
        string? SourceIcon = null,
        string? Sentiment = null
    )
    {
        // 互換プロパティ
        public string? Url => Link;
    }

    // API レスポンス全体(newsdata.io の構造に合わせる)
    public record ApiResponse(
        string? Status = null,
        List<ApiArticle>? Results = null,
        int? TotalResults = null,
        // サンプルに nextPage が含まれているため追加
        string? NextPage = null
    );
}

NewsDataTools.csの作成

Toolsフォルダに、NewsDataTools.csファイルを作成し、NewsDataToolsクラスを定義します。
このクラスは、NewsData.io API と連携してニュースを取得するツールクラスです。

using System.ComponentModel;
using System.Text.Json;
using Microsoft.AspNetCore.WebUtilities;
using ModelContextProtocol.Server;

namespace NewsServer.Tools
{
    // newsdata.io を使ったニュース取得用の MCP ツール
    // NewsTools の機能と同等に、最新ニュース取得 (GetLatestNews) とキーワード検索 (SearchNews) を提供する。
    public class NewsDataTools
    {
        // HttpClient は static で再利用する(ソケット枯渇対策)
        private static readonly HttpClient httpClient = new ();

        // User-Agent を設定(APIプロバイダ向けの識別用)
        static NewsDataTools()
        {
            httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("MCP-NewsServer-NewsData/1.0 (+https://www.zead.co.jp/contact/)");
        }

        // JsonSerializerOptions を使い回す
        private static readonly JsonSerializerOptions JsonOptions = new()
        {
            PropertyNameCaseInsensitive = false,
            PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
        };

        [McpServerTool]
        [Description("カテゴリと国でニュースを検索します")]
        public async Task<Dtos.ApiResponse> SearchNewsByCategory(
            [Description("カテゴリ(例: top, business, technology, sports, entertainment, science, health, finance)")] string category = "top",
            [Description("国コード(jp, us, uk など)")] string country = "us",
            [Description("取得件数(最大10)")] int limit = 5)
        {
            // 引数の正規化(内部では API の生レスポンスを返すため、必要なら呼び出し側で件数を制御してください)
            limit = Math.Clamp(limit, 1, 10);
    
            var queryParams = new Dictionary<string, string?> {
                ["apikey"] = GetApiKey(),
                ["country"] = country,
                ["category"] = category,
                ["size"] = limit.ToString()
                // newsdata.io はページングをサポートしますが、ここでは API の生レスポンスをそのまま返します
            };
    
            var url = BuildUrl("https://newsdata.io/api/1/latest", queryParams);
            var apiResp = await MakeApiRequestAsync<Dtos.ApiResponse>(url);
    
            // 利用者側で結果数を扱えるよう、APIの生レスポンスをそのまま返す
            return apiResp;
        }

        [McpServerTool]
        [Description("キーワードでニュースを検索します")]
        public async Task<Dtos.ApiResponse> SearchNewsByKeyword(
            [Description("検索キーワード")] string query,
            [Description("言語コード(ja, en 等)")] string language = "en",
            [Description("取得件数(最大10)")] int limit = 5)
        {
            // 引数の正規化(内部では API の生レスポンスを返すため、必要なら呼び出し側で件数を制御してください)
            limit = Math.Clamp(limit, 1, 20);
    
            if (string.IsNullOrWhiteSpace(query))
                throw new ArgumentException("検索キーワードを指定してください", nameof(query));
    
            var queryParams = new Dictionary<string, string?> {
                ["apikey"] = GetApiKey(),
                ["q"] = query,
                ["language"] = language,
                ["size"] = limit.ToString()
                // 必要ならソート等のパラメータを追加可能
            };
    
            var url = BuildUrl("https://newsdata.io/api/1/latest", queryParams);
            var apiResp = await MakeApiRequestAsync<Dtos.ApiResponse>(url);
    
            // API の生レスポンスをそのまま返す
            return apiResp;
        }

        // QueryHelpers.AddQueryString を使って URL を作成するヘルパー
        private static string BuildUrl(string baseUrl, IDictionary<string, string?> parameters)
        {
            var filtered = parameters
                .Where(kv => !string.IsNullOrWhiteSpace(kv.Value))
                .ToDictionary(kv => kv.Key, kv => kv.Value);
            return QueryHelpers.AddQueryString(baseUrl, filtered);
        }

        // APIキー取得を共通化(環境変数のみを使用)
        private static string GetApiKey()
        {
            var apiKey = Environment.GetEnvironmentVariable("NEWSDATA_API_KEY");
            if (string.IsNullOrWhiteSpace(apiKey))
                throw new InvalidOperationException("newsdata.io の API キーが環境変数 NEWSDATA_API_KEY に設定されていません");
            return apiKey!;
        }

        // 非同期で API 呼び出しを行い、任意の型へデシリアライズする(デバッグ出力含む)
        private async Task<T> MakeApiRequestAsync<T>(string url)
        {
            // デバッグ用にリクエスト URL を出力(stderr)
            Console.Error.WriteLine($"DEBUG: Request URL: {url}");

            using var resp = await httpClient.GetAsync(url);
            resp.EnsureSuccessStatusCode();
            var body = await resp.Content.ReadAsStringAsync();
            return JsonSerializer.Deserialize<T>(body, JsonOptions)
                ?? throw new InvalidOperationException("JSON を指定した型にデシリアライズできませんでした。");
        }
    }
}

NewsDataTools クラスには以下のツールが実装されています:

  • SearchNewsByCategory: カテゴリ/国ごとの最新ニュース取得
  • SearchNewsByKeyword: キーワード検索によるニュース取得

[McpServerTool]属性、[Description]属性を使うのはこれまでと同じです。

今回作成したメソッドは、API の生レスポンスをそのまま返しています。もしかしたら、返すデータを絞った方が良いかもしれません。そのほうが、LLMでの負荷が軽くなると思われます。


エントリポイント: Program.cs

エントリポイントとなる Program.csを編集し、NewsDataToolsクラスをツールとして登録します。毎回同じようなコードなので、重要な箇所だけ掲載します。

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<NewsDataTools>();

ビルドと実行

ビルド

以下のコマンドでビルドします。

dotnet publish -c Release

bin\Release\net10.0\win-x64\publish\にexeファイルが作成されます。
この exe ファイルは、対象プラットフォーム用の .NET Runtime がインストールされていない環境でも実行できます。

実行ファイルとデータベースファイルをコピー

特定のフォルダに以下のファイルをコピーします。ここでは、C:\mcp-learning\mcpserverフォルダにコピーすることとします。

  1. NewsServer.exe
  2. NewsServer.pdb

claude_desktop_config.jsonを編集

Claude Desktopに組み込んで動作を確認します。
%APPDATA%\Claude\claude_desktop_config.json を開き、以下のように記述します。前回の記事で作成したWeatherServerも一緒に組み込んでいます。

OPENWEATHER_API_KEY、NEWSDATA_API_KEYには、事前に取得したAPIキーを設定します。

{
 "mcpServers": {
    "weather_server": {
      "command": "C:\\mcp-learning\\mcpserver\\WeatherServer.exe",
      "args": [],
      "env": {
        "OPENWEATHER_API_KEY": "ここにAPIキーを書く"
      }
    },
    "news_server": {
      "command": "C:\\mcp-learning\\mcpserver\\NewsServer.exe",
      "args": [],
      "env": {
        "NEWSDATA_API_KEY": "ここにAPIキーを書く"
"
      }
    }
}

Claude Desktopで確認

Claude Desktopを起動して、以下のような質問を投げてみます。

「最新のテクノロジーニュースを教えて」

「明日、シンガポールに行くので、シンガポールの天気とニュースを教えて。どんな服装が良いかな」


データフロー(概要)

ユーザー(質問) → Claude Desktop(LLM) → MCP Server(NewsServer) → NewsData.io API → MCP Server → Claude Desktop → ユーザー

概略図:


最後に

この記事では、C#を使用して外部 API(NewsData.io) と連携する MCPサーバーの作成方法について説明しました。
前回のOpenWeatherMap APIを利用したMCPサーバとともに使えば、明日、シンガポールに行くので、シンガポールの天気とニュースを教えて」などの質問ができることがわかりました。

次回は、第7章に掲載されている 位置情報API(IP-API)と連携する MCPサーバーを C#に移植してみようと思います。



記事目次

GitHubで編集を提案
株式会社ジード テックブログ

Discussion