mcpField
P
pdf-letter-mcp

pdf-letter-mcp

Local
github.com/bitterdevProductivity1Updated 1w ago

About pdf-letter-mcp

pdf-letter-mcp is a local MCP server for generating print-ready PDFs of German-style letters, with DIN 5008 positioning for window envelopes. It suits workflows that need consistent layouts and offline rendering.

Key features

  1. 01DIN 5008 letter layout
  2. 02PDF rendering and preview
  3. 03Sender profiles stored locally
  4. 04Signature image preparation
  5. 05Multilingual text and address rules
  6. 06Page breaks and continuation headers

Use cases

  1. 01Preparing letters for DL or C6/C5 window envelopes
  2. 02Previewing address placement before printing
  3. 03Reusing sender details and signature images
  4. 04Generating letters in supported locales

MCP support

Exposes tools to create and preview letters, prepare signatures, inspect DIN 5008 geometry, and list locales and fonts to MCP clients.

Configuration

Local server config is the same for every client. Copy it into your client’s MCP config file.

mcp.json · Universal config
{
"mcpServers": {
"bitterdev-pdf-letter": {
"command": "npx",
"args": [
"-y",
"pdf-letter-mcp"
],
"env": {
"PDF_LETTER_OUTPUT_DIR": "<PDF_LETTER_OUTPUT_DIR>",
"PDF_LETTER_FONT_DIR": "<PDF_LETTER_FONT_DIR>"
}
}
}
}
Run commandnpx -y pdf-letter-mcp
Environment variables to set
PDF_LETTER_OUTPUT_DIRDirectory for generated letter PDFs, defaults to the system temp directory
PDF_LETTER_FONT_DIRAdditional font directory searched when a font family is resolved by name

Local servers need a runtime on your machine (e.g. Node.js 18+). Config file locations differ by client; see your client’s docs.

Packages
npmpdf-letter-mcp1.0.3

Tools

7

Saved tool list · updated 2w ago

ParameterTypeRequiredDescription
bodystringYesLetter text. Blank lines separate paragraphs, "- " starts a bullet list, "1. " a numbered list, **bold** and *italic* are supported, "\pagebreak" forces a page break.
datestringNoISO date (2026-07-24), "today" or free text. Default "today".
marksarrayNoRemark zone above the address, max. 3 lines, e.g. ["Einschreiben"].
placestringNoPlace for the "place, date" line.
copyToarrayNo
localestringNoLanguage of the fixed texts and date format. Supported: de, en, fr, es, it, nl, pt, pl, tr, da, sv, cs. Regional tags such as "en-US" are accepted. Default "de".
senderobjectNoSender, printed as the small return address line above the recipient. Only needed when no profile is used, a profile always wins.
sender.webstringNo
sender.citystringNo
sender.namestringNo
sender.emailstringNo
sender.phonestringNo
sender.regionstringNoState or province, used for US/CA/AU style addresses.
sender.streetstringNoStreet and house number.
sender.companystringNo
sender.countrystringNoISO 3166-1 alpha-2 code (recommended) or plain country name.
sender.postalCodestringNo
sender.addressLinesarrayNoManual override, replaces every other field of this address.
sender.postOfficeBoxstringNoUsed instead of the street, e.g. "Postfach 12 34 56".
sender.additionalLinestringNoDepartment, "c/o", floor.
sender.salutationLinestringNoOwn line above the name, e.g. "Herrn", "Frau", "Firma".
closingstringNoDefault is the closing phrase of the locale.
profilestringNoSender identity, taken from the profile file on this machine (see list_profiles). The profile provides address, return address line and signature image. Use this instead of typing a sender.
subjectstringNoSubject line, printed without a "Betreff:" label as required by DIN 5008.
metadataobjectNo
metadata.titlestringNo
metadata.authorstringNo
metadata.subjectstringNo
metadata.keywordsarrayNo
dateStyleenumNoDefault "numeric" (24.07.2026).numeric | long | iso
overwritebooleanNoOverwrite an existing file instead of adding a counter, default false.
recipientobjectYesRecipient, positioned in the DIN 5008 address field so it shows in the envelope window.
recipient.webanyNo
recipient.cityanyNo
recipient.nameanyNo
recipient.emailanyNo
recipient.phoneanyNo
recipient.regionanyNo
recipient.streetanyNo
recipient.companyanyNo
recipient.countryanyNo
recipient.postalCodeanyNo
recipient.addressLinesanyNo
recipient.postOfficeBoxanyNo
recipient.additionalLineanyNo
recipient.salutationLineanyNo
signatureobjectNoSignature below the closing line. The image is scaled and placed by the renderer: it is laid over the blank line above the printed name and deliberately overlaps the closing line, exactly like a signature on paper. That overlap is intended and must not be compensated for.
signature.namestringNoPrinted name below the signature.
signature.pathstringNoPath to a PNG or JPEG file. Other formats need the optional sharp dependency.
signature.trimbooleanNoCrop white borders around the image (needs sharp).
signature.titlestringNoRole or function below the name.
signature.base64stringNoImage data as base64 or data URL, alternative to path.
signature.prefixstringNoLine above the signature, e.g. "i. A." or "ppa.".
signature.inkColorstringNoRecolour the ink, e.g. "#1a1a1a" (needs sharp).
signature.removeBackgroundbooleanNoTurn the white paper background of a scanned signature transparent (needs sharp).
signature.backgroundThresholdnumberNoLuminance cutoff for removeBackground, default 0.85.
enclosuresarrayNo
outputPathstringNoTarget PDF path. Default: PDF_LETTER_OUTPUT_DIR (or the temp directory) plus a name derived from the subject.
salutationstringNoDefault is the generic salutation of the locale.
returnBase64booleanNoAlso return the PDF as base64, default false.
subjectLinesarrayNoFurther bold lines right below the subject. This is where a normal German letter carries references, e.g. ["Zeichen: 535 B 335D028418", "Kunden-Nummer: 335D028418"].
countryDisplayenumNoWhen to print the country line, default "auto" (only for foreign recipients).auto | always | never
returnAddressLinestringNoReturn address line above the recipient. Generated from the sender, pass "" to hide it.

