Skip to main content
Glama
omgwtfwow

MCP Server for Crawl4AI

by omgwtfwow

capture_screenshot

Capture webpage screenshots as base64 PNG data for web content analysis. Wait for page loading before taking screenshots and optionally save images locally.

Instructions

[STATELESS] Capture webpage screenshot. Returns base64-encoded PNG data. Creates new browser each time. Optionally saves screenshot to local directory. IMPORTANT: Chained calls (execute_js then capture_screenshot) will NOT work - the screenshot won't see JS changes! For JS changes + screenshot use create_session + crawl(session_id, js_code, screenshot:true) in ONE call.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesThe URL to capture
screenshot_wait_forNoSeconds to wait before taking screenshot (allows page loading/animations)
save_to_directoryNoDirectory path to save screenshot (e.g., ~/Desktop, /tmp). Do NOT include filename - it will be auto-generated. Large screenshots (>800KB) won't be returned inline when saved.

Implementation Reference

  • Primary MCP tool handler: Calls backend screenshot service, optionally saves to local file, returns base64 PNG image in MCP format or file path text if too large.
    async captureScreenshot(options: ScreenshotEndpointOptions) {
      try {
        const result: ScreenshotEndpointResponse = await this.service.captureScreenshot(options);
    
        // Response has { success: true, screenshot: "base64string" }
        if (!result.success || !result.screenshot) {
          throw new Error('Screenshot capture failed - no screenshot data in response');
        }
    
        let savedFilePath: string | undefined;
    
        // Save to local directory if requested
        if (options.save_to_directory) {
          try {
            // Resolve home directory path
            let resolvedPath = options.save_to_directory;
            if (resolvedPath.startsWith('~')) {
              const homedir = os.homedir();
              resolvedPath = path.join(homedir, resolvedPath.slice(1));
            }
    
            // Check if user provided a file path instead of directory
            if (resolvedPath.endsWith('.png') || resolvedPath.endsWith('.jpg')) {
              console.warn(
                `Warning: save_to_directory should be a directory path, not a file path. Using parent directory.`,
              );
              resolvedPath = path.dirname(resolvedPath);
            }
    
            // Ensure directory exists
            await fs.mkdir(resolvedPath, { recursive: true });
    
            // Generate filename from URL and timestamp
            const url = new URL(options.url);
            const hostname = url.hostname.replace(/[^a-z0-9]/gi, '-');
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
            const filename = `${hostname}-${timestamp}.png`;
    
            savedFilePath = path.join(resolvedPath, filename);
    
            // Convert base64 to buffer and save
            const buffer = Buffer.from(result.screenshot, 'base64');
            await fs.writeFile(savedFilePath, buffer);
          } catch (saveError) {
            // Log error but don't fail the operation
            console.error('Failed to save screenshot locally:', saveError);
          }
        }
    
        const textContent = savedFilePath
          ? `Screenshot captured for: ${options.url}\nSaved to: ${savedFilePath}`
          : `Screenshot captured for: ${options.url}`;
    
        // If saved locally and screenshot is large (>800KB), don't return the base64 data
        const screenshotSize = Buffer.from(result.screenshot, 'base64').length;
        const shouldReturnImage = !savedFilePath || screenshotSize < 800 * 1024; // 800KB threshold
    
        const content = [];
    
        if (shouldReturnImage) {
          content.push({
            type: 'image',
            data: result.screenshot,
            mimeType: 'image/png',
          });
        }
    
        content.push({
          type: 'text',
          text: shouldReturnImage
            ? textContent
            : `${textContent}\n\nNote: Screenshot data not returned due to size (${Math.round(screenshotSize / 1024)}KB). View the saved file instead.`,
        });
    
        return { content };
      } catch (error) {
        throw this.formatError(error, 'capture screenshot');
      }
    }
  • Zod input schema for capture_screenshot tool validation.
    export const CaptureScreenshotSchema = createStatelessSchema(
      z.object({
        url: z.string().url(),
        screenshot_wait_for: z.number().optional(),
        save_to_directory: z.string().optional().describe('Local directory to save screenshot file'),
        // output_path not exposed as MCP needs base64 data
      }),
      'capture_screenshot',
    );
  • src/server.ts:829-835 (registration)
    MCP server switch case registration: Routes 'capture_screenshot' tool calls to contentHandlers.captureScreenshot with schema validation.
    case 'capture_screenshot':
      return await this.validateAndExecute(
        'capture_screenshot',
        args,
        CaptureScreenshotSchema,
        async (validatedArgs) => this.contentHandlers.captureScreenshot(validatedArgs),
      );
  • Service client helper: Proxies HTTP POST to backend /screenshot endpoint to capture and return base64 screenshot data.
    async captureScreenshot(options: ScreenshotEndpointOptions): Promise<ScreenshotEndpointResponse> {
      // Validate URL
      if (!validateURL(options.url)) {
        throw new Error('Invalid URL format');
      }
    
      try {
        const response = await this.axiosClient.post('/screenshot', {
          url: options.url,
          screenshot_wait_for: options.screenshot_wait_for,
          // output_path is omitted to get base64 response
        });
    
        return response.data;
      } catch (error) {
        return handleAxiosError(error);
      }
    }
  • src/server.ts:151-173 (registration)
    Tool metadata registration in MCP server.tools.list array, including name, description, and JSON input schema.
    name: 'capture_screenshot',
    description:
      "[STATELESS] Capture webpage screenshot. Returns base64-encoded PNG data. Creates new browser each time. Optionally saves screenshot to local directory. IMPORTANT: Chained calls (execute_js then capture_screenshot) will NOT work - the screenshot won't see JS changes! For JS changes + screenshot use create_session + crawl(session_id, js_code, screenshot:true) in ONE call.",
    inputSchema: {
      type: 'object',
      properties: {
        url: {
          type: 'string',
          description: 'The URL to capture',
        },
        screenshot_wait_for: {
          type: 'number',
          description: 'Seconds to wait before taking screenshot (allows page loading/animations)',
          default: 2,
        },
        save_to_directory: {
          type: 'string',
          description:
            "Directory path to save screenshot (e.g., ~/Desktop, /tmp). Do NOT include filename - it will be auto-generated. Large screenshots (>800KB) won't be returned inline when saved.",
        },
      },
      required: ['url'],
    },

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/omgwtfwow/mcp-crawl4ai-ts'

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