npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kontor-mcp/server

v1.0.4

Published

Kontor MCP server — sovereign e-invoice tools for AI agents over stdio and Streamable HTTP

Readme

@kontor-mcp/server

Sovereign MCP server for German/EU e-invoices (XRechnung, ZUGFeRD/Factur-X, EN 16931): parse, validate and explain — 100 % offline, no Java, no network.

Tools (v0.9)

| Tool | Purpose | |---|---| | parse_invoice | Detect format (UBL/CII · EN 16931 · XRechnung version/variant · ZUGFeRD profile) and return the EN 16931 semantic model | | validate_invoice | XSD + official EN 16931 / XRechnung Schematron with the KoSIT scenario model → valid / valid_with_warnings / invalid, findings with DE/EN explanations and fix hints | | audit_invoice | One call for AP: parse + validate + Kontor plausibility (totals recomputed, VAT rates, IBAN, Leitweg-ID check digits, dates, known_invoice_numbers duplicates) → header facts, VAT breakdown, verdict, grouped findings, accept / review / reject with rationale | | generate_invoice | Structured data → compliant XRechnung 3.0 (UBL) or, with target: zugferd-pdf, a ZUGFeRD 2.3 / Factur-X PDF/A-3 (zugferd_profile EN16931 / BASIC / EXTENDED, embedded factur-x.xml, veraPDF- and Mustang-verified): decimal-safe amounts/VAT/totals, internal validation (fail-honest valid — for PDFs the XML is read back out of the file first), deterministic auto-fixes reported, optional output_path (.xml / .pdf, never overwrites unless overwrite=true), otherwise pdf_base64 | | convert_invoice | extract-xml (ZUGFeRD PDF → XML), xrechnung-ubl / cii via the semantic model with post-validation and an honest lossReport, html-preview (self-contained HTML, no scripts/assets); optional output_path | | check_obligations | Offline decision tree over the German mandate (§ 14 / § 27 Abs. 38 UStG, UStDV §§ 33/34/34a, E-RechV): issuer/receiver × B2B/B2G/B2C, transition 2026 → 2027 (€800k) → 2028, exemptions, formats, Leitweg-ID — with primary sources, lastVerified and a non-advice disclaimer | | list_capabilities | Introspection: formats/profiles, bundled standard versions, KB stats, code lists, legal lastVerified, tools/resources/prompts, sovereignty statement | | explain_rule | Explain a rule id such as BR-DE-15 (official text, explanation, affected BTs, fix hint); unknown ids get suggestions |

Every document tool accepts either file_path (absolute; .xml/.pdf; ≤ 20 MB, KONTOR_MAX_FILE_MB) or content_base64 (+ optional content_type), and lang: "de" | "en" (default de).

Resources: kontor://samples/{name} — bundled sample invoices (valid-xrechnung-ubl.xml, valid-xrechnung-cii.xml, broken-missing-buyer-reference.xml, valid-zugferd-en16931.pdf, generated-zugferd-en16931.pdf).

Claude Desktop

claude_desktop_config.json (Settings → Developer → Edit Config):

{
  "mcpServers": {
    "kontor": {
      "command": "npx",
      "args": ["-y", "@kontor-mcp/server"]
    }
  }
}

From a local checkout (before the npm publish):

{
  "mcpServers": {
    "kontor": {
      "command": "node",
      "args": ["/absolute/path/to/kontor-mcp/packages/server/dist/bin.js"]
    }
  }
}

Use an absolute path to node (which node) if Desktop cannot find it — it launches servers with a minimal PATH. Quit Desktop fully (⌘Q) and reopen; the config is read at launch. Logs: ~/Library/Logs/Claude/mcp-server-kontor.log.

Restart Claude Desktop, attach an invoice (or +Add from kontorbroken-missing-buyer-reference.xml) and ask "Ist diese Rechnung gültig?".

All tools are offline and stateless; all except generate_invoice / convert_invoice (which may write a file when output_path is given) are read-only (readOnlyHint), so it is safe to set them to Always allow under Settings → Connectors → kontor; on first use Desktop shows a "Needs approval" prompt otherwise.

PDFs: Desktop does not hand attached PDF bytes to the server, so reference ZUGFeRD/Factur-X PDFs by local path instead: "Was steht in dieser Rechnung? /path/to/invoice.pdf". XML attachments work either way (the model re-sends them as content_base64).

Claude Code

claude mcp add kontor -- node /absolute/path/to/kontor-mcp/packages/server/dist/bin.js

MCP Inspector

npx @modelcontextprotocol/inspector@latest node packages/server/dist/bin.js
# or headless:
npx @modelcontextprotocol/inspector@latest --cli node packages/server/dist/bin.js --method tools/list

Streamable HTTP

The same server over HTTP (MCP Streamable HTTP, endpoint /mcp) for remote agents, Docker and clients that cannot hand attachment bytes to a stdio server:

