はじめに
皆さん、メリークリスマス! アディクシィ株式会社の野中康生と申します。
ちなみに日本では、クリスマスイブと当日の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の記事を取得するには:
- APIキーを取得
- HTTPリクエストを手動で組み立てる
- レスポンスをパースして表示
- エラーハンドリングを実装
といった手順が必要でした。しかし、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サーバーの例では、以下の機能を実装しました:
- 記事検索(search_articles): キーワードで記事を検索
- トップ記事取得(get_top_stories): セクション別のトップニュースを取得
- 人気記事取得(get_most_popular): 指定期間内の人気記事を取得
- 書評検索(get_book_reviews): 書籍のレビューを検索
- ベストセラーリスト(get_best_sellers): NYTのベストセラー書籍情報を取得
実際に使ってみた!
Cursor上で「ニューヨークタイムスで、クリスマスの記事を教えてください」と質問してみました。
すると、MCPサーバーが自動的に記事を検索し、以下のような結果を返してくれました:
取得できたクリスマス記事(一部)
-
What Are Shoppers Dreaming of? A Ralph Lauren Christmas.
- ラルフローレンのクリスマススタイルについての記事
- 記事を読む
-
'A Very Jonas Christmas Movie' Review
- ジョナス・ブラザーズのホリデーコメディ映画のレビュー
- 記事を読む
-
Behold! 'Christmas Adam' Is Born.
- クリスマスイブの前日「Christmas Adam」という新しいお祝いについて
- 記事を読む
このように、Cursorのチャット上で自然言語で質問するだけで、リアルタイムでThe New York Timesの記事が取得できるようになりました!!
まとめ
今回、The New York Times APIを活用したMCPサーバーを構築することで、Cursor上で「The New York Timesのクリスマスの記事を教えてください」と聞くだけで、リアルタイムで最新記事を取得できるようになりました。
MCPを使うメリット
- 自然言語でデータアクセスが可能: APIの詳細を知らなくても、普通の会話でデータを取得できる
- コードを書かずに情報収集ができる: HTTPリクエストやJSON解析を意識する必要がない
- AIチャットサービスとの統合: CursorやClaude Desktopから直接アクセス可能
MCPプロトコルの標準化により、様々なデータソースとAIアシスタントの連携がより簡単になっています。ぜひ皆さんも、お気に入りのAPIを使ってオリジナルのMCPサーバーを構築してみてください。
それでは、良いクリスマスと良いコーディングライフを!