buchpilot-mcp 0.1.1

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.
@@ -0,0 +1,92 @@
1
+ import { z } from "zod";
2
+ import type { IAccountingBackend } from "../backends/types.js";
3
+
4
+ export const contactSchemas = {
5
+ create_contact: z.object({
6
+ type: z.enum(["person", "company"]).describe("Kontakttyp: Person oder Firma"),
7
+ name: z.string().describe("Nachname (Person) oder Firmenname (Firma)"),
8
+ firstName: z.string().optional().describe("Vorname (nur bei Person)"),
9
+ email: z.string().optional().describe("E-Mail-Adresse"),
10
+ role: z.enum(["customer", "vendor", "both"]).default("customer").describe("Rolle: Kunde, Lieferant, oder beides"),
11
+ backend: z.string().optional().describe("Backend (z.B. 'lexoffice'). Leer = default"),
12
+ }),
13
+ get_contact: z.object({
14
+ id: z.string().describe("Kontakt-ID"),
15
+ backend: z.string().optional(),
16
+ }),
17
+ list_contacts: z.object({
18
+ name: z.string().optional().describe("Nach Name filtern"),
19
+ email: z.string().optional().describe("Nach E-Mail filtern"),
20
+ role: z.enum(["customer", "vendor"]).optional().describe("Nach Rolle filtern"),
21
+ limit: z.number().default(25).describe("Maximale Anzahl"),
22
+ backend: z.string().optional(),
23
+ }),
24
+ update_contact: z.object({
25
+ id: z.string().describe("Kontakt-ID"),
26
+ name: z.string().optional().describe("Neuer Name"),
27
+ firstName: z.string().optional().describe("Neuer Vorname"),
28
+ email: z.string().optional().describe("Neue E-Mail"),
29
+ backend: z.string().optional(),
30
+ }),
31
+ };
32
+
33
+ export function registerContactTools(
34
+ server: any,
35
+ getBackend: (name?: string) => IAccountingBackend,
36
+ ) {
37
+ server.tool(
38
+ "create_contact",
39
+ "Erstellt einen neuen Kontakt (Person oder Firma) im Buchhaltungssystem. Creates a new contact (person or company).",
40
+ contactSchemas.create_contact.shape,
41
+ async (args: z.infer<typeof contactSchemas.create_contact>) => {
42
+ const backend = getBackend(args.backend);
43
+ const result = await backend.createContact({
44
+ type: args.type,
45
+ name: args.name,
46
+ firstName: args.firstName,
47
+ email: args.email,
48
+ role: args.role,
49
+ });
50
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
51
+ },
52
+ );
53
+
54
+ server.tool(
55
+ "get_contact",
56
+ "Ruft einen Kontakt per ID ab. Gets a contact by ID.",
57
+ contactSchemas.get_contact.shape,
58
+ async (args: z.infer<typeof contactSchemas.get_contact>) => {
59
+ const backend = getBackend(args.backend);
60
+ const result = await backend.getContact(args.id);
61
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
62
+ },
63
+ );
64
+
65
+ server.tool(
66
+ "list_contacts",
67
+ "Listet Kontakte auf mit optionalen Filtern. Lists contacts with optional filters.",
68
+ contactSchemas.list_contacts.shape,
69
+ async (args: z.infer<typeof contactSchemas.list_contacts>) => {
70
+ const backend = getBackend(args.backend);
71
+ const result = await backend.listContacts({
72
+ name: args.name,
73
+ email: args.email,
74
+ role: args.role,
75
+ limit: args.limit,
76
+ });
77
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
78
+ },
79
+ );
80
+
81
+ server.tool(
82
+ "update_contact",
83
+ "Aktualisiert einen bestehenden Kontakt. Updates an existing contact.",
84
+ contactSchemas.update_contact.shape,
85
+ async (args: z.infer<typeof contactSchemas.update_contact>) => {
86
+ const backend = getBackend(args.backend);
87
+ const { id, backend: _, ...updateFields } = args;
88
+ const result = await backend.updateContact(id, updateFields);
89
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
90
+ },
91
+ );
92
+ }
@@ -0,0 +1,128 @@
1
+ import { z } from "zod";
2
+ import type { IAccountingBackend } from "../backends/types.js";
3
+
4
+ const lineItemSchema = z.object({
5
+ name: z.string().describe("Positionsname"),
6
+ quantity: z.number().default(1).describe("Menge"),
7
+ unitPrice: z.number().describe("Einzelpreis netto"),
8
+ taxRate: z.number().default(19).describe("Steuersatz in Prozent"),
9
+ });
10
+
11
+ export const invoiceSchemas = {
12
+ create_invoice: z.object({
13
+ contactId: z.string().describe("Kontakt-ID des Rechnungsempfaengers"),
14
+ date: z.string().describe("Rechnungsdatum (ISO 8601, z.B. 2026-03-18)"),
15
+ lineItems: z.array(lineItemSchema).min(1).describe("Rechnungspositionen"),
16
+ title: z.string().optional().describe("Rechnungstitel"),
17
+ introduction: z.string().optional().describe("Einleitungstext"),
18
+ remark: z.string().optional().describe("Schlusstext"),
19
+ currency: z.string().default("EUR").describe("Waehrung"),
20
+ finalize: z.boolean().default(false).describe("Rechnung direkt finalisieren (nicht mehr editierbar)"),
21
+ backend: z.string().optional(),
22
+ }),
23
+ get_invoice: z.object({
24
+ id: z.string().describe("Rechnungs-ID"),
25
+ backend: z.string().optional(),
26
+ }),
27
+ list_invoices: z.object({
28
+ status: z.enum(["draft", "open", "paid", "overdue"]).optional().describe("Nach Status filtern"),
29
+ contactId: z.string().optional().describe("Nach Kontakt filtern"),
30
+ limit: z.number().default(25).describe("Maximale Anzahl"),
31
+ backend: z.string().optional(),
32
+ }),
33
+ get_invoice_pdf: z.object({
34
+ id: z.string().describe("Rechnungs-ID"),
35
+ backend: z.string().optional(),
36
+ }),
37
+ update_invoice: z.object({
38
+ id: z.string().describe("Rechnungs-ID"),
39
+ title: z.string().optional(),
40
+ introduction: z.string().optional(),
41
+ remark: z.string().optional(),
42
+ backend: z.string().optional(),
43
+ }),
44
+ };
45
+
46
+ export function registerInvoiceTools(
47
+ server: any,
48
+ getBackend: (name?: string) => IAccountingBackend,
49
+ ) {
50
+ server.tool(
51
+ "create_invoice",
52
+ "Erstellt eine neue Rechnung mit Positionen. Creates a new invoice with line items.",
53
+ invoiceSchemas.create_invoice.shape,
54
+ async (args: z.infer<typeof invoiceSchemas.create_invoice>) => {
55
+ const backend = getBackend(args.backend);
56
+ const result = await backend.createInvoice({
57
+ contactId: args.contactId,
58
+ date: args.date,
59
+ lineItems: args.lineItems,
60
+ title: args.title,
61
+ introduction: args.introduction,
62
+ remark: args.remark,
63
+ currency: args.currency,
64
+ finalize: args.finalize,
65
+ });
66
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
67
+ },
68
+ );
69
+
70
+ server.tool(
71
+ "get_invoice",
72
+ "Ruft eine Rechnung per ID ab. Gets an invoice by ID.",
73
+ invoiceSchemas.get_invoice.shape,
74
+ async (args: z.infer<typeof invoiceSchemas.get_invoice>) => {
75
+ const backend = getBackend(args.backend);
76
+ const result = await backend.getInvoice(args.id);
77
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
78
+ },
79
+ );
80
+
81
+ server.tool(
82
+ "list_invoices",
83
+ "Listet Rechnungen auf. Kann nach Status filtern (draft, open, paid, overdue). Lists invoices with optional status filter.",
84
+ invoiceSchemas.list_invoices.shape,
85
+ async (args: z.infer<typeof invoiceSchemas.list_invoices>) => {
86
+ const backend = getBackend(args.backend);
87
+ const result = await backend.listInvoices({
88
+ status: args.status,
89
+ contactId: args.contactId,
90
+ limit: args.limit,
91
+ });
92
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
93
+ },
94
+ );
95
+
96
+ server.tool(
97
+ "get_invoice_pdf",
98
+ "Laedt eine Rechnung als PDF herunter (Base64-kodiert). Downloads an invoice as PDF (base64-encoded).",
99
+ invoiceSchemas.get_invoice_pdf.shape,
100
+ async (args: z.infer<typeof invoiceSchemas.get_invoice_pdf>) => {
101
+ const backend = getBackend(args.backend);
102
+ const { data, filename } = await backend.getInvoicePdf(args.id);
103
+ return {
104
+ content: [{
105
+ type: "text" as const,
106
+ text: JSON.stringify({
107
+ filename,
108
+ size_bytes: data.length,
109
+ base64: data.toString("base64"),
110
+ hint: "PDF ist base64-kodiert. Zum Speichern: Buffer.from(base64, 'base64') oder als data-URI verwenden.",
111
+ }, null, 2),
112
+ }],
113
+ };
114
+ },
115
+ );
116
+
117
+ server.tool(
118
+ "update_invoice",
119
+ "Aktualisiert eine bestehende Rechnung (nur Entwuerfe). Updates a draft invoice.",
120
+ invoiceSchemas.update_invoice.shape,
121
+ async (args: z.infer<typeof invoiceSchemas.update_invoice>) => {
122
+ const backend = getBackend(args.backend);
123
+ const { id, backend: _, ...updateFields } = args;
124
+ const result = await backend.updateInvoice(id, updateFields);
125
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
126
+ },
127
+ );
128
+ }
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ import type { IAccountingBackend } from "../backends/types.js";
3
+
4
+ const lineItemSchema = z.object({
5
+ name: z.string().describe("Positionsname"),
6
+ quantity: z.number().default(1).describe("Menge"),
7
+ unitPrice: z.number().describe("Einzelpreis netto"),
8
+ taxRate: z.number().default(19).describe("Steuersatz in Prozent"),
9
+ });
10
+
11
+ export function registerQuotationTools(
12
+ server: any,
13
+ getBackend: (name?: string) => IAccountingBackend,
14
+ ) {
15
+ server.tool(
16
+ "create_quotation",
17
+ "Erstellt ein neues Angebot mit Positionen. Creates a new quotation with line items.",
18
+ {
19
+ contactId: z.string().describe("Kontakt-ID"),
20
+ date: z.string().describe("Angebotsdatum (ISO 8601)"),
21
+ lineItems: z.array(lineItemSchema).min(1).describe("Angebotspositionen"),
22
+ expirationDate: z.string().optional().describe("Gueltig bis (ISO 8601)"),
23
+ title: z.string().optional().describe("Angebotstitel"),
24
+ introduction: z.string().optional().describe("Einleitungstext"),
25
+ remark: z.string().optional().describe("Schlusstext"),
26
+ finalize: z.boolean().default(false).describe("Angebot direkt finalisieren"),
27
+ backend: z.string().optional(),
28
+ },
29
+ async (args: any) => {
30
+ const backend = getBackend(args.backend);
31
+ const result = await backend.createQuotation({
32
+ contactId: args.contactId,
33
+ date: args.date,
34
+ lineItems: args.lineItems,
35
+ expirationDate: args.expirationDate,
36
+ title: args.title,
37
+ introduction: args.introduction,
38
+ remark: args.remark,
39
+ finalize: args.finalize,
40
+ });
41
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
42
+ },
43
+ );
44
+
45
+ server.tool(
46
+ "get_quotation",
47
+ "Ruft ein Angebot per ID ab. Gets a quotation by ID.",
48
+ {
49
+ id: z.string().describe("Angebots-ID"),
50
+ backend: z.string().optional(),
51
+ },
52
+ async (args: any) => {
53
+ const backend = getBackend(args.backend);
54
+ const result = await backend.getQuotation(args.id);
55
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
56
+ },
57
+ );
58
+ }
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ import type { IAccountingBackend } from "../backends/types.js";
3
+
4
+ export function registerSyncTools(
5
+ server: any,
6
+ getBackend: (name?: string) => IAccountingBackend,
7
+ ) {
8
+ server.tool(
9
+ "get_overdue_invoices",
10
+ "Listet alle ueberfaelligen Rechnungen mit Betrag und Faelligkeitsdatum. Lists all overdue invoices with amount and due date.",
11
+ {
12
+ days_overdue: z.number().default(0).describe("Mindestens X Tage ueberfaellig (0 = alle ueberfaelligen)"),
13
+ limit: z.number().default(50).describe("Maximale Anzahl"),
14
+ backend: z.string().optional(),
15
+ },
16
+ async (args: any) => {
17
+ const backend = getBackend(args.backend);
18
+ const invoices = await backend.listInvoices({ status: "overdue", limit: args.limit });
19
+
20
+ const now = Date.now();
21
+ const filtered = args.days_overdue > 0
22
+ ? invoices.filter((inv) => {
23
+ const dueTime = new Date(inv.dueDate).getTime();
24
+ const daysOver = (now - dueTime) / (1000 * 60 * 60 * 24);
25
+ return daysOver >= args.days_overdue;
26
+ })
27
+ : invoices;
28
+
29
+ const totalOverdue = filtered.reduce((sum, inv) => sum + inv.totalGross, 0);
30
+
31
+ return {
32
+ content: [{
33
+ type: "text" as const,
34
+ text: JSON.stringify({
35
+ count: filtered.length,
36
+ total_overdue_amount: Math.round(totalOverdue * 100) / 100,
37
+ currency: "EUR",
38
+ invoices: filtered.map((inv) => ({
39
+ id: inv.id,
40
+ number: inv.number,
41
+ totalGross: inv.totalGross,
42
+ dueDate: inv.dueDate,
43
+ daysOverdue: Math.floor((now - new Date(inv.dueDate).getTime()) / (1000 * 60 * 60 * 24)),
44
+ })),
45
+ }, null, 2),
46
+ }],
47
+ };
48
+ },
49
+ );
50
+ }
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ import type { IAccountingBackend } from "../backends/types.js";
3
+
4
+ export function registerVoucherTools(
5
+ server: any,
6
+ getBackend: (name?: string) => IAccountingBackend,
7
+ ) {
8
+ server.tool(
9
+ "create_voucher",
10
+ "Erstellt einen neuen Beleg (Eingangsrechnung, Gutschrift). Creates a new voucher (purchase invoice, credit note).",
11
+ {
12
+ type: z.enum(["purchaseinvoice", "purchasecreditnote", "any"]).describe("Belegtyp"),
13
+ date: z.string().describe("Belegdatum (ISO 8601)"),
14
+ totalGross: z.number().describe("Bruttobetrag"),
15
+ taxRate: z.number().default(19).describe("Steuersatz in Prozent"),
16
+ contactId: z.string().optional().describe("Kontakt-ID (optional)"),
17
+ remark: z.string().optional().describe("Bemerkung"),
18
+ backend: z.string().optional(),
19
+ },
20
+ async (args: any) => {
21
+ const backend = getBackend(args.backend);
22
+ const result = await backend.createVoucher({
23
+ type: args.type,
24
+ date: args.date,
25
+ totalGross: args.totalGross,
26
+ taxRate: args.taxRate,
27
+ contactId: args.contactId,
28
+ remark: args.remark,
29
+ });
30
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
31
+ },
32
+ );
33
+
34
+ server.tool(
35
+ "get_voucher",
36
+ "Ruft einen Beleg per ID ab. Gets a voucher by ID.",
37
+ {
38
+ id: z.string().describe("Beleg-ID"),
39
+ backend: z.string().optional(),
40
+ },
41
+ async (args: any) => {
42
+ const backend = getBackend(args.backend);
43
+ const result = await backend.getVoucher(args.id);
44
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
45
+ },
46
+ );
47
+
48
+ server.tool(
49
+ "list_vouchers",
50
+ "Listet Belege auf mit optionalen Filtern. Lists vouchers with optional filters.",
51
+ {
52
+ type: z.enum(["purchaseinvoice", "purchasecreditnote", "any"]).optional().describe("Nach Typ filtern"),
53
+ limit: z.number().default(25).describe("Maximale Anzahl"),
54
+ backend: z.string().optional(),
55
+ },
56
+ async (args: any) => {
57
+ const backend = getBackend(args.backend);
58
+ const result = await backend.listVouchers({
59
+ type: args.type,
60
+ limit: args.limit,
61
+ });
62
+ return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
63
+ },
64
+ );
65
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true,
12
+ "resolveJsonModule": true
13
+ },
14
+ "include": ["src/**/*"]
15
+ }