Skip to main content
Glama

analyze-notes

Analyze note.com articles to compare content performance, identify trends, and conduct competitive research using customizable search parameters.

Instructions

記事の詳細分析を行う(競合分析やコンテンツ成果の比較等)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYes検索キーワード
sizeNo取得する件数(分析に十分なデータ量を確保するため、初期値は多め)
startNo検索結果の開始位置
sortNoソート順(new: 新着順, popular: 人気順, hot: 急上昇)popular
includeUserDetailsNo著者情報を詳細に含めるかどうか
analyzeContentNoコンテンツの特徴(画像数、アイキャッチの有無など)を分析するか
categoryNo特定のカテゴリに絞り込む(オプション)
dateRangeNo日付範囲で絞り込む(例: 7d=7日以内、2m=2ヶ月以内)
priceRangeNo価格帯(all: 全て, free: 無料のみ, paid: 有料のみ)all

Implementation Reference

  • The core handler function for the 'analyze-notes' tool. It constructs API parameters, calls the note.com search API (with authentication), extracts and formats notes, computes analytics using the analyzeNotes helper, and returns structured results or errors.
    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);
    
        // 認証が必要なエンドポイントのため、requireAuth を true に設定
        const data = await noteApiRequest(
          `/v3/searches?context=note&${params.toString()}`,
          "GET",
          null,
          true
        );
        
        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, "記事分析");
      }
    }
  • Zod schema defining the input parameters for the 'analyze-notes' tool, including query, pagination, sorting, filtering options, and analysis flags.
    {
      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: 有料のみ)"),
    },
  • Local registration of the 'analyze-notes' tool using McpServer.tool(), including name, description, schema, and handler. This function is called from src/tools/index.ts via registerSearchTools(server).
    server.tool(
      "analyze-notes",
      "記事の詳細分析を行う(競合分析やコンテンツ成果の比較等)",
      {
        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: 有料のみ)"),
      },
      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);
    
          // 認証が必要なエンドポイントのため、requireAuth を true に設定
          const data = await noteApiRequest(
            `/v3/searches?context=note&${params.toString()}`,
            "GET",
            null,
            true
          );
          
          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, "記事分析");
        }
      }
    );
  • Supporting utility function that performs statistical analysis on formatted notes, calculating engagement metrics, content features, pricing stats, and author insights. Called within the tool handler.
    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
        }
      };
    }
  • Top-level registration entry point that calls registerSearchTools(server), which in turn registers the 'analyze-notes' tool among others. Invoked from the main server file.
    export function registerAllTools(server: McpServer): void {
      // 各カテゴリのツールを登録
      registerSearchTools(server);
      registerNoteTools(server);
      registerUserTools(server);
      registerMembershipTools(server);
      registerMagazineTools(server);
      registerImageTools(server);
      registerObsidianTools(server);
      registerPublishTools(server);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shimayuz/note-com-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server