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 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
@@ -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 as RechnungsApiClient2 } from "rechnungsapi-sdk";
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 RechnungsApiClient2(config);
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.1.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"