C#でMCP入門(NEWS API連携編)- 書籍『MCP入門』のPythonコードを移植する(5)
はじめに
シリーズ第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 キーを取得しておきます。
-
サイトにアクセスし、Loginリンクをクリックします。
-
Sign up here リンクをクリックします。
-
サインアップが完了するとダッシュボードページが表示されますので、左メニューのAPI Key項目をクリックします。
-
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フォルダにコピーすることとします。
- NewsServer.exe
- 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#に移植してみようと思います。
記事目次
- C#でMCP入門(HTTP方式編)- 書籍『MCP入門』のPythonコードを移植する(1)
- C#でMCP入門(STDIO方式編)- 書籍『MCP入門』のPythonコードを移植する(2)
- C#でMCP入門(DB接続編)- 書籍『MCP入門』のPythonコードを移植する(3)
- C#でMCP入門(Weather API連携編)- 書籍『MCP入門』のPythonコードを移植する(4)
- C#でMCP入門(NEWS API連携編)- 書籍『MCP入門』のPythonコードを移植する(5)
- C#でMCP入門(IP情報連携編)- 書籍『MCP入門』のPythonコードを移植する(6)
- C#でMCP入門(Webサーチ編)- 書籍『MCP入門』のPythonコードを移植する(7)
Discussion