ソースコード
主な技術とライブラリ等
@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
の対応するハンドラーメソッド)を指定します。
- ツールの名前、説明、パラメータスキーマ(Zod を使用)、そして実行する関数(
-
start
メソッドでStdioServerTransport
を作成し、server.connect()
を呼び出して Stdio を介した MCP 通信を開始します。
-
10. エントリーポイント (web-mcp.ts
)
- 役割: アプリケーション全体の起動シーケンスを管理します。
-
主な機能:
- 必要なすべてのモジュール(設定、サービス、サーバーラッパー)をインポートします。
-
startServer
非同期関数内で以下の処理を行います。-
ItemRepository
,LlmService
,SiteDataProvider
を初期化します(設定値や API キーを渡します)。 -
ItemSearchService
を、上記で作成したサービスインスタンスを注入して初期化します。 -
HttpServer
を、設定ポートとItemSearchService
を渡して初期化・起動します。 -
McpServerWrapper
を、ItemSearchService
を渡して初期化し、start()
メソッドを呼び出して MCP サーバーを起動します。
-
- プロセス終了シグナル (
SIGINT
,SIGTERM
) を捕捉し、グレースフルシャットダウン処理(現在はprocess.exit(0)
のみ)を呼び出します。 -
uncaughtException
やunhandledRejection
に対するグローバルなエラーハンドリングを設定し、予期せぬエラーでプロセスが終了するようにします。 - 最後に
startServer()
を呼び出してアプリケーションを起動します。
主要ワークフローの例
search_items
の流れ
-
クライアント (HTTP or MCP):
search_items
ツール/API を呼び出し、query
,category
,useWeb
パラメータを送信。 -
httpServer.ts
ormcpServer.ts
: リクエストを受け取り、バリデーション後、ItemSearchService.handleSearchItems
を呼び出す。 -
searchService.ts
(handleSearchItems
):-
searchItems
メソッドを呼び出す。
-
-
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
に返す。
-
searchService.ts
(handleSearchItems
): 結果をApiResponse
形式に整形。 -
httpServer.ts
ormcpServer.ts
: 整形されたレスポンスをクライアントに返す。
generate_markdown_summary
の流れ
-
クライアント (HTTP or MCP):
generate_markdown_summary
ツール/API を呼び出し、title
,category
等のパラメータを送信。 -
httpServer.ts
ormcpServer.ts
: リクエストを受け取り、ItemSearchService.handleGenerateMarkdownSummary
を呼び出す。 -
searchService.ts
(handleGenerateMarkdownSummary
):-
category
に基づいてItemRepository.getItemsByCategory
またはgetAllItems
を呼び出し、対象のアイテムリストを取得。 - 取得したアイテムリストとパラメータを
LlmService.generateMarkdownSummary
に渡す。 -
llmService.ts
(generateMarkdownSummary
):- アイテムリストを JSON 文字列化。
- 適切なプロンプトを生成し、Gemini API を呼び出す。
- 結果のマークダウンテキストを返す。
- 結果を
ApiResponse
形式に整形。
-
-
httpServer.ts
ormcpServer.ts
: 整形されたレスポンスをクライアントに返す。