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
| Name | Required | Description | Default |
|---|---|---|---|
| filename | No | Filename for the screenshot (default: screenshot.png) | screenshot.png |
| monitor | No | Which monitor to capture: "all" (default), "primary", or monitor number (1, 2, etc.) | all |
| windowTitle | No | Capture a specific window by its title (partial match supported) | |
| processName | No | Capture a specific window by process name (e.g., "notepad.exe" or just "notepad") | |
| folder | No | Custom 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/' } } } } ] }; }); - index.js:33-59 (schema)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/' } } }