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