Skip to main content
Glama

Convert PDF to Image

pdf-image

Convert PDF pages to PNG images for visual verification without external applications. Choose specific pages or all pages with adjustable scale for thumbnail, preview, or detailed views.

Instructions

Generate PNG image(s) from PDF pages.

Use this to visually verify PDF output without opening external applications.

Pages Options:

  • Single page: pages: 1 or pages: 3

  • Multiple pages: pages: [1, 3, 5]

  • All pages: pages: "all"

  • Default: page 1 only

Viewport Scale Recommendations:

  • 0.25 (thumbnail): ~150px wide, smallest file (~15-30KB). Use for quick verification.

  • 0.5 (preview): ~300px wide, good balance (~40-80KB). Recommended default.

  • 1.0 (full): ~612px wide, detailed view (~150-300KB). Use when details matter.

Lower scales produce smaller files, reducing context usage when sharing images.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pdfPathYesAbsolute path to the PDF file
pagesNoPages to render: single number (e.g., 1), array (e.g., [1, 3, 5]), or "all". Default: 1
viewportScaleNoScale factor for the image. Recommended: 0.25 (thumbnail, ~150px wide, smallest), 0.5 (preview, ~300px wide, good balance), 1.0 (full size, ~612px wide). Default: 0.5

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
imagesYesArray of generated images
totalPagesYesTotal pages rendered

