analyze-notes
Analyze note.com articles to perform competitive analysis and compare content performance metrics using search queries, date ranges, and sorting options.
Instructions
記事の詳細分析を行う(競合分析やコンテンツ成果の比較等)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | 検索キーワード | |
| size | No | 取得する件数(分析に十分なデータ量を確保するため、初期値は多め) | |
| start | No | 検索結果の開始位置 | |
| sort | No | ソート順(new: 新着順, popular: 人気順, hot: 急上昇) | popular |
| includeUserDetails | No | 著者情報を詳細に含めるかどうか | |
| analyzeContent | No | コンテンツの特徴(画像数、アイキャッチの有無など)を分析するか | |
| category | No | 特定のカテゴリに絞り込む(オプション) | |
| dateRange | No | 日付範囲で絞り込む(例: 7d=7日以内、2m=2ヶ月以内) | |
| priceRange | No | 価格帯(all: 全て, free: 無料のみ, paid: 有料のみ) | all |
Implementation Reference
- src/tools/search-tools.ts:73-115 (handler)Handler function for 'analyze-notes' tool: searches notes via API using provided parameters, formats them, analyzes using analyzeNotes helper, and returns analytics and notes.async ({ query, size, start, sort, includeUserDetails, analyzeContent, category, dateRange, priceRange }) => { try { const params = new URLSearchParams({ q: query, size: size.toString(), start: start.toString(), sort: sort }); if (category) params.append("category", category); if (dateRange) params.append("date_range", dateRange); if (priceRange !== "all") params.append("price", priceRange); const data = await noteApiRequest(`/v3/searches?context=note&${params.toString()}`); if (env.DEBUG) { console.error(`API Response structure for analyze-notes: ${JSON.stringify(data, null, 2)}`); } if (!data || !data.data) { return createErrorResponse(`APIレスポンスが空です: ${JSON.stringify(data)}`); } if (data.status === "error" || data.error) { return createErrorResponse(`APIエラー: ${JSON.stringify(data)}`); } const notesArray = safeExtractData(data, commonExtractors.notes); const totalCount = safeExtractTotal(data, notesArray.length); const formattedNotes = notesArray.map(note => formatNote(note, undefined, includeUserDetails, analyzeContent) ); const analytics = analyzeNotes(formattedNotes, query, sort); return createSuccessResponse({ analytics, notes: formattedNotes }); } catch (error) { return handleApiError(error, "記事分析"); }
- src/utils/formatters.ts:214-253 (helper)analyzeNotes helper function: performs detailed analysis on formatted notes including engagement metrics, content features, pricing stats, and author profiles.export function analyzeNotes(formattedNotes: FormattedNote[], query: string, sort: string) { return { totalFound: formattedNotes.length, analyzed: formattedNotes.length, query, sort, // エンゲージメント分析 engagementAnalysis: { averageLikes: formattedNotes.reduce((sum, note) => sum + note.likesCount, 0) / formattedNotes.length || 0, averageComments: formattedNotes.reduce((sum, note) => sum + (note.commentsCount || 0), 0) / formattedNotes.length || 0, maxLikes: Math.max(...formattedNotes.map(note => note.likesCount)), maxComments: Math.max(...formattedNotes.map(note => note.commentsCount || 0)) }, // コンテンツタイプ分析 contentTypeAnalysis: { withEyecatch: formattedNotes.filter(note => note.contentAnalysis?.hasEyecatch).length, withVideo: formattedNotes.filter(note => note.contentAnalysis?.hasVideo).length, withAudio: formattedNotes.filter(note => note.contentAnalysis?.hasAudio).length, averageImageCount: formattedNotes.reduce((sum, note) => sum + (note.contentAnalysis?.imageCount || 0), 0) / formattedNotes.length || 0 }, // 価格分析 priceAnalysis: { free: formattedNotes.filter(note => !note.isPaid).length, paid: formattedNotes.filter(note => note.isPaid).length, averagePrice: formattedNotes.filter(note => note.isPaid).reduce((sum, note) => sum + (note.price || 0), 0) / formattedNotes.filter(note => note.isPaid).length || 0, maxPrice: Math.max(...formattedNotes.map(note => note.price || 0)), minPrice: Math.min(...formattedNotes.filter(note => note.isPaid).map(note => note.price || 0)) || 0 }, // 著者分析 authorAnalysis: { uniqueAuthors: [...new Set(formattedNotes.map(note => note.author?.id))].length, averageFollowers: formattedNotes.reduce((sum, note) => sum + (note.author?.details?.followerCount || 0), 0) / formattedNotes.length || 0, maxFollowers: Math.max(...formattedNotes.map(note => note.author?.details?.followerCount || 0)), officialAccounts: formattedNotes.filter(note => note.author?.details?.isOfficial).length, withTwitterConnection: formattedNotes.filter(note => note.author?.details?.twitterConnected).length, withCustomEngagement: formattedNotes.filter(note => note.author?.details?.hasLikeAppeal || note.author?.details?.hasFollowAppeal).length } };
- src/tools/search-tools.ts:62-72 (schema)Zod input schema for analyze-notes tool defining parameters like query, size, sort, filters for analysis.{ query: z.string().describe("検索キーワード"), size: z.number().default(20).describe("取得する件数(分析に十分なデータ量を確保するため、初期値は多め)"), start: z.number().default(0).describe("検索結果の開始位置"), sort: z.enum(["new", "popular", "hot"]).default("popular").describe("ソート順(new: 新着順, popular: 人気順, hot: 急上昇)"), includeUserDetails: z.boolean().default(true).describe("著者情報を詳細に含めるかどうか"), analyzeContent: z.boolean().default(true).describe("コンテンツの特徴(画像数、アイキャッチの有無など)を分析するか"), category: z.string().optional().describe("特定のカテゴリに絞り込む(オプション)"), dateRange: z.string().optional().describe("日付範囲で絞り込む(例: 7d=7日以内、2m=2ヶ月以内)"), priceRange: z.enum(["all", "free", "paid"]).default("all").describe("価格帯(all: 全て, free: 無料のみ, paid: 有料のみ)"), },
- src/tools/index.ts:12-18 (registration)Top-level registration function registerAllTools calls registerSearchTools among others.export function registerAllTools(server: McpServer): void { // 各カテゴリのツールを登録 registerSearchTools(server); registerNoteTools(server); registerUserTools(server); registerMembershipTools(server); registerMagazineTools(server);
- src/note-mcp-server-refactored.ts:39-41 (registration)Main server initialization calls registerAllTools(server) to register all tools including analyze-notes.// ツールの登録 console.error("📝 ツールを登録中..."); registerAllTools(server);