0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TypeScript:Qiitaと連携するMCPを作って記事を分析してもらおう!

0
Last updated at Posted at 2025-08-07

まえがき

こんにちは、ECN技術部所属Fuseです。
突然ですが、皆さんQiitaで記事は書いていますか?
記事をうまいこと伸ばすにはタイトルやタグが重要です。
私にはその辺のセンスがないのでQiitaと連携するMCPを作ってClaudeforDesktop(以下Claude)に人気記事の傾向を調べてもらうことにしました。

MCPサーバを作る

環境構築

まずはプロジェクトを生成し移動します。

npx @modelcontextprotocol/create-server qiita-search-server
cd qiita-search-server

その後データを取得するためのaxiosとトークンを環境変数から得るためのdotenvを導入。

yarn add axios dotenv

実装

ここからコードを書き始めていきます。
まずはAPI用のクライアントを用意します。

src/api/client.ts
import axios from "axios";
import { configDotenv } from "dotenv";
configDotenv({ quiet: true });
const axios_client = axios.create({
    baseURL: "https://qiita.com",
    headers: {
        Authorization: `Bearer ${process.env.QIITA_API_KEY}`,
    }
})
export default axios_client

configDotenv()を忘れるとトークンを取得できず401になるので気を付けましょう(1敗)
次にAPI周りの型情報を用意します。

src/types/schema.ts
export type Article = {
    body: string
    comments_count: number
    created_at: string
    id: string
    likes_count: number
    reactions_count: number
    stocks_count: number
    tags: Tag[]
    title: string
    updated_at: string
    url: string
    user: User
    page_views_count: number | null
    organization_url_name: string | null
}
export type SmallArticle = {
    comments_count: number
    created_at: string
    id: string
    likes_count: number
    reactions_count: number
    stocks_count: number
    tags: Tag[]
    title: string
    updated_at: string
    url: string
    user: User
    page_views_count: number | null
    organization_url_name: string | null
}
export type Tag = {
    name: string
    versions: string[]
}

export type User = {
    description: string
    facebook_id: string
    followees_count: number
    followers_count: number
    github_login_name: string
    id: string
    items_count: number
    linkedin_id: string
    location: string
    name: string
    organization: string
    permanent_id: number
    profile_image_url: string
    team_only: boolean
    twitter_screen_name: string
    website_url: string
}

そしたら次に処理用の関数を作成します。まずは検索処理。

src/handlers/SearchArticleHandler.ts
import axios_client from "../api/client.js";
import { Article } from "../types/schema.js";

const SearchArticleHandler = async (query: string, per_page = 100, page = 1) => {
    const param = new URLSearchParams()//400を防ぐために必ずURLSearchParamsを使う
    param.append("query", query)
    if (per_page) {
        param.append("per_page", per_page.toString())
    }
    if (page) {
        param.append("page", page.toString())
    }
    const result = await axios_client.get<Article[]>(`/api/v2/items?${param.toString()}`)
    return result.data.map(e => {
        return {
            comments_count: e.comments_count,
            created_at: e.created_at,
            id: e.id,
            likes_count: e.likes_count,
            reactions_count: e.reactions_count,
            stocks_count: e.stocks_count,
            tags: e.tags,
            title: e.title,
            updated_at: e.updated_at,
            url: e.url,
            user: e.user,
            page_views_count: e.page_views_count,
            organization_url_name: e.organization_url_name,
        }
    })//いらない情報や大きすぎる本文をそぎ落として小さくする
}
export default SearchArticleHandler

本文情報をそぎ落とすことで情報の肥大化によるエラーを防いでいます。
あとは記事の詳細取得処理。

src/handlers/GetArticleHandler.ts
import axios_client from "../api/client.js";
import { Article } from "../types/schema.js";

const GetArticleHandler = async (id: string) => {
    const result = await axios_client.get<Article>(`/api/v2/items/${id}`)
    return {
        body: result.data.body,
        comments_count: result.data.comments_count,
        created_at: result.data.created_at,
        id: result.data.id,
        likes_count: result.data.likes_count,
        reactions_count: result.data.reactions_count,
        stocks_count: result.data.stocks_count,
        tags: result.data.tags,
        title: result.data.title,
        updated_at: result.data.updated_at,
        url: result.data.url,
        user: result.data.user,
        page_views_count: result.data.page_views_count,
        organization_url_name: result.data.organization_url_name,
    }
}
export default GetArticleHandler

