Skip to main content
Glama

create-pdf

Generate PDF documents with text, images, shapes, and full layout control. Supports Unicode fonts, custom page sizes, colors, and precise positioning for simple to complex designs.

Instructions

Create a PDF document with text, images, shapes, and full layout control. Supports progressive enhancement from simple documents to complex designs.

Key Features: • Unicode support: Chinese, Japanese, Korean, Arabic, emoji - auto-detects system fonts • Page setup (custom size, margins, background color) • Text with colors, fonts, positioning, and styling (oblique, spacing, etc.) • Shapes (rectangles, circles, lines) for visual design • Emoji rendering with inline image support

Common Patterns:

  1. Simple Document (no pageSetup needed): [{"type": "heading", "text": "Title"}, {"type": "text", "text": "Body"}]

  2. Styled Document (add colors/background): pageSetup: {"backgroundColor": "#F5F5F5"} content: [{"type": "heading", "text": "Title", "color": "#4A90E2"}]

  3. Custom Layout (letterhead, certificates): [{"type": "rect", "x": 0, "y": 0, "width": 612, "height": 80, "fillColor": "navy"}, {"type": "heading", "text": "Company", "color": "white", "y": 25, "align": "center"}]

  4. Algorithmic Design (tapering fonts, progressive layouts): Calculate values in a loop, then pass to tool: for (let i = 0; i < lines.length; i++) { const progress = i / lines.length; const fontSize = 8 + (progress * 16); // 8pt → 24pt content.push({"type": "text", "text": lines[i], "fontSize": fontSize}); }

Coordinate System: • Origin (0, 0) is top-left corner • Letter size page: 612 x 792 points (8.5" x 11") • 72 points = 1 inch

Tips: • Use "align": "center" for centered text (works with or without "width") • Use "x" to manually position (calculate as: (pageWidth - textWidth) / 2 for centering) • Colors: hex ("#FFD700") or named ("gold", "navy", "black") • Oblique: true for default slant, or number for degrees (15 = italic look) • All pageSetup and visual styling fields are optional - defaults match standard documents

Input Schema

NameRequiredDescriptionDefault
authorNoDocument author metadata
contentYesArray of content items to add to the PDF
filenameNoOptional filename for the PDF (defaults to "document.pdf"). SECURITY: Filenames are sanitized and written to a sandboxed directory: • Default: ~/.mcp-pdf/ • Override: Set PDF_OUTPUT_DIR environment variable • Path traversal attempts (.., /, etc) are blocked • Only alphanumeric, spaces, hyphens, underscores, and dots allowed • If file exists, timestamp is appended automatically
fontNoFont for the PDF (optional - defaults to "auto"). **Default**: "auto" auto-detects Unicode fonts on macOS/Linux/Windows. Works for Chinese, Japanese, Korean, Arabic, emoji, and all languages. Advanced options: • Built-in: Helvetica, Times-Roman, Courier - ASCII/Latin only, no Chinese support • Custom: Absolute path to TTF/OTF font file for special needs **You can omit this parameter** - auto-detection works for 99% of use cases.
pageSetupNoOptional page setup configuration
titleNoDocument title metadata

Input Schema (JSON Schema)

{ "properties": { "author": { "description": "Document author metadata", "type": "string" }, "content": { "description": "Array of content items to add to the PDF", "items": { "anyOf": [ { "additionalProperties": false, "properties": { "align": { "description": "Text alignment (default: left)", "enum": [ "left", "center", "right", "justify" ], "type": "string" }, "bold": { "description": "Use bold font", "type": "boolean" }, "characterSpacing": { "description": "Letter spacing in points", "type": "number" }, "color": { "description": "Text color (hex like #FFD700 or named color like \"gold\", default: black)", "type": "string" }, "continued": { "description": "Text continues inline with next item", "type": "boolean" }, "fontSize": { "description": "Font size in points (default: 12)", "type": "number" }, "indent": { "description": "First line indent in points", "type": "number" }, "lineBreak": { "description": "Enable line wrapping (default: true)", "type": "boolean" }, "lineGap": { "description": "Space between lines in points", "type": "number" }, "link": { "description": "URL to link this text to", "type": "string" }, "moveDown": { "description": "Lines to move down after this item (0 = no spacing)", "type": "number" }, "oblique": { "description": "Slant text (true or angle in degrees)", "type": [ "boolean", "number" ] }, "paragraphGap": { "description": "Space between paragraphs in points", "type": "number" }, "strike": { "description": "Strikethrough text", "type": "boolean" }, "text": { "description": "Text content", "type": "string" }, "type": { "const": "text", "description": "Text content", "type": "string" }, "underline": { "description": "Underline text", "type": "boolean" }, "width": { "description": "Text wrapping width in points", "type": "number" }, "wordSpacing": { "description": "Word spacing in points", "type": "number" }, "x": { "description": "X position", "type": "number" }, "y": { "description": "Y position", "type": "number" } }, "required": [ "type" ], "type": "object" }, { "additionalProperties": false, "properties": { "align": { "description": "Text alignment (default: left)", "enum": [ "left", "center", "right", "justify" ], "type": "string" }, "bold": { "description": "Use bold font (default: true)", "type": "boolean" }, "characterSpacing": { "description": "Letter spacing in points", "type": "number" }, "color": { "description": "Text color (hex like #FFD700 or named color like \"gold\", default: black)", "type": "string" }, "continued": { "description": "Text continues inline with next item", "type": "boolean" }, "fontSize": { "description": "Font size in points (default: 24)", "type": "number" }, "indent": { "description": "First line indent in points", "type": "number" }, "lineBreak": { "description": "Enable line wrapping (default: true)", "type": "boolean" }, "lineGap": { "description": "Space between lines in points", "type": "number" }, "link": { "description": "URL to link this text to", "type": "string" }, "moveDown": { "description": "Lines to move down after this item (0 = no spacing)", "type": "number" }, "oblique": { "description": "Slant text (true or angle in degrees)", "type": [ "boolean", "number" ] }, "paragraphGap": { "description": "Space between paragraphs in points", "type": "number" }, "strike": { "description": "Strikethrough text", "type": "boolean" }, "text": { "description": "Text content", "type": "string" }, "type": { "const": "heading", "description": "Heading content", "type": "string" }, "underline": { "description": "Underline text", "type": "boolean" }, "width": { "description": "Text wrapping width in points", "type": "number" }, "wordSpacing": { "description": "Word spacing in points", "type": "number" }, "x": { "description": "X position", "type": "number" }, "y": { "description": "Y position", "type": "number" } }, "required": [ "type" ], "type": "object" }, { "additionalProperties": false, "properties": { "height": { "description": "Image height", "type": "number" }, "imagePath": { "description": "Path to image file", "type": "string" }, "type": { "const": "image", "description": "Image content", "type": "string" }, "width": { "description": "Image width", "type": "number" }, "x": { "description": "X position", "type": "number" }, "y": { "description": "Y position", "type": "number" } }, "required": [ "type", "imagePath" ], "type": "object" }, { "additionalProperties": false, "properties": { "fillColor": { "description": "Fill color (hex like #FFD700 or named color)", "type": "string" }, "height": { "description": "Height in points", "type": "number" }, "lineWidth": { "description": "Stroke width in points (default: 1)", "type": "number" }, "strokeColor": { "description": "Stroke/border color", "type": "string" }, "type": { "const": "rect", "description": "Rectangle shape", "type": "string" }, "width": { "description": "Width in points", "type": "number" }, "x": { "description": "X position (top-left corner)", "type": "number" }, "y": { "description": "Y position (top-left corner)", "type": "number" } }, "required": [ "type", "x", "y", "width", "height" ], "type": "object" }, { "additionalProperties": false, "properties": { "fillColor": { "description": "Fill color (hex like #FFD700 or named color)", "type": "string" }, "lineWidth": { "description": "Stroke width in points (default: 1)", "type": "number" }, "radius": { "description": "Radius in points", "type": "number" }, "strokeColor": { "description": "Stroke/border color", "type": "string" }, "type": { "const": "circle", "description": "Circle shape", "type": "string" }, "x": { "description": "Center X position", "type": "number" }, "y": { "description": "Center Y position", "type": "number" } }, "required": [ "type", "x", "y", "radius" ], "type": "object" }, { "additionalProperties": false, "properties": { "lineWidth": { "description": "Line width in points (default: 1)", "type": "number" }, "strokeColor": { "description": "Line color (default: black)", "type": "string" }, "type": { "const": "line", "description": "Line shape", "type": "string" }, "x1": { "description": "Start X position", "type": "number" }, "x2": { "description": "End X position", "type": "number" }, "y1": { "description": "Start Y position", "type": "number" }, "y2": { "description": "End Y position", "type": "number" } }, "required": [ "type", "x1", "y1", "x2", "y2" ], "type": "object" }, { "additionalProperties": false, "properties": { "type": { "const": "pageBreak", "description": "Page break", "type": "string" } }, "required": [ "type" ], "type": "object" } ] }, "type": "array" }, "filename": { "description": "Optional filename for the PDF (defaults to \"document.pdf\").\n\nSECURITY: Filenames are sanitized and written to a sandboxed directory:\n• Default: ~/.mcp-pdf/\n• Override: Set PDF_OUTPUT_DIR environment variable\n• Path traversal attempts (.., /, etc) are blocked\n• Only alphanumeric, spaces, hyphens, underscores, and dots allowed\n• If file exists, timestamp is appended automatically", "type": "string" }, "font": { "description": "Font for the PDF (optional - defaults to \"auto\").\n\n**Default**: \"auto\" auto-detects Unicode fonts on macOS/Linux/Windows. Works for Chinese, Japanese, Korean, Arabic, emoji, and all languages.\n\nAdvanced options:\n• Built-in: Helvetica, Times-Roman, Courier - ASCII/Latin only, no Chinese support\n• Custom: Absolute path to TTF/OTF font file for special needs\n\n**You can omit this parameter** - auto-detection works for 99% of use cases.", "type": "string" }, "pageSetup": { "additionalProperties": false, "description": "Optional page setup configuration", "properties": { "backgroundColor": { "description": "Page background color (hex like #000000 or named color like \"black\")", "type": "string" }, "margins": { "additionalProperties": false, "description": "Page margins in points (default: {top: 72, bottom: 72, left: 72, right: 72})", "properties": { "bottom": { "description": "Bottom margin in points", "type": "number" }, "left": { "description": "Left margin in points", "type": "number" }, "right": { "description": "Right margin in points", "type": "number" }, "top": { "description": "Top margin in points", "type": "number" } }, "required": [ "top", "bottom", "left", "right" ], "type": "object" }, "size": { "description": "Page size [width, height] in points (default: [612, 792] = Letter)", "items": [ { "type": "number" }, { "type": "number" } ], "maxItems": 2, "minItems": 2, "type": "array" } }, "type": "object" }, "title": { "description": "Document title metadata", "type": "string" } }, "required": [ "content" ], "type": "object" }

Implementation Reference

  • The core handler function for the 'create-pdf' tool. It creates a PDF document using PDFKit, supports complex layouts with text (including emoji and fonts), images, vector shapes (rect, circle, line), page breaks, custom page setup, and saves the output file.
    async (args: CreatePdfArgs) => { const { filename = 'document.pdf', title, author, font, pageSetup, content } = args; try { const docOptions: PDFKit.PDFDocumentOptions = { info: { ...(title && { Title: title }), ...(author && { Author: author }), ...(filename && { Subject: filename }), }, }; if (pageSetup?.size) docOptions.size = pageSetup.size; if (pageSetup?.margins) docOptions.margins = pageSetup.margins; const doc = new PDFDocument(docOptions); const chunks: Buffer[] = []; doc.on('data', (c: Buffer) => chunks.push(c)); const pdfPromise = new Promise<Buffer>((resolve, reject) => { doc.on('end', () => resolve(Buffer.concat(chunks))); doc.on('error', reject); }); if (pageSetup?.backgroundColor) { const pageSize = pageSetup?.size || [612, 792]; doc.rect(0, 0, pageSize[0], pageSize[1]).fill(pageSetup.backgroundColor); } const contentText = JSON.stringify(content); const containsEmoji = hasEmoji(contentText); const emojiAvailable = containsEmoji ? registerEmojiFont() : false; const fonts = await setupFonts(doc, font); const { regular: regularFont, bold: boldFont } = fonts; const warnings: string[] = []; for (const item of content) { if ((item.type === 'text' || item.type === 'heading') && item.text) { const fnt = item.bold ? boldFont : regularFont; const validation = validateTextForFont(item.text, fnt); if (validation.hasUnsupportedCharacters) warnings.push(...validation.warnings); } } const drawBackgroundOnPage = () => { if (pageSetup?.backgroundColor) { const pageSize = pageSetup?.size || [612, 792]; const x = doc.x; const y = doc.y; doc.rect(0, 0, pageSize[0], pageSize[1]).fill(pageSetup.backgroundColor); doc.x = x; doc.y = y; } }; doc.on('pageAdded', drawBackgroundOnPage); for (const item of content) { switch (item.type) { case 'text': { if (item.x !== undefined && item.align !== undefined) throw new Error('Cannot use both x and align in text element'); const fontSize = item.fontSize ?? 12; const fnt = item.bold ? boldFont : regularFont; if (item.color) doc.fillColor(item.color); const options = extractTextOptions(item); renderTextWithEmoji(doc, item.text ?? '', fontSize, fnt, emojiAvailable, options); if (item.color) doc.fillColor('black'); if (item.moveDown !== undefined) doc.moveDown(item.moveDown); break; } case 'heading': { if (item.x !== undefined && item.align !== undefined) throw new Error('Cannot use both x and align in heading element'); const fontSize = item.fontSize ?? 24; const fnt = item.bold !== false ? boldFont : regularFont; if (item.color) doc.fillColor(item.color); const options = extractTextOptions(item); renderTextWithEmoji(doc, item.text ?? '', fontSize, fnt, emojiAvailable, options); if (item.color) doc.fillColor('black'); if (item.moveDown !== undefined) doc.moveDown(item.moveDown); break; } case 'image': { const opts: Record<string, unknown> = {}; if (item.width !== undefined) opts.width = item.width; if (item.height !== undefined) opts.height = item.height; if (item.x !== undefined && item.y !== undefined) doc.image(item.imagePath, item.x, item.y, opts); else doc.image(item.imagePath, opts); break; } case 'rect': { doc.rect(item.x, item.y, item.width, item.height); if (item.fillColor && item.strokeColor) { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc.fillAndStroke(item.fillColor, item.strokeColor); } else if (item.fillColor) doc.fill(item.fillColor); else if (item.strokeColor) { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc.stroke(item.strokeColor); } doc.fillColor('black'); break; } case 'circle': { doc.circle(item.x, item.y, item.radius); if (item.fillColor && item.strokeColor) { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc.fillAndStroke(item.fillColor, item.strokeColor); } else if (item.fillColor) doc.fill(item.fillColor); else if (item.strokeColor) { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc.stroke(item.strokeColor); } doc.fillColor('black'); break; } case 'line': { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc .moveTo(item.x1, item.y1) .lineTo(item.x2, item.y2) .stroke(item.strokeColor || 'black'); break; } case 'pageBreak': { doc.addPage(); break; } } } doc.end(); const pdfBuffer = await pdfPromise; const uuid = crypto.randomUUID(); const storedFilename = `${uuid}.pdf`; const { fullPath } = await writePdfToFile(pdfBuffer, storedFilename, config.storageDir); const includePath = config.includePath; const warningText = warnings.length > 0 ? `\n\n⚠️ Character Warnings:\n${warnings.map((w) => `• ${w}`).join('\n')}` : ''; return { content: [ { type: 'text' as const, text: ['PDF created successfully', `Resource: mcp-pdf://${uuid}`, includePath ? `Output: ${fullPath}` : undefined, `Size: ${pdfBuffer.length} bytes`, filename !== 'document.pdf' ? `Filename: ${filename}` : undefined, warningText || undefined].filter(Boolean).join('\n'), }, ], }; } catch (error) { const message = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text' as const, text: `Error creating PDF: ${message}` }], isError: true }; } }
  • Comprehensive Zod input schema defining parameters for PDF creation: metadata, page setup, and typed content array supporting text/heading with styling, images, geometric shapes, and page breaks.
    inputSchema: { filename: z.string().optional().describe('Optional logical filename (metadata only). Storage uses UUID. Defaults to "document.pdf".'), title: z.string().optional().describe('Document title metadata'), author: z.string().optional().describe('Document author metadata'), font: z.string().optional().describe('Font strategy (default: auto). Built-ins: Helvetica, Times-Roman, Courier. Use a path or URL for Unicode.'), pageSetup: z .object({ size: z.tuple([z.number(), z.number()]).optional(), margins: z .object({ top: z.number(), bottom: z.number(), left: z.number(), right: z.number(), }) .optional(), backgroundColor: z.string().optional(), }) .optional(), content: z.array( z.union([ z.object({ type: z.literal('text'), text: z.string().optional(), fontSize: z.number().optional(), bold: z.boolean().optional(), color: z.string().optional(), x: z.number().optional(), y: z.number().optional(), align: z.enum(['left', 'center', 'right', 'justify']).optional(), indent: z.number().optional(), lineGap: z.number().optional(), paragraphGap: z.number().optional(), width: z.number().optional(), moveDown: z.number().optional(), underline: z.boolean().optional(), strike: z.boolean().optional(), oblique: z.union([z.boolean(), z.number()]).optional(), link: z.string().optional(), characterSpacing: z.number().optional(), wordSpacing: z.number().optional(), continued: z.boolean().optional(), lineBreak: z.boolean().optional(), }), z.object({ type: z.literal('heading'), text: z.string().optional(), fontSize: z.number().optional(), bold: z.boolean().optional(), color: z.string().optional(), x: z.number().optional(), y: z.number().optional(), align: z.enum(['left', 'center', 'right', 'justify']).optional(), indent: z.number().optional(), lineGap: z.number().optional(), paragraphGap: z.number().optional(), width: z.number().optional(), moveDown: z.number().optional(), underline: z.boolean().optional(), strike: z.boolean().optional(), oblique: z.union([z.boolean(), z.number()]).optional(), link: z.string().optional(), characterSpacing: z.number().optional(), wordSpacing: z.number().optional(), continued: z.boolean().optional(), lineBreak: z.boolean().optional(), }), z.object({ type: z.literal('image'), imagePath: z.string(), x: z.number().optional(), y: z.number().optional(), width: z.number().optional(), height: z.number().optional(), }), z.object({ type: z.literal('rect'), x: z.number(), y: z.number(), width: z.number(), height: z.number(), fillColor: z.string().optional(), strokeColor: z.string().optional(), lineWidth: z.number().optional(), }), z.object({ type: z.literal('circle'), x: z.number(), y: z.number(), radius: z.number(), fillColor: z.string().optional(), strokeColor: z.string().optional(), lineWidth: z.number().optional(), }), z.object({ type: z.literal('line'), x1: z.number(), y1: z.number(), x2: z.number(), y2: z.number(), strokeColor: z.string().optional(), lineWidth: z.number().optional(), }), z.object({ type: z.literal('pageBreak') }), ]) ), } as Record<string, z.ZodTypeAny>, },
  • The registerCreatePdfTool function that registers the 'create-pdf' MCP tool with the server, specifying name, metadata, input schema, and handler.
    export function registerCreatePdfTool(server: McpServer, config: PdfServerConfig) { server.registerTool( 'create-pdf', { title: 'Create PDF', description: 'Create a PDF document with text, images, shapes, and layout control. Supports Unicode + emoji fonts, backgrounds, and vector shapes.', inputSchema: { filename: z.string().optional().describe('Optional logical filename (metadata only). Storage uses UUID. Defaults to "document.pdf".'), title: z.string().optional().describe('Document title metadata'), author: z.string().optional().describe('Document author metadata'), font: z.string().optional().describe('Font strategy (default: auto). Built-ins: Helvetica, Times-Roman, Courier. Use a path or URL for Unicode.'), pageSetup: z .object({ size: z.tuple([z.number(), z.number()]).optional(), margins: z .object({ top: z.number(), bottom: z.number(), left: z.number(), right: z.number(), }) .optional(), backgroundColor: z.string().optional(), }) .optional(), content: z.array( z.union([ z.object({ type: z.literal('text'), text: z.string().optional(), fontSize: z.number().optional(), bold: z.boolean().optional(), color: z.string().optional(), x: z.number().optional(), y: z.number().optional(), align: z.enum(['left', 'center', 'right', 'justify']).optional(), indent: z.number().optional(), lineGap: z.number().optional(), paragraphGap: z.number().optional(), width: z.number().optional(), moveDown: z.number().optional(), underline: z.boolean().optional(), strike: z.boolean().optional(), oblique: z.union([z.boolean(), z.number()]).optional(), link: z.string().optional(), characterSpacing: z.number().optional(), wordSpacing: z.number().optional(), continued: z.boolean().optional(), lineBreak: z.boolean().optional(), }), z.object({ type: z.literal('heading'), text: z.string().optional(), fontSize: z.number().optional(), bold: z.boolean().optional(), color: z.string().optional(), x: z.number().optional(), y: z.number().optional(), align: z.enum(['left', 'center', 'right', 'justify']).optional(), indent: z.number().optional(), lineGap: z.number().optional(), paragraphGap: z.number().optional(), width: z.number().optional(), moveDown: z.number().optional(), underline: z.boolean().optional(), strike: z.boolean().optional(), oblique: z.union([z.boolean(), z.number()]).optional(), link: z.string().optional(), characterSpacing: z.number().optional(), wordSpacing: z.number().optional(), continued: z.boolean().optional(), lineBreak: z.boolean().optional(), }), z.object({ type: z.literal('image'), imagePath: z.string(), x: z.number().optional(), y: z.number().optional(), width: z.number().optional(), height: z.number().optional(), }), z.object({ type: z.literal('rect'), x: z.number(), y: z.number(), width: z.number(), height: z.number(), fillColor: z.string().optional(), strokeColor: z.string().optional(), lineWidth: z.number().optional(), }), z.object({ type: z.literal('circle'), x: z.number(), y: z.number(), radius: z.number(), fillColor: z.string().optional(), strokeColor: z.string().optional(), lineWidth: z.number().optional(), }), z.object({ type: z.literal('line'), x1: z.number(), y1: z.number(), x2: z.number(), y2: z.number(), strokeColor: z.string().optional(), lineWidth: z.number().optional(), }), z.object({ type: z.literal('pageBreak') }), ]) ), } as Record<string, z.ZodTypeAny>, }, async (args: CreatePdfArgs) => { const { filename = 'document.pdf', title, author, font, pageSetup, content } = args; try { const docOptions: PDFKit.PDFDocumentOptions = { info: { ...(title && { Title: title }), ...(author && { Author: author }), ...(filename && { Subject: filename }), }, }; if (pageSetup?.size) docOptions.size = pageSetup.size; if (pageSetup?.margins) docOptions.margins = pageSetup.margins; const doc = new PDFDocument(docOptions); const chunks: Buffer[] = []; doc.on('data', (c: Buffer) => chunks.push(c)); const pdfPromise = new Promise<Buffer>((resolve, reject) => { doc.on('end', () => resolve(Buffer.concat(chunks))); doc.on('error', reject); }); if (pageSetup?.backgroundColor) { const pageSize = pageSetup?.size || [612, 792]; doc.rect(0, 0, pageSize[0], pageSize[1]).fill(pageSetup.backgroundColor); } const contentText = JSON.stringify(content); const containsEmoji = hasEmoji(contentText); const emojiAvailable = containsEmoji ? registerEmojiFont() : false; const fonts = await setupFonts(doc, font); const { regular: regularFont, bold: boldFont } = fonts; const warnings: string[] = []; for (const item of content) { if ((item.type === 'text' || item.type === 'heading') && item.text) { const fnt = item.bold ? boldFont : regularFont; const validation = validateTextForFont(item.text, fnt); if (validation.hasUnsupportedCharacters) warnings.push(...validation.warnings); } } const drawBackgroundOnPage = () => { if (pageSetup?.backgroundColor) { const pageSize = pageSetup?.size || [612, 792]; const x = doc.x; const y = doc.y; doc.rect(0, 0, pageSize[0], pageSize[1]).fill(pageSetup.backgroundColor); doc.x = x; doc.y = y; } }; doc.on('pageAdded', drawBackgroundOnPage); for (const item of content) { switch (item.type) { case 'text': { if (item.x !== undefined && item.align !== undefined) throw new Error('Cannot use both x and align in text element'); const fontSize = item.fontSize ?? 12; const fnt = item.bold ? boldFont : regularFont; if (item.color) doc.fillColor(item.color); const options = extractTextOptions(item); renderTextWithEmoji(doc, item.text ?? '', fontSize, fnt, emojiAvailable, options); if (item.color) doc.fillColor('black'); if (item.moveDown !== undefined) doc.moveDown(item.moveDown); break; } case 'heading': { if (item.x !== undefined && item.align !== undefined) throw new Error('Cannot use both x and align in heading element'); const fontSize = item.fontSize ?? 24; const fnt = item.bold !== false ? boldFont : regularFont; if (item.color) doc.fillColor(item.color); const options = extractTextOptions(item); renderTextWithEmoji(doc, item.text ?? '', fontSize, fnt, emojiAvailable, options); if (item.color) doc.fillColor('black'); if (item.moveDown !== undefined) doc.moveDown(item.moveDown); break; } case 'image': { const opts: Record<string, unknown> = {}; if (item.width !== undefined) opts.width = item.width; if (item.height !== undefined) opts.height = item.height; if (item.x !== undefined && item.y !== undefined) doc.image(item.imagePath, item.x, item.y, opts); else doc.image(item.imagePath, opts); break; } case 'rect': { doc.rect(item.x, item.y, item.width, item.height); if (item.fillColor && item.strokeColor) { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc.fillAndStroke(item.fillColor, item.strokeColor); } else if (item.fillColor) doc.fill(item.fillColor); else if (item.strokeColor) { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc.stroke(item.strokeColor); } doc.fillColor('black'); break; } case 'circle': { doc.circle(item.x, item.y, item.radius); if (item.fillColor && item.strokeColor) { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc.fillAndStroke(item.fillColor, item.strokeColor); } else if (item.fillColor) doc.fill(item.fillColor); else if (item.strokeColor) { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc.stroke(item.strokeColor); } doc.fillColor('black'); break; } case 'line': { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc .moveTo(item.x1, item.y1) .lineTo(item.x2, item.y2) .stroke(item.strokeColor || 'black'); break; } case 'pageBreak': { doc.addPage(); break; } } } doc.end(); const pdfBuffer = await pdfPromise; const uuid = crypto.randomUUID(); const storedFilename = `${uuid}.pdf`; const { fullPath } = await writePdfToFile(pdfBuffer, storedFilename, config.storageDir); const includePath = config.includePath; const warningText = warnings.length > 0 ? `\n\n⚠️ Character Warnings:\n${warnings.map((w) => `• ${w}`).join('\n')}` : ''; return { content: [ { type: 'text' as const, text: ['PDF created successfully', `Resource: mcp-pdf://${uuid}`, includePath ? `Output: ${fullPath}` : undefined, `Size: ${pdfBuffer.length} bytes`, filename !== 'document.pdf' ? `Filename: ${filename}` : undefined, warningText || undefined].filter(Boolean).join('\n'), }, ], }; } catch (error) { const message = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text' as const, text: `Error creating PDF: ${message}` }], isError: true }; } } ); }
  • src/server.ts:26-26 (registration)
    Call to registerCreatePdfTool during MCP server initialization, passing the server instance and config.
    registerCreatePdfTool(server, serverConfig);

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