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

Tests

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:

  1. Llavero del sistema (recomendado: el token nunca se escribe en disco en texto plano)
  2. 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_SERVICE personalizado (p. ej., acme)? Establece TargetName en api-token.acme para 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

VariablePredeterminadoDescripción
LEXWARE_API_TOKEN—Token de API; se usa cuando el llavero no tiene una entrada para el servicio configurado
LEXWARE_KEYRING_SERVICElexware-mcpNombre 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

ComandoDominiosHerramientas
lexware-mcp-serverLos 20 dominios66
lexware-mcp-salesFacturas, Notas de crédito, Presupuestos, Confirmaciones de pedido, Albaranes, Facturas de anticipo, Recordatorios, Lista de comprobantes32
lexware-mcp-contactsContactos, Artículos10
lexware-mcp-bookkeepingComprobantes, Lista de comprobantes, Pagos8
lexware-mcp-referencePaíses, Condiciones de pago, Categorías de contabilización, Perfil, Diseños de impresión5
lexware-mcp-systemSuscripciones a eventos, Archivos, Plantillas recurrentes12

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: true sigue la paginación hasta recuperar todas las páginas, con un límite de 100 solicitudes. El resultado agrega fetchedPages y truncated; este último marca un conjunto truncado por el límite. Los campos de la API, como totalElements, se conservan.
  • contactName (comodines de estilo SQL %/_, sin distinguir mayúsculas de minúsculas) y hasOpenAmount filtran del lado del cliente después de la obtención, y cada uno implica fetchAllPages. Se aplican aquí en lugar de en lexware_list_vouchers porque /voucherlist es la forma de respuesta que lleva contactName y openAmount.

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:

  1. Incrementa la versión en package.json, package-lock.json y server.json (npm version <x.y.z> --no-git-tag-version actualiza los dos primeros juntos). npm run check-versions falla de forma contundente a menos que package.json#/version, server.json#/packages[0].version y ambos campos de versión package-lock.json (raíz y packages[""]) coincidan. server.json#/version se verifica de forma más flexible: debe estar presente, pero solo se compara contra packages[0].version como 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ínea WARN: 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.md no se verifica en absoluto.

  2. Actualiza CHANGELOG.md.

  3. Haz commit y fusiona el incremento de versión en main antes 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 create coloca 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 el package.json de ese commit, y obtienes una GitHub Release vX.Y.Z que republica silenciosamente la versión anterior. Desde 5.2.0, el propio flujo de publicación se niega a continuar cuando GITHUB_REF_NAME no es v<package.json version> (#103), por lo que una release mal etiquetada ahora falla antes de npm publish en 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.oid nombra el commit de squash propio del PR de la release. No sustituyas git rev-parse origin/main — eso es simplemente lo que esté en main en 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. gh sale con 0 e imprime nada para un PR no fusionado, de ahí el test -n explícito.
    • La cadena && se detiene en el primer fallo en lugar de pasar al paso irreversible. Ambas llamadas git show se 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 que pipefail esté configurado, lo cual no se asume aquí.
    • git merge-base --is-ancestor demuestra que el commit es realmente alcanzable desde main. 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.json del commit objetivo, no del árbol de trabajo, que aún mostraría la versión correcta mientras $SHA apuntara a otro lugar.
    • El awk extrae la sección de esa versión del CHANGELOG.md del commit para --notes-file. Sin él, el cuerpo de la release es lo que --notes-from-tag encuentre en la anotación — para este flujo, la cadena literal vX.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 -q en lugar de test -s protege el resultado: una sección que está vacía aparte de su línea en blanco aún produce un archivo de un byte, que test -s acepta.
    • --verify-tag hace que gh se interrumpa en lugar de inventar una etiqueta si el push no se realizó — la protección contra que gh caiga en el comportamiento de punta-de-rama-predeterminada descrito anteriormente.

    Si gh release create falla después de que la etiqueta ya se haya enviado, no vuelvas a ejecutar todo el bloque — se detendrá en git tag, lo cual es correcto. Vuelve a ejecutar solo el comando final.

  4. El flujo de trabajo Publish to npm + MCP Registry se ejecuta automáticamente: npm publish con procedencia, consulta el registro hasta que el tarball esté disponible, luego envía el server.json correspondiente al Registro MCP mediante mcp-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.