3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Web情報取得・要約MCPサーバー

Last updated at Posted at 2025-04-16

ソースコード

主な技術とライブラリ等

@modelcontextprotocol/sdk: Model Context Protocol (MCP) を実装するためのSDK(ソフトウェア開発キット)です。これを使って、AIモデルや外部ツールと連携するためのMCPサーバー (McpServer) を構築しています。StdioServerTransport は、標準入出力(stdin/stdout)を使ってMCPクライアントと通信するための方法です。

@google/generative-ai: Googleの生成AIモデル(Gemini)を利用するための公式ライブラリです。テキスト生成などのAI機能をサーバーに組み込むために使用しています (GoogleGenerativeAI, GenerativeModel)。

express: Node.jsでWebサーバーやAPIを簡単に構築するための人気のあるフレームワークです。MCPサーバーとは別に、通常のHTTPリクエストを受け付けるためのAPIエンドポイント (/api/tools/... など) を作成するのに使われています。

sitemcp (外部ツール): このコードから child_process を介して呼び出されるコマンドラインツールです。指定されたWebサイトをクロールし、コンテンツを抽出してキャッシュする機能を持っています。Webからの情報収集に使用されます。

プロジェクト構成

リファクタリングにより、コードは機能ごとに以下のファイルに分割されています。

.
├── config.ts           # 設定管理、環境変数
├── logger.ts           # ロギング機能
├── types.ts            # TypeScript 型定義
├── repository.ts       # データ永続化層 (JSON ファイル)
├── llmService.ts       # LLM (Gemini) 連携サービス
├── siteDataProvider.ts # Webサイトデータ取得サービス (sitemcp)
├── searchService.ts    # コアビジネスロジック、ハンドラー
├── httpServer.ts       # HTTP サーバー (Express)
├── mcpServer.ts        # MCP サーバー (Stdio)
├── web-mcp.ts          # アプリケーションエントリーポイント
├── package.json
├── tsconfig.json
└── README.md

主要モジュールの解説

1. 設定 (config.ts)

  • 役割: アプリケーション全体の設定値を管理します。
  • 主な機能:
    • .env ファイルから環境変数 (GEMINI_API_KEY, MCP_SERVER_PORT, LOG_LEVEL 等) を読み込みます。
    • GEMINI_API_KEY が設定されていない場合はエラーをスローします。
    • CONFIG オブジェクトをエクスポートし、HTTP ポート、ログレベル、使用する Gemini モデル名、各種ディレクトリパス (DB_DIR, TMP_DIR, ALTERNATIVE_CACHE_DIR)、タイムアウト値、同時実行数などの設定を定義します。
    • setupDirectories 関数をエクスポートし、起動時に必要なディレクトリ(DB、一時ファイル、キャッシュ)が存在しない場合に作成します。この関数はモジュール読み込み時に自動実行されます。

2. ロギング (logger.ts)

  • 役割: 標準化されたログ出力を提供します。
  • 主な機能:
    • Logger クラスをエクスポートします。
    • コンストラクタでコンテキスト名(例: "HttpServer", "LlmService")を受け取ります。
    • config.ts で設定されたログレベル (LOG_LEVEL) に基づいて、指定レベル以上のログのみをコンソールに出力します (debug, info, warn, error)。
    • ログメッセージにはタイムスタンプとコンテキスト名が含まれ、デバッグが容易になります。

3. 型定義 (types.ts)

  • 役割: プロジェクト全体で共有される TypeScript のインターフェースを定義します。
  • 主な機能:
    • ItemInfo: 取得・保存する情報の基本構造を定義します(ID, 名前, 組織, 説明, URL, カテゴリ等)。
    • ApiSearchRequest: 検索 API のリクエストパラメータ型。
    • ApiResponse: MCP および HTTP API で共通して使用されるレスポンスの基本構造。content (表示用テキスト)、data (実際のデータ)、isError, error (エラー情報) を含みます。
    • WebsiteConfig: sitemcp でデータ収集対象とする Web サイトの設定情報(名前, URL, 有効/無効等)。
    • PageData: sitemcp が生成するキャッシュファイルのデータ構造(タイトル, コンテンツ, URL)。