Implementation Reference

  • The core handler function that validates input PDF, converts specified pages to PNG images at given scale using pdf-to-png-converter, stores images in storage directory, generates access URIs, and returns structured content with image metadata including paths, dimensions, and sizes.
    async function handler(args: Input, extra: StorageExtra): Promise<CallToolResult> {
      const { storageContext } = extra;
      const { storageDir, baseUrl, transport } = storageContext;
      const { pdfPath, pages = 1, viewportScale = 0.5 } = args;
    
      try {
        // Validate PDF exists
        if (!existsSync(pdfPath)) {
          throw new McpError(ErrorCode.InvalidParams, `PDF file not found: ${pdfPath}`);
        }
    
        // Determine pages to process
        let pagesToProcess: number[] | undefined;
        if (pages === 'all') {
          pagesToProcess = undefined; // undefined means all pages in pdf-to-png-converter
        } else if (typeof pages === 'number') {
          pagesToProcess = [pages];
        } else {
          pagesToProcess = pages;
        }
    
        // Convert to PNG
        const pngPages = await pdfToPng(pdfPath, {
          viewportScale,
          pagesToProcess,
          verbosityLevel: 0,
        });
    
        if (pngPages.length === 0) {
          throw new McpError(ErrorCode.InternalError, 'Failed to render any pages. The PDF may be empty or invalid.');
        }
    
        const pdfBasename = basename(pdfPath, '.pdf');
        const images: z.infer<typeof imageResultSchema>[] = [];
    
        for (const pngPage of pngPages) {
          const pngBuffer = pngPage.content;
          const pageNum = pngPage.pageNumber;
    
          // Generate output filename
          const outputFilename = `${pdfBasename}-p${pageNum}.png`;
    
          // Write to storage
          const { storedName } = await writeFile(pngBuffer, outputFilename, { storageDir });
    
          // Generate URI
          const uri = getFileUri(storedName, transport, {
            storageDir,
            ...(baseUrl && { baseUrl }),
            endpoint: '/files',
          });
    
          images.push({
            imagePath: storedName,
            uri,
            width: pngPage.width,
            height: pngPage.height,
            pageNumber: pageNum,
            fileSizeBytes: pngBuffer.length,
          });
        }
    
        const result: Output = {
          images,
          totalPages: images.length,
        };
    
        return {
          content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
          structuredContent: result,
        };
      } catch (error) {
        if (error instanceof McpError) throw error;
        const message = error instanceof Error ? error.message : String(error);
        throw new McpError(ErrorCode.InternalError, `Error generating PDF image: ${message}`, {
          stack: error instanceof Error ? error.stack : undefined,
        });
      }
    }
  • Zod schemas for input parameters (pdfPath, pages, viewportScale), output structure (images array with metadata), and tool configuration including detailed description and usage recommendations.
    // ============================================================================
    // Schemas
    // ============================================================================
    
    const pagesSchema = z
      .union([z.number().int().min(1), z.array(z.number().int().min(1)), z.literal('all')])
      .optional()
      .describe('Pages to render: single number (e.g., 1), array (e.g., [1, 3, 5]), or "all". Default: 1');
    
    const inputSchema = z.object({
      pdfPath: z.string().describe('Absolute path to the PDF file'),
      pages: pagesSchema,
      viewportScale: z.number().min(0.1).max(3.0).optional().describe('Scale factor for the image. Recommended: 0.25 (thumbnail, ~150px wide, smallest), 0.5 (preview, ~300px wide, good balance), 1.0 (full size, ~612px wide). Default: 0.5'),
    });
    
    const imageResultSchema = z.object({
      imagePath: z.string().describe('Path to the generated PNG image'),
      uri: z.string().describe('URI to access the image'),
      width: z.number().describe('Image width in pixels'),
      height: z.number().describe('Image height in pixels'),
      pageNumber: z.number().describe('Page number that was rendered'),
      fileSizeBytes: z.number().describe('File size in bytes'),
    });
    
    const outputSchema = z.object({
      images: z.array(imageResultSchema).describe('Array of generated images'),
      totalPages: z.number().describe('Total pages rendered'),
    });
    
    const config = {
      title: 'Convert PDF to Image',
      description: `Generate PNG image(s) from PDF pages.
    
    Use this to visually verify PDF output without opening external applications.
    
    **Pages Options:**
    - Single page: \`pages: 1\` or \`pages: 3\`
    - Multiple pages: \`pages: [1, 3, 5]\`
    - All pages: \`pages: "all"\`
    - Default: page 1 only
    
    **Viewport Scale Recommendations:**
    - **0.25 (thumbnail)**: ~150px wide, smallest file (~15-30KB). Use for quick verification.
    - **0.5 (preview)**: ~300px wide, good balance (~40-80KB). **Recommended default.**
    - **1.0 (full)**: ~612px wide, detailed view (~150-300KB). Use when details matter.
    
    Lower scales produce smaller files, reducing context usage when sharing images.`,
      inputSchema,
      outputSchema,
    } as const;
  • The tool factory function createTool() that defines and returns the ToolModule for 'pdf-image', including the name, config (with schemas and description), and the handler function.
    export default function createTool() {
      async function handler(args: Input, extra: StorageExtra): Promise<CallToolResult> {
        const { storageContext } = extra;
        const { storageDir, baseUrl, transport } = storageContext;
        const { pdfPath, pages = 1, viewportScale = 0.5 } = args;
    
        try {
          // Validate PDF exists
          if (!existsSync(pdfPath)) {
            throw new McpError(ErrorCode.InvalidParams, `PDF file not found: ${pdfPath}`);
          }
    
          // Determine pages to process
          let pagesToProcess: number[] | undefined;
          if (pages === 'all') {
            pagesToProcess = undefined; // undefined means all pages in pdf-to-png-converter
          } else if (typeof pages === 'number') {
            pagesToProcess = [pages];
          } else {
            pagesToProcess = pages;
          }
    
          // Convert to PNG
          const pngPages = await pdfToPng(pdfPath, {
            viewportScale,
            pagesToProcess,
            verbosityLevel: 0,
          });
    
          if (pngPages.length === 0) {
            throw new McpError(ErrorCode.InternalError, 'Failed to render any pages. The PDF may be empty or invalid.');
          }
    
          const pdfBasename = basename(pdfPath, '.pdf');
          const images: z.infer<typeof imageResultSchema>[] = [];
    
          for (const pngPage of pngPages) {
            const pngBuffer = pngPage.content;
            const pageNum = pngPage.pageNumber;
    
            // Generate output filename
            const outputFilename = `${pdfBasename}-p${pageNum}.png`;
    
            // Write to storage
            const { storedName } = await writeFile(pngBuffer, outputFilename, { storageDir });
    
            // Generate URI
            const uri = getFileUri(storedName, transport, {
              storageDir,
              ...(baseUrl && { baseUrl }),
              endpoint: '/files',
            });
    
            images.push({
              imagePath: storedName,
              uri,
              width: pngPage.width,
              height: pngPage.height,
              pageNumber: pageNum,
              fileSizeBytes: pngBuffer.length,
            });
          }
    
          const result: Output = {
            images,
            totalPages: images.length,
          };
    
          return {
            content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
            structuredContent: result,
          };
        } catch (error) {
          if (error instanceof McpError) throw error;
          const message = error instanceof Error ? error.message : String(error);
          throw new McpError(ErrorCode.InternalError, `Error generating PDF image: ${message}`, {
            stack: error instanceof Error ? error.stack : undefined,
          });
        }
      }
    
      return {
        name: 'pdf-image',
        config,
        handler,
      } satisfies ToolModule;
    }
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 does well by explaining output characteristics (PNG images), file size implications, and context usage considerations. However, it doesn't mention potential limitations like maximum PDF size, processing time, or error conditions, which would be helpful for a mutation tool.

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 perfectly structured and front-loaded with the core purpose, followed by organized sections for pages options and viewport scale recommendations. Every sentence earns its place by providing essential guidance without redundancy. The bullet points and bold formatting enhance readability without adding fluff.

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

Completeness5/5

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

Given the tool's complexity (conversion with multiple parameters) and the presence of an output schema, the description provides complete context. It covers the transformation purpose, parameter semantics with practical examples, usage scenarios, and performance considerations. The output schema will handle return values, so the description appropriately focuses on input guidance and behavioral context.

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

Parameters5/5

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

Despite 100% schema description coverage, the description adds significant value beyond the schema. It provides concrete examples for the 'pages' parameter (single page, multiple pages, all pages), detailed recommendations for 'viewportScale' with pixel dimensions and file sizes, and practical guidance on when to use different scales. This transforms technical parameters into actionable decisions.

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 ('Generate PNG image(s) from PDF pages') and distinguishes it from sibling tools by focusing on visual verification rather than document analysis or text measurement. It goes beyond the title by specifying the output format (PNG) and the transformation process.

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 explicitly states when to use this tool ('Use this to visually verify PDF output without opening external applications') and provides clear alternatives through viewport scale recommendations for different use cases (thumbnail for quick verification, preview as default, full for detailed views). It effectively guides the agent on selecting appropriate parameters based on context.

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/mcp-z/mcp-pdf'

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