Skip to content

Releases: marselsel/Lexware-MCP-Server

v0.1.13 — robustness fixes from a full audit + review

Choose a tag to compare

@marselsel marselsel released this 13 Aug 19:59

Robustness fixes surfaced by a full security audit and code-review pass of the server. No behaviour changes for valid input, and no security vulnerabilities were found — these are correctness/hygiene touches.

Fixed

  • upload-file-from-url: an empty-string mimeType override no longer files a receipt with a blank content type (falls through to the response type; || not ??).
  • A present-but-unusable Content-Disposition filename* (empty / control-chars only) degrades to the plain filename= instead of discarding it. RFC 6266 precedence preserved.
  • get-document rejects an unknown voucherType cleanly via Object.hasOwn instead of letting a prototype key stringify into the request path.
  • postMultipart classifies a mid-read failure after a 2xx like the other client methods.
  • The default upload host allow-list is returned as a copy, not the shared module array.

Full detail in CHANGELOG.md.

v0.1.12 — upload-file-from-url (pinned transport, opt-in)

Choose a tag to compare

@marselsel marselsel released this 13 Aug 19:16

The server-side URL fetcher held back in 0.1.11 — now with its DNS-rebinding TOCTOU closed by connection-level IP pinning. Based on the contribution by @gutencoder (#39, merged as #41).

Added

  • upload-file-from-url — fetch a file from a share link server-side and store it in Lexware, so a receipt already in OneDrive/SharePoint reaches the books without its bytes crossing the model context. Off by default (LEXWARE_ENABLE_URL_UPLOAD); its own capability; requires the drafts tier (does not enable it).
  • LEXWARE_UPLOAD_ALLOWED_HOSTS — hosts the tool may fetch from (dot-boundary match; unset = Microsoft file-sharing defaults; a set value replaces them; empty blocks every host).

Security

  • The connection is pinned to the address that was checked (src/uploads/pinned-fetch.ts, on node:https — no new dependency). The guard's one DNS resolution is both range-checked and fed to the socket, so there is no second lookup to disagree with the first. TLS is untouched: SNI and certificate validation still bind to the hostname; only the dialled address comes from the pin.
  • Three layers at every redirect hop: host allow-list → resolved-address range check (loopback/private/link-local/metadata/CGNAT/… in every IPv4/IPv6 spelling, fail-closed) → pinning.
  • URLs with embedded credentials are refused; filenames are sanitized (the same trust boundary as the ticket flow).

Verification

  • 321 tests, including real-certificate TLS tests proving pinning doesn't weaken cert validation, a raw-ClientHello SNI check, and an RFC-6761 .invalid-host connection proof.
  • Reviewed through multiple adversarial passes, /code-review high, and /security-review high (no findings).

Default-off, so deploying changes no behavior until an operator sets LEXWARE_ENABLE_URL_UPLOAD=true. Full detail in CHANGELOG.md.

v0.1.11 — Upload without base64 (ticket flow)

Choose a tag to compare

@marselsel marselsel released this 13 Aug 10:22

Upload a receipt without pushing its bytes through the model context. Based on the contribution by @gutencoder (#34, merged as #38); the server-side URL-fetch tool from that PR was intentionally held back (see the CHANGELOG Security note).

Added

  • create-upload-ticket / get-upload-result (drafts tier): a short-lived (15 min), single-use ticket with a browser drag-and-drop URL and a ready-to-run curl command. Bytes go client → server → Lexware; the model only sees the file id. Filenames travel as X-Filename-B64 (base64url of the UTF-8 bytes), so umlauts, dashes, quotes and emoji survive.
  • SERVER_URL (or OAUTH_RESOURCE) now applies in every auth mode — upload links are built from it; static-token deployments behind a real domain no longer hand out loopback links.

Security & hardening

  • The /upload/:ticket route mounts only when the drafts capability is enabled; the ticket page is served no-store + nosniff + frame-denied.
  • One buffering body per ticket at a time; invalid/expired/used tickets are rejected before any body is read; gzip framing refused (inflate: false).
  • Operator credential failures (Lexware 401/403) answered as generic 502 to the unauthenticated uploader; unknown-outcome transport failures name the duplicate risk.
  • upload-file-from-url deferred: its DNS-rebinding TOCTOU is moot for the built-in Microsoft allow-list but live for custom allow-lists; it returns only with connection-level IP pinning, disabled by default.

Notes

  • The ticket store is in-process — a single-instance feature (documented in the CHANGELOG).
  • 250 tests, including an end-to-end test that executes the emitted curl command against the live routes.

Full details in CHANGELOG.md.

v0.1.7 — Line-item optional/alternative flags

Choose a tag to compare

@marselsel marselsel released this 06 Jul 13:15
19c6893

Document line items now expose two lexoffice fields the tools didn't model:

  • optional — an optional position: shown with its price but not counted in the document total (Optionale Position).
  • alternative — an alternative position (Alternativposition).

Added to the shared lineItemSchema, so it applies to every document create tool (invoice, quotation, credit-note, order-confirmation, delivery-note). Both are string-coercible and forwarded to the API; the model can now set them (previously they were only ever returned on reads).

Verified live: a two-position quotation with the second marked optional: true returns lineItems[1].optional === true and a total that excludes the optional position (net 100, not 150).

v0.1.6 — Security & correctness hardening

Choose a tag to compare

@marselsel marselsel released this 05 Jul 19:30
1152296

First tagged release of the open-source Lexware Office MCP server — a self-hostable Model Context Protocol server exposing the Lexware Office (lexoffice) API to AI agents, with read / drafts / finalize capability tiers and OAuth or static-token auth.

This release folds in a review-driven security & correctness hardening pass (0.1.4 → 0.1.6), verified live against a real Lexware account and running in production. See CHANGELOG.md for the full per-version breakdown.

Highlights

Correctness

  • create-draft-* rejects a stale finalize=true loudly instead of silently returning a draft; issuing lives only in the dedicated create-finalized-* tools.
  • Error-body read failures are classified as LexwareApiError (a 404 stays a 404).
  • update-voucher one-off contactName handling; get-document dispatches recurringtemplate; base64 uploads validated (no silent corruption); idempotent deletes; empty-list pagination text.
  • Removed the read-only, silently-ignored archived param from the contact tools.

Security

  • The 12 MB upload body limit is parsed on /mcp after auth, so unauthenticated requests can't force a multi-MB parse.
  • Webhook event-subscription create + delete moved to the finalize tier (off by default); create-event-subscription requires an https:// callback.
  • OAuth-domain denial returns 403 (not a 401 re-auth loop); static 401s carry a WWW-Authenticate challenge; OAuth endpoints overridable for non-WorkOS IdPs.
  • Patched a transitive hono high-severity advisory (→ 4.12.27).

Robustness

  • additionalFields escape hatch on every create tool (reserved control keys stripped) so valid unmodeled fields (e.g. xRechnung) aren't dropped; shared helpers; the tools layer no longer imports Skybridge at runtime.

Typecheck clean; 134 tests passing.