Labsco
InvoiceXML logo

InvoiceXML

β˜… 2

from InvoiceXML

Global e-invoicing compliance: Factur-X, Peppol UBL, CII & more

πŸ”₯πŸ”₯πŸ”₯βœ“ VerifiedPaid serviceAdvanced setup

InvoiceXML MCP Server

A Model Context Protocol server that exposes the InvoiceXML API to AI agents. Covers Factur-X, ZUGFeRD, XRechnung, UBL / CII, and Peppol BIS Billing 3.0.

The same codebase runs in two deployment shapes, selected at startup by one environment variable:

ModeWho runs itAuth
Self-hostedYou, on your own machine or serverA single API key in env or appsettings.json
HostedInvoiceXML, on its own infrastructureOAuth 2.1 + Dynamic Client Registration against invoicexml.com

Both run the same binary; only the configuration differs. The repository is platform-independent β€” it knows nothing about where or how you host it.

Architecture

+----------------------+   ProjectReference   +----------------------+
|  InvoiceXml.Mcp.Core | -------------------> |  InvoiceXml.Mcp.Host |
|  (SDK: client+tools) |                      |  (the deployable)    |
+----------------------+                      +----------------------+

InvoiceXml.Mcp.Core is a small, transport-agnostic SDK:

  • IInvoiceXmlClient β€” typed client over the public REST API
  • HttpInvoiceXmlClient β€” the only implementation; consumes an HttpClient from IHttpClientFactory
  • InvoiceXmlClientOptions β€” base URL, timeout (no auth)
  • AddInvoiceXmlMcpCore(IServiceCollection, IConfiguration) β€” DI entry point; returns the IHttpClientBuilder so the host attaches auth as a DelegatingHandler

The SDK never sees credentials. The host applies them through the HTTP pipeline. That seam is what lets one codebase serve both deployment modes.

InvoiceXml.Mcp.Host is an ASP.NET Core 10 app:

  • Reads Mcp:AuthMode (ApiKey or OAuth) at startup
  • Wires the matching DelegatingHandler onto the Core HTTP client via AddHostAuth(...)
  • Serves the MCP endpoint at POST /, a human-friendly welcome page at GET /, and /health

Adding a new auth mode = one arm in AuthExtensions.cs plus a small folder under Auth/<Mode>/. Adding a new tool = one [McpServerTool] class. Nothing else changes.

Repository layout

invoicexml-mcp/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ InvoiceXml.Mcp.Core/              # the SDK: client, models, tools
β”‚   β”‚   β”œβ”€β”€ Enums/  Interfaces/  Models/  Options/  Services/  Tools/  Extensions/
β”‚   └── InvoiceXml.Mcp.Host/              # the deployable host
β”‚       β”œβ”€β”€ Auth/{ApiKey,OAuth}/          # the two auth modes
β”‚       β”œβ”€β”€ Configuration/
β”‚       β”œβ”€β”€ Program.cs
β”‚       └── appsettings.json              # safe defaults, no secrets
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ InvoiceXml.Mcp.Core.Tests/
β”‚   └── InvoiceXml.Mcp.Host.Tests/
β”œβ”€β”€ Directory.Build.props                 # repo-wide MSBuild defaults
β”œβ”€β”€ Directory.Packages.props              # Central Package Management
β”œβ”€β”€ global.json                           # pins the .NET SDK
└── InvoiceXml.Mcp.slnx

OAuth mode

When Mcp:AuthMode=OAuth the host stops accepting a static API key and instead:

  1. Returns 401 with WWW-Authenticate: Bearer resource_metadata="…" for any POST / that has no Bearer token.
  2. Serves GET /.well-known/oauth-protected-resource pointing MCP clients at invoicexml.com as the authorization server.
  3. Forwards the inbound Bearer token verbatim on every outbound call to the InvoiceXML API (the API is the source of truth for token validity; the MCP server does not validate tokens locally).

The dance an MCP client performs:

client β†’ MCP  POST /                           β†’ 401 + resource_metadata
client β†’ /.well-known/oauth-protected-resource β†’ { authorization_servers: [invoicexml.com] }
client β†’ invoicexml.com/.well-known/oauth-authorization-server
                                               β†’ { authorize, token, register endpoints }
client β†’ invoicexml.com/oauth/register         β†’ client_id + client_secret  (DCR)
client β†’ invoicexml.com/oauth/authorize        β†’ user consents, gets code
client β†’ invoicexml.com/oauth/token            β†’ access_token  (= user's API key)
client β†’ MCP  POST /  + Authorization: Bearer  β†’ 200, tool call flows through