4. データ永続化 (repository.ts)

  • 役割: データの永続化(ファイルシステムへの JSON 保存)を担当します。
  • 主な機能:
    • ItemRepository クラスをエクスポートします。
    • コンストラクタでデータベースディレクトリ (CONFIG.DB_DIR) のパスを受け取ります。
    • saveItem(item: ItemInfo): ItemInfo オブジェクトを db/<item.id>.json として保存します。
    • updateItem(id: string, newData: Omit<ItemInfo, "id">): 既存のアイテムを更新します。
    • findDuplicateItem(item: Partial<ItemInfo>): 名前や組織、URL に基づいて重複する可能性のあるアイテムの ID を検索します(簡易的な重複チェック)。
    • getItem(id: string): 指定された ID のアイテム情報を取得します。
    • getAllItems(): 保存されているすべてのアイテム情報を取得します。
    • getItemsByCategory(category: string): 指定されたカテゴリに一致するアイテム情報を取得します。

5. LLM 連携 (llmService.ts)

  • 役割: Google Gemini API とのすべての直接的なやり取りを担当します。
  • 主な機能:
    • LlmService クラスをエクスポートします。
    • コンストラクタで API キーとモデル名を受け取り、Gemini クライアント (GenerativeModel) を初期化します。
    • searchWithAI(query: string, category?: string): キーワードに基づいて Gemini API に問い合わせ、関連情報を構造化された ItemInfo[] として取得します。指定された JSON 形式での応答を要求するプロンプトを使用します。
    • generateMarkdownSummary(params): ItemInfo のリストを受け取り、Gemini API を利用してマークダウン形式の要約記事を生成します。
    • generateWebItems(query: string, category?: string): Gemini API の Web 検索機能(またはそれに類する機能)を使って直接 Web から情報を収集し、ItemInfo[] を生成します。
    • extractInfoFromPage(pageData: PageData, ...): sitemcp が取得した Web ページのデータ(タイトル、コンテンツ)から、指定された JSON 形式に従って ItemInfo オブジェクトを抽出します。プロンプトで構造化を指示します。
    • 各メソッド内で API レスポンスのパース(特に JSON)と基本的なエラーハンドリングを行います。

6. Web データ収集 (siteDataProvider.ts)

  • 役割: 特定の Web サイトからのデータ収集プロセス(sitemcp の実行とキャッシュ処理)を担当します。
  • 主な機能:
    • SiteDataProvider クラスをエクスポートします。
    • コンストラクタで関連する設定 (SiteDataProviderConfig) と LlmService のインスタンスを受け取ります (LlmService は取得したページコンテンツから情報を抽出するために必要)。
    • searchWebsiteWithSitemcp(website: WebsiteConfig, query: string):
      • 対象サイトのキャッシュディレクトリ(標準パスまたは代替パス)を探します。
      • キャッシュがない場合、runSitemcpWithTimeout を呼び出して sitemcp を実行し、サイトデータを取得・キャッシュします。タイムアウト処理も含まれます。
      • キャッシュが見つかった場合、processWebsiteCache を呼び出します。
      • キャッシュが見つからない/生成できない場合、フォールバック設定 (USE_FALLBACK_FOR_FAILED_SITES) に応じて generateSimpleWebItems を呼び出すか、空の配列を返します。
    • runSitemcpWithTimeout(website: WebsiteConfig, altCacheDir: string): npx sitemcp コマンドを指定された設定(同時実行数、タイムアウト等)で実行する Promise ベースのラッパーです。標準エラー出力などもロギングします。
    • processWebsiteCache(cacheDir: string, ...): キャッシュディレクトリ内の JSON ファイルを読み込み、query に関連するページを見つけ、LlmService.extractInfoFromPage を使って情報を抽出します。
    • generateSimpleWebItems(website: WebsiteConfig, query: string): sitemcp が失敗した場合やキャッシュがない場合に、サイト URL を含む基本的なフォールバック情報 (ItemInfo) を生成します。