Details

From the project README

pdf-letter-mcp

Local MCP server (stdio) that turns structured letter data into a print ready PDF. One fixed layout, the everyday German letter, with the recipient address positioned per DIN 5008 so it sits exactly in the window of DL and C6/C5 window envelopes. No web service, no external API, everything runs offline on your machine.

Features

  • One layout, no variants: return address line, recipient, right aligned "place, date", bold subject block with references below it, salutation, body, closing, signature, enclosures
  • Address field 45 mm high ending at 105 mm from the left, with return address line, remark zone (Einschreiben, Nicht nachsenden) and address zone
  • Return address, remarks and recipient are balanced inside the address field, so the block sits evenly in the envelope window and further address lines still fit. addressLayout: "din" switches back to the fixed zone boundaries
  • Page breaks keep closing, signature image, name and enclosures together, follow-up pages carry recipient, date and page number
  • Subject 33 mm below the address field, place and date three lines above it
  • Fold marks at 105 mm and 210 mm, punch mark at 148.5 mm
  • DIN 5008 writing margin of 25 mm on the left, and the address field follows it, so address, subject and body share one edge. The field keeps its right edge at 105 mm and stays inside the 90 mm envelope window
  • Closing, one blank line, printed name. signature.spaceMm reserves more room for signing by hand
  • Signature as an image file: PNG with alpha channel keeps its transparency, the image is laid over the blank line above the printed name and never shifts the text. Optional crop, background removal and ink recolouring for scans
  • Multilingual: de, en, fr, es, it, nl, pt, pl, tr, da, sv, cs with locale aware date formats and address rules per country
  • Language aware hyphenation, bold/italic markup, bullet and numbered lists, automatic page breaks with continuation headers
  • Page numbers and a layout debug mode that draws the DIN 5008 zones
  • Embedded Unicode fonts (DejaVu), any installed system font or a .ttf path

Install

npm install
npm run build

Register the server in Claude Code:

claude mcp add pdf-letter -- node "/absolute/path/to/pdf-letter-mcp/dist/src/index.js"

Or in ~/.claude.json / claude_desktop_config.json:

{
  "mcpServers": {
    "pdf-letter": {
      "command": "node",
      "args": ["/absolute/path/to/pdf-letter-mcp/dist/src/index.js"],
      "env": {
        "PDF_LETTER_OUTPUT_DIR": "~/Documents/Briefe"
      }
    }
  }
}

Environment

VariablePurpose
PDF_LETTER_OUTPUT_DIRDefault directory for generated PDFs. Falls back to the system temp directory.
PDF_LETTER_FONT_DIRAdditional directory that is searched when a font family is resolved by name.
PDF_LETTER_FONTFont family for every letter, e.g. Arial. Default is the bundled DejaVu Sans.
PDF_LETTER_FONT_SIZEFont size in pt for every letter, default 11.
PDF_LETTER_PROFILESPath to the sender profiles, default ~/.config/pdf-letter-mcp/profiles.json.
PDF_LETTER_PROFILEProfile used when a letter names none and the file has no defaultProfile.

Sender profiles

Sender addresses and signatures live in a file on the machine, never in the prompt. A letter names a profile, the server fills in address, return address line and signature image verbatim.