最後にsrc/index.tsを改造すればMCPサーバは完成です。

src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import SearchArticleHandler from "./handlers/SearchArticleHandler.js";
import GetArticleHandler from "./handlers/GetArticleHandler.js";
const server = new Server(
  {
    name: "qiita-search-server",
    version: "0.1.0",
  },
  {
    capabilities: {
      resources: {},
      tools: {},
      prompts: {},
    },
  }
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "search_article",
        description: "Search article from Qiita",
        inputSchema: {
          type: "object",
          properties: {
            query: {
              type: "string",
              description: `
              search query 
              * basic syntax
                * word1 word2 (AND) | "phrase" | A OR B | (A|B) AND C
              * search by fields
                * (title|body|code|tag|user|org):(word1|word2|...)
              * search by date
                * (created|updated):(>=yyyy-mm-dd)
              `.trim()
            },
            per_page: {
              type: "number",
              description: "Number of elements per page (1 to 100)"
            },
            page: {
              type: "number",
              description: "page number (1 to 100)"
            },
          },
          required: ["query"]
        }
      },
      {
        name: "get_article",
        description: "Get article body and data from Qiita by id of article.",
        inputSchema: {
          type: "object",
          properties: {
            id: {
              type: "string",
              description: `id of article`
            },
          },
          required: ["id"]
        }
      }
    ]
  };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  switch (request.params.name) {
    case "search_article": {
      const query = String(request.params.arguments?.query);
      const page = Number(request.params.arguments?.page);
      const per_page = Number(request.params.arguments?.per_page);
      if (!query) {
        throw new Error("Query are required");
      }

      const result = await SearchArticleHandler(query, per_page, page)

      return {
        content: [{
          type: "text",
          mimeType: "application/json",
          text: JSON.stringify(result, null, 2)
        }]
      };
    }
    case "get_article": {
      const id = String(request.params.arguments?.id);
      if (!id) {
        throw new Error("ID are required");
      }

      const result = await GetArticleHandler(id)

      return {
        content: [{
          type: "text",
          mimeType: "application/json",
          text: JSON.stringify(result, null, 2)
        }]
      };
    }

    default:
      throw new Error("Unknown tool");
  }
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch((error) => {
  console.error("Server error:", error);
  process.exit(1);
});

最後にyarn build でビルドすれば作業は完了。Claude側に移りましょう。

Claudeで使う

Qiitaでトークンを発行する

まずはQiitaAPIを使うのに必要なトークンを発行します。
今回は書き込みは行わないのでスコープはread_qiitaのみで十分です。
スクリーンショット 2025-08-07 105445.png
発行したトークンはこの後使うので忘れずにコピーしておきましょう。

MCPサーバを登録する

次に作ったMCPサーバをClaudeに登録していきます。

    "qiita-search-server": {
      "command": "node",
      "args": [
        "{プロジェクトまでのパス}\\qiita-search-server\\build\\index.js"
      ],
      "env": {
        "QIITA_API_KEY": "{先ほど発行したトークン}"
      }
    }

検索とツールボタンを押したときの選択肢にqiita-search-serverが追加されていれば成功です。

実際に質問してみる

入力

Qiita上のMCPタグが付いている直近の記事50件からいいね数上位5つを抽出、タイトルの傾向をまとめてください。

生成された検索クエリ

{
  `page`: 1,
  `query`: `tag:MCP`,
  `per_page`: 50
}

出力

QiitaでMCPタグが付いている記事を検索して、いいね数上位5件のタイトル傾向を分析しますね。これで50件の記事を取得しました。次に、いいね数でソートして上位5件を抽出します。Qiita上のMCPタグが付いた直近50件の記事から、いいね数上位5件を分析した結果をお示ししました。

**主な傾向として以下が浮かび上がりました:**

1. **実践的なハンズオン記事が圧倒的に人気** - 概念説明より具体的な実装方法を求めている

