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:
| Mode | Who runs it | Auth |
|---|
| Self-hosted | You, on your own machine or server | A single API key in env or appsettings.json |
| Hosted | InvoiceXML, on its own infrastructure | OAuth 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
Running locally
You need a .NET 10 SDK and an InvoiceXML API key.
# 1. Provide your API key (pick one):
# A. dotnet user-secrets (recommended โ kept outside the repo)
dotnet user-secrets --project src/InvoiceXml.Mcp.Host set "Mcp:ApiKey:Value" "your-key"
# B. environment variable
$env:INVOICEXML_API_KEY = "your-key"
# 2. Run
dotnet run --project src/InvoiceXml.Mcp.Host
GET http://localhost:5004/ shows a welcome page in a browser; the MCP endpoint is
POST http://localhost:5004/; GET /health returns { "status": "ok" }.
Configuration
{
"McpUri": "https://mcp.example.com",
"InvoiceXml": {
"BaseUrl": "https://api.invoicexml.com",
"Timeout": "00:01:40"
},
"Mcp": {
"AuthMode": "ApiKey",
"ApiKey": {
"Value": ""
},
"OAuth": {
"AuthorizationServer": "https://invoicexml.com",
"ScopesSupported": [ "api_token.read" ]
},
"FileInput": {
"MaxFileSizeBytes": 5242880,
"FetchTimeout": "00:00:30"
}
}
}
Environment variable equivalents (double underscore = nesting):
| Variable | Maps to |
|---|
INVOICEXML_API_KEY | Mcp:ApiKey:Value (friendly alias) |
Mcp__ApiKey__Value | Mcp:ApiKey:Value |
Mcp__AuthMode | Mcp:AuthMode (ApiKey or OAuth) |
Mcp__OAuth__AuthorizationServer | Mcp:OAuth:AuthorizationServer |
McpUri | McpUri (root-level) |
InvoiceXml__BaseUrl | InvoiceXml:BaseUrl |
OAuth mode
When Mcp:AuthMode=OAuth the host stops accepting a static API key and instead:
- Returns 401 with
WWW-Authenticate: Bearer resource_metadata="โฆ" for any
POST / that has no Bearer token.
- Serves
GET /.well-known/oauth-protected-resource pointing MCP clients at
invoicexml.com as the authorization server.
- 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
Deployment
The host is a standard ASP.NET Core app โ run it however you run .NET services
(systemd, a container, a PaaS, etc.; the repo doesn't prescribe one):
dotnet publish src/InvoiceXml.Mcp.Host -c Release -o ./publish
# then run ./publish/InvoiceXml.Mcp.Host on your host
Set configuration via environment variables on the host (never commit secrets):
ASPNETCORE_ENVIRONMENT=Production
Mcp__AuthMode=ApiKey (or OAuth)
Mcp__ApiKey__Value=โฆ / INVOICEXML_API_KEY=โฆ (ApiKey mode)
McpUri=https://your-public-url and Mcp__OAuth__AuthorizationServer=https://invoicexml.com (OAuth mode)
Terminate TLS at your reverse proxy / load balancer and forward to the host's HTTP port.
The server is stateless, so you can run multiple instances behind a load balancer.
License
MIT โ see LICENSE.