rechnungsapi-mcp 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/dist/chunk-MIISOIDM.js +152 -0
- package/dist/chunk-MIISOIDM.js.map +1 -0
- package/dist/http.js +71 -0
- package/dist/http.js.map +1 -0
- package/dist/index.js +5 -148
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -34,6 +34,56 @@ Get your API token from the [RechnungsAPI dashboard](https://rechnungsapi.de), t
|
|
|
34
34
|
| `RECHNUNGSAPI_BASE_URL` | No | Override the gateway base URL (e.g. for a sandbox environment) |
|
|
35
35
|
| `RECHNUNGSAPI_V2_BASE_URL` | No | Override the v2 analyzer host |
|
|
36
36
|
|
|
37
|
+
## Self-hosting (Streamable HTTP)
|
|
38
|
+
|
|
39
|
+
By default this runs as a local `stdio` process, spawned per-user by their own AI client — that's what the setup above does, and it's the standard way MCP servers work. If instead you want to run **one shared, always-on server** that many users connect to remotely (no local install on their end at all — just a URL, like Apollo.io's hosted MCP server), use the HTTP mode instead.
|
|
40
|
+
|
|
41
|
+
The key architectural difference: `stdio` mode reads one fixed `RECHNUNGSAPI_TOKEN` from the environment at startup and reuses it for the whole process's life. HTTP mode instead reads **each request's own token** from its `Authorization: Bearer <token>` header, and builds a fresh, isolated client per request — so many different customers can safely share the same running server, each authenticated as themselves, never seeing each other's data.
|
|
42
|
+
|
|
43
|
+
### Run it
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
yarn build
|
|
47
|
+
yarn start:http # listens on $PORT, default 3939
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or via Docker:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
docker build -t rechnungsapi-mcp .
|
|
54
|
+
docker run -p 3939:3939 rechnungsapi-mcp
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or as a Portainer stack: paste [`docker-compose.yml`](docker-compose.yml) into Portainer's Stacks → Add stack → Web editor, then deploy.
|
|
58
|
+
|
|
59
|
+
No `RECHNUNGSAPI_TOKEN` is configured on the server itself in this mode — each caller supplies their own.
|
|
60
|
+
|
|
61
|
+
### Endpoints
|
|
62
|
+
|
|
63
|
+
| Endpoint | Description |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `POST /mcp` | The MCP endpoint. Requires `Authorization: Bearer <caller's own RechnungsAPI token>`. |
|
|
66
|
+
| `GET /health` | Health check (used by Docker's `HEALTHCHECK` / Portainer / load balancers). No auth required. |
|
|
67
|
+
|
|
68
|
+
### Putting it behind a domain
|
|
69
|
+
|
|
70
|
+
See [`deploy/nginx-mcp.conf`](deploy/nginx-mcp.conf) for a ready-to-use reverse proxy config (e.g. for `mcp.rechnungsapi.de`), including the settings needed so nginx doesn't buffer/break the SSE streaming responses. Pair it with `certbot --nginx -d mcp.rechnungsapi.de` for HTTPS — required in practice, since real API tokens travel in every request.
|
|
71
|
+
|
|
72
|
+
### What users configure, once it's hosted
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"mcpServers": {
|
|
77
|
+
"rechnungsapi": {
|
|
78
|
+
"url": "https://mcp.rechnungsapi.de/mcp",
|
|
79
|
+
"headers": { "Authorization": "Bearer <their-own-rechnungsapi-token>" }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
No install, no `npx`, nothing running on their machine.
|
|
86
|
+
|
|
37
87
|
## Tools
|
|
38
88
|
|
|
39
89
|
| Tool | Description |
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/tools.ts
|
|
4
|
+
import { RechnungsApiError } from "rechnungsapi-sdk";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
var invoiceJsonSchema = z.any().describe("Structured invoice data (line items, parties, totals, etc.) as defined by the RechnungsAPI invoice schema");
|
|
7
|
+
var transportSchema = z.any().optional().describe("Optional delivery/transport metadata (e.g. email delivery options)");
|
|
8
|
+
async function asToolResult(fn) {
|
|
9
|
+
try {
|
|
10
|
+
const result = await fn();
|
|
11
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (error instanceof RechnungsApiError) {
|
|
14
|
+
return {
|
|
15
|
+
isError: true,
|
|
16
|
+
content: [
|
|
17
|
+
{
|
|
18
|
+
type: "text",
|
|
19
|
+
text: JSON.stringify({ status: error.status, message: error.message, body: error.body }, null, 2)
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
25
|
+
return { isError: true, content: [{ type: "text", text: message }] };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function registerTools(server, client) {
|
|
29
|
+
server.registerTool(
|
|
30
|
+
"create_zugferd_invoice",
|
|
31
|
+
{
|
|
32
|
+
title: "Create ZUGFeRD invoice",
|
|
33
|
+
description: "Create a ZUGFeRD PDF/A-3 e-invoice (visual PDF with embedded XRechnung XML) from structured invoice JSON plus a visual PDF template.",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
invoice: invoiceJsonSchema,
|
|
36
|
+
invoicePdf64: z.string().describe("Base64-encoded visual PDF the XML will be embedded into"),
|
|
37
|
+
transport: transportSchema
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
async ({ invoice, invoicePdf64, transport }) => asToolResult(() => client.createZugferdFromJson(invoice, invoicePdf64, { transport }))
|
|
41
|
+
);
|
|
42
|
+
server.registerTool(
|
|
43
|
+
"create_xinvoice",
|
|
44
|
+
{
|
|
45
|
+
title: "Create X-Invoice (XRechnung/UBL)",
|
|
46
|
+
description: "Create an X-Invoice (XRechnung/UBL) XML document from structured invoice JSON.",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
invoice: invoiceJsonSchema,
|
|
49
|
+
transport: transportSchema
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
async ({ invoice, transport }) => asToolResult(() => client.createXInvoiceFromJson(invoice, { transport }))
|
|
53
|
+
);
|
|
54
|
+
server.registerTool(
|
|
55
|
+
"create_zugferd_pdf",
|
|
56
|
+
{
|
|
57
|
+
title: "Embed X-Invoice XML into a PDF",
|
|
58
|
+
description: "Embed an existing X-Invoice XML document into a visual PDF to produce a ZUGFeRD PDF.",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
invoicePdf64: z.string().describe("Base64-encoded visual PDF"),
|
|
61
|
+
xInvoiceXml: z.string().describe("X-Invoice XML to embed")
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
async ({ invoicePdf64, xInvoiceXml }) => asToolResult(() => client.createZugferdPdf(invoicePdf64, xInvoiceXml))
|
|
65
|
+
);
|
|
66
|
+
server.registerTool(
|
|
67
|
+
"extract_xinvoice_from_zugferd",
|
|
68
|
+
{
|
|
69
|
+
title: "Extract X-Invoice from ZUGFeRD PDF",
|
|
70
|
+
description: "Extract the embedded XRechnung XML from a ZUGFeRD PDF and return it as structured JSON.",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
zugferd64: z.string().describe("Base64-encoded ZUGFeRD PDF")
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
async ({ zugferd64 }) => asToolResult(() => client.extractXInvoiceFromZugferd(zugferd64))
|
|
76
|
+
);
|
|
77
|
+
server.registerTool(
|
|
78
|
+
"validate_xinvoice_xml",
|
|
79
|
+
{
|
|
80
|
+
title: "Validate X-Invoice XML",
|
|
81
|
+
description: "Validate an X-Invoice (XRechnung/UBL) XML document against schema and business rules.",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
xml: z.string().describe("The X-Invoice XML content to validate")
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
async ({ xml }) => asToolResult(() => client.validateXInvoiceXml(xml))
|
|
87
|
+
);
|
|
88
|
+
server.registerTool(
|
|
89
|
+
"validate_zugferd_pdf",
|
|
90
|
+
{
|
|
91
|
+
title: "Validate ZUGFeRD PDF",
|
|
92
|
+
description: "Validate a ZUGFeRD PDF's embedded XML content.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
zugferdFile64: z.string().describe("Base64-encoded ZUGFeRD PDF"),
|
|
95
|
+
comparePDF2XML: z.boolean().optional().describe("Also cross-check the embedded XML against the visual PDF content")
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
async ({ zugferdFile64, comparePDF2XML }) => asToolResult(() => client.validateZugferdPdf(zugferdFile64, { comparePDF2XML }))
|
|
99
|
+
);
|
|
100
|
+
server.registerTool(
|
|
101
|
+
"analyze_pdf_invoice",
|
|
102
|
+
{
|
|
103
|
+
title: "Analyze a PDF/scanned invoice",
|
|
104
|
+
description: "Extract structured invoice JSON from a scanned or digital PDF/PNG/JPEG/TIFF invoice using the high-accuracy analyzer.",
|
|
105
|
+
inputSchema: {
|
|
106
|
+
pdfBase64: z.string().describe("Base64-encoded PDF, PNG, JPEG, or TIFF invoice"),
|
|
107
|
+
withLineItems: z.boolean().optional().describe("Also extract individual line items")
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
async ({ pdfBase64, withLineItems }) => asToolResult(() => client.analyzePdfInvoiceV2(pdfBase64, withLineItems ?? false))
|
|
111
|
+
);
|
|
112
|
+
server.registerTool(
|
|
113
|
+
"analyze_pdf_invoice_async_submit",
|
|
114
|
+
{
|
|
115
|
+
title: "Submit a large PDF invoice for async analysis",
|
|
116
|
+
description: "Submit a large scanned/PDF invoice for asynchronous analysis (for files too large for the synchronous analyzer). Returns a job_id \u2014 poll it with analyze_pdf_invoice_async_status.",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
pdfBase64: z.string().describe("Base64-encoded PDF, PNG, JPEG, or TIFF invoice"),
|
|
119
|
+
withLineItems: z.boolean().optional().describe("Also extract individual line items")
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
async ({ pdfBase64, withLineItems }) => asToolResult(() => client.analyzePdfInvoiceAsync(pdfBase64, withLineItems))
|
|
123
|
+
);
|
|
124
|
+
server.registerTool(
|
|
125
|
+
"analyze_pdf_invoice_async_status",
|
|
126
|
+
{
|
|
127
|
+
title: "Check async PDF analysis status",
|
|
128
|
+
description: "Poll the status of an asynchronous PDF invoice analysis job submitted via analyze_pdf_invoice_async_submit.",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
jobId: z.string().describe("The job_id returned by analyze_pdf_invoice_async_submit")
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
async ({ jobId }) => asToolResult(() => client.getAnalysisStatus(jobId))
|
|
134
|
+
);
|
|
135
|
+
server.registerTool(
|
|
136
|
+
"create_zugferd_from_pdf",
|
|
137
|
+
{
|
|
138
|
+
title: "Convert a PDF/scan directly into a ZUGFeRD PDF",
|
|
139
|
+
description: "Convert a PDF or scanned image invoice directly into a validated ZUGFeRD PDF/A-3 in one call (analyzes, validates, and embeds in a single step).",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
pdfBase64: z.string().describe("Base64-encoded PDF, PNG, JPEG, or TIFF invoice"),
|
|
142
|
+
fileName: z.string().optional().describe("Original filename, used to name the resulting PDF")
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
async ({ pdfBase64, fileName }) => asToolResult(() => client.createZugferdFromPdf(pdfBase64, fileName))
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
registerTools
|
|
151
|
+
};
|
|
152
|
+
//# sourceMappingURL=chunk-MIISOIDM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools.ts"],"sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { RechnungsApiClient, RechnungsApiError } from \"rechnungsapi-sdk\"\nimport { z } from \"zod\"\n\nconst invoiceJsonSchema = z\n .any()\n .describe(\"Structured invoice data (line items, parties, totals, etc.) as defined by the RechnungsAPI invoice schema\")\n\nconst transportSchema = z\n .any()\n .optional()\n .describe(\"Optional delivery/transport metadata (e.g. email delivery options)\")\n\n/** Wraps a client call so thrown RechnungsApiError/generic errors become MCP tool errors instead of crashing the server. */\nasync function asToolResult(fn: () => Promise<unknown>) {\n try {\n const result = await fn()\n return { content: [{ type: \"text\" as const, text: JSON.stringify(result, null, 2) }] }\n } catch (error) {\n if (error instanceof RechnungsApiError) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ status: error.status, message: error.message, body: error.body }, null, 2),\n },\n ],\n }\n }\n const message = error instanceof Error ? error.message : String(error)\n return { isError: true, content: [{ type: \"text\" as const, text: message }] }\n }\n}\n\nexport function registerTools(server: McpServer, client: RechnungsApiClient): void {\n server.registerTool(\n \"create_zugferd_invoice\",\n {\n title: \"Create ZUGFeRD invoice\",\n description:\n \"Create a ZUGFeRD PDF/A-3 e-invoice (visual PDF with embedded XRechnung XML) from structured invoice JSON plus a visual PDF template.\",\n inputSchema: {\n invoice: invoiceJsonSchema,\n invoicePdf64: z.string().describe(\"Base64-encoded visual PDF the XML will be embedded into\"),\n transport: transportSchema,\n },\n },\n async ({ invoice, invoicePdf64, transport }) =>\n asToolResult(() => client.createZugferdFromJson(invoice, invoicePdf64, { transport })),\n )\n\n server.registerTool(\n \"create_xinvoice\",\n {\n title: \"Create X-Invoice (XRechnung/UBL)\",\n description: \"Create an X-Invoice (XRechnung/UBL) XML document from structured invoice JSON.\",\n inputSchema: {\n invoice: invoiceJsonSchema,\n transport: transportSchema,\n },\n },\n async ({ invoice, transport }) => asToolResult(() => client.createXInvoiceFromJson(invoice, { transport })),\n )\n\n server.registerTool(\n \"create_zugferd_pdf\",\n {\n title: \"Embed X-Invoice XML into a PDF\",\n description: \"Embed an existing X-Invoice XML document into a visual PDF to produce a ZUGFeRD PDF.\",\n inputSchema: {\n invoicePdf64: z.string().describe(\"Base64-encoded visual PDF\"),\n xInvoiceXml: z.string().describe(\"X-Invoice XML to embed\"),\n },\n },\n async ({ invoicePdf64, xInvoiceXml }) => asToolResult(() => client.createZugferdPdf(invoicePdf64, xInvoiceXml)),\n )\n\n server.registerTool(\n \"extract_xinvoice_from_zugferd\",\n {\n title: \"Extract X-Invoice from ZUGFeRD PDF\",\n description: \"Extract the embedded XRechnung XML from a ZUGFeRD PDF and return it as structured JSON.\",\n inputSchema: {\n zugferd64: z.string().describe(\"Base64-encoded ZUGFeRD PDF\"),\n },\n },\n async ({ zugferd64 }) => asToolResult(() => client.extractXInvoiceFromZugferd(zugferd64)),\n )\n\n server.registerTool(\n \"validate_xinvoice_xml\",\n {\n title: \"Validate X-Invoice XML\",\n description: \"Validate an X-Invoice (XRechnung/UBL) XML document against schema and business rules.\",\n inputSchema: {\n xml: z.string().describe(\"The X-Invoice XML content to validate\"),\n },\n },\n async ({ xml }) => asToolResult(() => client.validateXInvoiceXml(xml)),\n )\n\n server.registerTool(\n \"validate_zugferd_pdf\",\n {\n title: \"Validate ZUGFeRD PDF\",\n description: \"Validate a ZUGFeRD PDF's embedded XML content.\",\n inputSchema: {\n zugferdFile64: z.string().describe(\"Base64-encoded ZUGFeRD PDF\"),\n comparePDF2XML: z.boolean().optional().describe(\"Also cross-check the embedded XML against the visual PDF content\"),\n },\n },\n async ({ zugferdFile64, comparePDF2XML }) =>\n asToolResult(() => client.validateZugferdPdf(zugferdFile64, { comparePDF2XML })),\n )\n\n server.registerTool(\n \"analyze_pdf_invoice\",\n {\n title: \"Analyze a PDF/scanned invoice\",\n description:\n \"Extract structured invoice JSON from a scanned or digital PDF/PNG/JPEG/TIFF invoice using the high-accuracy analyzer.\",\n inputSchema: {\n pdfBase64: z.string().describe(\"Base64-encoded PDF, PNG, JPEG, or TIFF invoice\"),\n withLineItems: z.boolean().optional().describe(\"Also extract individual line items\"),\n },\n },\n async ({ pdfBase64, withLineItems }) =>\n asToolResult(() => client.analyzePdfInvoiceV2(pdfBase64, withLineItems ?? false)),\n )\n\n server.registerTool(\n \"analyze_pdf_invoice_async_submit\",\n {\n title: \"Submit a large PDF invoice for async analysis\",\n description:\n \"Submit a large scanned/PDF invoice for asynchronous analysis (for files too large for the synchronous analyzer). \" +\n \"Returns a job_id — poll it with analyze_pdf_invoice_async_status.\",\n inputSchema: {\n pdfBase64: z.string().describe(\"Base64-encoded PDF, PNG, JPEG, or TIFF invoice\"),\n withLineItems: z.boolean().optional().describe(\"Also extract individual line items\"),\n },\n },\n async ({ pdfBase64, withLineItems }) =>\n asToolResult(() => client.analyzePdfInvoiceAsync(pdfBase64, withLineItems)),\n )\n\n server.registerTool(\n \"analyze_pdf_invoice_async_status\",\n {\n title: \"Check async PDF analysis status\",\n description: \"Poll the status of an asynchronous PDF invoice analysis job submitted via analyze_pdf_invoice_async_submit.\",\n inputSchema: {\n jobId: z.string().describe(\"The job_id returned by analyze_pdf_invoice_async_submit\"),\n },\n },\n async ({ jobId }) => asToolResult(() => client.getAnalysisStatus(jobId)),\n )\n\n server.registerTool(\n \"create_zugferd_from_pdf\",\n {\n title: \"Convert a PDF/scan directly into a ZUGFeRD PDF\",\n description:\n \"Convert a PDF or scanned image invoice directly into a validated ZUGFeRD PDF/A-3 in one call \" +\n \"(analyzes, validates, and embeds in a single step).\",\n inputSchema: {\n pdfBase64: z.string().describe(\"Base64-encoded PDF, PNG, JPEG, or TIFF invoice\"),\n fileName: z.string().optional().describe(\"Original filename, used to name the resulting PDF\"),\n },\n },\n async ({ pdfBase64, fileName }) => asToolResult(() => client.createZugferdFromPdf(pdfBase64, fileName)),\n )\n}\n"],"mappings":"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","names":[]}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
registerTools
|
|
4
|
+
} from "./chunk-MIISOIDM.js";
|
|
5
|
+
|
|
6
|
+
// src/http.ts
|
|
7
|
+
import http from "http";
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
10
|
+
import { RechnungsApiClient } from "rechnungsapi-sdk";
|
|
11
|
+
var PORT = Number(process.env.PORT ?? 3939);
|
|
12
|
+
function extractBearerToken(req) {
|
|
13
|
+
const header = req.headers["authorization"];
|
|
14
|
+
if (!header || Array.isArray(header)) return null;
|
|
15
|
+
const match = /^Bearer\s+(.+)$/i.exec(header);
|
|
16
|
+
return match ? match[1] : null;
|
|
17
|
+
}
|
|
18
|
+
async function readJsonBody(req) {
|
|
19
|
+
const chunks = [];
|
|
20
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
21
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
22
|
+
return raw ? JSON.parse(raw) : void 0;
|
|
23
|
+
}
|
|
24
|
+
function sendJson(res, status, body) {
|
|
25
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
26
|
+
res.end(JSON.stringify(body));
|
|
27
|
+
}
|
|
28
|
+
async function handleMcpRequest(req, res) {
|
|
29
|
+
const token = extractBearerToken(req);
|
|
30
|
+
if (!token) {
|
|
31
|
+
sendJson(res, 401, { error: "Missing Authorization: Bearer <your-rechnungsapi-token> header" });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const client = new RechnungsApiClient({
|
|
35
|
+
apiToken: token,
|
|
36
|
+
baseUrl: process.env.RECHNUNGSAPI_BASE_URL,
|
|
37
|
+
v2BaseUrl: process.env.RECHNUNGSAPI_V2_BASE_URL
|
|
38
|
+
});
|
|
39
|
+
const mcpServer = new McpServer({ name: "rechnungsapi-mcp", version: "0.1.0" });
|
|
40
|
+
registerTools(mcpServer, client);
|
|
41
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
42
|
+
res.on("close", () => {
|
|
43
|
+
transport.close();
|
|
44
|
+
mcpServer.close();
|
|
45
|
+
});
|
|
46
|
+
await mcpServer.connect(transport);
|
|
47
|
+
const body = req.method === "POST" ? await readJsonBody(req) : void 0;
|
|
48
|
+
await transport.handleRequest(req, res, body);
|
|
49
|
+
}
|
|
50
|
+
var server = http.createServer(async (req, res) => {
|
|
51
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
52
|
+
sendJson(res, 200, { ok: true });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (req.url !== "/mcp") {
|
|
56
|
+
sendJson(res, 404, { error: "Not found. POST to /mcp." });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
await handleMcpRequest(req, res);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("Request failed:", error);
|
|
63
|
+
if (!res.headersSent) {
|
|
64
|
+
sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
server.listen(PORT, () => {
|
|
69
|
+
console.log(`rechnungsapi-mcp HTTP server listening on port ${PORT}`);
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/http.ts"],"sourcesContent":["import http from \"node:http\"\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\"\nimport { RechnungsApiClient } from \"rechnungsapi-sdk\"\nimport { registerTools } from \"./tools.js\"\n\nconst PORT = Number(process.env.PORT ?? 3939)\n\nfunction extractBearerToken(req: http.IncomingMessage): string | null {\n const header = req.headers[\"authorization\"]\n if (!header || Array.isArray(header)) return null\n const match = /^Bearer\\s+(.+)$/i.exec(header)\n return match ? match[1] : null\n}\n\nasync function readJsonBody(req: http.IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = []\n for await (const chunk of req) chunks.push(chunk as Buffer)\n const raw = Buffer.concat(chunks).toString(\"utf8\")\n return raw ? JSON.parse(raw) : undefined\n}\n\nfunction sendJson(res: http.ServerResponse, status: number, body: unknown) {\n res.writeHead(status, { \"content-type\": \"application/json\" })\n res.end(JSON.stringify(body))\n}\n\n/**\n * One request = one throwaway MCP server + SDK client, built from that\n * request's own Bearer token. Stateless mode (sessionIdGenerator: undefined)\n * is what makes this safe for many different customers hitting the same\n * running process concurrently — nothing is shared or cached between them.\n */\nasync function handleMcpRequest(req: http.IncomingMessage, res: http.ServerResponse) {\n const token = extractBearerToken(req)\n if (!token) {\n sendJson(res, 401, { error: \"Missing Authorization: Bearer <your-rechnungsapi-token> header\" })\n return\n }\n\n const client = new RechnungsApiClient({\n apiToken: token,\n baseUrl: process.env.RECHNUNGSAPI_BASE_URL,\n v2BaseUrl: process.env.RECHNUNGSAPI_V2_BASE_URL,\n })\n\n const mcpServer = new McpServer({ name: \"rechnungsapi-mcp\", version: \"0.1.0\" })\n registerTools(mcpServer, client)\n\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined })\n res.on(\"close\", () => {\n transport.close()\n mcpServer.close()\n })\n\n await mcpServer.connect(transport)\n const body = req.method === \"POST\" ? await readJsonBody(req) : undefined\n await transport.handleRequest(req, res, body)\n}\n\nconst server = http.createServer(async (req, res) => {\n if (req.method === \"GET\" && req.url === \"/health\") {\n sendJson(res, 200, { ok: true })\n return\n }\n\n if (req.url !== \"/mcp\") {\n sendJson(res, 404, { error: \"Not found. POST to /mcp.\" })\n return\n }\n\n try {\n await handleMcpRequest(req, res)\n } catch (error) {\n console.error(\"Request failed:\", error)\n if (!res.headersSent) {\n sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) })\n }\n }\n})\n\nserver.listen(PORT, () => {\n console.log(`rechnungsapi-mcp HTTP server listening on port ${PORT}`)\n})\n"],"mappings":";;;;;;AAAA,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAC1B,SAAS,qCAAqC;AAC9C,SAAS,0BAA0B;AAGnC,IAAM,OAAO,OAAO,QAAQ,IAAI,QAAQ,IAAI;AAE5C,SAAS,mBAAmB,KAA0C;AACpE,QAAM,SAAS,IAAI,QAAQ,eAAe;AAC1C,MAAI,CAAC,UAAU,MAAM,QAAQ,MAAM,EAAG,QAAO;AAC7C,QAAM,QAAQ,mBAAmB,KAAK,MAAM;AAC5C,SAAO,QAAQ,MAAM,CAAC,IAAI;AAC5B;AAEA,eAAe,aAAa,KAA6C;AACvE,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,IAAK,QAAO,KAAK,KAAe;AAC1D,QAAM,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AACjD,SAAO,MAAM,KAAK,MAAM,GAAG,IAAI;AACjC;AAEA,SAAS,SAAS,KAA0B,QAAgB,MAAe;AACzE,MAAI,UAAU,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AAC5D,MAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAC9B;AAQA,eAAe,iBAAiB,KAA2B,KAA0B;AACnF,QAAM,QAAQ,mBAAmB,GAAG;AACpC,MAAI,CAAC,OAAO;AACV,aAAS,KAAK,KAAK,EAAE,OAAO,iEAAiE,CAAC;AAC9F;AAAA,EACF;AAEA,QAAM,SAAS,IAAI,mBAAmB;AAAA,IACpC,UAAU;AAAA,IACV,SAAS,QAAQ,IAAI;AAAA,IACrB,WAAW,QAAQ,IAAI;AAAA,EACzB,CAAC;AAED,QAAM,YAAY,IAAI,UAAU,EAAE,MAAM,oBAAoB,SAAS,QAAQ,CAAC;AAC9E,gBAAc,WAAW,MAAM;AAE/B,QAAM,YAAY,IAAI,8BAA8B,EAAE,oBAAoB,OAAU,CAAC;AACrF,MAAI,GAAG,SAAS,MAAM;AACpB,cAAU,MAAM;AAChB,cAAU,MAAM;AAAA,EAClB,CAAC;AAED,QAAM,UAAU,QAAQ,SAAS;AACjC,QAAM,OAAO,IAAI,WAAW,SAAS,MAAM,aAAa,GAAG,IAAI;AAC/D,QAAM,UAAU,cAAc,KAAK,KAAK,IAAI;AAC9C;AAEA,IAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;AACnD,MAAI,IAAI,WAAW,SAAS,IAAI,QAAQ,WAAW;AACjD,aAAS,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC;AAC/B;AAAA,EACF;AAEA,MAAI,IAAI,QAAQ,QAAQ;AACtB,aAAS,KAAK,KAAK,EAAE,OAAO,2BAA2B,CAAC;AACxD;AAAA,EACF;AAEA,MAAI;AACF,UAAM,iBAAiB,KAAK,GAAG;AAAA,EACjC,SAAS,OAAO;AACd,YAAQ,MAAM,mBAAmB,KAAK;AACtC,QAAI,CAAC,IAAI,aAAa;AACpB,eAAS,KAAK,KAAK,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;AAAA,IACtF;AAAA,EACF;AACF,CAAC;AAED,OAAO,OAAO,MAAM,MAAM;AACxB,UAAQ,IAAI,kDAAkD,IAAI,EAAE;AACtE,CAAC;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
registerTools
|
|
4
|
+
} from "./chunk-MIISOIDM.js";
|
|
2
5
|
|
|
3
6
|
// src/index.ts
|
|
4
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
-
import { RechnungsApiClient
|
|
9
|
+
import { RechnungsApiClient } from "rechnungsapi-sdk";
|
|
7
10
|
|
|
8
11
|
// src/config.ts
|
|
9
12
|
function loadConfigFromEnv() {
|
|
@@ -20,156 +23,10 @@ function loadConfigFromEnv() {
|
|
|
20
23
|
};
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
// src/tools.ts
|
|
24
|
-
import { RechnungsApiError } from "rechnungsapi-sdk";
|
|
25
|
-
import { z } from "zod";
|
|
26
|
-
var invoiceJsonSchema = z.any().describe("Structured invoice data (line items, parties, totals, etc.) as defined by the RechnungsAPI invoice schema");
|
|
27
|
-
var transportSchema = z.any().optional().describe("Optional delivery/transport metadata (e.g. email delivery options)");
|
|
28
|
-
async function asToolResult(fn) {
|
|
29
|
-
try {
|
|
30
|
-
const result = await fn();
|
|
31
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
32
|
-
} catch (error) {
|
|
33
|
-
if (error instanceof RechnungsApiError) {
|
|
34
|
-
return {
|
|
35
|
-
isError: true,
|
|
36
|
-
content: [
|
|
37
|
-
{
|
|
38
|
-
type: "text",
|
|
39
|
-
text: JSON.stringify({ status: error.status, message: error.message, body: error.body }, null, 2)
|
|
40
|
-
}
|
|
41
|
-
]
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
-
return { isError: true, content: [{ type: "text", text: message }] };
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function registerTools(server, client) {
|
|
49
|
-
server.registerTool(
|
|
50
|
-
"create_zugferd_invoice",
|
|
51
|
-
{
|
|
52
|
-
title: "Create ZUGFeRD invoice",
|
|
53
|
-
description: "Create a ZUGFeRD PDF/A-3 e-invoice (visual PDF with embedded XRechnung XML) from structured invoice JSON plus a visual PDF template.",
|
|
54
|
-
inputSchema: {
|
|
55
|
-
invoice: invoiceJsonSchema,
|
|
56
|
-
invoicePdf64: z.string().describe("Base64-encoded visual PDF the XML will be embedded into"),
|
|
57
|
-
transport: transportSchema
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
async ({ invoice, invoicePdf64, transport }) => asToolResult(() => client.createZugferdFromJson(invoice, invoicePdf64, { transport }))
|
|
61
|
-
);
|
|
62
|
-
server.registerTool(
|
|
63
|
-
"create_xinvoice",
|
|
64
|
-
{
|
|
65
|
-
title: "Create X-Invoice (XRechnung/UBL)",
|
|
66
|
-
description: "Create an X-Invoice (XRechnung/UBL) XML document from structured invoice JSON.",
|
|
67
|
-
inputSchema: {
|
|
68
|
-
invoice: invoiceJsonSchema,
|
|
69
|
-
transport: transportSchema
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
async ({ invoice, transport }) => asToolResult(() => client.createXInvoiceFromJson(invoice, { transport }))
|
|
73
|
-
);
|
|
74
|
-
server.registerTool(
|
|
75
|
-
"create_zugferd_pdf",
|
|
76
|
-
{
|
|
77
|
-
title: "Embed X-Invoice XML into a PDF",
|
|
78
|
-
description: "Embed an existing X-Invoice XML document into a visual PDF to produce a ZUGFeRD PDF.",
|
|
79
|
-
inputSchema: {
|
|
80
|
-
invoicePdf64: z.string().describe("Base64-encoded visual PDF"),
|
|
81
|
-
xInvoiceXml: z.string().describe("X-Invoice XML to embed")
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
async ({ invoicePdf64, xInvoiceXml }) => asToolResult(() => client.createZugferdPdf(invoicePdf64, xInvoiceXml))
|
|
85
|
-
);
|
|
86
|
-
server.registerTool(
|
|
87
|
-
"extract_xinvoice_from_zugferd",
|
|
88
|
-
{
|
|
89
|
-
title: "Extract X-Invoice from ZUGFeRD PDF",
|
|
90
|
-
description: "Extract the embedded XRechnung XML from a ZUGFeRD PDF and return it as structured JSON.",
|
|
91
|
-
inputSchema: {
|
|
92
|
-
zugferd64: z.string().describe("Base64-encoded ZUGFeRD PDF")
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
async ({ zugferd64 }) => asToolResult(() => client.extractXInvoiceFromZugferd(zugferd64))
|
|
96
|
-
);
|
|
97
|
-
server.registerTool(
|
|
98
|
-
"validate_xinvoice_xml",
|
|
99
|
-
{
|
|
100
|
-
title: "Validate X-Invoice XML",
|
|
101
|
-
description: "Validate an X-Invoice (XRechnung/UBL) XML document against schema and business rules.",
|
|
102
|
-
inputSchema: {
|
|
103
|
-
xml: z.string().describe("The X-Invoice XML content to validate")
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
async ({ xml }) => asToolResult(() => client.validateXInvoiceXml(xml))
|
|
107
|
-
);
|
|
108
|
-
server.registerTool(
|
|
109
|
-
"validate_zugferd_pdf",
|
|
110
|
-
{
|
|
111
|
-
title: "Validate ZUGFeRD PDF",
|
|
112
|
-
description: "Validate a ZUGFeRD PDF's embedded XML content.",
|
|
113
|
-
inputSchema: {
|
|
114
|
-
zugferdFile64: z.string().describe("Base64-encoded ZUGFeRD PDF"),
|
|
115
|
-
comparePDF2XML: z.boolean().optional().describe("Also cross-check the embedded XML against the visual PDF content")
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
async ({ zugferdFile64, comparePDF2XML }) => asToolResult(() => client.validateZugferdPdf(zugferdFile64, { comparePDF2XML }))
|
|
119
|
-
);
|
|
120
|
-
server.registerTool(
|
|
121
|
-
"analyze_pdf_invoice",
|
|
122
|
-
{
|
|
123
|
-
title: "Analyze a PDF/scanned invoice",
|
|
124
|
-
description: "Extract structured invoice JSON from a scanned or digital PDF/PNG/JPEG/TIFF invoice using the high-accuracy analyzer.",
|
|
125
|
-
inputSchema: {
|
|
126
|
-
pdfBase64: z.string().describe("Base64-encoded PDF, PNG, JPEG, or TIFF invoice"),
|
|
127
|
-
withLineItems: z.boolean().optional().describe("Also extract individual line items")
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
async ({ pdfBase64, withLineItems }) => asToolResult(() => client.analyzePdfInvoiceV2(pdfBase64, withLineItems ?? false))
|
|
131
|
-
);
|
|
132
|
-
server.registerTool(
|
|
133
|
-
"analyze_pdf_invoice_async_submit",
|
|
134
|
-
{
|
|
135
|
-
title: "Submit a large PDF invoice for async analysis",
|
|
136
|
-
description: "Submit a large scanned/PDF invoice for asynchronous analysis (for files too large for the synchronous analyzer). Returns a job_id \u2014 poll it with analyze_pdf_invoice_async_status.",
|
|
137
|
-
inputSchema: {
|
|
138
|
-
pdfBase64: z.string().describe("Base64-encoded PDF, PNG, JPEG, or TIFF invoice"),
|
|
139
|
-
withLineItems: z.boolean().optional().describe("Also extract individual line items")
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
async ({ pdfBase64, withLineItems }) => asToolResult(() => client.analyzePdfInvoiceAsync(pdfBase64, withLineItems))
|
|
143
|
-
);
|
|
144
|
-
server.registerTool(
|
|
145
|
-
"analyze_pdf_invoice_async_status",
|
|
146
|
-
{
|
|
147
|
-
title: "Check async PDF analysis status",
|
|
148
|
-
description: "Poll the status of an asynchronous PDF invoice analysis job submitted via analyze_pdf_invoice_async_submit.",
|
|
149
|
-
inputSchema: {
|
|
150
|
-
jobId: z.string().describe("The job_id returned by analyze_pdf_invoice_async_submit")
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
async ({ jobId }) => asToolResult(() => client.getAnalysisStatus(jobId))
|
|
154
|
-
);
|
|
155
|
-
server.registerTool(
|
|
156
|
-
"create_zugferd_from_pdf",
|
|
157
|
-
{
|
|
158
|
-
title: "Convert a PDF/scan directly into a ZUGFeRD PDF",
|
|
159
|
-
description: "Convert a PDF or scanned image invoice directly into a validated ZUGFeRD PDF/A-3 in one call (analyzes, validates, and embeds in a single step).",
|
|
160
|
-
inputSchema: {
|
|
161
|
-
pdfBase64: z.string().describe("Base64-encoded PDF, PNG, JPEG, or TIFF invoice"),
|
|
162
|
-
fileName: z.string().optional().describe("Original filename, used to name the resulting PDF")
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
async ({ pdfBase64, fileName }) => asToolResult(() => client.createZugferdFromPdf(pdfBase64, fileName))
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
26
|
// src/index.ts
|
|
170
27
|
async function main() {
|
|
171
28
|
const config = loadConfigFromEnv();
|
|
172
|
-
const client = new
|
|
29
|
+
const client = new RechnungsApiClient(config);
|
|
173
30
|
const server = new McpServer({ name: "rechnungsapi-mcp", version: "0.1.0" });
|
|
174
31
|
registerTools(server, client);
|
|
175
32
|
const transport = new StdioServerTransport();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/tools.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\"\nimport { RechnungsApiClient } from \"rechnungsapi-sdk\"\nimport { loadConfigFromEnv } from \"./config.js\"\nimport { registerTools } from \"./tools.js\"\n\nasync function main() {\n const config = loadConfigFromEnv()\n const client = new RechnungsApiClient(config)\n\n const server = new McpServer({ name: \"rechnungsapi-mcp\", version: \"0.1.0\" })\n registerTools(server, client)\n\n const transport = new StdioServerTransport()\n await server.connect(transport)\n}\n\nmain().catch((error) => {\n console.error(\"rechnungsapi-mcp failed to start:\", error instanceof Error ? error.message : error)\n process.exit(1)\n})\n","export interface RechnungsApiMcpConfig {\n apiToken: string\n baseUrl?: string\n v2BaseUrl?: string\n}\n\nexport function loadConfigFromEnv(): RechnungsApiMcpConfig {\n const apiToken = process.env.RECHNUNGSAPI_TOKEN\n if (!apiToken) {\n throw new Error(\n \"RECHNUNGSAPI_TOKEN environment variable is required. Get your API token from the RechnungsAPI dashboard \" +\n \"and set it in your MCP client's server config (e.g. Claude Desktop/Code's mcp.json under \\\"env\\\").\",\n )\n }\n return {\n apiToken,\n baseUrl: process.env.RECHNUNGSAPI_BASE_URL,\n v2BaseUrl: process.env.RECHNUNGSAPI_V2_BASE_URL,\n }\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { RechnungsApiClient, RechnungsApiError } from \"rechnungsapi-sdk\"\nimport { z } from \"zod\"\n\nconst invoiceJsonSchema = z\n .any()\n .describe(\"Structured invoice data (line items, parties, totals, etc.) as defined by the RechnungsAPI invoice schema\")\n\nconst transportSchema = z\n .any()\n .optional()\n .describe(\"Optional delivery/transport metadata (e.g. email delivery options)\")\n\n/** Wraps a client call so thrown RechnungsApiError/generic errors become MCP tool errors instead of crashing the server. */\nasync function asToolResult(fn: () => Promise<unknown>) {\n try {\n const result = await fn()\n return { content: [{ type: \"text\" as const, text: JSON.stringify(result, null, 2) }] }\n } catch (error) {\n if (error instanceof RechnungsApiError) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ status: error.status, message: error.message, body: error.body }, null, 2),\n },\n ],\n }\n }\n const message = error instanceof Error ? error.message : String(error)\n return { isError: true, content: [{ type: \"text\" as const, text: message }] }\n }\n}\n\nexport function registerTools(server: McpServer, client: RechnungsApiClient): void {\n server.registerTool(\n \"create_zugferd_invoice\",\n {\n title: \"Create ZUGFeRD invoice\",\n description:\n \"Create a ZUGFeRD PDF/A-3 e-invoice (visual PDF with embedded XRechnung XML) from structured invoice JSON plus a visual PDF template.\",\n inputSchema: {\n invoice: invoiceJsonSchema,\n invoicePdf64: z.string().describe(\"Base64-encoded visual PDF the XML will be embedded into\"),\n transport: transportSchema,\n },\n },\n async ({ invoice, invoicePdf64, transport }) =>\n asToolResult(() => client.createZugferdFromJson(invoice, invoicePdf64, { transport })),\n )\n\n server.registerTool(\n \"create_xinvoice\",\n {\n title: \"Create X-Invoice (XRechnung/UBL)\",\n description: \"Create an X-Invoice (XRechnung/UBL) XML document from structured invoice JSON.\",\n inputSchema: {\n invoice: invoiceJsonSchema,\n transport: transportSchema,\n },\n },\n async ({ invoice, transport }) => asToolResult(() => client.createXInvoiceFromJson(invoice, { transport })),\n )\n\n server.registerTool(\n \"create_zugferd_pdf\",\n {\n title: \"Embed X-Invoice XML into a PDF\",\n description: \"Embed an existing X-Invoice XML document into a visual PDF to produce a ZUGFeRD PDF.\",\n inputSchema: {\n invoicePdf64: z.string().describe(\"Base64-encoded visual PDF\"),\n xInvoiceXml: z.string().describe(\"X-Invoice XML to embed\"),\n },\n },\n async ({ invoicePdf64, xInvoiceXml }) => asToolResult(() => client.createZugferdPdf(invoicePdf64, xInvoiceXml)),\n )\n\n server.registerTool(\n \"extract_xinvoice_from_zugferd\",\n {\n title: \"Extract X-Invoice from ZUGFeRD PDF\",\n description: \"Extract the embedded XRechnung XML from a ZUGFeRD PDF and return it as structured JSON.\",\n inputSchema: {\n zugferd64: z.string().describe(\"Base64-encoded ZUGFeRD PDF\"),\n },\n },\n async ({ zugferd64 }) => asToolResult(() => client.extractXInvoiceFromZugferd(zugferd64)),\n )\n\n server.registerTool(\n \"validate_xinvoice_xml\",\n {\n title: \"Validate X-Invoice XML\",\n description: \"Validate an X-Invoice (XRechnung/UBL) XML document against schema and business rules.\",\n inputSchema: {\n xml: z.string().describe(\"The X-Invoice XML content to validate\"),\n },\n },\n async ({ xml }) => asToolResult(() => client.validateXInvoiceXml(xml)),\n )\n\n server.registerTool(\n \"validate_zugferd_pdf\",\n {\n title: \"Validate ZUGFeRD PDF\",\n description: \"Validate a ZUGFeRD PDF's embedded XML content.\",\n inputSchema: {\n zugferdFile64: z.string().describe(\"Base64-encoded ZUGFeRD PDF\"),\n comparePDF2XML: z.boolean().optional().describe(\"Also cross-check the embedded XML against the visual PDF content\"),\n },\n },\n async ({ zugferdFile64, comparePDF2XML }) =>\n asToolResult(() => client.validateZugferdPdf(zugferdFile64, { comparePDF2XML })),\n )\n\n server.registerTool(\n \"analyze_pdf_invoice\",\n {\n title: \"Analyze a PDF/scanned invoice\",\n description:\n \"Extract structured invoice JSON from a scanned or digital PDF/PNG/JPEG/TIFF invoice using the high-accuracy analyzer.\",\n inputSchema: {\n pdfBase64: z.string().describe(\"Base64-encoded PDF, PNG, JPEG, or TIFF invoice\"),\n withLineItems: z.boolean().optional().describe(\"Also extract individual line items\"),\n },\n },\n async ({ pdfBase64, withLineItems }) =>\n asToolResult(() => client.analyzePdfInvoiceV2(pdfBase64, withLineItems ?? false)),\n )\n\n server.registerTool(\n \"analyze_pdf_invoice_async_submit\",\n {\n title: \"Submit a large PDF invoice for async analysis\",\n description:\n \"Submit a large scanned/PDF invoice for asynchronous analysis (for files too large for the synchronous analyzer). \" +\n \"Returns a job_id — poll it with analyze_pdf_invoice_async_status.\",\n inputSchema: {\n pdfBase64: z.string().describe(\"Base64-encoded PDF, PNG, JPEG, or TIFF invoice\"),\n withLineItems: z.boolean().optional().describe(\"Also extract individual line items\"),\n },\n },\n async ({ pdfBase64, withLineItems }) =>\n asToolResult(() => client.analyzePdfInvoiceAsync(pdfBase64, withLineItems)),\n )\n\n server.registerTool(\n \"analyze_pdf_invoice_async_status\",\n {\n title: \"Check async PDF analysis status\",\n description: \"Poll the status of an asynchronous PDF invoice analysis job submitted via analyze_pdf_invoice_async_submit.\",\n inputSchema: {\n jobId: z.string().describe(\"The job_id returned by analyze_pdf_invoice_async_submit\"),\n },\n },\n async ({ jobId }) => asToolResult(() => client.getAnalysisStatus(jobId)),\n )\n\n server.registerTool(\n \"create_zugferd_from_pdf\",\n {\n title: \"Convert a PDF/scan directly into a ZUGFeRD PDF\",\n description:\n \"Convert a PDF or scanned image invoice directly into a validated ZUGFeRD PDF/A-3 in one call \" +\n \"(analyzes, validates, and embeds in a single step).\",\n inputSchema: {\n pdfBase64: z.string().describe(\"Base64-encoded PDF, PNG, JPEG, or TIFF invoice\"),\n fileName: z.string().optional().describe(\"Original filename, used to name the resulting PDF\"),\n },\n },\n async ({ pdfBase64, fileName }) => asToolResult(() => client.createZugferdFromPdf(pdfBase64, fileName)),\n )\n}\n"],"mappings":"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","names":["RechnungsApiClient","RechnungsApiClient"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\"\nimport { RechnungsApiClient } from \"rechnungsapi-sdk\"\nimport { loadConfigFromEnv } from \"./config.js\"\nimport { registerTools } from \"./tools.js\"\n\nasync function main() {\n const config = loadConfigFromEnv()\n const client = new RechnungsApiClient(config)\n\n const server = new McpServer({ name: \"rechnungsapi-mcp\", version: \"0.1.0\" })\n registerTools(server, client)\n\n const transport = new StdioServerTransport()\n await server.connect(transport)\n}\n\nmain().catch((error) => {\n console.error(\"rechnungsapi-mcp failed to start:\", error instanceof Error ? error.message : error)\n process.exit(1)\n})\n","export interface RechnungsApiMcpConfig {\n apiToken: string\n baseUrl?: string\n v2BaseUrl?: string\n}\n\nexport function loadConfigFromEnv(): RechnungsApiMcpConfig {\n const apiToken = process.env.RECHNUNGSAPI_TOKEN\n if (!apiToken) {\n throw new Error(\n \"RECHNUNGSAPI_TOKEN environment variable is required. Get your API token from the RechnungsAPI dashboard \" +\n \"and set it in your MCP client's server config (e.g. Claude Desktop/Code's mcp.json under \\\"env\\\").\",\n )\n }\n return {\n apiToken,\n baseUrl: process.env.RECHNUNGSAPI_BASE_URL,\n v2BaseUrl: process.env.RECHNUNGSAPI_V2_BASE_URL,\n }\n}\n"],"mappings":";;;;;;AAAA,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;AACrC,SAAS,0BAA0B;;;ACI5B,SAAS,oBAA2C;AACzD,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,IACA,SAAS,QAAQ,IAAI;AAAA,IACrB,WAAW,QAAQ,IAAI;AAAA,EACzB;AACF;;;ADbA,eAAe,OAAO;AACpB,QAAM,SAAS,kBAAkB;AACjC,QAAM,SAAS,IAAI,mBAAmB,MAAM;AAE5C,QAAM,SAAS,IAAI,UAAU,EAAE,MAAM,oBAAoB,SAAS,QAAQ,CAAC;AAC3E,gBAAc,QAAQ,MAAM;AAE5B,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAChC;AAEA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,UAAQ,MAAM,qCAAqC,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AACjG,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rechnungsapi-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP (Model Context Protocol) server for RechnungsAPI — lets AI agents create, validate, and analyze ZUGFeRD and X-Invoice e-invoices.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Finbc GmbH <support@rechnungsapi.de>",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"build": "tsup",
|
|
21
21
|
"dev": "tsup --watch",
|
|
22
22
|
"start": "node dist/index.js",
|
|
23
|
+
"start:http": "node dist/http.js",
|
|
23
24
|
"typecheck": "tsc --noEmit",
|
|
24
25
|
"test": "vitest run",
|
|
25
26
|
"inspect": "npx @modelcontextprotocol/inspector node dist/index.js"
|