2. **AWS等のクラウドプラットフォームとの連携事例が高評価** - エンタープライズでの活用可能性が注目されている  

3. **Serena MCP Serverが特に注目** - プロジェクト全体把握や開発効率化に特化したツールとして評価

4. **開発者の生産性向上に直結する内容が支持される** - 業務自動化、効率化への関心の高さ

5. **具体的なツール名やベネフィットを明記したタイトルが効果的** - 読者にとって内容が明確で検索しやすい

MCPは単なる技術トレンドではなく、実際の開発現場で価値を生み出すツールとして認識され、実用的な活用事例に大きな関心が集まっていることがわかります。

感想

きちんとタグ検索を行ってくれていました。
傾向に対する感想としてはやはりハンズオン系の記事が人気らしいですね。他プラットフォームとの連携記事にも今後挑戦してみたいです。


入力

Qiita上のorganizations"ecn"と紐づいている直近の記事50件のタグとタイトルの傾向をまとめてください。

生成された検索クエリ

{
  `page`: 1,
  `query`: `org:ecn`,
  `per_page`: 50
}

出力

Qiita上の組織"ecn"に関連する記事を検索して、直近50件の記事のタグとタイトルの傾向を調べてみます。検索結果では5件しか取得できませんでした。より多くの記事を取得するために、条件を調整して再度検索してみます。組織"ecn"に紐づいている記事が現在5件のみのようです。これらの記事を分析してタグとタイトルの傾向をまとめます。組織"ecn"に関連するQiita記事の分析結果をまとめます。現在確認できる記事は5件と限られていますが、明確な傾向が見えています。

## Qiita組織"ecn"の記事傾向分析(直近5件)

### **タグの傾向**
**最頻出タグ:**
- **生成AI**: 5回(全記事に登場)
- **ChatGPT**: 2回
- **MCP**: 2回  
- **Claude**: 2回
- **AI**: 2回

その他のタグとして、Go、ネタ、DocBase、プロンプトエンジニアリング、ClaudeCode、擬人化、MyGPTsがそれぞれ1回ずつ登場しています。

### **タイトルの傾向**

**構造的特徴:**
- 多くのタイトルでコロン(:)を使った「技術/ツール名:具体的な内容」の形式を採用
- 実践的な取り組みや実験を紹介する記事が中心

**キーワード頻度:**
- **Claude**: 4回
- **MyGPTs/MyGPT**: 4回(合計)
- **Go**: 2回
- **MCP**: 2回

### **内容の特徴**
1. **生成AI関連技術への強い関心**: 全記事が生成AIに関連
2. **実践・実験志向**: 実際にツールを使って何かを作る記事が多い
3. **技術連携への注目**: MCPサーバーやAPI連携などの技術統合に関心
4. **ユニークな活用法**: 「自爆ボタン」「検閲AI」「概念擬人化」など創造的なアプローチ

### **時期**
- 投稿期間: 2025年8月1日〜8月5日(非常に最近の投稿)
- 投稿頻度: 短期間に集中的に投稿されている

### **総評**
組織"ecn"は生成AI、特にClaude、ChatGPT、MCPといった最新の AI技術に関する実践的な記事を積極的に投稿している組織と言えます。技術的な実験や新しい活用方法の探求に重点を置いており、実装例やハンズオン的な内容が中心となっています。

感想

私自身もこの記事を書くまで知らなかった組織を使った検索にもばっちり対応してくれました。
傾向に関する感想としては生成AIメインの組織として評価してもらえたのはかなりうれしいですね。今後も積極的に記事を書いていこうと思います。

あとがき

いかかでしたか?今回はQiitaと連携するMCPサーバを作り、人気記事の傾向や自社の記事の傾向などを分析してもらいました。今後もAIを使った様々な記事を積極的に投稿していこうと思いますので今後ともよろしくお願いします。



株式会社ECNはPHP、JavaScriptを中心にお客様のご要望に合わせたwebサービス、システム開発を承っております。 ビジネスの最初から最後までをサポートを行い お客様のイメージに合わせたWebサービス、システム開発、デザインを行います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?