responsive.search
Search responsive design analysis results using natural language queries to find layout changes, navigation differences, and visibility shifts across viewports like desktop, tablet, and mobile.
Instructions
レスポンシブデザイン分析結果をセマンティック検索します。ビューポート間の差異(レイアウト変化、ナビゲーション変化、表示切替等)を自然言語で検索できます。差異カテゴリ、ビューポートペア、ブレークポイント範囲、スクリーンショット差分率でフィルタリング可能です。
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | 検索クエリ(自然言語、1-500文字)。例: "モバイルでハンバーガーメニューに変わるサイト" | |
| limit | No | 取得件数(1-50、デフォルト: 10) | |
| offset | No | オフセット(0以上、デフォルト: 0) | |
| filters | No | 検索フィルター | |
| profile_id | No | 嗜好プロファイルID(検索結果のリランキングに使用) / Preference profile ID (used for search result reranking) |
Implementation Reference
- The handler function responsiveSearchHandler executes the "responsive.search" tool logic, including validation, embedding generation, semantic search, and result reranking.
export async function responsiveSearchHandler(input: unknown): Promise<ResponsiveSearchOutput> { const startTime = Date.now(); if (isDevelopment()) { logger.info("[MCP Tool] responsive.search called", { query: (input as Record<string, unknown>)?.query, }); } // 入力バリデーション let validated: ResponsiveSearchInputType; try { validated = responsiveSearchInputSchema.parse(input); } catch (error) { if (error instanceof ZodError) { const errorMessage = error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", "); if (isDevelopment()) { logger.error("[MCP Tool] responsive.search validation error", { errors: error.errors, }); } return { success: false, error: { code: RESPONSIVE_MCP_ERROR_CODES.VALIDATION_ERROR, message: `Validation error: ${errorMessage}`, }, }; } throw error; } // サービスファクトリーチェック if (!responsiveSearchServiceFactory) { if (isDevelopment()) { logger.error("[MCP Tool] responsive.search service factory not set"); } return { success: false, error: { code: RESPONSIVE_MCP_ERROR_CODES.SERVICE_UNAVAILABLE, message: "Responsive search service is not available", }, }; } const service = responsiveSearchServiceFactory(); try { // E5 モデル用 query: プレフィックスを付与 const processedQuery = `query: ${validated.query}`; // Embedding 生成 const queryEmbedding = await service.generateQueryEmbedding(processedQuery); if (queryEmbedding === null) { if (isDevelopment()) { logger.warn( "[MCP Tool] responsive.search embedding not available, returning empty results" ); } return { success: true, data: { results: [], total: 0, query: validated.query, searchTimeMs: Date.now() - startTime, }, }; } // 検索オプション構築 const searchOptions: ResponsiveSearchOptions = { limit: validated.limit, offset: validated.offset, }; if (validated.filters) { const filters: ResponsiveSearchOptions["filters"] = {}; if (validated.filters.diffCategory) { filters.diffCategory = validated.filters.diffCategory; } if (validated.filters.viewportPair) { filters.viewportPair = validated.filters.viewportPair; } if (validated.filters.breakpointRange) { filters.breakpointRange = { ...(validated.filters.breakpointRange.min !== undefined && { min: validated.filters.breakpointRange.min, }), ...(validated.filters.breakpointRange.max !== undefined && { max: validated.filters.breakpointRange.max, }), }; } if (validated.filters.minDiffPercentage !== undefined) { filters.minDiffPercentage = validated.filters.minDiffPercentage; } if (validated.filters.webPageId) { filters.webPageId = validated.filters.webPageId; } if (Object.keys(filters).length > 0) { searchOptions.filters = filters; } } // 検索実行 const searchResult = await service.searchResponsiveAnalyses(queryEmbedding, searchOptions); // 結果マッピング let mappedResults: ResponsiveSearchResultItem[] = searchResult.results.map( (r: ResponsiveSearchResult) => ({ id: r.id, similarity: r.similarity, url: r.url, source: { webPageId: r.webPageId, responsiveAnalysisId: r.responsiveAnalysisId, }, viewportsAnalyzed: r.viewportsAnalyzed, differencesCount: Array.isArray(r.differences) ? r.differences.length : 0, breakpointsCount: Array.isArray(r.breakpoints) ? r.breakpoints.length : 0, screenshotDiffs: r.screenshotDiffs, analysisTimeMs: r.analysisTimeMs, textRepresentation: r.textRepresentation, }) ); const searchTimeMs = Date.now() - startTime; if (isDevelopment()) { logger.info("[MCP Tool] responsive.search completed", { query: validated.query, resultCount: mappedResults.length, total: searchResult.total, searchTimeMs, }); } // 嗜好プロファイルによるリランキング / Preference profile reranking mappedResults = await applyPreferenceReranking( mappedResults, validated.profile_id, prismaClientFactory, "responsive", "responsive.search" ); return { success: true, data: { results: mappedResults, total: searchResult.total, query: validated.query, searchTimeMs, }, }; } catch (error) { const errorInstance = error instanceof Error ? error : new Error(String(error)); const errorCode = mapErrorToCode(errorInstance); if (isDevelopment()) { logger.error("[MCP Tool] responsive.search error", { code: errorCode, error: errorInstance.message, }); } return { success: false, error: { code: errorCode, message: errorInstance.message, }, }; } } - apps/mcp-server/src/tools/responsive/search.tool.ts:327-417 (registration)The tool registration and input schema definition for "responsive.search".
export const responsiveSearchToolDefinition = { name: "responsive.search", description: "レスポンシブデザイン分析結果をセマンティック検索します。" + "ビューポート間の差異(レイアウト変化、ナビゲーション変化、表示切替等)を" + "自然言語で検索できます。差異カテゴリ、ビューポートペア、ブレークポイント範囲、" + "スクリーンショット差分率でフィルタリング可能です。", annotations: { title: "Responsive Search", readOnlyHint: true, idempotentHint: true, openWorldHint: false, }, inputSchema: { type: "object" as const, properties: { query: { type: "string", description: '検索クエリ(自然言語、1-500文字)。例: "モバイルでハンバーガーメニューに変わるサイト"', minLength: 1, maxLength: 500, }, limit: { type: "number", description: "取得件数(1-50、デフォルト: 10)", minimum: 1, maximum: 50, default: 10, }, offset: { type: "number", description: "オフセット(0以上、デフォルト: 0)", minimum: 0, default: 0, }, filters: { type: "object", description: "検索フィルター", properties: { diffCategory: { type: "string", enum: [ "layout", "typography", "spacing", "visibility", "navigation", "image", "interaction", "animation", ], description: "レスポンシブ差異カテゴリでフィルター", }, viewportPair: { type: "string", enum: ["desktop-tablet", "desktop-mobile", "tablet-mobile"], description: "ビューポートペアでフィルター", }, breakpointRange: { type: "object", description: "ブレークポイント範囲でフィルター", properties: { min: { type: "number", description: "最小ブレークポイント(px)" }, max: { type: "number", description: "最大ブレークポイント(px)" }, }, }, minDiffPercentage: { type: "number", description: "最小スクリーンショット差分率(0-100)", minimum: 0, maximum: 100, }, webPageId: { type: "string", format: "uuid", description: "WebページIDでフィルター", }, }, }, // Preference reranking profile_id: { type: "string", format: "uuid", description: "嗜好プロファイルID(検索結果のリランキングに使用) / Preference profile ID (used for search result reranking)", }, }, required: ["query"], }, };