pingen-mcp
This MCP server integrates with the Pingen v2 API to programmatically send physical letters (A‑Post, B‑Post, registered, etc.) from PDF files, manage drafts, and track deliveries. It securely stores credentials in the macOS keychain or environment variables.
Key capabilities
Verify credentials and view organization details (
pingen_status)Upload a PDF to create a letter as a draft or automatically mail it (
pingen_send_letter); set address window position and delivery productIrreversibly submit a draft for physical mailing (
pingen_submit_letter) with explicit confirmation, delivery product, print mode (simplex/duplex), and color spectrum (color/grayscale)List and paginate recent letters (
pingen_list_letters)Retrieve a letter’s status and tracking information (
pingen_get_letter)Cancel a submitted or sent letter when allowed (
pingen_cancel_letter)Permanently delete a draft letter (
pingen_delete_letter), requiring confirmationView the full tracking/status event history (
pingen_letter_events)Download the final processed letter PDF (
pingen_download_letter)
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pingen-mcpSend a registered letter from /documents/letter.pdf as a draft."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
pingen-mcp
Send physical letters — A-Post, B-Post, registered — straight from a PDF via the Pingen v2 API.
MCP server for Pingen v2 — send real, physical letters (A-Post / B-Post / registered / Einschreiben) from a PDF and track them, straight from any MCP client (Claude Code, Claude Desktop, …). It talks to the official Pingen REST API — no browser automation. Pingen prints and mails the letter for you; data is hosted in Switzerland.
Credentials are never stored in this repo. They are read at runtime from the macOS login keychain (or from environment variables). Nothing secret is committed.
Prerequisites
Node.js ≥ 20.19 (
node --version)A Pingen account with an OAuth client (
client_credentialsgrant) — see belowmacOS (the credential lookup uses the
securitykeychain tool; on other platforms use the environment-variable alternative instead)
git clone https://github.com/sapn95/pingen-mcp.git
cd pingen-mcp
npm installRelated MCP server: MCP Email Service
Credential setup (step by step)
This is the part that must be right, or nothing works. You need three values from Pingen and you store them in the macOS keychain.
1. Create a Pingen account
Sign up at https://app.pingen.com. A free account is enough to create drafts and test; you only pay when you actually mail a letter.
2. Create an OAuth client (to get Client-ID + Client-Secret)
In the Pingen dashboard open Settings → API / Developer (also reachable at https://app.pingen.com/organisation → API).
Create a new OAuth client / application.
Choose the
client_credentialsgrant (machine-to-machine; no redirect URL, no user login at request time).Copy the Client-ID and the Client-Secret. The secret is shown only once — copy it now.
3. Find your Organisation UUID
Each Pingen organisation has a UUID. It appears in the dashboard URL when the
organisation is selected (https://app.pingen.com/.../organisations/<UUID>/...),
or under Settings → Organisation. It is optional only when your account has exactly one
organisation: the server then calls GET /organisations and uses it. With
several, it refuses and lists them rather than deciding — silently — which
account pays for and franks the letter.
4. Store all three in the macOS keychain
Run these exactly (the service names must match what index.js reads). Replace
the angle-bracket placeholders with your real values:
-w without a value prompts instead of taking the secret from the command
line, so it never reaches your shell history or a process listing — where a
credential that can print and post mail at your expense has no business being.
security add-generic-password -a pingen -s pingen-mcp-client-id -w -U # prompts
security add-generic-password -a pingen -s pingen-mcp-client-secret -w -U # prompts
# Optional — auto-detected from /organisations if you skip it, and only when
# the account has exactly one:
security add-generic-password -a pingen -s pingen-mcp-org-uuid -w -U # promptsThe exact service names read by index.js are:
Value | Keychain service name | Env-var alternative |
Client-ID |
|
|
Client-Secret |
|
|
Organisation UUID (optional) |
|
|
(PINGEN_API_BASE overrides the API base URL, default https://api.pingen.com.)
The organisation UUID is optional only where there is nothing to choose: it is discovered automatically when the account has exactly one organisation, and asked for otherwise. "Exactly one" means one in the account, not one on the page that came back — an organisation list long enough to paginate is a question too, because picking the first entry off it would decide, silently, which account pays for and franks the letter.
An environment variable that is set always wins — even when it is empty. A
blank PINGEN_CLIENT_SECRET means no secret, not go and look in the
keychain; a blank PINGEN_API_BASE means no endpoint, not use production.
Only an entirely absent variable falls through to the keychain, so blanking one
is a reliable way to make sure a run cannot reach your real account.
Verify a value is stored (prints the value to your terminal — run only when you're OK seeing it):
security find-generic-password -a pingen -s pingen-mcp-client-id -wThe first time the server reads the keychain, macOS may pop up "node wants to use your confidential information". Click Always Allow so it doesn't prompt on every start.
Never commit credentials. The .gitignore already excludes .env files;
the keychain path keeps secrets out of the filesystem entirely.
Register in Claude Code
claude mcp add pingen --scope user -- node /absolute/path/to/pingen-mcp/index.jsThat writes an entry into ~/.claude.json. Equivalent manual snippet:
{
"mcpServers": {
"pingen": {
"command": "node",
"args": ["/absolute/path/to/pingen-mcp/index.js"]
}
}
}Then, in a Claude Code session, run pingen_status — it should print your
organisation. That confirms the credentials and registration are correct.
For Claude Desktop, add the same mcpServers block to
~/Library/Application Support/Claude/claude_desktop_config.json.
Draft-vs-send safety model (read this)
Sending a letter is two explicit steps, so you never mail something by accident:
pingen_send_letteruploads the PDF and creates a DRAFT (auto_send = false). Nothing is mailed. You can review it in the Pingen dashboard.pingen_submit_lettertakes an existing draft and physically mails it. This is the step that costs money and puts paper in the post, so it needsconfirm: true.pingen_delete_letterneeds the same, because a deleted draft does not come back.pingen_cancel_letterdoes not: stopping a letter is the safe direction.
flowchart TD
PDF["your PDF"] --> SEND["pingen_send_letter"]
SEND -->|"default"| DRAFT["draft — nothing is mailed,<br/>review it in the dashboard"]
SEND -->|"auto_send: true<br/>(needs delivery_product)"| ST
DRAFT --> ST{"what Pingen says<br/>about the letter"}
ST -->|"valid — a draft it will take"| SUB["pingen_submit_letter<br/>confirm: true"]
ST -->|"processing, sent"| POST
ST -->|"validating"| WAIT["still being checked —<br/>ask again in a moment"]
ST -->|"action_required, invalid"| STOP["Pingen will not take it.<br/>Sending it again does not help;<br/>a corrected PDF does"]
WAIT -.-> ST
SUB --> POST[("mailed — paper in the post,<br/>and it costs money")]
SUB -.->|"no answer at all: timeout,<br/>dropped line, 5xx"| MAYBE["it says it cannot know.<br/>The letter may already be printing,<br/>so check before sending a second one"]
SEND -.->|"same, on the auto_send half"| MAYBE
STOP -.->|"the letter carries the status,<br/>the trail carries the reason"| EV["pingen_letter_events"]
MAYBE -.-> EV
DRAFT -.->|"confirm: true —<br/>a deleted draft does not come back"| DEL["pingen_delete_letter"]
POST -.->|"no confirm needed —<br/>stopping is the safe direction"| CAN["pingen_cancel_letter"]
classDef gate fill:#fff4e5,stroke:#d9822b
classDef bad fill:#fdecea,stroke:#c0392b
classDef done fill:#eafaf1,stroke:#27ae60
class SUB,DEL gate
class STOP,MAYBE bad
class POST doneThe only shortcut is passing auto_send: true to pingen_send_letter, which
mails immediately without a review step — use deliberately. It also requires
delivery_product: the product is optional on a draft only because
pingen_submit_letter asks for one later, and auto_send: true is the single
route that never reaches that call.
A draft must reach status valid before it can be submitted. If Pingen
still needs something (e.g. the address couldn't be read), the draft is
action_required and submit will fail — see Troubleshooting. pingen_send_letter
says so on the spot: a letter that comes back action_required or invalid is
reported as a draft Pingen will not take, with the status and where to look,
and not with the usual "now submit it" — on both of its halves, the draft one
and auto_send: true, because a refused PDF is refused either way and the half
that was told to mail it is the half most likely to be told to try again.
pingen_submit_letter says the same thing about the same two statuses rather
than "try again": a letter Pingen has refused does not become sendable by being
sent a second time, and the note names the one step that helps — a corrected PDF.
All three of those notes point at pingen_letter_events for the reason,
because that is where Pingen keeps it: the letter itself carries the status and
nothing about why, while the trail carries a bare code (layout_unsupported_format
and the like). pingen_get_letter cannot answer that question — it returns the
letter row — so being sent there is being told to look somewhere the answer has
never been.
All of that reads an answer. The two calls that put paper in the post also say
what they do not know when there is no answer to read: a timeout, a dropped
connection, a body that stopped halfway. Pingen may well have taken the letter
before the line went dead, so a bare "the request failed" — which is what a
retry gets triggered by — would be a guess in the direction of a second letter,
printed and charged. Instead the error says the letter may be on its way and
names the tool that can settle it: pingen_get_letter after a submit, where the
id is in hand, and pingen_list_letters after an auto_send: true create, where
it is not, because the id was in the answer that went missing.
Two things — and only two — take that warning off again, because a warning worth
ignoring is worth nothing. Pingen answering for itself takes it off: a 404, a
conflict_state, anything below 500 says the request was stopped and no paper
moved. Never having sent the request takes it off too, in the same direction:
a missing client secret or a refused token grant is a failure this server
reaches on its own, with the line to Pingen never opened, and "Keine
Pingen-Credentials" used to come back with a paragraph about a letter that
might be printing. Everything else is genuinely unknown and keeps the warning —
including a 502, 503 or 504, which look like an answer and are not:
those are written by whatever sits in front of the API saying it could not get
one back, which covers the letter already on the press exactly as well as the
letter that never existed.
Tool reference
Tool | Parameters | What it does / returns |
| — | Verifies credentials; returns your organisations ( |
|
| Lists recent letters, newest first: |
|
| Uploads the PDF and creates a letter. DRAFT by default — nothing is mailed. Returns the created letter row plus a note. Set |
|
| Physically mails an existing draft, at your cost, with no undo. Requires the letter to be |
|
| Status/tracking of one letter (single letter row). |
|
| Cancels an already-submitted/sent letter where Pingen still allows it. Returns |
|
| Deletes a draft / not-yet-sent letter for good. To stop a letter already on its way use |
|
| Tracking/status history (created → submitted → sent → delivered → undeliverable …): |
|
| Downloads the final letter PDF to |
Example
// 1) create a DRAFT (nothing mailed yet)
pingen_send_letter { "file_path": "/Users/me/Einsprache_2024.pdf", "delivery_product": "registered", "address_position": "left" }
// → { created: { id: "<letter_id>", status: "draft", … },
// note: "DRAFT erstellt (nichts versandt). Zum Senden: pingen_submit_letter." }
// 2) review in the Pingen dashboard, then physically mail it
pingen_submit_letter { "letter_id": "<letter_id>", "delivery_product": "registered", "print_mode": "duplex", "print_spectrum": "grayscale", "confirm": true }Delivery products & print options (Switzerland)
Pass delivery_product to pingen_send_letter / pingen_submit_letter:
Value | Swiss product | Notes |
| B-Post | economy, slower |
| A-Post | priority, next-day where available |
| Einschreiben | tracked + signed-for delivery |
| priority/premium | availability depends on plan |
Print options on pingen_submit_letter:
print_mode:simplex(single-sided, default) orduplex(double-sided)print_spectrum:color(default) orgrayscale
Exact product availability depends on your organisation/plan — check the Pingen dashboard.
PDF layout gotcha (make your letters pass validation)
Pingen reads the recipient address optically from the first page and reserves
a franking zone. If your PDF doesn't respect the Swiss letter window, Pingen
returns action_required with protected_stamp_area and the letter can't be
submitted. To pass validation:
Recipient address inside the address window. For
address_position: leftthe window sits roughly from 60 mm down from the top, left column starting ~22 mm from the left edge. Put the full recipient block there.Keep the franking zone (top ~40–60 mm) blank. No logo, no text, no line in the top strip — that area is reserved for the stamp/frank.
Do not put a sender return line inside the window. A return address in the same window confuses address recognition — keep only the recipient in the window (a sender line, if any, belongs above/outside it).
If in doubt, create a draft with pingen_send_letter, open it in the dashboard,
and check the address preview before submitting.
Troubleshooting
Symptom | Cause | Fix |
| Address outside the window, or something in the franking zone / a sender line inside the window | Reposition the recipient into the address window and clear the top ~40–60 mm (see PDF layout gotcha), re-upload. Which of them it was is on the letter's trail, not on the letter: |
| The letter isn't | Fix the address issue so the draft reaches |
| The | Re-run the |
Token error (401/400 on | Wrong Client-ID/Secret, or the OAuth client isn't a | Recreate the OAuth client with the |
| The request left, a usable answer did not — Pingen may have taken the letter anyway. A gateway status is not Pingen's answer, it is the box in front saying it could not get one | Do not repeat the call. Check first: |
Keychain prompt on every start | macOS didn't remember the access grant | On the popup click Always Allow for |
Releasing
Published from CI with npm Trusted Publishing (OIDC) — there is no npm token anywhere: no secret to store, rotate or leak. npm recommends this over an automation token, and is restricting tokens that bypass 2FA.
One-time setup per package, on npmjs.com -> the package -> Settings -> Trusted Publisher:
Field | Value |
Organization or user | sapn95 |
Repository | pingen-mcp |
Workflow filename | release.yml |
Allowed actions | npm publish |
The workflow filename must match exactly. That is deliberate: it stops any other workflow in the repo from publishing under your name.
Then every release is one command:
npm version patch && git push --follow-tagsThe tag triggers the release workflow: it upgrades npm (trusted publishing needs
= 11.5.1 and Node >= 22.14), refuses a tag whose version disagrees with package.json, runs the gate, and publishes with a signed provenance statement.
If the publish fails with 404
npm notice publish Signed provenance statement ... from GitHub Actions
npm error 404 Not Found - PUT https://registry.npmjs.org/pingen-mcpProvenance was signed, so OIDC worked — the registry simply does not accept this workflow as a publisher yet. That means the trusted publisher is not configured, or the repository / workflow name does not match. npm answers 404 rather than 403 so as not to reveal whether the package exists. It is not a credential problem: there is no credential, by design.
Checks
npm run gate
npm run mutate # mutation-test the lines this branch changedRuns exactly what CI runs, offline and without credentials: a syntax check,
ESLint, the protocol smoke test, the hygiene scan, and the test suite under
coverage. npm test runs just the suite, npm run lint just the linter.
The smoke test completes the MCP handshake over stdio and asserts the things that have actually broken here — a server version drifting from package.json, a tool in the dispatcher but missing from the tool list (or advertised and unhandled), a required property absent from a schema, and descriptions too thin to choose a tool from. The hygiene scan refuses secrets, tracked session files and personal identifiers.
The suite in test/ drives the server over stdio exactly as a real MCP client
does, against a local stand-in for api.pingen.com (test/mock-pingen.mjs) that
listens on an ephemeral port. Credentials are fake, PINGEN_API_BASE points at
the mock, and a security stub that finds nothing goes first on PATH: no
test can reach the real API, the real login keychain, or the post. Alongside
the happy paths it pins the properties that matter — that pingen_send_letter
creates a draft and submits nothing, that a non-boolean auto_send still yields
a draft, that neither of the two calls that reach the post will do so without a
delivery_product, that neither of them reports a send Pingen did not confirm —
in the note or in the key the letter is filed under — that a draft Pingen has
flagged is never announced as ready to post and never answered with an
instruction to send it again, by any of the three branches that see that status
— the two halves of pingen_send_letter and pingen_submit_letter — that each
of them names a tool that can actually say why the letter was refused, that
neither of the two calls that mail reports a request Pingen never answered as a
letter that stayed put — whether the answer was silence or a gateway's 502 —
while the two failures that do settle the question are left alone, a status
Pingen wrote itself and a request that was never sent, that
submitting is a PATCH, and that no token or client secret can
appear in a tool result or on stderr even when the upstream error body quotes it
back. The gate fails below 90% line, 90% function and 80% branch coverage of
index.js.
test/hygiene.test.mjs points the hygiene scan at throwaway git repositories
instead, because run over this repository — where everything is clean — a scan
that silently skipped half the files would look exactly like one that worked.
Mutation testing
npm run mutate asks a different question from everything above: not "do the
tests pass" but "would they notice if a guard were removed". StrykerJS deletes
one piece of behaviour at a time and reruns the suite; whatever survives is
something no assertion is watching.
That found eleven real gaps here after the model review rounds had stopped turning anything up — among them a path that walked out of the letters collection, a warning that went missing at exactly one HTTP status, and a page of one organisation being read as an account with one organisation. Most of the fixes were to the fixture rather than the code: a stand-in that accepts more than the real server does is a stand-in that hides the difference.
There is no browser in this suite, so npm run mutate:all over the whole file
is about forty minutes and worth running. stryker.config.json explains every
setting that is not a default — including why incremental mode is off, and why
the number to watch when tuning it is the timeout count rather than the score.
License
MIT — see LICENSE.
Available Tools
9 toolspingen_cancel_letterB
Cancel a letter that has already been submitted/sent (where cancellable).
| Name | Required | Description | Default |
|---|---|---|---|
| letter_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden for behavioral disclosure. It only adds that cancellation is possible 'where cancellable', but does not disclose side effects, reversibility, failure modes, or whether this is a destructive operation. The caveat is helpful but insufficient for a mutation-like operation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, clear sentence that states the core purpose without extraneous words. It is front-loaded and efficiently conveys the essential information, making it highly concise and well-structured.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
This is a simple one-parameter tool, and the description covers the primary action. However, without annotations or an output schema, it lacks details on what happens after cancellation, error conditions, or what 'cancellable' entails. It is minimally adequate but leaves gaps for the agent to handle unknown edge cases.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has only letter_id with 0% description coverage. The description does not explicitly explain that letter_id identifies the letter to cancel, nor does it add format or source information. The parameter's meaning is inferable from context and the tool name, but the description adds no value beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('Cancel') and the object ('a letter that has already been submitted/sent'), which distinguishes it from sibling tools like delete_letter (likely for drafts) or submit/send. The specificity of 'submitted/sent' and the conditional 'where cancellable' adds clear scope, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies this tool is used when a letter has already been submitted/sent, which hints at the appropriate scenario. However, it does not provide explicit when-to-use vs alternatives, nor does it mention any exclusions or prerequisites (e.g., do not use for drafts, use delete_letter instead).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pingen_delete_letterA
DESTRUCTIVE: delete a draft / not-yet-sent letter for good. Requires confirm:true. To stop a letter already on its way use pingen_cancel_letter instead.
| Name | Required | Description | Default |
|---|---|---|---|
| confirm | Yes | ||
| letter_id | Yes |
TDQS
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 labels the action as 'DESTRUCTIVE', notes irreversibility with 'for good', and explains the confirm:true requirement. It does not mention permissions or error handling, but for a simple delete operation, this is strong transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, front-loaded sentence with no wasted words. The 'DESTRUCTIVE' warning is immediately visible, and every clause adds essential information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple 2-parameter delete tool with no output schema and no annotations, the description covers the destructive nature, the required confirmation, and the alternative tool for canceling in-transit letters. This is sufficient for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate. It clarifies the confirm parameter by stating 'Requires confirm:true', and implies letter_id refers to the draft/not-yet-sent letter being deleted. However, it does not detail letter_id format or where to find it, which would add further value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'delete a draft / not-yet-sent letter for good.' It uses a specific verb (delete) with a specific resource (draft/not-yet-sent letter) and distinguishes itself from the sibling pingen_cancel_letter by noting the alternative use case.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly provides usage context: use for drafts/not-yet-sent letters, and explicitly names the alternative pingen_cancel_letter for letters already in transit. This gives clear when-to-use and when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pingen_download_letterA
Download the letter PDF to output_path (available once the letter is processed/sent).
| Name | Required | Description | Default |
|---|---|---|---|
| letter_id | Yes | ||
| output_path | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses the key prerequisite (letter being processed/sent) but doesn't mention potential errors, file overwrite behavior, or that it saves to a local filesystem. This is minimal but sufficient for a simple download operation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, highly informative sentence. It includes the action, target, destination, and a timing constraint without any redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple two-parameter tool with no output schema, the description covers the essential aspects: what it does, where it writes, and when it's available. It doesn't need to explain return values, and the lack of nested objects keeps it straightforward.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate. It explicitly explains output_path as the destination, but letter_id is left implicit, relying on the tool name for meaning. This partial clarification justifies a mid-range score.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action: 'Download the letter PDF to output_path'. It specifies the verb (download), the resource (letter PDF), and the destination (output_path), making it distinct from siblings like pingen_get_letter (which likely retrieves metadata) and pingen_list_letters.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The parenthetical '(available once the letter is processed/sent)' provides clear timing guidance for when this tool should be used. It doesn't explicitly name alternatives, but the context and sibling names imply the appropriate scenario, earning a score above baseline.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pingen_get_letterB
Get one letter status/tracking by id.
| Name | Required | Description | Default |
|---|---|---|---|
| letter_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure. 'Get' implies a read operation, but there is no mention of error handling, read-only behavior, response structure, or limitations. The phrase 'status/tracking' only hints at the return content.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, concise sentence with no redundant words. It is front-loaded with the action and resource.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with one parameter and no output schema, the description is far too sparse. It does not provide enough context about expected response, usage timing, or how it differs from related tools, leaving the agent potentially under-informed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has 0% description coverage, so the description must compensate. 'By id' tells the agent that the parameter is the letter identifier, but it lacks format details, examples, or where to obtain the ID. This is minimal but not entirely absent.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('Get'), the resource ('letter'), and the scope ('status/tracking by id'). This distinguishes it from sibling tools like pingen_list_letters (multiple letters) and pingen_cancel_letter (mutation).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided on when to use this tool versus alternatives such as pingen_status or pingen_letter_events. The description does not mention any exclusions or scenarios where this tool is preferred.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pingen_letter_eventsB
Tracking/status history of a letter (created, submitted, sent, delivered, undeliverable …).
| Name | Required | Description | Default |
|---|---|---|---|
| letter_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure. It adds value by enumerating event types (created, submitted, sent, delivered, undeliverable), giving a sense of what the tool returns. However, it does not explicitly state that the operation is read-only, nor does it describe response format, pagination, or ordering.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single sentence that is front-loaded with the core purpose and includes relevant examples. Every word contributes meaning, with no fluff or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with one parameter and no output schema, the description gives enough to understand the basic function. However, it omits details about the return structure (e.g., whether it returns a list of events, timestamps, or statuses) and potential error conditions. This is acceptable for basic use but leaves gaps for an agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 0% description coverage, so the description must compensate for the sole parameter 'letter_id'. The phrase 'of a letter' implicitly indicates that the parameter identifies the letter whose history is being retrieved, but it does not explicitly state the parameter name, type, or constraints. This is adequate for a single obvious parameter but not exemplary.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies this as a tracking/status history tool for letters, listing example event types. It distinguishes itself from sibling tools like pingen_status (which likely provides current status) by emphasizing 'history' and 'events'.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided on when to use this tool versus alternatives. It does not mention that pingen_status is for current status or that pingen_get_letter is for details. The reader must infer usage from the tool name and description.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pingen_list_lettersB
List letters with status and tracking. Optional page size.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | page size (default 20) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description must carry the burden of disclosure. It mentions 'with status and tracking' and optional page size, which hints at output content and pagination. However, it does not explicitly state that it is a read-only operation, nor does it describe pagination behavior beyond the existence of a page size.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise—two short sentences that immediately state the tool's purpose and its only parameter. No unnecessary words or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple list tool with one optional parameter and no output schema, the description covers the essentials: what is listed and the page size. It lacks details on return structure or filtering, but given the low complexity and sibling context, it is reasonably complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% as the limit parameter is described as 'page size (default 20)'. The description's 'Optional page size' adds no additional meaning beyond what the schema already provides, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool lists letters and includes status and tracking, using a specific verb and resource. It is distinguishable from siblings like send or delete, though it does not explicitly contrast with get_letter or status.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided on when to use this tool versus alternatives such as pingen_get_letter or pingen_status. The description only states what the tool does, leaving the user to infer appropriate usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pingen_send_letterA
Upload a PDF and create a letter. By default a DRAFT (auto_send=false) — nothing is mailed until pingen_submit_letter. Set auto_send=true to mail immediately; that path also requires delivery_product, because it is the one route that never reaches pingen_submit_letter and so nothing asks for a product later.
| Name | Required | Description | Default |
|---|---|---|---|
| auto_send | No | default false = create draft only | |
| file_path | Yes | absolute path to the PDF | |
| address_position | No | window position of the address on the first page (default left) | |
| delivery_product | No | e.g. cheap (B-Post), fast (A-Post), registered (Einschreiben), premium — CH values. Optional for a draft, required with auto_send=true |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description discloses the key behavioral distinction between draft and immediate send, and importantly explains why delivery_product is required on the auto_send path ('it is the one route that never reaches pingen_submit_letter'). It does not cover return values or error behavior, but the core send semantics are transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two dense sentences. The first states the main action, the second explains the draft/send branching and product requirement. Every clause adds information without padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool has two distinct modes and 4 parameters, with no output schema. The description covers the modes and the parameter dependency, but omits what the operation returns (e.g., a letter ID) and any file validation details. This is a notable gap given there is no output schema to fill it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already documents all 4 parameters, so the baseline is 3. The description adds valuable context connecting auto_send and delivery_product, explaining the logical reason for the dependency. This goes beyond the schema's individual field descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Upload a PDF and create a letter', a precise verb+resource statement. It clearly distinguishes the draft/send behavior from the sibling pingen_submit_letter by explaining that drafts are not mailed until that tool is called, and auto_send=true creates the letter immediately.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly explains the default draft workflow ('nothing is mailed until pingen_submit_letter') and the alternative immediate-send path (auto_send=true) including the delivery_product requirement. It does not explicitly list when not to use the tool, but the context is clear enough.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pingen_statusA
Verify credentials and show the active Pingen organisation (name, plan, id).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description must carry the burden. It states the tool verifies credentials and returns organisation details, but it does not disclose error behavior, side effects, or explicitly confirm that it is a read-only operation. The simplicity helps, but more detail on failure modes would improve transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, well-structured sentence that immediately presents the verb and expected output. It is front-loaded and contains no extraneous information, every word earning its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple status tool with no parameters and no output schema, the description sufficiently covers the key outputs (name, plan, id) and the primary action (credential verification). It could mention the response format or error handling, but the scope is simple enough that current coverage is adequate.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, and the schema is empty, so schema coverage is 100%. The description correctly avoids adding parameter details since there are none, meeting the baseline for a no-parameter tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
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 'Verify' and 'show' naming both the credential check and the returned organisation fields (name, plan, id). This distinguishes it from the sibling tools, which all handle letter operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The usage context is implied through the description, as it indicates credential verification and organisation status, which is distinct from the letter-focused siblings. However, it does not explicitly state when to use this tool versus alternatives or any exclusions, leaving the agent to infer the appropriate scenario.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pingen_submit_letterA
IRREVERSIBLE AND CHARGEABLE: prints and physically mails an existing DRAFT letter. Requires confirm:true. Optional print_mode (simplex/duplex) and print_spectrum (color/grayscale).
| Name | Required | Description | Default |
|---|---|---|---|
| confirm | Yes | ||
| letter_id | Yes | ||
| print_mode | No | ||
| print_spectrum | No | ||
| delivery_product | Yes |
TDQS
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 explicitly warns that the action is 'IRREVERSIBLE AND CHARGEABLE,' states that it requires confirm:true, and details the physical mailing action. It also mentions optional print parameters. This is exemplary transparency for a side-effect-heavy tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, dense sentence that front-loads the most critical warnings ('IRREVERSIBLE AND CHARGEABLE') and efficiently packs in the action, the confirm requirement, and optional parameters. Every word earns its place with no fluff or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with five parameters, no annotations, and no output schema, the description covers the essential operational context: action, risk, confirmation, and printing options. The main gap is the lack of explanation for the required 'delivery_product' parameter, which could confuse an agent about what values are acceptable. Otherwise, it presents a clear picture of what happens upon invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has zero description coverage for parameters. The description adds meaning for confirm (requires true) and enumerates possible values for print_mode and print_spectrum. However, it leaves letter_id and the required delivery_product unexplained. While it adds value for three of five parameters, the required delivery_product is completely unaddressed, limiting the compensatory effect.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'prints and physically mails an existing DRAFT letter.' This is a specific verb+resource, and it distinguishes the tool from siblings like pingen_send_letter (likely digital) and pingen_delete_letter by emphasizing physical mailing of a draft. The term 'submit' is clarified by the action of printing and mailing.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear usage context: use when you need to physically mail an existing draft letter, with an explicit warning about irreversibility and chargeability. It implicitly distinguishes from alternatives by specifying 'DRAFT letter' and physical mailing, but it does not explicitly name alternatives or state when not to use this tool. It conveys the need for confirm:true.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
9 tool updates
v0.3.0- First observed
pingen_cancel_letter - First observed
pingen_delete_letter - First observed
pingen_download_letter - First observed
pingen_get_letter - First observed
pingen_letter_events - First observed
pingen_list_letters - First observed
pingen_send_letter - First observed
pingen_status - First observed
pingen_submit_letter
TDQS
Scored across 9 tools
Most tools target distinct operations: send, submit, get, list, download, cancel, delete, events, and status. pingen_get_letter and pingen_letter_events both relate to tracking, but the former gives current status while the latter gives history, so they are distinguishable with the provided descriptions.
All tools share the 'pingen_' prefix and most follow verb_noun naming (e.g., pingen_send_letter, pingen_delete_letter). pingen_letter_events and pingen_status deviate from the verb pattern, but the overall convention is predictable and clear.
Nine tools cover the full letter lifecycle—creating, submitting, tracking, downloading, cancelling, deleting—plus an org status check. The count is well-scoped and each tool serves a clear purpose without unnecessary bloat.
The tool set covers the complete workflow for sending physical letters: upload/create, submit, monitor events/status, download PDF, cancel sent letters, and delete drafts. No obvious gaps in the core domain of letter management.
Maintenance
Related MCP Connectors
The agentic layer of letters. Agents send real printed mail worldwide, German compliance built in.
Physical mail API for AI agents. Send letters, certified mail. Sandbox + live keys via MCP.
Print and mail physical documents in the US via USPS, with quotes, agent payment and tracking.
Verify US & international addresses and send physical mail (postcards, letters, checks) via Lob.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables AI agents to send physical letters and postcards to over 200 countries using Solana cryptocurrency for payment. It provides tools for generating mail quotes, managing wallet balances, and automating physical correspondence directly through the Model Context Protocol.449 npm1MIT
- FlicenseNot gradedqualityDmaintenanceEnables sending emails (including mass emailing), querying, updating, and canceling delayed emails via the Resend API.-
- FlicenseAqualityDmaintenanceEnables sending letters and MICR-encoded checks, managing contacts and templates, and verifying US/Canadian addresses via the PostGrid Print & Mail and Address Verification APIs from Claude.301-
- AlicenseAqualityBmaintenanceLocal MCP server that generates print-ready PDF letters with DIN 5008 compliant address positioning for window envelopes, handling structured content offline without external APIs.755 npmMIT