KONTOR_TRANSPORT=http KONTOR_AUTH_TOKEN="$(openssl rand -hex 24)" node packages/server/dist/bin.js
# → [kontor-mcp] kontor-mcp 0.9.0 listening on http://127.0.0.1:3333/mcp (auth: bearer, origins: localhost)
npx @modelcontextprotocol/inspector@latest --cli http://127.0.0.1:3333/mcp --transport http \
  --header "Authorization: Bearer $KONTOR_AUTH_TOKEN" --method tools/list

| Variable | Default | Meaning | |---|---|---| | KONTOR_TRANSPORT | stdio | stdio or http | | KONTOR_PORT | 3333 | TCP port (0 = ephemeral) | | KONTOR_BIND | 127.0.0.1 | Bind address. Loopback (127.0.0.1, localhost, ::1) enables Host-header / DNS-rebinding protection | | KONTOR_AUTH_TOKEN | — | Bearer token (≥ 16 chars), compared in constant time. Required in HTTP mode | | KONTOR_ALLOW_NO_AUTH | — | 1 runs without a token — only honoured on a loopback bind (local experiments) | | KONTOR_ALLOWED_ORIGINS | — | Comma-separated browser origins allowed in addition to http(s)://localhost|127.0.0.1|[::1]. Requests without an Origin header (non-browser clients) always pass; any other origin → 403 | | KONTOR_ALLOWED_HOSTS | — | Comma-separated hostnames accepted in the Host header besides loopback (behind a reverse proxy / in a container). On a loopback bind only loopback hosts pass; on 0.0.0.0 without this list the Host header is not checked and the token is the guard | | KONTOR_MAX_SESSIONS | 100 | Concurrent MCP sessions; beyond it new initialize requests get 503 + Retry-After | | KONTOR_SESSION_IDLE_MINUTES | 30 | Sessions without a request for this long are closed (clients that never DELETE) | | KONTOR_MAX_FILE_MB | 20 | Also sizes the JSON body cap (content_base64 inflates by 4/3) |

Behaviour: one MCP session per client (Mcp-Session-Id, UUID), DELETE /mcp ends it, unknown ids → 404; GET /healthz (unauthenticated) returns { ok, name, version, sessions } for container health checks; SIGINT/SIGTERM close all sessions, then the listener. Wrong or missing token → 401 with WWW-Authenticate: Bearer. There is no TLS in the server — terminate it in your reverse proxy (Caddy, nginx, Traefik) and forward to the loopback port; see SECURITY.md.

Docker

The image (Dockerfile at the repo root; multi-stage, node:22-alpine, runs as node, ~70 MB, linux/amd64 + linux/arm64) defaults to KONTOR_TRANSPORT=http, KONTOR_BIND=0.0.0.0, port 3333 and refuses to start without KONTOR_AUTH_TOKEN. A HEALTHCHECK polls /healthz. The samples ship at /app/samples/; mount your own invoices read-only (-v ./invoices:/data:ro) and reference them as /data/<file>.

docker build -t kontor-mcp .
docker run -d --name kontor -p 127.0.0.1:3333:3333 -e KONTOR_AUTH_TOKEN="$(openssl rand -hex 24)" -v "$PWD/invoices:/data:ro" kontor-mcp
docker run -i --rm -e KONTOR_TRANSPORT=stdio kontor-mcp        # stdio inside a container also works

docker-compose.yml is a hardened example (loopback-published port, read_only, cap_drop: ALL, no-new-privileges, token from .env). CI builds both platforms and runs an Inspector-over-HTTP smoke test against the image on every push.

Privacy / sovereignty

Stateless; nothing is stored or transmitted; invoice contents are never logged (see PRD NFR-2/NFR-5/NFR-6). Findings are formal/technical checks, not tax or legal advice.

Resources and prompts

| URI / name | What | |---|---| | kontor://samples/{name} | Sample invoices (UBL, CII, ZUGFeRD PDF, one broken) | | kontor://reference/rules | Rule knowledge-base index (all EN 16931 / XRechnung rule ids, severity, curated flag) | | kontor://reference/codelists/{list} | units, vat-categories, payment-means, eas, vatex, invoice-types, currencies, countries, allowance-reasons, charge-reasons, identifier-schemes, mime-types, vat-point-date-codes — official code values with DE/EN names for the common ones | | kontor://reference/cheatsheet | One-page EN 16931 / XRechnung / ZUGFeRD orientation incl. the German mandate timeline (Markdown) | | prompt audit-incoming-invoice | Run audit_invoice and present a decision-ready AP summary | | prompt draft-supplier-rejection | Draft (never send) a German rejection e-mail citing the concrete rule violations | | prompt create-invoice-interview | Interview for the minimal XRechnung field set, then generate_invoice |