Skip to main content
Glama
peterparker57

Screenshot MCP Server

take_screenshot

Capture screenshots of full desktops, specific monitors, or individual windows by title or process name, with DPI awareness and automatic path conversion.

Instructions

Take a screenshot of all monitors, specific monitor, or a specific window

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
filenameNoFilename for the screenshot (default: screenshot.png)screenshot.png
monitorNoWhich monitor to capture: "all" (default), "primary", or monitor number (1, 2, etc.)all
windowTitleNoCapture a specific window by its title (partial match supported)
processNameNoCapture a specific window by process name (e.g., "notepad.exe" or just "notepad")
folderNoCustom folder path to save the screenshot (supports both WSL and Windows paths). Defaults to workspace/screenshots/

Implementation Reference

  • index.js:65-383 (handler)
    Main handler for the 'take_screenshot' tool that executes when the tool is called. Contains the complete implementation logic including parameter extraction, path handling, PowerShell script generation for different capture modes (all monitors, specific monitor, or specific window), PowerShell execution, error handling, and response formatting.
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      if (request.params.name === 'take_screenshot') {
        const filename = request.params.arguments?.filename || 'screenshot.png';
        const monitor = request.params.arguments?.monitor || 'all';
        const windowTitle = request.params.arguments?.windowTitle;
        const processName = request.params.arguments?.processName;
        const customFolder = request.params.arguments?.folder;
        
        // Debug logging
        console.error('Screenshot parameters:', {
          filename,
          monitor,
          windowTitle,
          processName,
          folder: customFolder
        });
        
        // Determine the folder to use
        let screenshotsDir;
        let windowsPath;
        
        if (customFolder) {
          // Handle custom folder - could be WSL path or Windows path
          if (customFolder.match(/^[A-Za-z]:\\/)) {
            // It's already a Windows path (e.g., "C:\Users\...")
            windowsPath = path.join(customFolder, filename).replace(/\//g, '\\');
            // Convert Windows path to WSL path for fs operations
            const driveLetter = customFolder[0].toLowerCase();
            screenshotsDir = customFolder.replace(/^[A-Za-z]:/, `/mnt/${driveLetter}`).replace(/\\/g, '/');
          } else if (customFolder.startsWith('/mnt/')) {
            // It's a WSL path
            screenshotsDir = customFolder;
            const windowsFolder = customFolder.replace(/^\/mnt\/([a-z])\//, '$1:\\').replace(/\//g, '\\');
            windowsPath = windowsFolder + '\\' + filename;
          } else {
            // It's a relative path or Linux-style absolute path
            screenshotsDir = path.resolve(customFolder);
            windowsPath = path.join(screenshotsDir.replace(/^\/mnt\/([a-z])\//, '$1:\\').replace(/\//g, '\\'), filename);
          }
        } else {
          // Default to workspace screenshots folder
          screenshotsDir = path.resolve(process.cwd(), 'screenshots');
          const outputPath = path.join(screenshotsDir, filename);
          windowsPath = outputPath.startsWith('/mnt/') 
            ? outputPath.replace(/^\/mnt\/([a-z])\//, '$1:\\').replace(/\//g, '\\')
            : outputPath;
        }
        
        // Create the directory if it doesn't exist
        await fs.mkdir(screenshotsDir, { recursive: true });
        
        try {
          let psScript;
          
          if (windowTitle || processName) {
            // Capture specific window by title or process name
            psScript = `
              Add-Type -AssemblyName System.Windows.Forms
              Add-Type -AssemblyName System.Drawing
              Add-Type @"
                using System;
                using System.Runtime.InteropServices;
                using System.Drawing;
                
                public class Win32 {
                  [DllImport("user32.dll")]
                  public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
                  
                  [DllImport("user32.dll")]
                  public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
                  
                  [DllImport("user32.dll")]
                  public static extern bool SetForegroundWindow(IntPtr hWnd);
                  
                  [DllImport("user32.dll")]
                  public static extern IntPtr GetForegroundWindow();
                  
                  [DllImport("user32.dll")]
                  public static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, int nFlags);
                  
                  [DllImport("user32.dll")]
                  public static extern int SetProcessDPIAware();
                  
                  public struct RECT {
                    public int Left;
                    public int Top;
                    public int Right;
                    public int Bottom;
                  }
                }
    "@
              
              # Enable per-monitor DPI awareness for accurate capture
              Add-Type @"
                using System.Runtime.InteropServices;
                public class DPI {
                  [DllImport("shcore.dll")]
                  public static extern int SetProcessDpiAwareness(int value);
                }
    "@
              [DPI]::SetProcessDpiAwareness(2)
              [Win32]::SetProcessDPIAware() # Fallback
              
              # Find all windows and match by title or process name
              $allWindows = Get-Process | Where-Object {$_.MainWindowTitle -ne ""}
              Write-Host "Available windows:"
              $allWindows | ForEach-Object { Write-Host "  - $($_.MainWindowTitle) (Process: $($_.ProcessName))" }
              
              # Search by title or process name
              if ("${windowTitle}" -ne "") {
                $windows = $allWindows | Where-Object {$_.MainWindowTitle -like "*${windowTitle}*"}
                if ($windows.Count -eq 0) {
                  Write-Host "Search term: '${windowTitle}'"
                  throw "No window found with title containing: ${windowTitle}"
                }
              } elseif ("${processName}" -ne "") {
                # Strip .exe if provided
                $searchProcess = "${processName}".Replace(".exe", "")
                $windows = $allWindows | Where-Object {$_.ProcessName -like "*$searchProcess*"}
                if ($windows.Count -eq 0) {
                  Write-Host "Search process: '${processName}'"
                  throw "No window found for process: ${processName}"
                }
              }
              
              $window = $windows[0]
              $hwnd = $window.MainWindowHandle
              
              # Get window bounds
              $rect = New-Object Win32+RECT
              [Win32]::GetWindowRect($hwnd, [ref]$rect) | Out-Null
              
              # Add padding to ensure we capture the full window including shadows
              $padding = 10
              $rect.Left = [Math]::Max(0, $rect.Left - $padding)
              $rect.Top = [Math]::Max(0, $rect.Top - $padding)
              $rect.Right = $rect.Right + $padding
              $rect.Bottom = $rect.Bottom + $padding
              
              $width = $rect.Right - $rect.Left
              $height = $rect.Bottom - $rect.Top
              
              # Bring window to foreground and wait for it to fully render
              [Win32]::SetForegroundWindow($hwnd) | Out-Null
              Start-Sleep -Milliseconds 200
              
              # Capture the window with DPI awareness
              $bitmap = New-Object System.Drawing.Bitmap $width, $height
              $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
              $graphics.CopyFromScreen($rect.Left, $rect.Top, 0, 0, $bitmap.Size)
              
              $bitmap.Save('${windowsPath.replace(/\\/g, '\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png)
              $graphics.Dispose()
              $bitmap.Dispose()
              if ("${windowTitle}" -ne "") {
                Write-Host "Screenshot of window '${windowTitle}' saved successfully"
              } else {
                Write-Host "Screenshot of process '${processName}' saved successfully"
              }
            `;
          } else if (monitor === 'all') {
            // Current behavior - capture all screens
            psScript = `
              Add-Type -AssemblyName System.Windows.Forms
              Add-Type -AssemblyName System.Drawing
              # Enable per-monitor DPI awareness
              Add-Type @"
                using System.Runtime.InteropServices;
                public class DPI {
                  [DllImport("shcore.dll")]
                  public static extern int SetProcessDpiAwareness(int value);
                }
    "@
              [DPI]::SetProcessDpiAwareness(2)
              
              $screen = [System.Windows.Forms.SystemInformation]::VirtualScreen
              $bitmap = New-Object System.Drawing.Bitmap $screen.Width, $screen.Height
              $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
              $graphics.CopyFromScreen($screen.Left, $screen.Top, 0, 0, $bitmap.Size)
              $bitmap.Save('${windowsPath.replace(/\\/g, '\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png)
              $graphics.Dispose()
              $bitmap.Dispose()
              Write-Host 'Screenshot saved successfully'
            `;
          } else {
            // Capture specific monitor
            psScript = `
              Add-Type -AssemblyName System.Windows.Forms
              Add-Type -AssemblyName System.Drawing
              
              # Enable DPI awareness - MUST be per-monitor for multi-monitor setups with scaling
              Add-Type @"
                using System;
                using System.Runtime.InteropServices;
                public class DPIAware {
                  [DllImport("user32.dll")]
                  public static extern bool SetProcessDPIAware();
                  
                  [DllImport("shcore.dll")]
                  public static extern int SetProcessDpiAwareness(int value);
                }
    "@
              # Always use per-monitor DPI awareness (2) for accurate capture
              [DPIAware]::SetProcessDpiAwareness(2)
              
              $screens = [System.Windows.Forms.Screen]::AllScreens
              $targetScreen = $null
              
              # Sort by X position ascending (left to right) to match Windows display numbering
              $sortedScreens = $screens | Sort-Object { $_.Bounds.X }
              
              # Debug: List all monitors
              Write-Host "Available monitors:"
              for ($i = 0; $i -lt $sortedScreens.Count; $i++) {
                $screen = $sortedScreens[$i]
                Write-Host "  Monitor $($i + 1): $($screen.DeviceName) - Bounds: X=$($screen.Bounds.X), Y=$($screen.Bounds.Y), Width=$($screen.Bounds.Width), Height=$($screen.Bounds.Height) - Primary: $($screen.Primary)"
              }
              
              if ('${monitor}' -eq 'primary') {
                $targetScreen = [System.Windows.Forms.Screen]::PrimaryScreen
              } elseif ('${monitor}' -match '^\\d+$') {
                $index = [int]'${monitor}' - 1
                if ($index -ge 0 -and $index -lt $sortedScreens.Count) {
                  $targetScreen = $sortedScreens[$index]
                } else {
                  throw "Monitor ${monitor} not found. Available monitors: 1 to $($sortedScreens.Count)"
                }
              }
              
              if ($targetScreen -eq $null) {
                throw "Invalid monitor parameter: ${monitor}"
              }
              
              $bounds = $targetScreen.Bounds
              Write-Host "Capturing monitor ${monitor} - Bounds: X=$($bounds.X), Y=$($bounds.Y), Width=$($bounds.Width), Height=$($bounds.Height)"
              
              $bitmap = New-Object System.Drawing.Bitmap($bounds.Width, $bounds.Height)
              $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
              # Use explicit coordinates for accurate capture
              $graphics.CopyFromScreen($bounds.X, $bounds.Y, 0, 0, $bitmap.Size)
              
              $bitmap.Save('${windowsPath.replace(/\\/g, '\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png)
              $graphics.Dispose()
              $bitmap.Dispose()
              Write-Host "Screenshot of monitor ${monitor} saved successfully"
            `;
          }
          
          // Convert to base64 to avoid escaping issues
          const encodedCommand = Buffer.from(psScript, 'utf16le').toString('base64');
          
          // Execute PowerShell with encoded command (suppress CLIXML output)
          const { stdout, stderr } = await execAsync(
            `powershell.exe -ExecutionPolicy Bypass -OutputFormat Text -EncodedCommand ${encodedCommand}`
          );
          
          // PowerShell often outputs CLIXML format to stderr even on success
          // Only throw if there's a real error (not CLIXML or success messages)
          // Check if the stderr contains actual error messages
          const hasRealError = stderr && (
              stderr.includes('throw') ||
              stderr.includes('Exception') ||
              stderr.includes('not found') ||
              (stderr.includes('Error') && !stderr.includes('ErrorId'))
          );
              
          if (hasRealError) {
            // Try to extract available windows from stdout if it's a window not found error
            if (stderr.includes('No window found') && stdout) {
              console.error('Available windows from stdout:', stdout);
            }
            throw new Error(stderr);
          }
          
          // Verify file was created
          const outputPath = path.join(screenshotsDir, filename);
          await fs.access(outputPath);
          
          // Generate appropriate success message based on folder used
          let successPath;
          if (customFolder) {
            // Show the custom folder path as provided by the user
            successPath = path.join(customFolder, filename).replace(/\\/g, '/');
          } else {
            // Show relative path for default screenshots folder
            successPath = `screenshots/${filename}`;
          }
          
          return {
            content: [
              {
                type: 'text',
                text: `Screenshot saved successfully to: ${successPath}`
              }
            ]
          };
        } catch (error) {
          return {
            content: [
              {
                type: 'text',
                text: `Failed to take screenshot: ${error.message}`
              }
            ],
            isError: true
          };
        }
      }
      
      return {
        content: [
          {
            type: 'text',
            text: `Unknown tool: ${request.params.name}`
          }
        ],
        isError: true
      };
    });
  • index.js:27-63 (registration)
    Registration of the 'take_screenshot' tool via the ListToolsRequestSchema handler. Defines the tool name, description, and registers it with the MCP server.
    server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: 'take_screenshot',
            description: 'Take a screenshot of all monitors, specific monitor, or a specific window',
            inputSchema: {
              type: 'object',
              properties: {
                filename: {
                  type: 'string',
                  description: 'Filename for the screenshot (default: screenshot.png)',
                  default: 'screenshot.png'
                },
                monitor: {
                  type: ['string', 'number'],
                  description: 'Which monitor to capture: "all" (default), "primary", or monitor number (1, 2, etc.)',
                  default: 'all'
                },
                windowTitle: {
                  type: 'string',
                  description: 'Capture a specific window by its title (partial match supported)'
                },
                processName: {
                  type: 'string',
                  description: 'Capture a specific window by process name (e.g., "notepad.exe" or just "notepad")'
                },
                folder: {
                  type: 'string',
                  description: 'Custom folder path to save the screenshot (supports both WSL and Windows paths). Defaults to workspace/screenshots/'
                }
              }
            }
          }
        ]
      };
    });
  • Input schema definition for the 'take_screenshot' tool, defining the parameters: filename, monitor, windowTitle, processName, and folder. Specifies types, descriptions, and default values for each parameter.
    inputSchema: {
      type: 'object',
      properties: {
        filename: {
          type: 'string',
          description: 'Filename for the screenshot (default: screenshot.png)',
          default: 'screenshot.png'
        },
        monitor: {
          type: ['string', 'number'],
          description: 'Which monitor to capture: "all" (default), "primary", or monitor number (1, 2, etc.)',
          default: 'all'
        },
        windowTitle: {
          type: 'string',
          description: 'Capture a specific window by its title (partial match supported)'
        },
        processName: {
          type: 'string',
          description: 'Capture a specific window by process name (e.g., "notepad.exe" or just "notepad")'
        },
        folder: {
          type: 'string',
          description: 'Custom folder path to save the screenshot (supports both WSL and Windows paths). Defaults to workspace/screenshots/'
        }
      }
    }
Install Server

Other Tools

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/peterparker57/screenshot-mcp'

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