この記事自体もClaudeが書いています。
はじめに
こんにちは。
X(Twitter)の記事を PowerPoint で共有したいと思ったことはありませんか?
※ただし、Xの記事自体は、ポストした人(ユーザ)の著作物なので、そのポストしたユーザの著作権を尊重してください。
通常であれば、ツイート内容を手動でスライドに入力し、手作業でレイアウトを整える必要があります。
しかし、Claude Desktop の MCP(Model Context Protocol) を使えば、この作業を完全に自動化できます。
この記事では、x-to-pptx MCP という X のツイートから自動で PowerPoint ファイルを生成するツールの実装方法と使い方を、実例を交えて解説します。
目次
- はじめに
- x-to-pptx MCP とは
- 動作の仕組み
- 環境構築
- セットアップ手順
- 使用方法
- 実装のコツ
- おわりに
x-to-pptx MCP とは
x-to-pptx MCP は、X(Twitter)のツイート URL から自動的に PowerPoint ファイルを生成する Claude Desktop 用の MCP(Model Context Protocol)サーバーです。
できること
- ツイート URL を入力 → Puppeteer がツイート内容をスクレイピング
- コンテンツを抽出 → タイトル、本文、著者名、画像を自動取得
- PowerPoint を生成 → python-pptx で美しいスライドを作成
- 自動保存 → Windows のダウンロードフォルダに PPTX ファイルを保存
用途
- 技術情報をまとめた記事の共有
- X のスレッドをスライド資料に変換
- チーム内での情報共有フロー の自動化
- ツイート情報からプレゼンテーション資料を自動生成
動作の仕組み
全体フロー
-
入力: X のツイート URL(例:
https://x.com/user/status/123456789) - スクレイピング: Puppeteer がブラウザでツイートを読み込み、テキスト・著者名・画像を抽出
- データ処理: 抽出したデータを JSON 形式に変換
- PPTX 生成: python-pptx が複数スライドを自動生成
-
出力:
X_Article_YYYYMMDD_HHMMSS.pptxとして保存
技術スタック
- Node.js + Puppeteer: X のコンテンツを動的にスクレイピング
- Python + python-pptx: PowerPoint スライドを生成
- Claude Desktop MCP: Claude がツールを呼び出すインターフェース
なぜ MCP を使うのか?
MCP は Claude Desktop 上で外部ツールを拡張可能にするプロトコルです。このアーキテクチャにより:
- Claude が直接 Python や Node.js スクリプトを実行可能
- 複雑な処理を段階的に実行可能
- エラーハンドリングが簡潔
環境構築
前提条件
以下をすべて用意してください:
- Claude Desktop アプリ
- Python 3.12 以上
- Node.js LTS 版
- インターネット接続
Python 3.12 のインストール
- Python 公式サイト からダウンロード
- インストール時に 「Add Python 3.12 to PATH」にチェック を入れる
- ターミナルで確認:
python --version
# Python 3.12.x が表示されれば成功
Node.js のインストール
- Node.js 公式サイト から LTS 版をダウンロード
- インストーラーを実行(デフォルト設定で OK)
- ターミナルで確認:
node --version
npm --version
# バージョン番号が表示されれば成功
Claude Desktop のインストール
- Claude Desktop ダウンロード ページから入手
- インストールして起動
- Anthropic アカウントでログイン
セットアップ手順
ステップ 1: プロジェクトフォルダを作成
Windows PowerShell を「管理者として実行」して、以下を実行:
mkdir "C:\Users\[ユーザー名]\Claude_MCP\x-to-pptx-mcp"
cd "C:\Users\[ユーザー名]\Claude_MCP\x-to-pptx-mcp"
[ユーザー名] は自分の Windows ユーザー名に置き換えてください。
ステップ 2: package.json を作成
テキストエディタで以下の内容を保存:
{
"name": "x-to-pptx-mcp",
"version": "1.0.0",
"type": "module",
"dependencies": {
"puppeteer": "^21.0.0"
}
}
ステップ 3: npm パッケージをインストール
npm install
ステップ 4: index.js を作成
テキストエディタで以下の内容を index.js として保存:
// index.js - Puppeteer スクレイピング版 MCP サーバー
import { spawn } from "child_process";
import path from "path";
import { fileURLToPath } from "url";
import readline from "readline";
import puppeteer from "puppeteer";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
class MCPServer {
constructor() {
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false,
});
this.rl.on("line", (line) => {
try {
const message = JSON.parse(line);
this.handleMessage(message);
} catch (e) {
// サイレント
}
});
}
handleMessage(message) {
const { jsonrpc, id, method, params } = message;
if (method === "initialize") {
this.respond(id, {
protocolVersion: "2024-11-05",
capabilities: { tools: {} },
serverInfo: {
name: "x-to-pptx-mcp",
version: "1.0.0",
},
});
} else if (method === "tools/list") {
this.respond(id, {
tools: [
{
name: "x_article_fetch",
description:
"X のツイート URL からコンテンツを取得します。",
inputSchema: {
type: "object",
properties: {
x_url: {
type: "string",
description: "X のツイート URL",
},
},
required: ["x_url"],
},
},
{
name: "x_to_pptx",
description:
"X の記事情報から PPTX ファイルを生成します。",
inputSchema: {
type: "object",
properties: {
x_url: {
type: "string",
description: "X の記事 URL",
},
title: {
type: "string",
description: "記事のタイトル",
},
content: {
type: "string",
description: "記事の本文",
},
author: {
type: "string",
description: "著者名",
},
image_url: {
type: "string",
description: "画像 URL(オプション)",
},
},
required: ["x_url", "title", "content", "author"],
},
},
],
});
} else if (method === "tools/call") {
const { name, arguments: args } = params;
if (name === "x_article_fetch") {
this.fetchXArticlePuppeteer(args, id);
} else if (name === "x_to_pptx") {
this.generatePPTX(args, id);
} else {
this.respond(id, { error: `Unknown tool: ${name}` });
}
}
}
async fetchXArticlePuppeteer(args, id) {
let browser = null;
try {
const { x_url } = args;
if (!x_url.includes("x.com") && !x_url.includes("twitter.com")) {
this.respond(id, {
content: [
{
type: "text",
text: "❌ エラー: X の URL が正しくありません。",
},
],
});
return;
}
browser = await puppeteer.launch({
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();
await page.goto(x_url, {
waitUntil: "networkidle2",
timeout: 30000,
});
const tweetData = await page.evaluate(() => {
const tweetTextElement = document.querySelector(
'[data-testid="tweet"] [data-testid="tweetText"]'
);
const text = tweetTextElement
? tweetTextElement.innerText
: "テキスト取得失敗";
const authorElement = document.querySelector(
'[data-testid="tweet"] [data-testid="Tweet-User-Name"]'
);
const author = authorElement
? authorElement.innerText
: "不明";
const imageElements = document.querySelectorAll(
'[data-testid="tweet"] img[alt*="画像"]'
);
let imageUrl = null;
if (imageElements.length > 0) {
imageUrl = imageElements[0].src;
}
return {
text,
author,
imageUrl,
};
});
await browser.close();
const title =
tweetData.text.substring(0, 100) +
(tweetData.text.length > 100 ? "..." : "");
const authorName = tweetData.author.split("\n")[0];
this.respond(id, {
content: [
{
type: "text",
text: `✅ ツイート内容を取得しました!
【タイトル】
${title}
【本文】
${tweetData.text}
【著者】
${authorName}
${tweetData.imageUrl ? `【画像 URL】\n${tweetData.imageUrl}` : ""}`,
},
],
});
} catch (error) {
if (browser) await browser.close();
this.respond(id, {
content: [
{
type: "text",
text: `❌ スクレイピングエラー: ${error.message}`,
},
],
});
}
}
generatePPTX(args, id) {
const pythonScript = path.join(__dirname, "generate_pptx.py");
const pythonPath =
"C:\\Users\\[ユーザー名]\\AppData\\Local\\Programs\\Python\\Python312\\python.exe";
const argsWithDefaults = {
...args,
image_url: args.image_url || "",
};
const process = spawn(
pythonPath,
[pythonScript, JSON.stringify(argsWithDefaults)],
{
stdio: ["pipe", "pipe", "pipe"],
}
);
let stdout = "";
let stderr = "";
process.stdout.on("data", (data) => {
stdout += data.toString();
});
process.stderr.on("data", (data) => {
stderr += data.toString();
});
process.on("close", (code) => {
if (code === 0) {
this.respond(id, {
content: [
{
type: "text",
text: stdout,
},
],
});
} else {
this.respond(id, {
content: [
{
type: "text",
text: `❌ PPTX 生成エラー:\n${stderr}`,
},
],
});
}
});
}
respond(id, result) {
const response = {
jsonrpc: "2.0",
id,
result,
};
console.log(JSON.stringify(response));
}
}
new MCPServer();
[ユーザー名] を自分の Windows ユーザー名に置き換えてください。
ステップ 5: generate_pptx.py を作成
テキストエディタで以下の内容を generate_pptx.py として保存:
# -*- coding: utf-8 -*-
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
import json
import os
from datetime import datetime
from pathlib import Path
try:
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
import requests
except ImportError:
print("❌ 必要なパッケージがありません: pip install python-pptx pillow requests")
sys.exit(1)
def generate_pptx(args):
x_url = args.get("x_url", "")
title = args.get("title", "無題")
content = args.get("content", "内容なし")
author = args.get("author", "不明")
image_url = args.get("image_url", "")
output_dir = Path.home() / "Downloads"
output_dir.mkdir(parents=True, exist_ok=True)
prs = Presentation()
prs.slide_width = Inches(10)
prs.slide_height = Inches(7.5)
# スライド1: タイトル
slide1 = prs.slides.add_slide(prs.slide_layouts[6])
background = slide1.background
fill = background.fill
fill.solid()
fill.fore_color.rgb = RGBColor(25, 29, 34)
title_box = slide1.shapes.add_textbox(
Inches(0.5), Inches(2), Inches(9), Inches(2)
)
title_frame = title_box.text_frame
title_frame.word_wrap = True
title_frame.text = title
title_frame.paragraphs[0].font.size = Pt(44)
title_frame.paragraphs[0].font.bold = True
title_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
author_box = slide1.shapes.add_textbox(
Inches(0.5), Inches(4.2), Inches(9), Inches(1)
)
author_frame = author_box.text_frame
author_frame.text = f"著者: {author}"
author_frame.paragraphs[0].font.size = Pt(18)
author_frame.paragraphs[0].font.color.rgb = RGBColor(113, 118, 123)
url_box = slide1.shapes.add_textbox(
Inches(0.5), Inches(5.5), Inches(9), Inches(1)
)
url_frame = url_box.text_frame
url_frame.text = f"Source: {x_url}"
url_frame.paragraphs[0].font.size = Pt(12)
url_frame.paragraphs[0].font.color.rgb = RGBColor(113, 118, 123)
# スライド2: 記事概要
slide2 = prs.slides.add_slide(prs.slide_layouts[6])
background2 = slide2.background
fill2 = background2.fill
fill2.solid()
fill2.fore_color.rgb = RGBColor(247, 249, 249)
heading = slide2.shapes.add_textbox(
Inches(0.5), Inches(0.5), Inches(9), Inches(0.8)
)
heading_frame = heading.text_frame
heading_frame.text = "📝 記事概要"
heading_frame.paragraphs[0].font.size = Pt(32)
heading_frame.paragraphs[0].font.bold = True
heading_frame.paragraphs[0].font.color.rgb = RGBColor(25, 29, 34)
content_box = slide2.shapes.add_textbox(
Inches(0.5), Inches(1.5), Inches(9), Inches(5.5)
)
content_frame = content_box.text_frame
content_frame.word_wrap = True
content_frame.text = content
content_frame.paragraphs[0].font.size = Pt(16)
content_frame.paragraphs[0].font.color.rgb = RGBColor(25, 29, 34)
# スライド3: 画像(オプション)
if image_url:
slide3 = prs.slides.add_slide(prs.slide_layouts[6])
background3 = slide3.background
fill3 = background3.fill
fill3.solid()
fill3.fore_color.rgb = RGBColor(247, 249, 249)
img_heading = slide3.shapes.add_textbox(
Inches(0.5), Inches(0.5), Inches(9), Inches(0.8)
)
img_heading_frame = img_heading.text_frame
img_heading_frame.text = "🖼️ 参考画像"
img_heading_frame.paragraphs[0].font.size = Pt(32)
img_heading_frame.paragraphs[0].font.bold = True
img_heading_frame.paragraphs[0].font.color.rgb = RGBColor(25, 29, 34)
try:
response = requests.get(image_url, timeout=10)
temp_img_path = output_dir / "temp_image.jpg"
with open(temp_img_path, 'wb') as f:
f.write(response.content)
slide3.shapes.add_picture(
str(temp_img_path),
Inches(1),
Inches(1.5),
width=Inches(8)
)
temp_img_path.unlink()
except Exception as e:
print(f"⚠️ 画像追加失敗: {e}", file=sys.stderr)
# ファイル保存
filename = f"X_Article_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pptx"
filepath = output_dir / filename
prs.save(str(filepath))
return str(filepath)
if __name__ == "__main__":
if len(sys.argv) > 1:
try:
args = json.loads(sys.argv[1])
filepath = generate_pptx(args)
print(f"✅ PPTX ファイルを生成しました")
print(f"📁 保存先: {filepath}")
print(f'PowerShell: start "{filepath}"')
print(f"🎉 処理完了!")
except Exception as e:
print(f"❌ エラー: {e}", file=sys.stderr)
sys.exit(1)
else:
print("❌ 引数がありません", file=sys.stderr)
sys.exit(1)
ステップ 6: Python パッケージをインストール
PowerShell を「管理者として実行」して:
pip install python-pptx pillow requests --break-system-packages
ステップ 7: Claude Desktop の設定を編集
以下のパスにある claude_desktop_config.json を開く:
C:\Users\[ユーザー名]\AppData\Local\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude_desktop_config.json
以下の内容を追加:
{
"mcpServers": {
"x-to-pptx": {
"command": "node",
"args": [
"C:\\Users\\[ユーザー名]\\Claude_MCP\\x-to-pptx-mcp\\index.js"
]
}
}
}
ステップ 8: Claude Desktop を再起動
Claude Desktop を完全に終了して、再度起動してください。
使用方法
基本的な使い方
Claude Desktop で以下のように入力:
URL: https://x.com/user/status/1234567890 を x_to_pptx でまとめて。
処理フロー
- 入力: X のツイート URL を指定
-
ツイート取得: Claude が
x_article_fetchツールを呼び出し - コンテンツ抽出: タイトル、本文、著者、画像を自動取得
-
PPTX 生成: Claude が
x_to_pptxツールを呼び出し -
ファイル保存:
C:\Users\[ユーザー名]\Downloads\X_Article_YYYYMMDD_HHMMSS.pptxに保存
出力ファイルの確認
生成されたファイルは Windows のダウンロードフォルダに保存されます。
自動的に PowerPoint が開きます。
実装のコツ
カスタマイズ例
1. スライドデザインの変更
generate_pptx.py の色設定を編集:
# ダークテーマ(現在)
fill.fore_color.rgb = RGBColor(25, 29, 34)
# 自分の好みに変更
fill.fore_color.rgb = RGBColor(100, 150, 200) # 青系
2. フォントサイズの調整
title_frame.paragraphs[0].font.size = Pt(44) # タイトル
content_frame.paragraphs[0].font.size = Pt(16) # 本文
3. スライド数の追加
generate_pptx.py に新しい slide を追加:
slide4 = prs.slides.add_slide(prs.slide_layouts[6])
# スライド4の設定...
トラブルシューティング
| エラー | 原因 | 対処法 |
|---|---|---|
Command not found: python |
Python が PATH に登録されていない | Python を再インストール(PATH チェック) |
Module not found: pptx |
python-pptx がインストールされていない |
pip install python-pptx を実行 |
Puppeteer timeout |
ネットワーク接続が遅い | URL を再度確認、ネット接続を確認 |
| Claude Desktop でツール表示なし | MCP サーバーが起動していない | Claude Desktop を再起動 |
おわりに
x-to-pptx MCP は、X のコンテンツを効率的に PowerPoint に変換できる強力なツールです。
このツールで実現できること
- 手作業でのコンテンツコピーが不要
- デザイン統一されたスライドが自動生成
- チーム内での情報共有が加速
- コンテンツライブラリの構築が容易
次のステップ
- 環境構築を完了 → セットアップ手順に従う
- テスト実行 → サンプルツイート URL で試す
- カスタマイズ → デザインや機能を自分好みに調整
- 運用自動化 → 日常のワークフローに組み込む
分かりやすい記事を書くことは、読者に情報を効果的に伝えるためのスキルです。
このツールが、皆さんの情報発信をより効率的にしてくれることを願っています!
ぜひ実装してみて、フィードバックがあればお聞きかせください。




