4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cursorでクリスマス記事を取得するために、The New York Times MCP サーバーを構築してみた🎄

4
Last updated at Posted at 2025-12-24

はじめに

皆さん、メリークリスマス! アディクシィ株式会社の野中康生と申します。

ちなみに日本では、クリスマスイブと当日の24日、25日に「メリークリスマス」を使うタイミングが多いですが、キリスト教圏では11月くらいから使うことも多いみたいです。

またクリスマスイブ自体12月24日の日没から12月25日の日没までを指し、さらに聖夜とは24日の夜なので25日の記事にメリークリスマスで導入するのはおかしいかもという思いにふけながらクリスマス用の記事を書いておりました。

そんなクリスマスに関連して、今回はCursor上で「The New York Timesのクリスマスの記事を教えてください」と聞くだけで、最新記事を取得できる仕組みを作ってみました!

私が普段愛読するThe New York TimesではAPIを提供しており、今回の記事では、そのAPIを活用したMCPサーバーを構築し、The New York Times上でクリスマスに関する記事を検索してみます!

The New York Times APIとは?

The New York Timesは、開発者向けに複数のAPIを公開しています。主なAPIには以下のようなものがあります:

  • Article Search API: 1851年以降の記事を検索できる
  • Books API: ベストセラーリストや書評にアクセス
  • Most Popular API: 最も人気のある記事を取得
  • Top Stories API: 最新のトップニュースを取得
  • Archive API: 月別アーカイブにアクセス

今回は、これらのAPIを活用したMCPサーバーを構築します。

なぜMCPを使うのか?

MCPとは

MCP(Model Context Protocol)は、AIアシスタントがローカルやリモートのデータソースと安全に連携するための標準プロトコルです。

従来の方法との違い

これまでは、The New York Timesの記事を取得するには:

  1. APIキーを取得
  2. HTTPリクエストを手動で組み立てる
  3. レスポンスをパースして表示
  4. エラーハンドリングを実装

といった手順が必要でした。しかし、MCPサーバーを構築すると、これらすべてを自動化できます。

MCPの仕組み

Cursor
    ↓ 「The New York Timesのクリスマスの記事を教えて」
MCPサーバー(今回作成するPythonプログラム)
    ↓ APIリクエストを自動生成
The NY Times API
    ↓ 記事データを返却
MCPサーバー
    ↓ 整形して返す
Cursor(ユーザーに表示)

つまり、Cursor上で自然言語を使って質問するだけで、裏側でMCPサーバーが自動的にAPIを呼び出し、結果を取得してくれます。

これによって、開発者はAPIの詳細を気にすることなく、対話的にデータを取得できるようになります。

環境構築

1. Pythonバージョンの確認

asdfを使用している場合:

# 例としてPython 3.12.0を使用
echo "python 3.12.0" > .tool-versions

2. 必要なパッケージのインストール

プロジェクトディレクトリを作成し、必要なパッケージをインストールします。

mkdir nytimes-mcp-server
cd nytimes-mcp-server
python -m venv venv
source venv/bin/activate
pip install mcp httpx python-dotenv

3. APIキーの設定

The New York TimesのDeveloper Portalでアカウントを作成し、APIキーを取得します。

.envファイルを作成し、APIキーを保存します:

NYTIMES_API_KEY=your_api_key_here

MCPサーバーの実装

次にMCPサーバー側であるnytimes_mcp_server.pyを作成します:

import os
import asyncio
import httpx
from mcp.server.fastmcp import FastMCP
from dotenv import load_dotenv

# 環境変数の読み込み
load_dotenv()
API_KEY = os.getenv("NYTIMES_API_KEY")

if not API_KEY:
    raise ValueError("NYTIMES_API_KEY環境変数が設定されていません")

# MCPサーバーの初期化
mcp = FastMCP("The New York Times MCP Server")

