Skip to main content
Glama
steveclarke

MCP Printer Server

by steveclarke

print_file

Print PDF, text, and other file formats to specified printers with options for copies, orientation, and formatting.

Instructions

Print a file to a specified printer. Supports PDF, text, and other common formats. Can specify number of copies and print options.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
filesYesArray of files to print (use single-element array for one file)

Implementation Reference

  • Registers the 'print_file' MCP tool with input schema supporting batch printing, rendering options, and a handler that processes files via handlePrint in batch-helpers.
    server.registerTool(
      "print_file",
      {
        title: "Print File",
        description:
          "Print a file to a specified printer. Supports PDF, text, and other common formats. Can specify number of copies and print options.",
        inputSchema: {
          files: z
            .array(
              z.object({
                file_path: z.string().describe("Full path to the file to print"),
                printer: z
                  .string()
                  .optional()
                  .describe(
                    "Printer name (use list_printers to see available printers). Optional if default printer is set."
                  ),
                copies: z
                  .number()
                  .min(1)
                  .optional()
                  .default(1)
                  .describe("Number of copies to print (default: 1)"),
                options: z
                  .string()
                  .optional()
                  .describe(
                    "Additional CUPS options (e.g., 'landscape', 'sides=two-sided-long-edge')"
                  ),
                skip_confirmation: z
                  .boolean()
                  .optional()
                  .describe(
                    "Skip page count confirmation check (bypasses MCP_PRINTER_CONFIRM_IF_OVER_PAGES threshold)"
                  ),
                ...renderingParametersSchema,
              })
            )
            .describe("Array of files to print (use single-element array for one file)"),
        },
      },
      async ({ files }) => {
        // Check for large batch size
        const batchSizeWarning = checkBatchSizeLimit(files.length, "files")
        if (batchSizeWarning) {
          return batchSizeWarning
        }
    
        // Process each file in the batch
        const results: PrintResult[] = []
        for (const fileSpec of files) {
          const result = await handlePrint(fileSpec)
          results.push(result)
        }
    
        return formatPrintResults(results)
      }
    )
  • handlePrint: Implements the core printing logic for a single file, including optional rendering to PDF, page count confirmation check, execution via executePrintJob, and cleanup.
    export async function handlePrint(spec: FilePrintSpec): Promise<PrintResult> {
      const {
        file_path,
        printer,
        copies = 1,
        options,
        skip_confirmation,
        line_numbers,
        color_scheme,
        font_size,
        line_spacing,
        force_markdown_render,
        force_code_render,
      } = spec
    
      try {
        // Use shared rendering function
        const { actualFilePath, renderedPdf, renderType } = await prepareFileForPrinting({
          filePath: file_path,
          lineNumbers: line_numbers,
          colorScheme: color_scheme,
          fontSize: font_size,
          lineSpacing: line_spacing,
          forceMarkdownRender: force_markdown_render,
          forceCodeRender: force_code_render,
        })
    
        try {
          // Check if we need to trigger page count confirmation
          // Try to parse as PDF - if it works, do the page count check. If it fails, it's not a PDF.
          if (!skip_confirmation && config.confirmIfOverPages > 0) {
            try {
              const pdfPages = await getPdfPageCount(actualFilePath)
              const isDuplex = isDuplexEnabled(options)
              const physicalSheets = calculatePhysicalSheets(pdfPages, isDuplex)
    
              // If exceeds threshold, return error indicating confirmation needed
              if (shouldTriggerConfirmation(physicalSheets)) {
                return {
                  success: false,
                  file_path,
                  message: `Confirmation required: ${pdfPages} pages (${physicalSheets} sheets${formatDuplexInfo(isDuplex)})`,
                  error: ERROR_CODES.PAGE_COUNT_CONFIRMATION_REQUIRED,
                  renderType,
                }
              }
            } catch {
              // Not a PDF or failed to parse - just continue with normal print
              // (This is expected for plain text files, images, etc.)
            }
          }
    
          // Execute print job
          const { printerName } = await executePrintJob(actualFilePath, printer, copies, options)
    
          const copiesInfo = copies > 1 ? ` × ${copies} copies` : ""
          return {
            success: true,
            file_path,
            message: `Printed to ${printerName}${copiesInfo}${formatRenderInfo(renderType)}`,
            renderType,
          }
        } finally {
          // Clean up rendered PDF if it was created
          cleanupRenderedPdf(renderedPdf)
        }
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error)
        return {
          success: false,
          file_path,
          message: `Failed to print: ${message}`,
          error: message,
        }
      }
    }
  • Shared Zod schema for rendering options (line numbers, color scheme, etc.) included in print_file input schema.
    const renderingParametersSchema = {
      line_numbers: z
        .boolean()
        .optional()
        .describe("Show line numbers when rendering code files (overrides global setting)"),
      color_scheme: z
        .string()
        .optional()
        .describe(
          "Syntax highlighting color scheme for code files (e.g., 'github', 'monokai', 'atom-one-light')"
        ),
      font_size: z
        .string()
        .optional()
        .describe("Font size for code files (e.g., '8pt', '10pt', '12pt')"),
      line_spacing: z
        .string()
        .optional()
        .describe("Line spacing for code files (e.g., '1', '1.5', '2')"),
      force_markdown_render: z
        .boolean()
        .optional()
        .describe(
          "Force markdown rendering to PDF (true=always render, false=never render, undefined=use config)"
        ),
      force_code_render: z
        .boolean()
        .optional()
        .describe(
          "Force code rendering to PDF with syntax highlighting (true=always render, false=never render, undefined=use config)"
        ),
    }
  • executePrintJob: Executes the lpr command to send the file to the printer with specified copies, options, and handling defaults.
    export async function executePrintJob(
      filePath: string,
      printer?: string,
      copies: number = 1,
      options?: string
    ): Promise<{ printerName: string; allOptions: string[] }> {
      // Validate copy count against configured maximum
      if (config.maxCopies > 0 && copies > config.maxCopies) {
        throw new Error(
          `Copy count (${copies}) exceeds maximum allowed (${config.maxCopies}). ` +
            `Set MCP_PRINTER_MAX_COPIES environment variable to increase or use 0 for unlimited.`
        )
      }
    
      const args: string[] = []
    
      // Use configured default printer if none specified
      const targetPrinter = printer || config.defaultPrinter
      if (targetPrinter) {
        args.push("-P", targetPrinter)
      }
    
      if (copies > 1) {
        args.push(`-#${copies}`)
      }
    
      // Build options with defaults
      let allOptions = []
    
      // Add default duplex if auto-enabled in config and not already specified
      if (config.autoDuplex && !options?.includes("sides=")) {
        allOptions.push("sides=two-sided-long-edge")
      }
    
      // Add default options if configured
      if (config.defaultOptions.length > 0) {
        allOptions.push(...config.defaultOptions)
      }
    
      // Add user-specified options (these override defaults, split by spaces)
      if (options) {
        allOptions.push(...options.split(/\s+/))
      }
    
      // Add each option with -o flag
      for (const option of allOptions) {
        args.push("-o", option)
      }
    
      // Add file path
      args.push(filePath)
    
      await execa("lpr", args)
    
      // Determine the printer name used
      const printerName =
        targetPrinter || (await execCommand("lpstat", ["-d"])).split(": ")[1] || "default"
    
      return { printerName, allOptions }
    }
  • prepareFileForPrinting: Determines if markdown or code file needs rendering to PDF, performs rendering if needed, returns path to print-ready file.
    export async function prepareFileForPrinting(options: RenderOptions): Promise<RenderResult> {
      // Validate file path security
      validateFilePath(options.filePath)
    
      let actualFilePath = options.filePath
      let renderedPdf: string | null = null
      let renderType = ""
    
      // Check if file should be auto-rendered to PDF (markdown)
      const shouldRenderMarkdown =
        options.forceMarkdownRender !== undefined
          ? options.forceMarkdownRender &&
            MARKDOWN_EXTENSIONS.some((ext) => options.filePath.toLowerCase().endsWith(`.${ext}`))
          : shouldRenderToPdf(options.filePath)
    
      if (shouldRenderMarkdown) {
        try {
          renderedPdf = await renderMarkdownToPdf(options.filePath)
          actualFilePath = renderedPdf
          renderType = "markdown → PDF"
        } catch (error) {
          // If fallback is enabled, use original file; otherwise throw error
          if (config.fallbackOnRenderError) {
            console.error(`Warning: Failed to render ${options.filePath}, using as-is:`, error)
          } else {
            throw error
          }
        }
      }
      // Check if file should be rendered as code with syntax highlighting
      else if (
        options.forceCodeRender !== undefined
          ? options.forceCodeRender
          : await shouldRenderCode(options.filePath)
      ) {
        try {
          renderedPdf = await renderCodeToPdf(options.filePath, {
            lineNumbers: options.lineNumbers,
            colorScheme: options.colorScheme,
            fontSize: options.fontSize,
            lineSpacing: options.lineSpacing,
          })
          actualFilePath = renderedPdf
          renderType = "code → PDF (syntax highlighted)"
        } catch (error) {
          // If fallback is enabled, use original file; otherwise throw error
          if (config.fallbackOnRenderError) {
            console.error(`Warning: Failed to render code ${options.filePath}, using as-is:`, error)
          } else {
            throw error
          }
        }
      }
    
      return { actualFilePath, renderedPdf, renderType }
    }

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/steveclarke/mcp-printer'

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