{
  "defaultProfile": "erika",
  "profiles": {
    "erika": {
      "description": "Erika Musterfrau, privat",
      "sender": {
        "name": "Erika Musterfrau",
        "street": "Musterstraße 12",
        "postalCode": "12345",
        "city": "Musterstadt",
        "country": "DE"
      },
      "place": "Musterstadt",
      "locale": "de",
      "signature": { "path": "/pfad/zur/unterschrift.png" }
    }
  }
}

create_letter then only needs "profile": "erika" plus the content. The profile owns the identity: a sender or signature passed in the call is replaced by the profile, place, locale and closing are defaults that a letter may override. list_profiles shows what is available. Without a profile and without a sender the letter is refused instead of being invented.

The file belongs outside the repository, profiles.json is in .gitignore.

Layout is fixed

The MCP tools take content only: addresses, date, subject, references, text, signature, enclosures. There are no parameters for margins, spacing, font, image size or position, and unknown fields sent by a client are dropped. Typography is an installation setting (PDF_LETTER_FONT, PDF_LETTER_FONT_SIZE), so every letter of one installation looks identical. The building blocks for a company letterhead, an information block and a footer are still in the library, but they are not reachable through the MCP.

Tools

ToolPurpose
create_letterRenders the letter and writes the PDF. Returns path, page count, layout metrics and warnings.
preview_letterSame rendering without writing a file, for checking the layout.
prepare_signatureCleans a scanned signature (crop, transparent background, ink colour) and writes a PNG.
get_din5008_specReturns the millimetre geometry of a form.
list_localesLists the supported languages and their fixed texts.
list_fontsLists built-in families and resolves a font name against the installed system fonts.
create_letter

The sender appears only in the small return address line above the recipient. Place and date sit right aligned above the bold subject block, references go directly below the subject.

{
  "locale": "de",
  "sender": {
    "name": "Erika Musterfrau",
    "street": "Musterstraße 12",
    "postalCode": "12345",
    "city": "Musterstadt",
    "country": "DE"
  },
  "recipient": {
    "company": "Stadtwerke Musterstadt",
    "street": "Industriestraße 8",
    "postalCode": "12345",
    "city": "Musterstadt"
  },
  "place": "Musterstadt",
  "date": "2026-07-24",
  "dateStyle": "long",
  "subject": "Widerspruch gegen die Jahresabrechnung vom 01.07.2026",
  "subjectLines": ["Zeichen: SW-2026-0815", "Kunden-Nummer: 000000000"],
  "body": "hiermit widerspreche ich der Jahresabrechnung.\n\n- korrigierte Abrechnung\n- Eingangsbestätigung",
  "signature": {
    "path": "/pfad/zur/unterschrift.png",
    "widthMm": 45,
    "removeBackground": true,
    "trim": true,
    "name": "Erika Musterfrau"
  },
  "enclosures": ["Kopie der Abrechnung"]
}

Everything except sender, recipient and body is optional. The salutation is derived from the recipient (Frau plus Dr. Erika Mustermann becomes Sehr geehrte Frau Dr. Mustermann,), the date defaults to today, the closing to the locale default.

Body markup
  • Blank line: new paragraph
  • Single newline: line break (set bodyMode to markdown to reflow instead)
  • - item or 1. item: bullet or numbered list
  • **bold**, *italic*
  • \pagebreak on its own line: forced page break
Signature

Two ways, both end up as a real image in the PDF:

  1. Pass the file directly: signature.path plus trim and removeBackground. The scan is cleaned on every render.
  2. Clean it once with prepare_signature and reuse the resulting PNG. Faster and lets you check the result before it goes into a letter.

trim, removeBackground and inkColor need the optional sharp dependency, which is installed by default. A PNG that already has a transparent background works without it.

The image is an overlay: closing, one blank line, printed name stay exactly where they are, and the signature sits on top of that blank line and across the closing, the way a signature is written over "Mit freundlichen Grüßen" on paper. That overlap is the intended result.

Size and position are decided by the renderer, not by the caller. The signature is scaled to roughly the width of the closing line, around 50 mm, capped proportionally at 1.2 times that width and 28 mm in height. There is no parameter for width, height, offset or spacing, so nothing can shift the layout letter by letter.

Verification

npm test          # geometry, address rules, typography, rendering
npm run examples  # writes sample letters to examples/

examples/ holds the German letter, the same letter in English and a debug variant that draws the DIN 5008 zones, so the address field position can be checked against a window envelope. preview_letter reports the same measurements as JSON, plus warnings when the address is too long or does not fit the address field.

License

MIT, see LICENSE. The bundled DejaVu fonts are covered by the Bitstream Vera and Arev licenses.

Comments

Markdown supported

New to MCP?

From what the protocol is to installing your first server, these answers cover most first questions.

An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.