screenshot_element
Take a screenshot of a single webpage element by providing its CSS selector or XPath, capturing only that part.
Instructions
Take a screenshot of a specific element only. Provide a CSS selector or XPath to capture just that element.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| selector | Yes | CSS selector or XPath for the element to screenshot | |
| selectorType | No | Selector type | css |
| format | No | Image format: png (default) or jpeg | |
| quality | No | JPEG quality 0-100 | |
| tabId | No | Target tab ID (defaults to currently active tab) | |
| apiKey | No | API key for authentication if enabled |
Implementation Reference
- src/tools/screenshot.ts:69-100 (handler)The handler function for the 'screenshot_element' tool. It accepts a CSS selector or XPath, optionally a selectorType, format, quality, tabId, and apiKey. It sends the 'screenshot_element' command via the WebSocket bridge and returns the resulting image.
server.tool( 'screenshot_element', 'Take a screenshot of a specific element only. Provide a CSS selector or XPath to capture just that element.', { selector: z.string().describe('CSS selector or XPath for the element to screenshot'), selectorType: z.enum(['css', 'xpath']).optional().default('css').describe('Selector type'), format: z.enum(['png', 'jpeg']).optional().describe('Image format: png (default) or jpeg'), quality: z.number().min(0).max(100).optional().describe('JPEG quality 0-100'), tabId: z.number().optional().describe('Target tab ID (defaults to currently active tab)'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, async ({ selector, selectorType, format, quality, tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'screenshot_element', params: { selector, selectorType, format, quality }, tabId, apiKey, timeout: LONG_TIMEOUT, }); if (!result.success) { return { content: [{ type: 'text', text: `Error: ${result.error?.message}` }], isError: true }; } const data = result.data as { image: string; mimeType: string }; return { content: [{ type: 'image', data: data.image, mimeType: data.mimeType, }], }; } ); - src/tools/screenshot.ts:72-79 (schema)Input schema for screenshot_element using Zod validation. Accepts: selector (required string), selectorType (optional enum 'css'/'xpath', default 'css'), format (optional enum 'png'/'jpeg'), quality (optional 0-100), tabId (optional number), apiKey (optional string).
{ selector: z.string().describe('CSS selector or XPath for the element to screenshot'), selectorType: z.enum(['css', 'xpath']).optional().default('css').describe('Selector type'), format: z.enum(['png', 'jpeg']).optional().describe('Image format: png (default) or jpeg'), quality: z.number().min(0).max(100).optional().describe('JPEG quality 0-100'), tabId: z.number().optional().describe('Target tab ID (defaults to currently active tab)'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, - src/tools/screenshot.ts:69-100 (registration)The tool 'screenshot_element' is registered with the MCP server via server.tool() inside registerScreenshotTools(). This function is called from src/tools/index.ts line 33.
server.tool( 'screenshot_element', 'Take a screenshot of a specific element only. Provide a CSS selector or XPath to capture just that element.', { selector: z.string().describe('CSS selector or XPath for the element to screenshot'), selectorType: z.enum(['css', 'xpath']).optional().default('css').describe('Selector type'), format: z.enum(['png', 'jpeg']).optional().describe('Image format: png (default) or jpeg'), quality: z.number().min(0).max(100).optional().describe('JPEG quality 0-100'), tabId: z.number().optional().describe('Target tab ID (defaults to currently active tab)'), apiKey: z.string().optional().describe('API key for authentication if enabled'), }, async ({ selector, selectorType, format, quality, tabId, apiKey }) => { const result = await bridge.sendCommand({ command: 'screenshot_element', params: { selector, selectorType, format, quality }, tabId, apiKey, timeout: LONG_TIMEOUT, }); if (!result.success) { return { content: [{ type: 'text', text: `Error: ${result.error?.message}` }], isError: true }; } const data = result.data as { image: string; mimeType: string }; return { content: [{ type: 'image', data: data.image, mimeType: data.mimeType, }], }; } ); - src/tools/index.ts:33-35 (registration)Registration call chain: registerAllTools() calls registerScreenshotTools(), which registers 'screenshot_element' on the MCP server.
registerScreenshotTools(server, bridge); registerClickTools(server, bridge); registerInputTools(server, bridge); - src/websocket-bridge.ts:4-129 (helper)WebSocketBridge.sendCommand() is the helper that dispatches the 'screenshot_element' command to the Chrome extension via WebSocket, returning the result asynchronously.
export class WebSocketBridge { private wss: WebSocketServer; private client: WebSocket | null = null; private pending = new Map<string, PendingRequest>(); private port: number; constructor(port: number) { this.port = port; this.wss = new WebSocketServer({ port }); this.wss.on('connection', (ws) => { if (this.client) { this.client.close(); } this.client = ws; console.error(`[MCP Bridge] Extension connected on port ${this.port}`); ws.on('message', (data) => { try { const message = JSON.parse(data.toString()); if (message.type === 'response' && message.requestId) { const pending = this.pending.get(message.requestId); if (pending) { clearTimeout(pending.timer); this.pending.delete(message.requestId); pending.resolve({ success: message.success, data: message.data, error: message.error, }); } } } catch (err) { console.error('[MCP Bridge] Failed to parse message:', err); } }); ws.on('close', () => { console.error('[MCP Bridge] Extension disconnected'); if (this.client === ws) { this.client = null; } this.rejectAllPending('Extension disconnected'); }); ws.on('error', (err) => { console.error('[MCP Bridge] WebSocket error:', err.message); }); }); this.wss.on('listening', () => { console.error(`[MCP Bridge] WebSocket server listening on port ${this.port}`); }); } isConnected(): boolean { return this.client !== null && this.client.readyState === WebSocket.OPEN; } async sendCommand(cmd: BridgeCommand): Promise<BridgeResponse> { if (!this.isConnected()) { return { success: false, error: { code: 'NOT_CONNECTED', message: 'Chrome extension is not connected. Ensure the extension is installed, enabled, and the browser is running.', }, }; } const id = crypto.randomUUID(); const timeout = cmd.timeout ?? DEFAULT_TIMEOUT; return new Promise<BridgeResponse>((resolve, reject) => { const timer = setTimeout(() => { this.pending.delete(id); resolve({ success: false, error: { code: 'TIMEOUT', message: `Command '${cmd.command}' timed out after ${timeout}ms`, }, }); }, timeout); this.pending.set(id, { resolve, reject, timer }); const message = { id, type: 'request', command: cmd.command, params: cmd.params, tabId: cmd.tabId, apiKey: cmd.apiKey, timestamp: Date.now(), }; this.client!.send(JSON.stringify(message)); }); } private rejectAllPending(reason: string): void { for (const [id, pending] of this.pending) { clearTimeout(pending.timer); pending.resolve({ success: false, error: { code: 'CONNECTION_LOST', message: reason, }, }); } this.pending.clear(); } async close(): Promise<void> { this.rejectAllPending('Server shutting down'); if (this.client) { this.client.close(); this.client = null; } return new Promise((resolve) => { this.wss.close(() => resolve()); }); } }