PDFが読めないClaudeに「目」を与えた!自作MCPで100ページ瞬間解析を実現した話
こんにちは!YaroTechです。
「このPDFファイルの内容を要約してください」
「申し訳ございませんが、PDFファイルを直接読み込むことはできません」
この会話、Claude Desktopユーザーなら誰もが経験したことがあるはずです。
でも、もうその苦労は終わりです。PDF読み込み専用のMCPを自作したら、100ページのPDFがたった5秒で読み込めるようになりました!今日はその実装方法と、実際に遭遇したトラブルも含めて全公開します。
※コメントで「公開希望!」があったら、自作MCPのコードを公開します!
🖥️ 実行環境
PC: Microsoft Surface Laptop 7th(Snapdragon X Elite / ARM64)
OS: Windows 11 Pro 24H2(ARM64版)
Claude Desktop: 最新版(Ver. 0.12.55 (d55c63))
Node.js: v20.11.0以降
TypeScript: 5.7.2
必要な空き容量: 約100MB
🎯 この記事で得られること
PDFを直接Claudeに読み込ませる方法
TypeScriptでMCPを自作する完全手順
pdf2jsonを使った実装テクニック
ファイルパスの落とし穴と解決策
実務で使える5つの活用パターン
📊 実際の成果
Before: PDF100ページをコピペ → 100分(1ページ1分×100)
After: MCP経由で一括読み込み → 5分5秒(読み込み5秒+分析5分)
削減時間: 94.9%削減!(ほぼ20倍速)
しかも、書式も表も崩れることなく、完璧に内容を理解してくれます。
🚀 実践内容
アプローチ1: PDF読み込みMCPの基本実装
まず、MCPの基本構造を作ります。今回は5つの主要機能を実装しました:
pdf_read_text - PDFからテキスト抽出
pdf_get_metadata - メタデータ取得(作成者、日時等)
pdf_extract_pages - 特定ページ範囲の抽出
pdf_search_text - PDF内テキスト検索
pdf_list_files - ディレクトリ内PDF一覧
パッケージ構成(package.json)
{
"name": "pdf-reader-mcp",
"version": "0.1.0",
"description": "PDF読み取りMCPサーバー",
"main": "dist/index.js",
"type": "module",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"pdf2json": "^3.1.4",
"glob": "^11.0.0"
},
"devDependencies": {
"@types/node": "^22.10.2",
"typescript": "^5.7.2"
}
}基本構造の実装コード(抜粋)
// src/index.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import PDFParser from 'pdf2json';
import { glob } from 'glob';
// PDFキャッシュ(メモリ効率化)
interface PDFCache {
text: string;
pageCount: number;
metadata: any;
}
const pdfCache = new Map<string, PDFCache>();
// MCPサーバーの作成
const server = new Server(
{
name: 'pdf-reader-mcp',
version: '0.1.0',
},
{
capabilities: {
tools: {}
}
}
);
// PDFファイルを読み込む関数
async function readPDF(filePath: string): Promise<PDFCache> {
// キャッシュチェック
if (pdfCache.has(filePath)) {
return pdfCache.get(filePath)!;
}
return new Promise((resolve, reject) => {
const pdfParser = new PDFParser();
pdfParser.on('pdfParser_dataError', (errData: any) => {
reject(new Error(`PDF解析エラー: ${errData.parserError}`));
});
pdfParser.on('pdfParser_dataReady', (pdfData: any) => {
try {
let fullText = '';
const pageTexts: string[] = [];
if (pdfData.Pages) {
pdfData.Pages.forEach((page: any) => {
let pageText = '';
if (page.Texts) {
page.Texts.forEach((text: any) => {
if (text.R) {
text.R.forEach((r: any) => {
if (r.T) {
// URLデコードしてテキストを取得
const decodedText = decodeURIComponent(r.T);
pageText += decodedText + ' ';
}
});
}
});
}
pageTexts.push(pageText.trim());
fullText += pageText + '\n\n';
});
}
const result: PDFCache = {
text: fullText.trim(),
pageCount: pageTexts.length,
metadata: pdfData.Meta || {}
};
// キャッシュに保存(最大10ファイル)
if (pdfCache.size >= 10) {
const firstKey = pdfCache.keys().next().value;
if (firstKey) pdfCache.delete(firstKey);
}
pdfCache.set(filePath, result);
resolve(result);
} catch (err) {
reject(err);
}
});
pdfParser.loadPDF(filePath);
});
}効果: 基本的なPDF読み込みが可能に!キャッシュで2回目以降は瞬速!
アプローチ2: 実用的な検索機能の実装
単に読み込むだけでなく、実務で使える検索機能を追加しました。
PDF内テキスト検索の実装
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'pdf_search_text') {
const { file_path, search_text, case_sensitive = false } = args;
try {
const pdfData = await readPDF(file_path);
const searchPattern = case_sensitive
? search_text
: search_text.toLowerCase();
const lines = pdfData.text.split('\n');
const results: Array<{line: number, text: string}> = [];
lines.forEach((line: string, index: number) => {
const lineToSearch = case_sensitive
? line
: line.toLowerCase();
if (lineToSearch.includes(searchPattern)) {
// 前後の文脈も含めて返す
const context = lines.slice(
Math.max(0, index - 1),
Math.min(lines.length, index + 2)
).join('\n');
results.push({
line: index + 1,
text: context
});
}
});
return {
content: [{
type: "text",
text: results.length > 0
? `検索結果: ${results.length}件\n\n` +
results.map(r => `【${r.line}行目】\n${r.text}`).join('\n\n---\n\n')
: `「${search_text}」は見つかりませんでした。`
}]
};
} catch (error: any) {
return {
content: [{
type: "text",
text: `エラー: ${error.message}`
}]
};
}
}
});複数PDFの一覧表示
if (name === 'pdf_list_files') {
const { directory, recursive = true } = args;
try {
const pattern = recursive
? path.join(directory, '**', '*.pdf')
: path.join(directory, '*.pdf');
const files = await glob(pattern, {
windowsPathsNoEscape: true
});
const fileInfos = await Promise.all(
files.map(async (file) => {
const stats = await fs.stat(file);
return {
path: file,
size: `${(stats.size / 1024 / 1024).toFixed(2)} MB`,
modified: stats.mtime.toISOString()
};
})
);
return {
content: [{
type: "text",
text: `PDFファイル一覧(${fileInfos.length}件):\n\n` +
fileInfos.map(f =>
`📄 ${path.basename(f.path)}\n` +
` パス: ${f.path}\n` +
` サイズ: ${f.size}\n` +
` 更新日: ${f.modified}`
).join('\n\n')
}]
};
} catch (error: any) {
return {
content: [{
type: "text",
text: `エラー: ${error.message}`
}]
};
}
}効果: 契約書の特定条項を秒速で発見!複数PDFの管理も楽々!
アプローチ3: Claude Desktopへの統合設定
作ったMCPをClaude Desktopで使えるように設定します。
設定ファイル(claude_desktop_config.json)
{
"mcpServers": {
"pdf-reader": {
"command": "node",
"args": ["C:\\Users\\YourName\\mcp-projects\\pdf-reader-mcp\\dist\\index.js"]
}
}
}設定ファイルの場所:
Windows: `%APPDATA%\Claude\claude_desktop_config.json`
注意: パスは必ずバックスラッシュを2つ(\)にする!
⚠️ つまずきポイントと解決策
1. 改行文字(\n)がファイルパスに入る問題 😱
問題: Claudeがファイルパスを渡すときに、なぜか改行文字が入ることがある
例: "C:\Users\YourName\Documents\n\sample.pdf"
→ \n が改行として解釈されてファイルが見つからない!解決策: パスの正規化処理を追加
// ファイルパスの正規化
function normalizePath(filePath: string): string {
// 改行文字を除去
let normalized = filePath.replace(/\n/g, '');
// バックスラッシュの正規化
normalized = normalized.replace(/\\/g, '/');
// 余分な空白を除去
normalized = normalized.trim();
return normalized;
}
// 使用時
const cleanPath = normalizePath(args.file_path);
const pdfData = await readPDF(cleanPath);2. 日本語がファイルパスに含まれるとエラー 🇯🇵
問題: 「C:\Users\YourName\ドキュメント\議事録.pdf」のような日本語パスでサーバーエラー
解決策: UTF-8エンコーディングとWindows対応
// globオプションでWindows対応
const files = await glob(pattern, {
windowsPathsNoEscape: true, // Windowsパス対応
absolute: true // 絶対パスで取得
});
// pdf2jsonは内部でUTF-8処理されるが、
// ファイルシステムアクセス時は注意
import { fileURLToPath } from 'url';
// 日本語パスを含むファイルの存在確認
try {
await fs.access(filePath);
} catch {
throw new Error(`ファイルが見つかりません: ${filePath}`);
}3. pdf-parseからpdf2jsonへの移行
問題: 最初はpdf-parseを使おうとしたが、TypeScriptの型定義エラーが解決できず
error TS7016: Could not find a declaration file for module 'pdf-parse'解決策: pdf2jsonに変更して解決!
npm uninstall pdf-parse
npm install pdf2jsonpdf2jsonの方が:
TypeScript対応が良好
日本語処理も安定
メモリ効率も良い
💡 実務での活用例
1. 契約書レビューの自動化
実際の使用例:
「C:\Users\YourName\Documents\contracts フォルダ内の全契約書から
解約条項に関する部分を抽出してください」
結果:
10件の契約書から該当箇所を5分で抽出完了!2. 技術仕様書の要約
実際の使用例:
「この200ページの仕様書から、APIエンドポイント一覧を作成してください」
結果:
45個のエンドポイントを自動で表形式に整理!3. 議事録の統合
実際の使用例:
「今月の議事録PDFをすべて読み込んで、決定事項だけをまとめてください」
結果:
15回分の会議から重要決定事項23個を抽出!🔗 関連記事
今回の内容に関連する過去記事もぜひご覧ください:
Day5: Excel自動化MCPを自作してみた!既存ツールでは物足りなかった私の挑戦 - MCP自作の基礎はこちら
Day12: 君のMCPを世界へ配布!自作Excel MCPをワンクリック導入できるDXTファイル化の魔法 - 作ったMCPを配布する方法
Day23: ChatGPTエージェントに対抗!ブラウザ操作MCPを自作してみたら想像以上に大変だった件 - TypeScriptでのMCP実装の詳細
📝 まとめ
PDF読み込みMCPを自作することで、Claudeの可能性が大きく広がりました。
実現できたこと
✅ 100ページのPDFを5秒で読み込み
✅ pdf2jsonによる安定した処理
✅ ファイルパスの落とし穴を克服
✅ 複数PDF横断検索
✅ キャッシュによる高速化
まだ残る課題
⚠️ スキャンPDF(画像PDF)のOCR未対応
⚠️ パスワード付きPDFは読めない
⚠️ 日本語パスは要注意
次のステップ
OCR機能の追加(tesseract.js検討中)
PDFの結合・分割機能
クラウドストレージ連携
もしこのMCPに興味がある方がいらっしゃったら、GitHubで公開することも検討します!コメントで教えてください。
🚀 次回予告
明日はついにDay30!30日間の集大成として、「30日間毎日投稿して分かった、続けるコツと得られたもの」をお届けします。
挫折しそうになった瞬間、モチベーション維持の秘訣、そして予想外の収穫まで、すべて正直にお話しします!
#YaroTech #ClaudeDesktop #MCP #PDF #TypeScript #pdf2json #業務効率化 #自動化
📌 この記事が参考になったら、ぜひ「スキ」をお願いします!
GitHubでの公開を希望される方は、コメントで「公開希望!」と教えてください。コメント確認したら、すぐに公開準備を始めます!
「改行文字問題」や「日本語パス問題」で困っている方がいたら、コメントで詳しい解決策をお伝えします!
いいなと思ったら応援しよう!
記事がお役に立てたなら嬉しいです!
いただいたチップは、新しいMCPツールの検証や、より深い実践実験の資金として大切に使わせていただきます。
あなたの応援が次の「AI活用の感動」を生み出す原動力になります✨
一緒に羽ばたき続けましょう!