Skip to main content
Glama

page.analyze

Analyze web pages to detect layout structures, extract motion patterns, and evaluate design quality in a unified parallel process.

Instructions

Analyze a web page URL with layout detection, motion pattern extraction, and quality evaluation. Executes layout.ingest, motion.detect, and quality.evaluate in parallel and returns unified results.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesTarget URL to analyze (required)
sourceTypeNoSource type: award_gallery or user_provided (default)user_provided
usageScopeNoUsage scope: inspiration_only (default) or owned_assetinspiration_only
featuresNoFeature flags for analysis (default: all true)
layoutOptionsNoLayout analysis options
motionOptionsNoMotion detection options
qualityOptionsNoQuality evaluation options
summaryNoReturn summary response (default: true). Set to false for full details.
timeoutNoOverall timeout in ms (default: 60000)
waitUntilNoPage load completion criteria (default: load)load
auto_timeoutNoEnable Pre-flight Probe for dynamic timeout calculation (v0.1.0). Analyzes page complexity (WebGL, SPA, heavy frameworks) before analysis and calculates optimal timeout. Results are included in preflightProbe response field.
responsiveOptionsNoResponsive layout analysis options. Captures layouts at multiple viewport sizes (desktop/tablet/mobile) and detects differences in typography, spacing, navigation, and layout structure.

