Lexware Office MCP Server
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| LEXWARE_OFFICE_API_KEY | Yes | Your Lexware Office API key. Required for authentication. | |
| LEXWARE_OFFICE_READ_ONLY | No | Set to 'true' to force read-only mode. Overrides ALLOW_WRITES. Default is read-only. | |
| LEXWARE_OFFICE_ALLOW_WRITES | No | Set to 'true' to allow write operations (POST, PUT, PATCH, DELETE). The server is read-only by default. |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| searchA | Search the curated Lexware Office API catalog by running a JavaScript async arrow function. Use this before execute to discover endpoints, request shapes, response notes, workflows, and domain-specific caveats. Available global: declare const spec: LexwareApiCatalog; Useful starting points:
Sandbox: no network, filesystem, process, fetch, imports, or API key. Return JSON-serializable data; console logs are captured. Examples: async () => Object.entries(spec.info.domainIndex) .filter(([name, domain]) => [name, ...domain.tags].some(value => value.toLowerCase().includes('contact'))) .map(([name, domain]) => ({ name, ...domain })) async () => { const op = spec.paths['/v1/voucherlist']?.get; return { summary: op?.summary, parameters: op?.parameters, notes: op?.notes, examples: op?.examples }; } |
| executeA | Execute a constrained Lexware Office API workflow by running a JavaScript async arrow function. Use search first when you need endpoint/domain guidance; do not guess Lexware paths from memory. The sandbox exposes no API key, filesystem, process, imports, fetch, or arbitrary network access. Available globals: declare const spec: LexwareApiCatalog; declare const lexware: { request<T = unknown>(input: LexwareRequest): Promise<LexwareResponse>; json(input: LexwareRequest): Promise; paginate<T = unknown>(input: LexwareRequest, options?: { maxPages?: number }): Promise<T[]>; requireNumber(row: unknown, fieldPath: string): number; requireMoney(row: unknown, fieldPath: string): number; sumMoney(rows: unknown[], fieldPath: string): number; formatMoney(cents: number, currency?: string): string; }; type LexwareRequest = { method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; path: string; // relative /v1/... only; no absolute URLs or // hosts query?: Record<string, string | number | boolean | Array<string | number | boolean> | null | undefined>; body?: unknown; // JSON by default; string when rawBody=true (UTF-8 encoded, NOT binary-safe) bodyBase64?: string; // raw binary body as base64; the host decodes it outside the sandbox multipart?: MultipartPart[]; // multipart/form-data uploads (e.g. POST /v1/files); host builds FormData contentType?: string; rawBody?: boolean; accept?: string; }; // At most one of body, bodyBase64, or multipart per request. type MultipartPart = { name: string; value?: string; // plain text form field contentBase64?: string; // binary part content as base64; host decodes it contentPath?: string; // absolute file path on the MCP server machine; host reads the file directly — preferred for local files (no base64, no size blowup) filename?: string; // defaults to the contentPath basename contentType?: string; }; // Exactly one of value, contentBase64, or contentPath per part. type LexwareResponse<T = unknown> = { ok: boolean; status: number; statusText: string; data?: T; text?: string; truncated?: boolean; contentType: string; headers: Record<string, string>; errorCategory?: string; retryAfterSeconds?: number; operation?: { operationId: string; method: string; pathTemplate: string; summary: string }; request: { method: string; path: string; query: Record<string, string[]> }; sent?: { bytes: number; sha256?: string; parts?: Array<{ name: string; filename?: string; bytes: number; sha256: string }> }; // echo of uploaded binary payloads for integrity checks }; lexware.request returns all HTTP responses, including non-OK, as LexwareResponse. Check response.ok/status for recovery logic, or use lexware.json(...) / lexware.paginate(...) when you want non-OK or non-JSON responses to throw. This server is read-only by default. POST, PUT, PATCH, and DELETE are blocked unless the server is started with LEXWARE_OFFICE_ALLOW_WRITES=true. Setting LEXWARE_OFFICE_READ_ONLY=true is a hard block that overrides ALLOW_WRITES. Check spec.info.writesEnabled to branch before attempting a write. Example: async () => { const response = await lexware.request({ path: '/v1/contacts', query: { page: 0, size: 5 } }); return { status: response.status, request: response.request, data: response.data }; } File upload example (bookkeeping Beleg). Never inline file bytes in code — pass the file's absolute path via contentPath and the host reads it from disk: async () => { const response = await lexware.request({ method: 'POST', path: '/v1/files', multipart: [ { name: 'file', contentType: 'application/pdf', contentPath: '/absolute/path/to/receipt.pdf' }, { name: 'type', value: 'voucher' }, ], }); return { status: response.status, id: response.data?.id, sent: response.sent }; } |
| match_bank_csv_to_vouchersA | Parses a bank statement CSV (date + EUR amount columns) and matches each transaction against Lexware vouchers by exact amount and a date-tolerance window. Fetches the voucher list itself (paginated, date-range padded by the tolerance). Narrow voucherType/voucherStatus (comma-separated, e.g. "purchaseinvoice" + "open,paid") when reconciling a specific category — the underlying /voucherlist endpoint refuses to traverse beyond 10,000 matching entries, so split by date range or narrow the filters if that happens. |
| match_receipts_to_bank_csvA | Matches receipt PDFs against a bank statement CSV (date + EUR amount) by extracting amount/date from each PDF's text and comparing with exact-amount + date-tolerance matching. Extraction is a best-effort regex heuristic — always review the 'unmatched' and 'extractionIssues' lists, don't assume completeness (no OCR: receipts that are pure scanned images without a text layer will not extract). |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 4 tools
The two match tools are distinct (bank CSV to vouchers vs. receipts to bank CSV) but share a similar 'match_*' prefix that could cause confusion. Search and execute are clearly separated for discovery vs. action, so overall boundaries are clear.
All tool names use lowercase snake_case with a clear verb prefix: match_bank_csv_to_vouchers, match_receipts_to_bank_csv, search, execute. The pattern is consistent and predictable.
With 4 tools, the server is well-scoped: two specialized matching helpers plus the essential search/execute pair for generic API access. No bloat and no obvious missing generic capability.
The generic execute tool wraps the entire Lexware API, covering any conceivable operation, while search provides full discovery. The two match tools cover specific reconciliation needs. Together they form a complete surface for the stated purpose.