execute_js
Execute JavaScript on web pages to extract data, trigger dynamic content, and check page state. Returns values from scripts with return statements while creating a new browser instance for each execution.
Instructions
[STATELESS] Execute JavaScript and get return values + page content. Creates new browser each time. Use for: extracting data, triggering dynamic content, checking page state. Scripts with "return" statements return actual values (strings, numbers, objects, arrays). Note: null returns as {"success": true}. Returns values but page state is lost. For persistent JS execution, use crawl with session_id.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| scripts | Yes | JavaScript to execute. Use "return" to get values back! Each string runs separately. Returns appear in results array. Examples: "return document.title", "return document.querySelectorAll('a').length", "return {url: location.href, links: [...document.links].map(a => a.href)}". Use proper JS syntax: real quotes, no HTML entities. | |
| url | Yes | The URL to load |
Implementation Reference
- src/handlers/utility-handlers.ts:5-68 (handler)Main MCP handler for execute_js tool. Validates inputs, calls Crawl4AI service's executeJS, formats JS results and page markdown into MCP ToolCallResult format.async executeJS(options: JSExecuteEndpointOptions) { try { // Check if scripts is provided if (!options.scripts || options.scripts === null) { throw new Error( 'scripts is required. Please provide JavaScript code to execute. Use "return" statements to get values back.', ); } const result: JSExecuteEndpointResponse = await this.service.executeJS(options); // Extract JavaScript execution results const jsResults = result.js_execution_result?.results || []; // Ensure scripts is always an array for mapping const scripts = Array.isArray(options.scripts) ? options.scripts : [options.scripts]; // Format results for display let formattedResults = ''; if (jsResults.length > 0) { formattedResults = jsResults .map((res: unknown, idx: number) => { const script = scripts[idx] || 'Script ' + (idx + 1); // Handle the actual return value or success/error status let resultStr = ''; if (res && typeof res === 'object' && 'success' in res) { // This is a status object (e.g., from null return or execution without return) const statusObj = res as { success: unknown; error?: unknown }; resultStr = statusObj.success ? 'Executed successfully (no return value)' : `Error: ${statusObj.error || 'Unknown error'}`; } else { // This is an actual return value resultStr = JSON.stringify(res, null, 2); } return `Script: ${script}\nReturned: ${resultStr}`; }) .join('\n\n'); } else { formattedResults = 'No results returned'; } // Handle markdown content - can be string or object let markdownContent = ''; if (result.markdown) { if (typeof result.markdown === 'string') { markdownContent = result.markdown; } else if (typeof result.markdown === 'object' && result.markdown.raw_markdown) { // Use raw_markdown from the object structure markdownContent = result.markdown.raw_markdown; } } return { content: [ { type: 'text', text: `JavaScript executed on: ${options.url}\n\nResults:\n${formattedResults}${markdownContent ? `\n\nPage Content After Execution:\n${markdownContent}` : ''}`, }, ], }; } catch (error) { throw this.formatError(error, 'execute JavaScript'); } }
- src/server.ts:842-845 (registration)Tool registration in CallToolRequestHandler switch statement. Validates args with ExecuteJsSchema and dispatches to utilityHandlers.executeJS.case 'execute_js': return await this.validateAndExecute('execute_js', args, ExecuteJsSchema, async (validatedArgs) => this.utilityHandlers.executeJS(validatedArgs), );
- src/server.ts:190-210 (registration)Tool specification (name, description, inputSchema) advertised in ListToolsRequestHandler response.{ name: 'execute_js', description: '[STATELESS] Execute JavaScript and get return values + page content. Creates new browser each time. Use for: extracting data, triggering dynamic content, checking page state. Scripts with "return" statements return actual values (strings, numbers, objects, arrays). Note: null returns as {"success": true}. Returns values but page state is lost. For persistent JS execution, use crawl with session_id.', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'The URL to load', }, scripts: { type: ['string', 'array'], items: { type: 'string' }, description: 'JavaScript to execute. Use "return" to get values back! Each string runs separately. Returns appear in results array. Examples: "return document.title", "return document.querySelectorAll(\'a\').length", "return {url: location.href, links: [...document.links].map(a => a.href)}". Use proper JS syntax: real quotes, no HTML entities.', }, }, required: ['url', 'scripts'], }, },
- Zod schema for validating execute_js tool inputs (url and scripts). Uses shared JsCodeSchema for JS validation.export const ExecuteJsSchema = createStatelessSchema( z.object({ url: z.string().url(), scripts: JsCodeSchema, }), 'execute_js', );
- src/crawl4ai-service.ts:177-206 (helper)Service layer proxy that validates inputs and forwards execute_js request to external Crawl4AI API endpoint.async executeJS(options: JSExecuteEndpointOptions): Promise<JSExecuteEndpointResponse> { // Validate URL if (!validateURL(options.url)) { throw new Error('Invalid URL format'); } // Ensure scripts is always an array const scripts = Array.isArray(options.scripts) ? options.scripts : [options.scripts]; // Validate each script for (const script of scripts) { if (!validateJavaScriptCode(script)) { throw new Error( 'Invalid JavaScript: Contains HTML entities ("), literal \\n outside strings, or HTML tags. Use proper JS syntax with real quotes and newlines.', ); } } try { const response = await this.axiosClient.post('/execute_js', { url: options.url, scripts: scripts, // Always send as array // Only url and scripts are supported by the endpoint }); return response.data; } catch (error) { return handleAxiosError(error); } }