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