7. コアサービス (searchService.ts)

  • 役割: アプリケーションの中心的なビジネスロジックを担い、他のサービス(Repository, LLM, SiteData)を組み合わせて機能を提供します。MCP ツールや HTTP API の具体的な処理(ハンドラー)も実装します。
  • 主な機能:
    • ItemSearchService クラスをエクスポートします。
    • コンストラクタで ItemRepository, LlmService, SiteDataProvider のインスタンスを受け取ります(Dependency Injection)。
    • searchItems(params): 検索の主要なオーケストレーションメソッド。
      • 同時実行数をチェックし、上限に達している場合は llmService.searchWithAI のみを呼び出します。
      • llmService.searchWithAI を呼び出して AI ベースの検索結果を取得します。
      • useWeb が true の場合、searchFromWeb を呼び出して Web 検索結果を取得します。
      • 結果を統合し、重複をチェック/更新 (repository.findDuplicateItem, repository.updateItem) します。
      • カテゴリでフィルタリングし、最終結果を返します。
      • 全体の処理に対するタイムアウト (CONFIG.SEARCH_TIMEOUT) を管理します。
    • searchFromWeb(query, category): Web 検索の内部ヘルパー。
      • llmService.generateWebItems を呼び出して LLM による直接 Web 検索を行います。
      • siteDataProvider.searchWebsiteWithSitemcp(TODO: 設定された TARGET_WEBSITES リストに対して) 並行して呼び出し、サイト固有のデータを取得します。 (現状は TARGET_WEBSITES が未解決のためプレースホルダー)
      • 結果を統合して返します。
    • saveItem(itemData): 受け取ったデータに ID や作成日時、デフォルトソースを付与し、repository.saveItem を呼び出して保存します。
    • ハンドラーメソッド (handleSearchItems, handleSaveItem, handleGetItemsByCategory, handleGenerateMarkdownSummary):
      • これらは元々 ItemMCPServer にあったメソッドで、各ツール/API エンドポイントのリクエストを受け取り、対応するコアロジック(searchItems, saveItem, repository.getItemsByCategory, llmService.generateMarkdownSummary 等)を呼び出し、ApiResponse 形式で結果を整形して返す役割を持ちます。
      • 入力バリデーション(例: saveItem の Zod スキーマ)や基本的なエラーハンドリングもここで行われます。

