Skip to main content
Glama
omgwtfwow

MCP Server for Crawl4AI

by omgwtfwow

execute_js

Execute JavaScript on web pages to extract data, trigger dynamic content, and check page state, returning values directly from scripts with return statements.

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

TableJSON Schema
NameRequiredDescriptionDefault
urlYesThe URL to load
scriptsYesJavaScript 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.

Implementation Reference

  • Core handler implementation for the 'execute_js' tool. Validates input, executes JS via Crawl4AI service, processes results from multiple scripts, formats output including return values and post-execution page markdown into MCP content blocks.
    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)
    Registration of the 'execute_js' tool in the MCP server request handler switch statement. Validates arguments using ExecuteJsSchema and delegates to utilityHandlers.executeJS.
    case 'execute_js':
      return await this.validateAndExecute('execute_js', args, ExecuteJsSchema, async (validatedArgs) =>
        this.utilityHandlers.executeJS(validatedArgs),
      );
  • Zod schema for validating 'execute_js' tool inputs: requires URL and scripts (string or array of JS code). Uses JsCodeSchema for JS validation.
    export const ExecuteJsSchema = createStatelessSchema(
      z.object({
        url: z.string().url(),
        scripts: JsCodeSchema,
      }),
      'execute_js',
    );
  • src/server.ts:191-210 (registration)
    Tool specification for 'execute_js' in the MCP listTools response, including name, detailed description, and inputSchema definition.
      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'],
      },
    },
  • Supporting schema for JavaScript code validation used in ExecuteJsSchema. Validates single string or array, rejects invalid JS with HTML entities or malformed syntax.
    export const JsCodeSchema = z
      .union([
        z.string().refine(validateJavaScriptCode, {
          message:
            'Invalid JavaScript: Contains HTML entities ("), literal \\n outside strings, or HTML tags. Use proper JS syntax with real quotes and newlines.',
        }),
        z.array(
          z.string().refine(validateJavaScriptCode, {
            message:
              'Invalid JavaScript: Contains HTML entities ("), literal \\n outside strings, or HTML tags. Use proper JS syntax with real quotes and newlines.',
          }),
        ),
      ])
      .describe('JavaScript code as string or array of strings');
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively describes key behavioral traits: the stateless nature ('[STATELESS]', 'Creates new browser each time', 'page state is lost'), return value handling ('Scripts with "return" statements return actual values', 'null returns as {"success": true}'), and execution characteristics ('Each string runs separately. Returns appear in results array'). The only minor gap is lack of explicit error handling or timeout information.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured with front-loaded key information ('[STATELESS] Execute JavaScript and get return values + page content'), followed by usage guidelines, behavioral notes, and sibling differentiation. Every sentence adds value without redundancy, and the information density is high while remaining readable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (JavaScript execution in browser context), no annotations, and no output schema, the description does an excellent job covering execution behavior, return value handling, and sibling differentiation. The only gap is the lack of explicit information about what the output structure looks like (though this is somewhat implied by the return value explanations). For a tool with this complexity level, it's nearly complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage, the baseline is 3. The description adds meaningful context beyond the schema by explaining how return values work ('Scripts with "return" statements return actual values'), providing concrete examples of script syntax, and clarifying that 'null returns as {"success": true}'. This significantly enhances understanding of how to use the scripts parameter effectively.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with specific verbs ('Execute JavaScript and get return values + page content') and distinguishes it from siblings by mentioning it 'Creates new browser each time' and contrasting with 'For persistent JS execution, use crawl with session_id.' This provides a clear differentiation from tools like crawl, manage_session, and smart_crawl.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool ('Use for: extracting data, triggering dynamic content, checking page state') and when not to use it ('For persistent JS execution, use crawl with session_id'). It names a specific alternative (crawl with session_id) and explains the trade-off between stateless execution and persistence.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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

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