@mcp.tool()
async def search_articles(query: str, page: int = 0) -> dict:
    """
    The New York Timesの記事を検索します
    
    Args:
        query: 検索キーワード
        page: ページ番号(デフォルト: 0)
    
    Returns:
        検索結果の記事リスト
    """
    url = "https://api.nytimes.com/svc/search/v2/articlesearch.json"
    params = {
        "q": query,
        "page": page,
        "api-key": API_KEY
    }
    
    async with httpx.AsyncClient() as client:
        response = await client.get(url, params=params)
        response.raise_for_status()
        data = response.json()
        
        articles = []
        if "response" in data and "docs" in data["response"]:
            for doc in data["response"]["docs"][:10]:  # 最大10件
                articles.append({
                    "headline": doc.get("headline", {}).get("main", ""),
                    "abstract": doc.get("abstract", ""),
                    "web_url": doc.get("web_url", ""),
                    "pub_date": doc.get("pub_date", ""),
                    "section": doc.get("section_name", ""),
                    "byline": doc.get("byline", {}).get("original", "")
                })
        
        return {
            "total_results": data.get("response", {}).get("meta", {}).get("hits", 0),
            "articles": articles
        }

@mcp.tool()
async def get_top_stories(section: str = "home") -> dict:
    """
    The New York Timesのトップ記事を取得します
    
    Args:
        section: セクション名(home, world, politics, business, technology, science, health, sports, arts, など)
    
    Returns:
        トップ記事のリスト
    """
    url = f"https://api.nytimes.com/svc/topstories/v2/{section}.json"
    params = {"api-key": API_KEY}
    
    async with httpx.AsyncClient() as client:
        response = await client.get(url, params=params)
        response.raise_for_status()
        data = response.json()
        
        articles = []
        for article in data.get("results", [])[:10]:
            articles.append({
                "title": article.get("title", ""),
                "abstract": article.get("abstract", ""),
                "url": article.get("url", ""),
                "published_date": article.get("published_date", ""),
                "section": article.get("section", ""),
                "byline": article.get("byline", "")
            })
        
        return {
            "section": section,
            "articles": articles
        }

@mcp.tool()
async def get_most_popular(period: int = 7) -> dict:
    """
    The New York Timesで最も人気のある記事を取得します
    
    Args:
        period: 期間(1, 7, 30日のいずれか)
    
    Returns:
        人気記事のリスト
    """
    if period not in [1, 7, 30]:
        period = 7
    
    url = f"https://api.nytimes.com/svc/mostpopular/v2/viewed/{period}.json"
    params = {"api-key": API_KEY}
    
    async with httpx.AsyncClient() as client:
        response = await client.get(url, params=params)
        response.raise_for_status()
        data = response.json()
        
        articles = []
        for article in data.get("results", [])[:10]:
            articles.append({
                "title": article.get("title", ""),
                "abstract": article.get("abstract", ""),
                "url": article.get("url", ""),
                "published_date": article.get("published_date", ""),
                "section": article.get("section", ""),
                "byline": article.get("byline", "")
            })
        
        return {
            "period_days": period,
            "articles": articles
        }

@mcp.tool()
async def get_book_reviews(title: str = None, author: str = None) -> dict:
    """
    The New York Timesの書評を検索します
    
    Args:
        title: 書籍のタイトル
        author: 著者名
    
    Returns:
        書評のリスト
    """
    url = "https://api.nytimes.com/svc/books/v3/reviews.json"
    params = {"api-key": API_KEY}
    
    if title:
        params["title"] = title
    if author:
        params["author"] = author
    
    async with httpx.AsyncClient() as client:
        response = await client.get(url, params=params)
        response.raise_for_status()
        data = response.json()
        
        reviews = []
        for review in data.get("results", []):
            reviews.append({
                "book_title": review.get("book_title", ""),
                "book_author": review.get("book_author", ""),
                "summary": review.get("summary", ""),
                "url": review.get("url", ""),
                "publication_dt": review.get("publication_dt", "")
            })
        
        return {
            "reviews": reviews
        }

