Lexware Office MCP Server
Servidor MCP para la API de Lexware Office: gestiona facturas, contactos, artículos, comprobantes y más. 65 herramientas en 20 dominios de recursos.
Documentación
lexware-mcp-server
Servidor MCP para la API de Lexware Office. Gestiona facturas, contactos, artículos, comprobantes y más a través del Protocolo de Contexto de Modelo (MCP).
No oficial — proyecto comunitario. No está afiliado, respaldado ni soportado por Lexware GmbH ni Haufe Group. "Lexware" y "Lexware Office" son marcas comerciales de sus respectivos propietarios; se usan aquí solo para identificar la API a la que apunta este cliente (uso nominativo legítimo).
66 herramientas en 20 dominios de recursos, con 6 puntos de entrada para que elijas el servidor adecuado según el límite de herramientas de tu cliente MCP.
Instalación
npm install -g @lazyants/lexware-mcp-server
O ejecuta directamente:
npx @lazyants/lexware-mcp-server
Configuración
El token de API se resuelve en este orden:
- Llavero del sistema (recomendado: el token nunca se escribe en disco en texto plano)
- Variable de entorno
LEXWARE_API_TOKEN
Guarda el token en el llavero del sistema
Obtén tu token desde la configuración de la API de Lexware Office y luego guárdalo con el administrador de credenciales nativo de tu sistema operativo.
[!IMPORTANTE] Los comandos siguientes leen el token desde un prompt interactivo en lugar de recibirlo como argumento, para que nunca quede en el historial del shell ni en la lista de procesos. Evita pegar el token directamente en la línea de comandos.
macOS
Omitir el valor después de -w hace que security solicite el token (con confirmación):
security add-generic-password -s "lexware-mcp" -a "api-token" -w
Windows (PowerShell)
cmdkey solo puede recibir el token como argumento de línea de comandos, lo que lo expone en
la lista de procesos. En su lugar, léelo desde un prompt oculto y escríbelo directamente
en el Administrador de credenciales de Windows mediante CredWrite, de modo que el token nunca llegue a argv.
El nombre de destino de la credencial es <account>.<service> — api-token.lexware-mcp
para el servicio predeterminado — que es exactamente lo que el servidor lee de vuelta:
$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
}
¿Usas un
LEXWARE_KEYRING_SERVICEpersonalizado (p. ej.,acme)? EstableceTargetNameenapi-token.acmepara que coincida: el servidor busca el token bajo<account>.<service>.
Linux
secret-tool store --label="Lexware Office API" service lexware-mcp username api-token
# (prompts for the token value)
Una vez guardado, los archivos de configuración de MCP no necesitan credenciales en absoluto: el servidor lee el token del llavero al iniciar.
Usa una variable de entorno en su lugar
Si prefieres no usar el llavero, establece LEXWARE_API_TOKEN en tu shell o en la configuración del cliente MCP:
export LEXWARE_API_TOKEN=your-token-here
Variables de entorno
| Variable | Predeterminado | Descripción |
|---|---|---|
LEXWARE_API_TOKEN | — | Token de API; se usa cuando el llavero no tiene una entrada para el servicio configurado |
LEXWARE_KEYRING_SERVICE | lexware-mcp | Nombre del servicio del llavero. Sobrescríbelo al conectar con varias cuentas de Lexware simultáneamente: ejecuta una instancia del servidor por cuenta, cada una con su propio nombre de servicio |
Opcionalmente, sobrescribe la clave pública de firma de webhooks usada por lexware_verify_webhook_signature
(por defecto se obtiene de Lexware y se almacena en caché):
export LEXWARE_WEBHOOK_PUBLIC_KEY="$(cat lexware-webhook-public.pem)"
Puntos de entrada
| Comando | Dominios | Herramientas |
|---|---|---|
lexware-mcp-server | Los 20 dominios | 66 |
lexware-mcp-sales | Facturas, Notas de crédito, Presupuestos, Confirmaciones de pedido, Albaranes, Facturas de anticipo, Recordatorios, Lista de comprobantes | 32 |
lexware-mcp-contacts | Contactos, Artículos | 10 |
lexware-mcp-bookkeeping | Comprobantes, Lista de comprobantes, Pagos | 8 |
lexware-mcp-reference | Países, Condiciones de pago, Categorías de contabilización, Perfil, Diseños de impresión | 5 |
lexware-mcp-system | Suscripciones a eventos, Archivos, Plantillas recurrentes | 12 |
Usa servidores divididos para reducir el tamaño del contexto: elige solo las divisiones que necesites.
Claude Code
Agrega a ~/.claude/settings.json. Si guardaste el token en el llavero del sistema con el nombre de servicio predeterminado lexware-mcp (recomendado), no se necesita la clave env:
{
"mcpServers": {
"lexware": {
"command": "npx",
"args": ["-y", "@lazyants/lexware-mcp-server"]
}
}
}
Si prefieres el enfoque de variable de entorno:
{
"mcpServers": {
"lexware": {
"command": "npx",
"args": ["-y", "@lazyants/lexware-mcp-server"],
"env": { "LEXWARE_API_TOKEN": "your-token-here" }
}
}
}
Servidores divididos
Usa servidores divididos para reducir el tamaño del contexto: elige solo los puntos de entrada que necesites. La bandera -p @lazyants/lexware-mcp-server le indica a npx de qué paquete tomar el comando; el argumento final (p. ej., lexware-mcp-sales) es el binario específico del punto de entrada definido en ese paquete (consulta Puntos de entrada):
{
"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"]
}
}
}
Ejemplo con varias cuentas (dos empresas de Lexware, tokens guardados bajo nombres de servicio de llavero separados):
{
"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" }
}
}
}
Claude Desktop
Agrega a claude_desktop_config.json. Con el llavero del sistema (recomendado: asume que el token está guardado bajo el nombre de servicio predeterminado lexware-mcp):
{
"mcpServers": {
"lexware": {
"command": "npx",
"args": ["-y", "@lazyants/lexware-mcp-server"]
}
}
}
Con una variable de entorno en su lugar:
{
"mcpServers": {
"lexware": {
"command": "npx",
"args": ["-y", "@lazyants/lexware-mcp-server"],
"env": { "LEXWARE_API_TOKEN": "your-token-here" }
}
}
}
Herramientas
Facturas (5 herramientas) — ventas
lexware_create_invoice (admite finalize=true al crearla), lexware_get_invoice, lexware_download_invoice_file, lexware_pursue_invoice, lexware_deeplink_invoice
Notas de crédito (5 herramientas) — ventas
lexware_create_credit_note, lexware_get_credit_note, lexware_download_credit_note_file, lexware_pursue_credit_note, lexware_deeplink_credit_note
Presupuestos (4 herramientas) — ventas
lexware_create_quotation, lexware_get_quotation, lexware_download_quotation_file, lexware_deeplink_quotation
Confirmaciones de pedido (5 herramientas) — ventas
lexware_create_order_confirmation, lexware_get_order_confirmation, lexware_download_order_confirmation_file, lexware_pursue_order_confirmation, lexware_deeplink_order_confirmation
Albaranes (5 herramientas) — ventas
lexware_create_delivery_note, lexware_get_delivery_note, lexware_download_delivery_note_file, lexware_pursue_delivery_note, lexware_deeplink_delivery_note
Las herramientas independientes de creación de facturas, notas de crédito, presupuestos, confirmaciones de pedido y albaranes
aceptan finalize: true para finalizar al crearlas. Omítelo o establece false para crear un borrador; solo
un true explícito envía el parámetro de consulta ?finalize=true de la API.
Facturas de anticipo (3 herramientas) — ventas
lexware_get_down_payment_invoice, lexware_download_down_payment_invoice_file, lexware_deeplink_down_payment_invoice
Recordatorios (4 herramientas) — ventas
lexware_get_dunning, lexware_download_dunning_file, lexware_pursue_dunning, lexware_deeplink_dunning
Lista de comprobantes (1 herramienta) — ventas, contabilidad
lexware_list_voucherlist
Para sincronización incremental, updatedDateFrom y updatedDateTo filtran por fecha de última modificación
(yyyy-MM-dd, días completos en CET/CEST). sort acepta voucherDate, voucherNumber, createdDate
o updatedDate seguido de ASC o DESC, por ejemplo updatedDate,DESC. Los filtros de estado incluyen
transferred y sepadebit; el estado transitorio overdue no se puede combinar con otros estados.
Por defecto, esto es un paso directo de una sola página de la respuesta de la API. Dos adiciones son opcionales:
fetchAllPages: truesigue la paginación hasta recuperar todas las páginas, con un límite de 100 solicitudes. El resultado agregafetchedPagesytruncated; este último marca un conjunto truncado por el límite. Los campos de la API, comototalElements, se conservan.contactName(comodines de estilo SQL%/_, sin distinguir mayúsculas de minúsculas) yhasOpenAmountfiltran del lado del cliente después de la obtención, y cada uno implicafetchAllPages. Se aplican aquí en lugar de enlexware_list_vouchersporque/voucherlistes la forma de respuesta que llevacontactNameyopenAmount.
page no se puede combinar con ninguno de los tres: esos modos leen todas las páginas, por lo que un desplazamiento inicial no
tiene sentido. Usa size para controlar el tamaño del lote en su lugar. La combinación se rechaza en lugar de
ignorarse silenciosamente, para que nadie crea que se respetó un desplazamiento cuando no fue así.
Contactos (5 herramientas) — contactos
lexware_list_contacts, lexware_get_contact, lexware_create_contact, lexware_update_contact, lexware_deeplink_contact
lexware_deeplink_contact devuelve deeplink y el alias permanente url, ambos con la
misma URL de contacto. Cualquiera de las dos claves de salida es compatible.
Artículos (5 herramientas) — contactos
lexware_list_articles, lexware_get_article, lexware_create_article, lexware_update_article, lexware_delete_article
Comprobantes (6 herramientas) — contabilidad
lexware_list_vouchers, lexware_get_voucher, lexware_create_voucher, lexware_update_voucher, lexware_upload_voucher_file, lexware_deeplink_voucher
lexware_list_vouchers requiere voucherNumber. GET /vouchers es un punto final de consulta, no una
colección navegable: la API responde 400 "voucherNumber parameter is required" sin él. Para
navegar o filtrar comprobantes, usa lexware_list_voucherlist, que es el punto final de colección y también
incluye los campos de resumen (contactName, openAmount) que /vouchers no tiene.
lexware_get_voucher normaliza voucherStatus a minúsculas y reintenta un 404 tres veces
(1 s / 2 s / 4 s) para cubrir el retraso de indexación después de una carga; si el comprobante aún falta,
devuelve { voucherId, status: "processing", message }. Otros fallos se informan como errores.
Pagos (1 herramienta) — contabilidad
lexware_get_payments
Países (1 herramienta) — referencia
lexware_list_countries
Condiciones de pago (1 herramienta) — referencia
lexware_list_payment_conditions
Categorías de contabilización (1 herramienta) — referencia
lexware_list_posting_categories
Perfil (1 herramienta) — referencia
lexware_get_profile
Diseños de impresión (1 herramienta) — referencia
lexware_list_print_layouts
Suscripciones a eventos (5 herramientas) — sistema
lexware_create_event_subscription, lexware_list_event_subscriptions, lexware_get_event_subscription, lexware_delete_event_subscription, lexware_verify_webhook_signature
Tu aplicación aloja el manejador de devoluciones de llamada; este servidor MCP gestiona las suscripciones y verifica las firmas, pero no aloja un receptor. Reconoce las devoluciones de llamada con prontitud con HTTP 200 o 204 dentro del tiempo de espera de 5 segundos (5000 ms) de Lexware, pon en cola la carga útil recibida y procésala de forma asíncrona.
Usa la cola para absorber ráfagas de eventos y aplicar reintentos controlados con retroceso exponencial de forma independiente de los reintentos de entrega de Lexware. Limita las llamadas API salientes para respetar los límites de velocidad de la API. Consulta las mejores prácticas de suscripción a eventos de Lexware.
Archivos (4 herramientas) — sistema
lexware_upload_file, lexware_download_file, lexware_get_file_status, lexware_deeplink_file
lexware_download_file acepta format: "pdf" (predeterminado) o format: "xml" para recuperar un
XML original de factura electrónica disponible. El XML incrustado en un PDF no se puede recuperar por separado; Lexware
devuelve 404 cuando el XML solicitado no está disponible. Las descargas devuelven los bytes como contentBase64.
lexware_get_file_status llama a GET /files/{id}/status. El GET /files/{id} simple es la ruta de descarga
binaria: con Accept: application/json igualmente responde 200 con el cuerpo del archivo
codificado en base64, por lo que nunca puede producir metadatos de estado. La ruta de estado está restringida por alcance: las claves de API
sin el permiso necesario reciben access_denied de Lexware en lugar de un estado.
Ambas herramientas de carga (lexware_upload_file y lexware_upload_voucher_file) toman el archivo ya sea como
contentBase64 o como filePath: una ruta absoluta legible por el proceso del servidor MCP. Prefiere
filePath para cualquier cosa de tamaño considerable: base64 infla la carga útil aproximadamente un tercio y debe viajar
a través de la ventana de contexto del modelo. Con filePath, fileName usa por defecto el nombre base del archivo y
contentType se detecta automáticamente para .png, .jpg/.jpeg, .tiff/.tif y .xml, con respaldo
a application/pdf. Proporciona exactamente uno de los dos: proporcionar ambos, o ninguno, es un error
de validación.
Las cargas están limitadas a 5 MB. Para filePath, el tamaño se toma del descriptor abierto antes de
leer el archivo, por lo que un archivo demasiado grande cuesta un stat en lugar de una carga completa en memoria, y cualquier cosa
que no sea un archivo regular se rechaza de inmediato (leer /dev/zero de otro modo nunca devolvería).
El recuento de bytes decodificados se verifica nuevamente después, lo que también cubre contentBase64. Los fallos
llevan un error file_too_large con los tamaños real y máximo.
Plantillas recurrentes (3 herramientas) — sistema
lexware_list_recurring_templates, lexware_get_recurring_template, lexware_deeplink_recurring_template
Seguridad
- Usa el llavero del sistema para mantener tu token de API fuera de los archivos de configuración y del historial del shell por completo (consulta Configuración)
- Nunca confirmes tu token de API en el control de versiones
- Usa acceso de solo lectura cuando solo necesites listar/obtener recursos
- Las herramientas de crear, actualizar y eliminar modifican datos comerciales reales: facturas, contactos y registros contables en tu cuenta de Lexware
- El límite de velocidad se maneja automáticamente: las solicitudes se reintentan con retroceso exponencial en 429, incluidas las cargas de archivos: el cuerpo multipart se reconstruye desde cero en cada intento de reintento, por lo que se puede reproducir de forma segura
Publicación
Las versiones se publican mediante el evento GitHub Release. Flujo del mantenedor:
-
Incrementa la versión en
package.json,package-lock.jsonyserver.json(npm version <x.y.z> --no-git-tag-versionactualiza los dos primeros juntos).npm run check-versionsfalla de forma contundente a menos quepackage.json#/version,server.json#/packages[0].versiony ambos campos de versiónpackage-lock.json(raíz ypackages[""]) coincidan.server.json#/versionse verifica de forma más flexible: debe estar presente, pero solo se compara contrapackages[0].versioncomo comprobación de regresión — puede estar legítimamente adelantado (las republicaciones solo del registro incrementan solo ese campo), por lo que un valor dejado atrás en la versión anterior pasa con una líneaWARN:y sin fallo. Para una versión ordinaria, ambos deben moverse juntos, así que lee la salida del script en lugar de confiar en su código de salida.CHANGELOG.mdno se verifica en absoluto. -
Actualiza
CHANGELOG.md. -
Haz commit y fusiona el incremento de versión en
mainantes de crear la release. Luego crea la etiqueta tú mismo, sobre un SHA que hayas verificado, y solo entonces crea la release a partir de ella: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"El fallo que esto previene: sin una etiqueta existente,
gh release createcoloca una en la punta de la rama predeterminada, por lo que ejecutarlo mientras el incremento sigue en una rama de release etiqueta el commit de la versión anterior. El flujo de trabajo entonces publica cualquier versión que encuentre en elpackage.jsonde ese commit, y obtienes una GitHub ReleasevX.Y.Zque republica silenciosamente la versión anterior. Desde 5.2.0, el propio flujo de publicación se niega a continuar cuandoGITHUB_REF_NAMEno esv<package.json version>(#103), por lo que una release mal etiquetada ahora falla antes denpm publishen lugar de republicar silenciosamente. Sin embargo, esa protección se activa solo cuando el flujo de trabajo ya está en ejecución — la secuencia anterior es lo que evita que se etiquete el commit incorrecto en primer lugar, así que sigue usándola en lugar de confiar en que el flujo de trabajo detecte el error.Cada elemento es esencial:
gh pr view … .mergeCommit.oidnombra el commit de squash propio del PR de la release. No sustituyasgit rev-parse origin/main— eso es simplemente lo que esté enmainen el momento en que mires, por lo que una fusión no relacionada que llegue en el intervalo se etiqueta y se publica en su lugar.ghsale con 0 e imprime nada para un PR no fusionado, de ahí eltest -nexplícito.- La cadena
&&se detiene en el primer fallo en lugar de pasar al paso irreversible. Ambas llamadasgit showse asignan a una variable en lugar de canalizarse directamente, por lo que su estado de salida se verifica realmente — una canalización informa solo el estado de su último comando a menos quepipefailesté configurado, lo cual no se asume aquí. git merge-base --is-ancestordemuestra que el commit es realmente alcanzable desdemain. La mera existencia no es suficiente — un commit puede estar presente localmente porque se obtuvo otra rama, y si sus archivos de versión coinciden, de otro modo pasaría todas las verificaciones restantes.- La prueba de versión lee
package.jsondel commit objetivo, no del árbol de trabajo, que aún mostraría la versión correcta mientras$SHAapuntara a otro lugar. - El
awkextrae la sección de esa versión delCHANGELOG.mddel commit para--notes-file. Sin él, el cuerpo de la release es lo que--notes-from-tagencuentre en la anotación — para este flujo, la cadena literalvX.Y.Z, que es una nota de release pobre para cualquier versión y una activamente engañosa para una mayor con un cambio disruptivo. Se detiene en el siguiente encabezado## [o en la primera definición de referencia de enlace, porque la entrada más antigua del archivo no tiene encabezado después y de otro modo se tragaría todo el bloque de referencias de enlace.grep -qen lugar detest -sprotege el resultado: una sección que está vacía aparte de su línea en blanco aún produce un archivo de un byte, quetest -sacepta. --verify-taghace queghse interrumpa en lugar de inventar una etiqueta si el push no se realizó — la protección contra queghcaiga en el comportamiento de punta-de-rama-predeterminada descrito anteriormente.
Si
gh release createfalla después de que la etiqueta ya se haya enviado, no vuelvas a ejecutar todo el bloque — se detendrá engit tag, lo cual es correcto. Vuelve a ejecutar solo el comando final. -
El flujo de trabajo
Publish to npm + MCP Registryse ejecuta automáticamente:npm publishcon procedencia, consulta el registro hasta que el tarball esté disponible, luego envía elserver.jsoncorrespondiente al Registro MCP mediantemcp-publisher.
El flujo de trabajo omite npm publish limpiamente si la versión ya está en npm (protección de transición para releases que se publicaron parcialmente de forma manual).
Autenticación de publicación — npm Trusted Publishing (sin token)
La publicación utiliza npm Trusted Publishing mediante OIDC — no hay secreto NPM_TOKEN. El permiso id-token: write del flujo de trabajo se intercambia por un token de publicación de un solo uso y corta duración en el momento de publicar, utilizando el enlace de editor de confianza configurado para @lazyants/lexware-mcp-server en la interfaz web de npm. La única configuración requerida es ese enlace de editor de confianza en npm; no es necesario almacenar nada en los secretos del repositorio.
Aviso legal
Este es un proyecto comunitario independiente y no oficial. No está afiliado, respaldado, patrocinado ni soportado por Lexware GmbH, Haufe Group ni ninguna de sus filiales. Para soporte oficial de Lexware, contacta directamente con Lexware — los problemas con este servidor MCP deben informarse aquí, no a Lexware.
"Lexware" y "Lexware Office" son marcas comerciales de sus respectivos propietarios y se utilizan en el nombre y la documentación de este proyecto bajo uso justo nominativo, únicamente para identificar la API de terceros a la que se conecta este cliente.
Las operaciones de crear, actualizar y eliminar modifican datos comerciales reales en tu cuenta de Lexware. Los autores proporcionan este software "tal cual" y no aceptan responsabilidad por cambios no intencionados, pérdida de datos o cualquier otro daño derivado de su uso. Prueba con una cuenta de sandbox o no crítica antes de ejecutar operaciones de escritura contra datos de producción.
Licencia
FSL-1.1-MIT — consulta LICENSE para los términos completos.