Lexware Office MCP Server
About
MCP server for the Lexware Office API — manage invoices, contacts, articles, vouchers, and more. 65 tools across 20 resource domains.
Details
- Author
- lazyants
- Categories
- Productivity
Jump to
Setup
Install Lexware Office MCP Server in your MCP client (Claude Desktop, Cursor, Windsurf, and others).
Repository: https://github.com/lazyants/lexware-mcp-server
Follow the installation instructions in the repository README, then restart your MCP client.
MCP server for the Lexware Office API — manage invoices, contacts, articles, vouchers, and more. 65 tools across 20 resource domains.
MCP server for theLexware Office API. Manage invoices, contacts, articles, vouchers, and more through the Model Context Protocol.
Unofficial — community project.Not affiliated with, endorsed by, or supported by Lexware GmbH or Haufe Group. "Lexware" and "Lexware Office" are trademarks of their respective owners; used here only to identify the API this client targets (nominative fair use).
66 toolsacross 20 resource domains, with 6 entry points so you can pick the right server for your MCP client's tool limit.
npm install -g @lazyants/lexware-mcp-server
The API token is resolved in this order:
- OS keyring(recommended — token never written to disk in plain text)
- Environment variableLEXWARE_API_TOKEN
Get your token from theLexware Office API settings, then store it with the native credential manager for your OS.
[!IMPORTANT] The commands below read the token from an interactive prompt rather than taking it as an argument, so it never lands in your shell history or the process list. Avoid pasting the token directly onto the command line.
Omitting the value after-wmakessecurityprompt for the token (with confirmation):
security add-generic-password -s "lexware-mcp" -a "api-token" -w
cmdkeycan only take the token as a command-line argument, which exposes it in the process list. Instead, read it from a hidden prompt and write it straight into Windows Credential Manager viaCredWrite, so the token never reaches argv. The credential's target name is<account>.<service>—api-token.lexware-mcpfor the default service — which is exactly what the server reads back:
$secure = Read-Host -AsSecureString "Lexware API token" Add-Type -Namespace LexwareKeyring -Name Native -MemberDefinition @' [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct CREDENTIAL { public uint Flags; public uint Type; [MarshalAs(UnmanagedType.LPWStr)] public string TargetName; [MarshalAs(UnmanagedType.LPWStr)] public string Comment; public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten; public uint CredentialBlobSize; public IntPtr CredentialBlob; public uint Persist; public uint AttributeCount; public IntPtr Attributes; [MarshalAs(UnmanagedType.LPWStr)] public string TargetAlias; [MarshalAs(UnmanagedType.LPWStr)] public string UserName; } [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool CredWriteW(ref CREDENTIAL credential, uint flags); '@ $blob = [Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($secure) try { $cred = New-Object LexwareKeyring.Native+CREDENTIAL $cred.Type = 1 # CRED_TYPE_GENERIC $cred.Persist = 2 # CRED_PERSIST_LOCAL_MACHINE $cred.TargetName = 'api-token.lexware-mcp' # "<account>.<service>" $cred.UserName = 'api-token' $cred.CredentialBlob = $blob $cred.CredentialBlobSize = $secure.Length 2 # UTF-16 bytes, no terminator if (-not [LexwareKeyring.Native]::CredWriteW([ref]$cred, 0)) { throw "CredWrite failed (Win32 error $([Runtime.InteropServices.Marshal]::GetLastWin32Error()))" } Write-Host 'Stored Lexware API token in Windows Credential Manager.' } finally { [Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($blob) $secure.Dispose() Remove-Variable secure, blob }
Using a customLEXWARE_KEYRING_SERVICE(e.g.acme)? SetTargetNametoapi-token.acmeto match — the server looks the token up under<account>.<service>.
secret-tool store --label="Lexware Office API" service lexware-mcp username api-token # (prompts for the token value)
Once stored, MCP config files need no credentials at all — the server reads the token from the keyring at startup.
If you prefer not to use the keyring, setLEXWARE_API_TOKENin your shell or MCP client config:
export LEXWARE_API_TOKEN=your-token-here
Optionally override the webhook-signature public key used bylexware_verify_webhook_signature(by default fetched from Lexware and cached):
export LEXWARE_WEBHOOK_PUBLIC_KEY="$(cat lexware-webhook-public.pem)"
Use split servers to reduce context size — pick only the splits you need.
Add to~/.claude/settings.json. If you stored the token in the OS keyring under the default service namelexware-mcp(recommended), noenvkey is needed:
{ "mcpServers": { "lexware": { "command": "npx", "args": ["-y", "@lazyants/lexware-mcp-server"] } } }
If you prefer the environment variable approach:
{ "mcpServers": { "lexware": { "command": "npx", "args": ["-y", "@lazyants/lexware-mcp-server"], "env": { "LEXWARE_API_TOKEN": "your-token-here" } } } }
Use split servers to reduce context size — pick only the entry points you need. The-p @lazyants/lexware-mcp-serverflag tellsnpxwhich package to source the command from; the final argument (e.g.lexware-mcp-sales) is the specific entry-point binary defined in that package (seeEntry Points):
{ "mcpServers": { "lexware-sales": { "command": "npx", "args": ["-y", "-p", "@lazyants/lexware-mcp-server", "lexware-mcp-sales"] }, "lexware-contacts": { "command": "npx", "args": ["-y", "-p", "@lazyants/lexware-mcp-server", "lexware-mcp-contacts"] } } }
Multi-account example (two Lexware companies, tokens stored under separate keyring service names):
{ "mcpServers": { "lexware-company-a": { "command": "npx", "args": ["-y", "@lazyants/lexware-mcp-server"], "env": { "LEXWARE_KEYRING_SERVICE": "lexware-company-a" } }, "lexware-company-b": { "command": "npx", "args": ["-y", "@lazyants/lexware-mcp-server"], "env": { "LEXWARE_KEYRING_SERVICE": "lexware-company-b" } } } }
Add toclaude_desktop_config.json. With the OS keyring (recommended — assumes the token is stored under the default service namelexware-mcp):
{ "mcpServers": { "lexware": { "command": "npx", "args": ["-y", "@lazyants/lexware-mcp-server"] } } }
{ "mcpServers": { "lexware": { "command": "npx", "args": ["-y", "@lazyants/lexware-mcp-server"], "env": { "LEXWARE_API_TOKEN": "your-token-here" } } } }
lexware_create_invoice(supportsfinalize=trueat creation),lexware_get_invoice,lexware_download_invoice_file,lexware_pursue_invoice,lexware_deeplink_invoice
lexware_create_credit_note,lexware_get_credit_note,lexware_download_credit_note_file,lexware_pursue_credit_note,lexware_deeplink_credit_note
lexware_create_quotation,lexware_get_quotation,lexware_download_quotation_file,lexware_deeplink_quotation
lexware_create_order_confirmation,lexware_get_order_confirmation,lexware_download_order_confirmation_file,lexware_pursue_order_confirmation,lexware_deeplink_order_confirmation
lexware_create_delivery_note,lexware_get_delivery_note,lexware_download_delivery_note_file,lexware_pursue_delivery_note,lexware_deeplink_delivery_note
lexware_get_down_payment_invoice,lexware_download_down_payment_invoice_file,lexware_deeplink_down_payment_invoice
lexware_get_dunning,lexware_download_dunning_file,lexware_pursue_dunning,lexware_deeplink_dunning
Voucherlist (1 tool) — sales, bookkeeping
lexware_list_contacts,lexware_get_contact,lexware_create_contact,lexware_update_contact,lexware_deeplink_contact
lexware_list_articles,lexware_get_article,lexware_create_article,lexware_update_article,lexware_delete_article
lexware_list_vouchers,lexware_get_voucher,lexware_create_voucher,lexware_update_voucher,lexware_upload_voucher_file,lexware_deeplink_voucher
lexware_create_event_subscription,lexware_list_event_subscriptions,lexware_get_event_subscription,lexware_delete_event_subscription,lexware_verify_webhook_signature
lexware_upload_file,lexware_download_file,lexware_get_file_status,lexware_deeplink_file
lexware_list_recurring_templates,lexware_get_recurring_template,lexware_deeplink_recurring_template
- Use the OS keyringto keep your API token out of config files and shell history entirely (seeConfiguration)
- Never commit your API tokento version control
- Useread-onlyaccess when you only need to list/get resources
- Create, update, and delete tools modify real business data— invoices, contacts, and accounting records in your Lexware account
- Rate limiting is handled automatically: requests retry with exponential backoff on 429, including file uploads — the multipart body is rebuilt fresh on every retry attempt, so it can be replayed safely
Releases ship via the GitHub Release event. Maintainer flow:
-
Bump the version inpackage.json,package-lock.json, andserver.json(npm version <x.y.z> --no-git-tag-versionupdates the first two together).npm run check-versionshard-fails unlesspackage.json#/version,server.json#/packages[0].version, and bothpackage-lock.jsonversion fields (root andpackages[""]) all agree.server.json#/versionis checked more loosely: it must be present, but it is only compared againstpackages[0].versionas a regression check — it may legitimately beahead(registry-only republishes bump just that field), so a value left behind at the previous release passes with aWARN:line and no failure. For an ordinary release both should move together, so read the script's output rather than trusting its exit code.CHANGELOG.mdis not checked at all.
Commit, andmerge the version bump tomainbefore creating the release. Then create the tag yourself, on a SHA you have checked, and only then create the release from it:
V=X.Y.Z && PR=<release-pr-number> && SHA="$(gh pr view "$PR" --json mergeCommit -q .mergeCommit.oid)" && test -n "$SHA" && git fetch origin main && git merge-base --is-ancestor "$SHA" origin/main && PKG="$(git show "$SHA:package.json")" && test "$(printf '%s' "$PKG" | node -pe 'JSON.parse(require("fs").readFileSync(0,"utf8")).version')" = "$V" && CL="$(git show "$SHA:CHANGELOG.md")" && printf '%s\n' "$CL" | awk -v v="$V" 'index($0,"## ["v"]")==1{f=1;next} /^## \[/{f=0} /^\[[0-9]+\.[0-9]+\.[0-9]+\]:/{f=0} f' > "/tmp/notes-v$V.md" && grep -q '[^[:space:]]' "/tmp/notes-v$V.md" && git tag -a "v$V" "$SHA" -m "v$V" && git push origin "v$V" && gh release create "v$V" --verify-tag --notes-file "/tmp/notes-v$V.md"
The failure this prevents: with no existing tag,gh release createplaces one on thetip of the default branch, so running it while the bump is still on a release branch tags thepreviousrelease's commit. The workflow then publishes whatever version it finds in that commit'spackage.json, and you get avX.Y.ZGitHub Release that silently republishes the old version. Nothing downstream catches it — neither the workflow norcheck-versionscompares the tag against the version files (tracked in #103), which is why the sequence above has to do it.
- gh pr view … .mergeCommit.oidnames the release PR's own squash commit. Do not substitutegit rev-parse origin/main— that is merely whatever is onmainat the moment you look, so an unrelated merge landing in the gap gets tagged and shipped instead.ghexits 0 and prints nothing for an unmerged PR, hence the explicittest -n.
- The&&chainstops on the first failure instead of falling through to the irreversible step. Bothgit showcalls are assigned to a variable rather than piped directly, so their exit status is actually checked — a pipeline reports only itslastcommand's status unlesspipefailis set, which is not assumed here.
- git merge-base --is-ancestorproves the commit is actually reachable frommain. Mere existence is not enough — a commit can be present locally because some other branch was fetched, and if its version files happen to match it would otherwise sail through every remaining check.
- The version test readspackage.jsonout of the target commit, not the working tree, which would still show the right version while$SHApointed elsewhere.
- Theawklifts that version's section out of the commit'sCHANGELOG.mdfor--notes-file. Without it the release body is whatever--notes-from-tagfinds in the annotation — for this flow, the literal stringvX.Y.Z, which is a poor release note for any version and an actively misleading one for a major carrying a breaking change. It stops at the next## headingor*at the first link-reference definition, because the oldest entry in the file has no heading after it and would otherwise swallow the entire link-reference block.grep -qrather thantest -sguards the result: a section that is empty apart from its blank line still produces a one-byte file, whichtest -saccepts.
- --verify-tagmakesghabort rather than invent a tag if the push did not land — the guard againstghfalling back to the tip-of-default-branch behavior described above.
Ifgh release createfails after the tag is already pushed, do not rerun the whole block — it will stop atgit tag, which is correct. Rerun only the final command.
ThePublish to npm + MCP Registryworkflow runs automatically: itnpm publishes with provenance, polls the registry until the tarball is available, then pushes the matchingserver.jsonto the MCP Registry viamcp-publisher.
The workflow skipsnpm publishcleanly if the version is already on npm (cutover guard for releases that were partially published manually).
Publishing auth — npm Trusted Publishing (no token)
Publishing usesnpm Trusted Publishingvia OIDC — there isnoNPM_TOKENsecret. The workflow'sid-token: writepermission is exchanged for a short-lived, one-shot publish token at publish time, using the trusted-publisher binding configured for@lazyants/lexware-mcp-serverin the npm web UI. The only setup required is that trusted-publisher binding on npm; nothing needs to be stored in repository secrets.
This is anunofficial, independent community project. It is not affiliated with, endorsed by, sponsored by, or supported by Lexware GmbH, Haufe Group, or any of their affiliates. For official Lexware support, contact Lexware directly — issues with this MCP server should be reported here, not to Lexware.
"Lexware" and "Lexware Office" are trademarks of their respective owners and are used in this project's name and documentation under nominative fair use, solely to identify the third-party API this client connects to.
Create, update, and delete operations modify real business data in your Lexware account. The authors provide this software "as-is" and accept no responsibility for unintended changes, data loss, or any other damages arising from its use. Test against a sandbox or non-critical account before running write operations against production data.
[FSL-1.1-MIT— seeLICENSEfor the full terms.
The 1Password MCP server creates a bridge that allows MCP clients such as Codex and Kiro to manage your 1Password Environments with secure authorization prompts.
This is the 1st, easiest, and cheapest PPT, slides, presentation AI generation MCP Server in the world.
Persistent memory for any AI assistant. Zero token cost until recall. Stores memories in local SQLite, ranks by 6-factor scoring, returns results 79% smaller than JSON. Works with Claude, ChatGPT, Grok, Cursor, Windsurf, and any MCP client.
A MCP server that enables AI assistants to interact with Anki, the spaced repetition flashcard application.
Enables LLM clients to interact with macOS applications through AppleScript. Built using the @beyondbetter/bb-mcp-server library, this server provides safe, controlled execution of predefined scripts with optional support for arbitrary script execution.
An MCP server for WordPress plugin audits
Turn your AI assistant into a digital marketing hub that creates, organizes, and analyzes links and QR Codes on demand.
Connect AI clients to Cal.com scheduling through the Model Context Protocol using the hosted server at mcp.cal.com or a local instance.
Sync Calendars, Scheduling Links, AI Executive Scheduling Assistant, Unified Calendar
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.