Implementation Reference

  • Main MCP tool handler for 'page.analyze'. Validates input, handles SSRF checks, manages job queue for async analysis (with Vision), and triggers sync processing with a hard timeout wrapper.
    export async function pageAnalyzeHandler(
      input: unknown,
      progressContext?: ProgressContext
    ): Promise<PageAnalyzeOutput> {
      const overallStartTime = Date.now();
    
      if (isDevelopment()) {
        logger.info("[MCP Tool] page.analyze called", {
          hasInput: input !== null && input !== undefined,
        });
      }
    
      // 入力バリデーション
      let validated: PageAnalyzeInput;
      try {
        if (input === null || input === undefined) {
          return {
            success: false,
            error: {
              code: PAGE_ANALYZE_ERROR_CODES.VALIDATION_ERROR,
              message: "Input is required",
            },
          };
        }
    
        validated = pageAnalyzeInputSchema.parse(input);
      } catch (error) {
        if (isDevelopment()) {
          logger.error("[MCP Tool] page.analyze validation error", { error });
        }
        return {
          success: false,
          error: {
            code: PAGE_ANALYZE_ERROR_CODES.VALIDATION_ERROR,
            message: error instanceof Error ? error.message : "Invalid input",
          },
        };
      }
    
      // SSRF対策: URL検証
      const urlValidation = validateExternalUrl(validated.url);
      if (!urlValidation.valid) {
        if (isDevelopment()) {
          logger.warn("[MCP Tool] page.analyze SSRF blocked", {
            url: validated.url,
            error: urlValidation.error,
          });
        }
        return {
          success: false,
          error: {
            code: PAGE_ANALYZE_ERROR_CODES.SSRF_BLOCKED,
            message: urlValidation.error ?? "URL is blocked for security reasons",
          },
        };
      }
    
      const normalizedUrl = urlValidation.normalizedUrl ?? normalizeUrlForValidation(validated.url);
    
      // robots.txt チェック(RFC 9309準拠)- 早期ブロック
      const robotsResult = await isUrlAllowedByRobotsTxt(validated.url, validated.respect_robots_txt);
      if (!robotsResult.allowed) {
        return {
          success: false,
          error: {
            code: PAGE_ANALYZE_ERROR_CODES.ROBOTS_TXT_BLOCKED,
            message:
              `Blocked by robots.txt: ${validated.url} (domain: ${robotsResult.domain}, reason: ${robotsResult.reason}). ` +
              `Use respect_robots_txt: false to override. ` +
              `Note: Overriding robots.txt may have legal implications depending on jurisdiction (e.g., EU DSM Directive Article 4).`,
          },
        };
      }
    
      // =====================================================
      // Smart Defaults: Vision有効時の自動非同期モード(v0.1.0)
      // =====================================================
      // Vision LLM (llama3.2-vision) はCPUモードで2-5分以上かかるため、
      // MCPの600秒ハードタイムアウトを回避するために自動的にasyncモードを有効化
      const useVisionEnabled = validated.layoutOptions?.useVision !== false; // デフォルトtrue
      const useNarrativeVisionEnabled = validated.narrativeOptions?.includeVision === true;
      const visionRequested = useVisionEnabled || useNarrativeVisionEnabled;
    
      // async が明示的に指定されていない場合のみ自動設定
      // (ユーザーが async: false を明示指定した場合は尊重)
      let autoAsyncEnabled = false;
      if (visionRequested && validated.async === undefined) {
        const redisCheck = await isRedisAvailable();
        if (redisCheck) {
          // Vision有効 + Redis利用可能 → 自動でasyncモードを有効化
          validated = { ...validated, async: true };
          autoAsyncEnabled = true;
          if (isDevelopment()) {
            logger.info("[page.analyze] Auto-async enabled for Vision analysis", {
              url: validated.url,
              useVision: useVisionEnabled,
              useNarrativeVision: useNarrativeVisionEnabled,
            });
          }
        } else if (isDevelopment()) {
          logger.warn("[page.analyze] Vision requested but Redis unavailable, sync mode will be used", {
            url: validated.url,
          });
        }
      }
    
      // =====================================================
      // 非同期モード処理(Phase3-2)
      // =====================================================
      // async=true の場合、ジョブをキューに投入して即座に返す
      if (validated.async === true) {
        if (isDevelopment()) {
          logger.info("[page.analyze] Async mode requested", { url: validated.url });
        }
    
        // Redis可用性チェック
        const redisAvailable = await isRedisAvailable();
        if (!redisAvailable) {
          if (isDevelopment()) {
            logger.warn("[page.analyze] Redis unavailable for async mode");
          }
          return {
            success: false,
            error: {
              code: "REDIS_UNAVAILABLE",
              message: "Async mode requires Redis. Please start Redis or use sync mode (async=false).",
            },
          };
        }
    
        // ワーカープロセスが起動していなければ起動する
        getWorkerSupervisor().ensureWorkerRunning();
    
        // ジョブIDとしてwebPageIdを事前生成
        const webPageId = uuidv7();
    
        // キューにジョブを追加
        const queue = createPageAnalyzeQueue();
    
        // バッチ投入前: orphaned/failed/stalledジョブを自動クリーンアップ
        const cleanupResult = await cleanupQueue(createQueueAdapter(queue));
        if (cleanupResult.strategy !== "skipped" && isDevelopment()) {
          logger.info("[page.analyze] Queue cleanup before job submission", {
            strategy: cleanupResult.strategy,
            totalCleaned: cleanupResult.totalCleaned,
          });
        }
    
        try {
          // ジョブオプションを構築(exactOptionalPropertyTypes対応)
          const jobOptions: PageAnalyzeJobOptions = {
            timeout: validated.timeout,
            features: {
              layout: validated.features?.layout,
              motion: validated.features?.motion,
              quality: validated.features?.quality,
            },
          };
    
          // layoutOptions(デフォルト値を常に設定 — undefinedの場合もデフォルトで構築)
          // Bug fix: デフォルトモード(layoutOptions未指定)でも useVision: true 等が適用されるように
          {
            const src = validated.layoutOptions;
            const layoutOpts: NonNullable<PageAnalyzeJobOptions["layoutOptions"]> = {
              useVision: src?.useVision ?? true,
              saveToDb: src?.saveToDb ?? true,
              autoAnalyze: src?.autoAnalyze ?? true,
              fullPage: src?.fullPage ?? true,
              scrollVision: src?.scrollVision ?? true,
              scrollVisionMaxCaptures: src?.scrollVisionMaxCaptures ?? 10,
            };
            if (src?.viewport) {
              layoutOpts.viewport = src.viewport;
            }
            jobOptions.layoutOptions = layoutOpts;
          }
    
          // motionOptions
          if (validated.motionOptions) {
            jobOptions.motionOptions = {
              detectJsAnimations: validated.motionOptions.detect_js_animations ?? true,
              detectWebglAnimations: validated.motionOptions.detect_webgl_animations ?? true,
              enableFrameCapture: validated.motionOptions.enable_frame_capture ?? false,
              analyzeFrames: validated.motionOptions.analyze_frames ?? false,
              saveToDb: validated.motionOptions.saveToDb ?? true,
              maxPatterns: validated.motionOptions.maxPatterns ?? 500,
              // v0.1.0: Motion検出タイムアウト(asyncモードでは長時間検出可能)
              timeout: validated.motionOptions.timeout ?? 300000,
            };
          }
    
          // qualityOptions(undefinedを明示的に除外)
          if (validated.qualityOptions) {
            const qualityOpts: NonNullable<PageAnalyzeJobOptions["qualityOptions"]> = {
              strict: validated.qualityOptions.strict ?? true,
            };
            if (validated.qualityOptions.weights) {
              qualityOpts.weights = {
                originality: validated.qualityOptions.weights.originality ?? 0.35,
                craftsmanship: validated.qualityOptions.weights.craftsmanship ?? 0.4,
                contextuality: validated.qualityOptions.weights.contextuality ?? 0.25,
              };
            }
            if (validated.qualityOptions.targetIndustry) {
              qualityOpts.targetIndustry = validated.qualityOptions.targetIndustry;
            }
            if (validated.qualityOptions.targetAudience) {
              qualityOpts.targetAudience = validated.qualityOptions.targetAudience;
            }
            jobOptions.qualityOptions = qualityOpts;
          }
    
          // narrativeOptions(デフォルト有効)
          if (validated.narrativeOptions) {
            jobOptions.narrativeOptions = {
              enabled: validated.narrativeOptions.enabled ?? true,
              saveToDb: validated.narrativeOptions.saveToDb ?? true,
              includeVision: validated.narrativeOptions.includeVision ?? true,
              visionTimeoutMs: validated.narrativeOptions.visionTimeoutMs ?? 300000,
              generateEmbedding: validated.narrativeOptions.generateEmbedding ?? true,
            };
          }
    
          // responsiveOptions(デフォルト有効)
          if (validated.responsiveOptions) {
            const rOpts = validated.responsiveOptions;
            jobOptions.responsiveOptions = {
              enabled: rOpts.enabled ?? true,
              ...(rOpts.viewports !== undefined ? { viewports: rOpts.viewports } : {}),
              ...(rOpts.include_screenshots !== undefined
                ? { include_screenshots: rOpts.include_screenshots }
                : {}),
              ...(rOpts.include_diff_images !== undefined
                ? { include_diff_images: rOpts.include_diff_images }
                : {}),
              ...(rOpts.diff_threshold !== undefined ? { diff_threshold: rOpts.diff_threshold } : {}),
              ...(rOpts.save_to_db !== undefined ? { save_to_db: rOpts.save_to_db } : {}),
              ...(rOpts.detect_navigation !== undefined
                ? { detect_navigation: rOpts.detect_navigation }
                : {}),
              ...(rOpts.detect_visibility !== undefined
                ? { detect_visibility: rOpts.detect_visibility }
                : {}),
              ...(rOpts.detect_layout !== undefined ? { detect_layout: rOpts.detect_layout } : {}),
            };
          }
    
          // respectRobotsTxt(Workerパスでもrobots.txtチェックに渡す)
          if (validated.respect_robots_txt !== undefined) {
            jobOptions.respectRobotsTxt = validated.respect_robots_txt;
          }
    
          const job = await addPageAnalyzeJob(queue, {
            webPageId,
            url: validated.url,
            options: jobOptions,
          });
    
          if (isDevelopment()) {
            logger.info("[page.analyze] Job queued successfully", {
              jobId: job.id,
              webPageId,
              url: validated.url,
            });
          }
    
          // 非同期レスポンスを返す
          const autoAsyncNote = autoAsyncEnabled
            ? " (Auto-enabled: Vision analysis requires async mode to avoid MCP timeout)"
            : "";
          const asyncResponse: PageAnalyzeAsyncOutput = {
            async: true,
            jobId: webPageId,
            status: "queued",
            message: `Job queued successfully.${autoAsyncNote} Use page.getJobStatus(job_id="${webPageId}") to check progress.`,
            polling: {
              intervalSeconds: 10, // Vision処理は長時間かかるため10秒間隔を推奨
              retentionHours: 24,
              howToCheck: `Call page.getJobStatus with job_id="${webPageId}" to check job status and retrieve results.`,
            },
          };
    
          return asyncResponse as unknown as PageAnalyzeOutput;
        } finally {
          await closeQueue(queue);
        }
      }
    
      // =====================================================
      // MCP 570秒ハードタイムアウトガード(v0.1.0)
      // =====================================================
      // MCP プロトコルの600秒タイムアウトを超えないよう、570秒(30秒安全マージン)で
      // sync mode全体をハードタイムアウトで保護する。
      // CPU Vision延長やフェーズ個別タイムアウトが膨らんでも、このガードで確実に打ち切る。
      // fetchExternalCss: true で多数の外部リソースを取得する際のハング防止が主目的。
      const OVERALL_HARD_TIMEOUT_MS = 570000; // 570秒 = MCP 600秒 - 30秒安全マージン
    
      // タイマーIDを保持してクリーンアップ可能にする
      let hardTimeoutId: ReturnType<typeof setTimeout> | undefined;
    
      const syncProcessingResult = await Promise.race([
        executeSyncProcessing(
          validated,
          normalizedUrl,
          overallStartTime,
          {
            getService: () => serviceFactory?.() ?? {},
            getPrismaClient,
          },
          progressContext
        ),
        new Promise<PageAnalyzeOutput>((_, reject) => {
          hardTimeoutId = setTimeout(() => {
            reject(new PhaseTimeoutError("page.analyze-overall", OVERALL_HARD_TIMEOUT_MS));
          }, OVERALL_HARD_TIMEOUT_MS);
        }),
      ])
        .catch((error): PageAnalyzeOutput => {
          const isTimeout = error instanceof PhaseTimeoutError;
          const elapsedMs = Date.now() - overallStartTime;
    
          if (isDevelopment()) {
            logger.error("[page.analyze] Overall hard timeout triggered", {
              timeoutMs: OVERALL_HARD_TIMEOUT_MS,
              elapsedMs,
              isTimeout,
              error: error instanceof Error ? error.message : String(error),
            });
          }
    
          return {
            success: false,
            error: {
              code: PAGE_ANALYZE_ERROR_CODES.TIMEOUT_ERROR,
              message: `page.analyze exceeded MCP hard timeout limit (${Math.round(elapsedMs / 1000)}s / ${OVERALL_HARD_TIMEOUT_MS / 1000}s). Consider using fetchExternalCss: false or reducing analysis scope.`,
            },
          };
        })
        .finally(() => {
          // Promise.race で executeSyncProcessing が先に完了した場合、タイマーをクリーンアップ
          if (hardTimeoutId) {
            clearTimeout(hardTimeoutId);
          }
        });
    
      return syncProcessingResult;
    }
  • Tool definition including name, description, and input schema for 'page.analyze'.
    export const pageAnalyzeToolDefinition = {
      name: "page.analyze",
      description:
        "Analyze a web page URL with layout detection, motion pattern extraction, and quality evaluation. Executes layout.ingest, motion.detect, and quality.evaluate in parallel and returns unified results.",
      annotations: {
        title: "Page Analyze",
        readOnlyHint: true,
        idempotentHint: true,
        openWorldHint: true,
      },
      inputSchema: {
        type: "object" as const,
        required: ["url"],
        properties: {
          url: {
            type: "string",
            format: "uri",
            description: "Target URL to analyze (required)",
          },
          sourceType: {
            type: "string",
            enum: ["award_gallery", "user_provided"],
            default: "user_provided",
            description: "Source type: award_gallery or user_provided (default)",
          },
          usageScope: {
            type: "string",
            enum: ["inspiration_only", "owned_asset"],
            default: "inspiration_only",
            description: "Usage scope: inspiration_only (default) or owned_asset",
          },
          features: {
            type: "object",
            description: "Feature flags for analysis (default: all true)",
            properties: {
              layout: {
                type: "boolean",
                default: true,
                description: "Enable layout analysis (default: true)",
              },
              motion: {
                type: "boolean",
                default: true,
                description: "Enable motion detection (default: true)",
              },
              quality: {
                type: "boolean",
                default: true,
                description: "Enable quality evaluation (default: true)",
              },
            },
          },
          layoutOptions: {
            type: "object",
            description: "Layout analysis options",
            properties: {
              fullPage: {
                type: "boolean",
                default: true,
                description: "Full page screenshot (default: true)",
              },
              viewport: {
                type: "object",
                properties: {
                  width: { type: "number", minimum: 320, maximum: 4096, default: 1440 },
                  height: { type: "number", minimum: 240, maximum: 16384, default: 900 },
                },
              },
              // MCP-RESP-03: snake_case正式形式(新規オプション推奨形式)
              include_html: {
                type: "boolean",
                default: false,
                description: "Include HTML in response (default: false) - snake_case正式形式",
              },
              include_screenshot: {
                type: "boolean",
                default: false,
                description: "Include screenshot in response (default: false) - snake_case正式形式",
              },
              // レガシー互換: camelCaseは後方互換として維持
              includeHtml: {
                type: "boolean",
                default: false,
                description:
                  "Include HTML in response (default: false) - レガシー互換、include_html推奨",
              },
              includeScreenshot: {
                type: "boolean",
                default: false,
                description:
                  "Include screenshot in response (default: false) - レガシー互換、include_screenshot推奨",
              },
              saveToDb: {
                type: "boolean",
                default: true,
                description: "Save to database (default: true)",
              },
              autoAnalyze: {
                type: "boolean",
                default: true,
                description: "Auto analyze sections and generate embeddings (default: true)",
              },
              fetchExternalCss: {
                type: "boolean",
                default: true,
                description: "Fetch external CSS files for layout analysis (default: true)",
              },
              useVision: {
                type: "boolean",
                default: true,
                description:
                  "Use Vision API (Ollama + llama3.2-vision) to analyze screenshot for section detection. Delegates to layout.inspect screenshot mode. (default: true)",
              },
            },
          },
          motionOptions: {
            type: "object",
            description: "Motion detection options",
            properties: {
              fetchExternalCss: {
                type: "boolean",
                default: false,
                description: "Fetch external CSS files (default: false)",
              },
              minDuration: {
                type: "number",
                minimum: 0,
                default: 0,
                description: "Minimum animation duration in ms (default: 0)",
              },
              maxPatterns: {
                type: "number",
                minimum: 1,
                maximum: 4000,
                default: 100,
                description: "Maximum patterns to detect (default: 100)",
              },
              includeWarnings: {
                type: "boolean",
                default: true,
                description: "Include warnings in response (default: true)",
              },
              saveToDb: {
                type: "boolean",
                default: true,
                description: "Save motion patterns to database (default: true)",
              },
              // Video Mode Options (Phase 5)
              enable_frame_capture: {
                type: "boolean",
                default: true,
                description: "Enable frame capture for scroll animation analysis (default: true)",
              },
              frame_capture_options: {
                type: "object",
                description: "Frame capture configuration",
                properties: {
                  frame_rate: {
                    type: "number",
                    minimum: 1,
                    maximum: 120,
                    default: 30,
                    description: "Frame rate (default: 30fps)",
                  },
                  frame_interval_ms: {
                    type: "number",
                    minimum: 1,
                    maximum: 1000,
                    default: 33,
                    description: "Frame interval in milliseconds (default: 33ms = 30fps)",
                  },
                  scroll_speed_px_per_sec: {
                    type: "number",
                    minimum: 1,
                    description: "Scroll speed in pixels per second (optional)",
                  },
                  scroll_px_per_frame: {
                    type: "number",
                    minimum: 0.01,
                    default: 15,
                    description: "Scroll pixels per frame (default: 15px)",
                  },
                  output_format: {
                    type: "string",
                    enum: ["png", "jpeg"],
                    default: "png",
                    description: "Output image format (default: png)",
                  },
                  output_dir: {
                    type: "string",
                    default: "/tmp/reftrix-frames/",
                    description: "Output directory for frames (default: /tmp/reftrix-frames/)",
                  },
                  filename_pattern: {
                    type: "string",
                    default: "frame-{0000}.png",
                    description:
                      "Filename pattern with frame number placeholder (default: frame-{0000}.png)",
                  },
                  page_height_px: {
                    type: "number",
                    minimum: 100,
                    maximum: 100000,
                    description: "Manual page height in pixels (optional, auto-detected if omitted)",
                  },
                  scroll_duration_sec: {
                    type: "number",
                    minimum: 0.1,
                    maximum: 300,
                    description: "Scroll duration in seconds (optional)",
                  },
                },
              },
              analyze_frames: {
                type: "boolean",
                default: true,
                description: "Enable frame image analysis with pixelmatch (default: true)",
              },
              frame_analysis_options: {
                type: "object",
                description: "Frame analysis configuration",
                properties: {
                  frame_dir: {
                    type: "string",
                    description:
                      "Frame image directory (optional, uses frame_capture_options.output_dir if omitted)",
                  },
                  sample_interval: {
                    type: "number",
                    minimum: 1,
                    maximum: 100,
                    default: 1,
                    description: "Analyze every Nth frame (default: 1 = all frames)",
                  },
                  diff_threshold: {
                    type: "number",
                    minimum: 0,
                    maximum: 1,
                    default: 0.01,
                    description: "Minimum diff percentage to consider as change (default: 0.01 = 1%)",
                  },
                  cls_threshold: {
                    type: "number",
                    minimum: 0,
                    maximum: 1,
                    default: 0.1,
                    description: "CLS (Cumulative Layout Shift) warning threshold (default: 0.1)",
                  },
                  motion_threshold: {
                    type: "number",
                    minimum: 1,
                    maximum: 500,
                    default: 5,
                    description: "Minimum pixels to detect motion vector (default: 5)",
                  },
                  output_diff_images: {
                    type: "boolean",
                    default: false,
                    description: "Save diff images to output_dir (default: false)",
                  },
                  parallel: {
                    type: "boolean",
                    default: true,
                    description: "Process frames in parallel (default: true)",
                  },
                },
              },
              // JS Animation Options (v0.1.0)
              detect_js_animations: {
                type: "boolean",
                default: false,
                description:
                  "Enable JavaScript animation detection via CDP + Web Animations API + library detection (default: false, requires Playwright)",
              },
              js_animation_options: {
                type: "object",
                description: "JS animation detection configuration",
                properties: {
                  enableCDP: {
                    type: "boolean",
                    default: true,
                    description: "Enable Chrome DevTools Protocol animation detection (default: true)",
                  },
                  enableWebAnimations: {
                    type: "boolean",
                    default: true,
                    description: "Enable Web Animations API detection (default: true)",
                  },
                  enableLibraryDetection: {
                    type: "boolean",
                    default: true,
                    description:
                      "Enable library detection (GSAP, Framer Motion, anime.js, Three.js, Lottie) (default: true)",
                  },
                  waitTime: {
                    type: "number",
                    minimum: 0,
                    maximum: 10000,
                    default: 1000,
                    description:
                      "Wait time in ms after page load before detecting animations (default: 1000)",
                  },
                },
              },
              // v0.1.0: Motion検出タイムアウト(asyncモードでは長時間検出可能)
              timeout: {
                type: "number",
                minimum: 30000,
                maximum: 600000,
                default: 180000,
                description:
                  "Motion detection timeout in milliseconds. MCP Protocol has a 60-second tool call limit. In async mode (page.analyze with async=true), this limit does not apply, allowing longer detection times for heavy WebGL/Three.js sites. (default: 180000 = 3 minutes, max: 600000 = 10 minutes)",
              },
            },
          },
          qualityOptions: {
            type: "object",
            description: "Quality evaluation options",
            properties: {
              weights: {
                type: "object",
                properties: {
                  originality: { type: "number", minimum: 0, maximum: 1, default: 0.35 },
                  craftsmanship: { type: "number", minimum: 0, maximum: 1, default: 0.4 },
                  contextuality: { type: "number", minimum: 0, maximum: 1, default: 0.25 },
                },
              },
              targetIndustry: {
                type: "string",
                maxLength: 100,
                description: "Target industry for contextual evaluation",
              },
              targetAudience: {
                type: "string",
                maxLength: 100,
                description: "Target audience for contextual evaluation",
              },
              strict: {
                type: "boolean",
                default: false,
                description: "Strict mode for AI cliche detection (default: false)",
              },
              includeRecommendations: {
                type: "boolean",
                default: true,
                description: "Include recommendations in response (default: true)",
              },
            },
          },
          summary: {
            type: "boolean",
            default: true,
            description: "Return summary response (default: true). Set to false for full details.",
          },
          timeout: {
            type: "number",
            minimum: 5000,
            maximum: 300000,
            default: 60000,
            description: "Overall timeout in ms (default: 60000)",
          },
          waitUntil: {
            type: "string",
            enum: ["load", "domcontentloaded", "networkidle"],
            default: "load",
            description: "Page load completion criteria (default: load)",
          },
          auto_timeout: {
            type: "boolean",
            default: false,
            description:
              "Enable Pre-flight Probe for dynamic timeout calculation (v0.1.0). Analyzes page complexity (WebGL, SPA, heavy frameworks) before analysis and calculates optimal timeout. Results are included in preflightProbe response field.",
          },
          responsiveOptions: {
            type: "object",
            description:
              "Responsive layout analysis options. Captures layouts at multiple viewport sizes (desktop/tablet/mobile) and detects differences in typography, spacing, navigation, and layout structure.",
            properties: {
              enabled: {
                type: "boolean",
                default: true,
                description: "Enable responsive analysis (default: true)",
              },
              viewports: {
                type: "array",
                description:
                  "Custom viewport configurations. Default: desktop (1920x1080), tablet (768x1024), mobile (375x667)",
                items: {
                  type: "object",
                  required: ["name", "width", "height"],
                  properties: {
                    name: {
                      type: "string",
                      description: "Viewport name (e.g., desktop, tablet, mobile)",
                    },
                    width: {
                      type: "number",
                      minimum: 320,
                      maximum: 4096,
                      description: "Width in pixels",
                    },
                    height: {
                      type: "number",
                      minimum: 240,
                      maximum: 16384,
                      description: "Height in pixels",
                    },
                  },
                },
              },
              include_screenshots: {
                type: "boolean",
                default: false,
                description:
                  "Include screenshots for each viewport in response (default: false, DB-first workflow)",
              },
              include_diff_images: {
                type: "boolean",
                default: false,
                description: "Include diff images in viewport comparison results (default: false)",
              },
              diff_threshold: {
                type: "number",
                minimum: 0,
                maximum: 1,
                default: 0.1,
                description: "Pixel diff threshold for viewport comparison (0-1, default: 0.1)",
              },
              save_to_db: {
                type: "boolean",
                default: true,
                description: "Save responsive analysis results to DB (default: true)",
              },
              detect_navigation: {
                type: "boolean",
                default: true,
                description:
                  "Detect navigation pattern changes (horizontal-menu to hamburger-menu, etc.) (default: true)",
              },
              detect_visibility: {
                type: "boolean",
                default: true,
                description: "Detect element visibility changes between viewports (default: true)",
              },
              detect_layout: {
                type: "boolean",
                default: true,
                description:
                  "Detect layout structure changes (grid columns, flex direction, etc.) (default: true)",
              },
            },
          },
        },
      },
    };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TKMD/reftrix-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server