@mcp.tool()
async def get_best_sellers(list_name: str = "combined-print-and-e-book-fiction") -> dict:
    """
    The New York Timesのベストセラーリストを取得します
    
    Args:
        list_name: リスト名(例: combined-print-and-e-book-fiction, hardcover-fiction, など)
    
    Returns:
        ベストセラー書籍のリスト
    """
    url = f"https://api.nytimes.com/svc/books/v3/lists/current/{list_name}.json"
    params = {"api-key": API_KEY}
    
    async with httpx.AsyncClient() as client:
        response = await client.get(url, params=params)
        response.raise_for_status()
        data = response.json()
        
        books = []
        for book in data.get("results", {}).get("books", []):
            books.append({
                "rank": book.get("rank", ""),
                "title": book.get("title", ""),
                "author": book.get("author", ""),
                "description": book.get("description", ""),
                "publisher": book.get("publisher", ""),
                "amazon_product_url": book.get("amazon_product_url", ""),
                "weeks_on_list": book.get("weeks_on_list", 0)
            })
        
        return {
            "list_name": list_name,
            "display_name": data.get("results", {}).get("display_name", ""),
            "books": books
        }

if __name__ == "__main__":
    mcp.run()

Cursorへの登録

MCPサーバーを作成したら、Cursorに登録します。

1. Cursor設定ファイルの編集

Cursorの設定ファイル ~/.cursor/mcp.json を開き、以下を追加します:
.envファイルでAPIキーを管理しているので、mcp.jsonではenvセクションを省略できます:

{
  "mcpServers": {
    "nytimes": {
      "command": "/absolute/path/to/your/venv/bin/python",
      "args": [
        "/absolute/path/to/your/nytimes_mcp_server.py"
      ],
      "type": "stdio"
    }
  }
}

重要なポイント:

  • commandには仮想環境内のPythonの絶対パスを指定します
  • argsにはMCPサーバーファイルの絶対パスを指定します
  • type: "stdio"を必ず追加します

2. Cursorの再起動

設定ファイルを保存したら、Cursorを再起動してください。これでMCPサーバーが読み込まれます。

実装した機能

今回のMCPサーバーの例では、以下の機能を実装しました:

  1. 記事検索(search_articles): キーワードで記事を検索
  2. トップ記事取得(get_top_stories): セクション別のトップニュースを取得
  3. 人気記事取得(get_most_popular): 指定期間内の人気記事を取得
  4. 書評検索(get_book_reviews): 書籍のレビューを検索
  5. ベストセラーリスト(get_best_sellers): NYTのベストセラー書籍情報を取得

実際に使ってみた!

Cursor上で「ニューヨークタイムスで、クリスマスの記事を教えてください」と質問してみました。

すると、MCPサーバーが自動的に記事を検索し、以下のような結果を返してくれました:

取得できたクリスマス記事(一部)

  1. What Are Shoppers Dreaming of? A Ralph Lauren Christmas.

    • ラルフローレンのクリスマススタイルについての記事
    • 記事を読む
  2. 'A Very Jonas Christmas Movie' Review

    • ジョナス・ブラザーズのホリデーコメディ映画のレビュー
    • 記事を読む
  3. Behold! 'Christmas Adam' Is Born.

    • クリスマスイブの前日「Christmas Adam」という新しいお祝いについて
    • 記事を読む

このように、Cursorのチャット上で自然言語で質問するだけで、リアルタイムでThe New York Timesの記事が取得できるようになりました!!

まとめ

今回、The New York Times APIを活用したMCPサーバーを構築することで、Cursor上で「The New York Timesのクリスマスの記事を教えてください」と聞くだけで、リアルタイムで最新記事を取得できるようになりました。

MCPを使うメリット

  1. 自然言語でデータアクセスが可能: APIの詳細を知らなくても、普通の会話でデータを取得できる
  2. コードを書かずに情報収集ができる: HTTPリクエストやJSON解析を意識する必要がない
  3. AIチャットサービスとの統合: CursorやClaude Desktopから直接アクセス可能

MCPプロトコルの標準化により、様々なデータソースとAIアシスタントの連携がより簡単になっています。ぜひ皆さんも、お気に入りのAPIを使ってオリジナルのMCPサーバーを構築してみてください。

それでは、良いクリスマスと良いコーディングライフを!

参考リンク

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?