8. HTTP サーバー (httpServer.ts)

  • 役割: Express を使用して HTTP API インターフェースを提供します。
  • 主な機能:
    • HttpServer クラスをエクスポートします。
    • コンストラクタでポート番号と ItemSearchService インスタンスを受け取ります。
    • Express アプリケーションを初期化し、ミドルウェア(CORS, bodyParser.json)を設定します。
    • ルート (/, /health) および API エンドポイント (/api/tools/*) を定義します。
    • 各 API エンドポイントは、対応する ItemSearchService のハンドラーメソッド (handleSearchItems など)を呼び出します。
    • createExpressHandler ヘルパーメソッドで非同期ハンドラーをラップし、共通のエラーハンドリングを行います。
    • コンストラクタ内で app.listen() を呼び出し、HTTP サーバーを指定ポートで起動します。

9. MCP サーバー (mcpServer.ts)

  • 役割: @modelcontextprotocol/sdk を使用して MCP サーバー(Stdio トランスポート)をセットアップし、ツールを登録します。
  • 主な機能:
    • McpServerWrapper クラスをエクスポートします。
    • コンストラクタで ItemSearchService インスタンスを受け取ります。
    • McpServer インスタンスを作成します。
    • registerTools メソッド内で、利用可能な各ツール(search_items, save_item など)を server.tool() を使用して登録します。
      • ツールの名前、説明、パラメータスキーマ(Zod を使用)、そして実行する関数(ItemSearchService の対応するハンドラーメソッド)を指定します。
    • start メソッドで StdioServerTransport を作成し、server.connect() を呼び出して Stdio を介した MCP 通信を開始します。

10. エントリーポイント (web-mcp.ts)

  • 役割: アプリケーション全体の起動シーケンスを管理します。
  • 主な機能:
    • 必要なすべてのモジュール(設定、サービス、サーバーラッパー)をインポートします。
    • startServer 非同期関数内で以下の処理を行います。
      1. ItemRepository, LlmService, SiteDataProvider を初期化します(設定値や API キーを渡します)。
      2. ItemSearchService を、上記で作成したサービスインスタンスを注入して初期化します。
      3. HttpServer を、設定ポートと ItemSearchService を渡して初期化・起動します。
      4. McpServerWrapper を、ItemSearchService を渡して初期化し、start() メソッドを呼び出して MCP サーバーを起動します。
    • プロセス終了シグナル (SIGINT, SIGTERM) を捕捉し、グレースフルシャットダウン処理(現在は process.exit(0) のみ)を呼び出します。
    • uncaughtExceptionunhandledRejection に対するグローバルなエラーハンドリングを設定し、予期せぬエラーでプロセスが終了するようにします。
    • 最後に startServer() を呼び出してアプリケーションを起動します。

主要ワークフローの例

search_items の流れ

  1. クライアント (HTTP or MCP): search_items ツール/API を呼び出し、query, category, useWeb パラメータを送信。
  2. httpServer.ts or mcpServer.ts: リクエストを受け取り、バリデーション後、ItemSearchService.handleSearchItems を呼び出す。
  3. searchService.ts (handleSearchItems):
    • searchItems メソッドを呼び出す。
  4. searchService.ts (searchItems):
    • 同時実行数をチェック。
    • LlmService.searchWithAI を呼び出し、AI ベースの結果を取得。
    • useWeb が true の場合:
      • searchFromWeb を呼び出す。
      • searchService.ts (searchFromWeb):
        • LlmService.generateWebItems を呼び出し、LLM 直接検索結果を取得。
        • (TARGET_WEBSITES リストに基づき) SiteDataProvider.searchWebsiteWithSitemcp を各サイトに対して呼び出す。
        • siteDataProvider.ts (searchWebsiteWithSitemcp):
          • キャッシュ確認。なければ sitemcp 実行 (runSitemcpWithTimeout)。
          • キャッシュ処理 (processWebsiteCache)。
          • siteDataProvider.ts (processWebsiteCache):
            • 該当ページが見つかれば LlmService.extractInfoFromPage を呼び出し情報抽出。
        • 結果を統合して返す。
      • AI 結果と Web 結果を統合・重複処理 (repository.findDuplicateItem, updateItem)。
    • カテゴリでフィルタリング。
    • タイムアウト処理。
    • 最終結果を handleSearchItems に返す。
  5. searchService.ts (handleSearchItems): 結果を ApiResponse 形式に整形。
  6. httpServer.ts or mcpServer.ts: 整形されたレスポンスをクライアントに返す。

generate_markdown_summary の流れ

  1. クライアント (HTTP or MCP): generate_markdown_summary ツール/API を呼び出し、title, category 等のパラメータを送信。
  2. httpServer.ts or mcpServer.ts: リクエストを受け取り、ItemSearchService.handleGenerateMarkdownSummary を呼び出す。
  3. searchService.ts (handleGenerateMarkdownSummary):
    • category に基づいて ItemRepository.getItemsByCategory または getAllItems を呼び出し、対象のアイテムリストを取得。
    • 取得したアイテムリストとパラメータを LlmService.generateMarkdownSummary に渡す。
    • llmService.ts (generateMarkdownSummary):
      • アイテムリストを JSON 文字列化。
      • 適切なプロンプトを生成し、Gemini API を呼び出す。
      • 結果のマークダウンテキストを返す。
    • 結果を ApiResponse 形式に整形。
  4. httpServer.ts or mcpServer.ts: 整形されたレスポンスをクライアントに返す。
3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?