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,120 @@
1
+ export interface Contact {
2
+ id: string;
3
+ type: "person" | "company";
4
+ name: string;
5
+ firstName?: string;
6
+ email?: string;
7
+ role: "customer" | "vendor" | "both";
8
+ backend: string;
9
+ raw: Record<string, unknown>;
10
+ }
11
+ export interface ContactInput {
12
+ type: "person" | "company";
13
+ name: string;
14
+ firstName?: string;
15
+ email?: string;
16
+ role?: "customer" | "vendor" | "both";
17
+ }
18
+ export interface ContactFilter {
19
+ name?: string;
20
+ email?: string;
21
+ role?: "customer" | "vendor";
22
+ limit?: number;
23
+ }
24
+ export interface LineItem {
25
+ name: string;
26
+ quantity: number;
27
+ unitPrice: number;
28
+ taxRate: number;
29
+ }
30
+ export interface Invoice {
31
+ id: string;
32
+ number: string;
33
+ contactId: string;
34
+ date: string;
35
+ dueDate: string;
36
+ status: "draft" | "open" | "paid" | "overdue";
37
+ totalNet: number;
38
+ totalGross: number;
39
+ currency: string;
40
+ lineItems: LineItem[];
41
+ backend: string;
42
+ raw: Record<string, unknown>;
43
+ }
44
+ export interface InvoiceInput {
45
+ contactId: string;
46
+ date: string;
47
+ lineItems: LineItem[];
48
+ title?: string;
49
+ introduction?: string;
50
+ remark?: string;
51
+ currency?: string;
52
+ finalize?: boolean;
53
+ }
54
+ export interface InvoiceFilter {
55
+ status?: "draft" | "open" | "paid" | "overdue";
56
+ contactId?: string;
57
+ limit?: number;
58
+ }
59
+ export interface Voucher {
60
+ id: string;
61
+ type: string;
62
+ date: string;
63
+ totalGross: number;
64
+ taxRate: number;
65
+ backend: string;
66
+ raw: Record<string, unknown>;
67
+ }
68
+ export interface VoucherInput {
69
+ type: "purchaseinvoice" | "purchasecreditnote" | "any";
70
+ date: string;
71
+ totalGross: number;
72
+ taxRate: number;
73
+ contactId?: string;
74
+ remark?: string;
75
+ }
76
+ export interface VoucherFilter {
77
+ type?: "purchaseinvoice" | "purchasecreditnote" | "any";
78
+ limit?: number;
79
+ }
80
+ export interface Quotation {
81
+ id: string;
82
+ contactId: string;
83
+ date: string;
84
+ expirationDate?: string;
85
+ totalNet: number;
86
+ totalGross: number;
87
+ lineItems: LineItem[];
88
+ backend: string;
89
+ raw: Record<string, unknown>;
90
+ }
91
+ export interface QuotationInput {
92
+ contactId: string;
93
+ date: string;
94
+ lineItems: LineItem[];
95
+ expirationDate?: string;
96
+ title?: string;
97
+ introduction?: string;
98
+ remark?: string;
99
+ finalize?: boolean;
100
+ }
101
+ export interface IAccountingBackend {
102
+ name: string;
103
+ createContact(input: ContactInput): Promise<Contact>;
104
+ getContact(id: string): Promise<Contact>;
105
+ listContacts(filter?: ContactFilter): Promise<Contact[]>;
106
+ updateContact(id: string, input: Partial<ContactInput>): Promise<Contact>;
107
+ createInvoice(input: InvoiceInput): Promise<Invoice>;
108
+ getInvoice(id: string): Promise<Invoice>;
109
+ listInvoices(filter?: InvoiceFilter): Promise<Invoice[]>;
110
+ getInvoicePdf(id: string): Promise<{
111
+ data: Buffer;
112
+ filename: string;
113
+ }>;
114
+ updateInvoice(id: string, input: Partial<InvoiceInput>): Promise<Invoice>;
115
+ createVoucher(input: VoucherInput): Promise<Voucher>;
116
+ getVoucher(id: string): Promise<Voucher>;
117
+ listVouchers(filter?: VoucherFilter): Promise<Voucher[]>;
118
+ createQuotation(input: QuotationInput): Promise<Quotation>;
119
+ getQuotation(id: string): Promise<Quotation>;
120
+ }
@@ -0,0 +1,2 @@
1
+ // === Gemeinsame Entity-Typen ===
2
+ export {};
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ declare const ConfigSchema: z.ZodObject<{
3
+ backends: z.ZodRecord<z.ZodString, z.ZodObject<{
4
+ api_key: z.ZodString;
5
+ }, "strip", z.ZodTypeAny, {
6
+ api_key: string;
7
+ }, {
8
+ api_key: string;
9
+ }>>;
10
+ default_backend: z.ZodString;
11
+ }, "strip", z.ZodTypeAny, {
12
+ backends: Record<string, {
13
+ api_key: string;
14
+ }>;
15
+ default_backend: string;
16
+ }, {
17
+ backends: Record<string, {
18
+ api_key: string;
19
+ }>;
20
+ default_backend: string;
21
+ }>;
22
+ export type BuchPilotConfig = z.infer<typeof ConfigSchema>;
23
+ export declare function loadConfig(): BuchPilotConfig;
24
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,44 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { z } from "zod";
5
+ import { ErrorCode, createError } from "./errors.js";
6
+ const BackendConfigSchema = z.object({
7
+ api_key: z.string().min(1, "API key must not be empty"),
8
+ });
9
+ const ConfigSchema = z.object({
10
+ backends: z.record(z.string(), BackendConfigSchema),
11
+ default_backend: z.string(),
12
+ });
13
+ export function loadConfig() {
14
+ const configPaths = [
15
+ process.env.BUCHPILOT_CONFIG,
16
+ join(homedir(), ".buchpilot.json"),
17
+ join(process.cwd(), ".buchpilot.json"),
18
+ ].filter(Boolean);
19
+ for (const configPath of configPaths) {
20
+ if (existsSync(configPath)) {
21
+ try {
22
+ const raw = readFileSync(configPath, "utf-8");
23
+ const parsed = JSON.parse(raw);
24
+ const validated = ConfigSchema.parse(parsed);
25
+ if (!validated.backends[validated.default_backend]) {
26
+ throw createError(ErrorCode.BACKEND_NOT_CONFIGURED, `Default backend "${validated.default_backend}" is not configured in backends`);
27
+ }
28
+ return validated;
29
+ }
30
+ catch (err) {
31
+ if (err && typeof err === "object" && "code" in err) {
32
+ throw err;
33
+ }
34
+ throw createError(ErrorCode.CONFIG_MISSING, `Invalid config at ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
35
+ }
36
+ }
37
+ }
38
+ throw createError(ErrorCode.CONFIG_MISSING, "No .buchpilot.json found. Create one at ~/.buchpilot.json or ./.buchpilot.json with your API keys.", {
39
+ example: {
40
+ backends: { lexoffice: { api_key: "YOUR_KEY" } },
41
+ default_backend: "lexoffice",
42
+ },
43
+ });
44
+ }
@@ -0,0 +1,18 @@
1
+ export declare enum ErrorCode {
2
+ AUTH_FAILED = "AUTH_FAILED",
3
+ RATE_LIMITED = "RATE_LIMITED",
4
+ NOT_FOUND = "NOT_FOUND",
5
+ VALIDATION_ERROR = "VALIDATION_ERROR",
6
+ BACKEND_ERROR = "BACKEND_ERROR",
7
+ SYNC_CONFLICT = "SYNC_CONFLICT",
8
+ CONFIG_MISSING = "CONFIG_MISSING",
9
+ BACKEND_NOT_CONFIGURED = "BACKEND_NOT_CONFIGURED"
10
+ }
11
+ export interface BuchPilotError {
12
+ code: ErrorCode;
13
+ message: string;
14
+ details?: Record<string, unknown>;
15
+ retry_after_ms?: number;
16
+ }
17
+ export declare function createError(code: ErrorCode, message: string, details?: Record<string, unknown>): BuchPilotError;
18
+ export declare function isRetryable(error: BuchPilotError): boolean;
package/dist/errors.js ADDED
@@ -0,0 +1,22 @@
1
+ export var ErrorCode;
2
+ (function (ErrorCode) {
3
+ ErrorCode["AUTH_FAILED"] = "AUTH_FAILED";
4
+ ErrorCode["RATE_LIMITED"] = "RATE_LIMITED";
5
+ ErrorCode["NOT_FOUND"] = "NOT_FOUND";
6
+ ErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
7
+ ErrorCode["BACKEND_ERROR"] = "BACKEND_ERROR";
8
+ ErrorCode["SYNC_CONFLICT"] = "SYNC_CONFLICT";
9
+ ErrorCode["CONFIG_MISSING"] = "CONFIG_MISSING";
10
+ ErrorCode["BACKEND_NOT_CONFIGURED"] = "BACKEND_NOT_CONFIGURED";
11
+ })(ErrorCode || (ErrorCode = {}));
12
+ export function createError(code, message, details) {
13
+ return {
14
+ code,
15
+ message,
16
+ ...(details && { details }),
17
+ ...(code === ErrorCode.RATE_LIMITED && { retry_after_ms: 500 }),
18
+ };
19
+ }
20
+ export function isRetryable(error) {
21
+ return error.code === ErrorCode.RATE_LIMITED || error.code === ErrorCode.BACKEND_ERROR;
22
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { loadConfig } from "./config.js";
5
+ import { ErrorCode, createError } from "./errors.js";
6
+ import { LexofficeBackend } from "./backends/lexoffice.js";
7
+ import { registerContactTools } from "./tools/contacts.js";
8
+ import { registerInvoiceTools } from "./tools/invoices.js";
9
+ import { registerVoucherTools } from "./tools/vouchers.js";
10
+ import { registerQuotationTools } from "./tools/quotations.js";
11
+ import { registerSyncTools } from "./tools/sync.js";
12
+ // === Backend Registry ===
13
+ const backends = new Map();
14
+ function getBackend(name) {
15
+ const config = loadConfig();
16
+ const backendName = name || config.default_backend;
17
+ // Lazy-initialize backends
18
+ if (!backends.has(backendName)) {
19
+ const backendConfig = config.backends[backendName];
20
+ if (!backendConfig) {
21
+ throw createError(ErrorCode.BACKEND_NOT_CONFIGURED, `Backend "${backendName}" is not configured. Available: ${Object.keys(config.backends).join(", ")}`);
22
+ }
23
+ switch (backendName) {
24
+ case "lexoffice":
25
+ backends.set(backendName, new LexofficeBackend(backendConfig.api_key));
26
+ break;
27
+ default:
28
+ throw createError(ErrorCode.BACKEND_NOT_CONFIGURED, `Unknown backend type: "${backendName}". Supported: lexoffice`);
29
+ }
30
+ }
31
+ return backends.get(backendName);
32
+ }
33
+ // === MCP Server ===
34
+ const server = new McpServer({
35
+ name: "buchpilot",
36
+ version: "0.1.1",
37
+ });
38
+ // Register all tools
39
+ registerContactTools(server, getBackend);
40
+ registerInvoiceTools(server, getBackend);
41
+ registerVoucherTools(server, getBackend);
42
+ registerQuotationTools(server, getBackend);
43
+ registerSyncTools(server, getBackend);
44
+ // === Start ===
45
+ async function main() {
46
+ const transport = new StdioServerTransport();
47
+ await server.connect(transport);
48
+ console.error("BuchPilot MCP Server running (stdio)");
49
+ }
50
+ main().catch((error) => {
51
+ console.error("Fatal error:", error);
52
+ process.exit(1);
53
+ });
@@ -0,0 +1,75 @@
1
+ import { z } from "zod";
2
+ import type { IAccountingBackend } from "../backends/types.js";
3
+ export declare const contactSchemas: {
4
+ create_contact: z.ZodObject<{
5
+ type: z.ZodEnum<["person", "company"]>;
6
+ name: z.ZodString;
7
+ firstName: z.ZodOptional<z.ZodString>;
8
+ email: z.ZodOptional<z.ZodString>;
9
+ role: z.ZodDefault<z.ZodEnum<["customer", "vendor", "both"]>>;
10
+ backend: z.ZodOptional<z.ZodString>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ type: "person" | "company";
13
+ name: string;
14
+ role: "customer" | "vendor" | "both";
15
+ firstName?: string | undefined;
16
+ email?: string | undefined;
17
+ backend?: string | undefined;
18
+ }, {
19
+ type: "person" | "company";
20
+ name: string;
21
+ firstName?: string | undefined;
22
+ email?: string | undefined;
23
+ role?: "customer" | "vendor" | "both" | undefined;
24
+ backend?: string | undefined;
25
+ }>;
26
+ get_contact: z.ZodObject<{
27
+ id: z.ZodString;
28
+ backend: z.ZodOptional<z.ZodString>;
29
+ }, "strip", z.ZodTypeAny, {
30
+ id: string;
31
+ backend?: string | undefined;
32
+ }, {
33
+ id: string;
34
+ backend?: string | undefined;
35
+ }>;
36
+ list_contacts: z.ZodObject<{
37
+ name: z.ZodOptional<z.ZodString>;
38
+ email: z.ZodOptional<z.ZodString>;
39
+ role: z.ZodOptional<z.ZodEnum<["customer", "vendor"]>>;
40
+ limit: z.ZodDefault<z.ZodNumber>;
41
+ backend: z.ZodOptional<z.ZodString>;
42
+ }, "strip", z.ZodTypeAny, {
43
+ limit: number;
44
+ email?: string | undefined;
45
+ name?: string | undefined;
46
+ role?: "customer" | "vendor" | undefined;
47
+ backend?: string | undefined;
48
+ }, {
49
+ email?: string | undefined;
50
+ name?: string | undefined;
51
+ role?: "customer" | "vendor" | undefined;
52
+ backend?: string | undefined;
53
+ limit?: number | undefined;
54
+ }>;
55
+ update_contact: z.ZodObject<{
56
+ id: z.ZodString;
57
+ name: z.ZodOptional<z.ZodString>;
58
+ firstName: z.ZodOptional<z.ZodString>;
59
+ email: z.ZodOptional<z.ZodString>;
60
+ backend: z.ZodOptional<z.ZodString>;
61
+ }, "strip", z.ZodTypeAny, {
62
+ id: string;
63
+ firstName?: string | undefined;
64
+ email?: string | undefined;
65
+ name?: string | undefined;
66
+ backend?: string | undefined;
67
+ }, {
68
+ id: string;
69
+ firstName?: string | undefined;
70
+ email?: string | undefined;
71
+ name?: string | undefined;
72
+ backend?: string | undefined;
73
+ }>;
74
+ };
75
+ export declare function registerContactTools(server: any, getBackend: (name?: string) => IAccountingBackend): void;
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ export const contactSchemas = {
3
+ create_contact: z.object({
4
+ type: z.enum(["person", "company"]).describe("Kontakttyp: Person oder Firma"),
5
+ name: z.string().describe("Nachname (Person) oder Firmenname (Firma)"),
6
+ firstName: z.string().optional().describe("Vorname (nur bei Person)"),
7
+ email: z.string().optional().describe("E-Mail-Adresse"),
8
+ role: z.enum(["customer", "vendor", "both"]).default("customer").describe("Rolle: Kunde, Lieferant, oder beides"),
9
+ backend: z.string().optional().describe("Backend (z.B. 'lexoffice'). Leer = default"),
10
+ }),
11
+ get_contact: z.object({
12
+ id: z.string().describe("Kontakt-ID"),
13
+ backend: z.string().optional(),
14
+ }),
15
+ list_contacts: z.object({
16
+ name: z.string().optional().describe("Nach Name filtern"),
17
+ email: z.string().optional().describe("Nach E-Mail filtern"),
18
+ role: z.enum(["customer", "vendor"]).optional().describe("Nach Rolle filtern"),
19
+ limit: z.number().default(25).describe("Maximale Anzahl"),
20
+ backend: z.string().optional(),
21
+ }),
22
+ update_contact: z.object({
23
+ id: z.string().describe("Kontakt-ID"),
24
+ name: z.string().optional().describe("Neuer Name"),
25
+ firstName: z.string().optional().describe("Neuer Vorname"),
26
+ email: z.string().optional().describe("Neue E-Mail"),
27
+ backend: z.string().optional(),
28
+ }),
29
+ };
30
+ export function registerContactTools(server, getBackend) {
31
+ server.tool("create_contact", "Erstellt einen neuen Kontakt (Person oder Firma) im Buchhaltungssystem. Creates a new contact (person or company).", contactSchemas.create_contact.shape, async (args) => {
32
+ const backend = getBackend(args.backend);
33
+ const result = await backend.createContact({
34
+ type: args.type,
35
+ name: args.name,
36
+ firstName: args.firstName,
37
+ email: args.email,
38
+ role: args.role,
39
+ });
40
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
41
+ });
42
+ server.tool("get_contact", "Ruft einen Kontakt per ID ab. Gets a contact by ID.", contactSchemas.get_contact.shape, async (args) => {
43
+ const backend = getBackend(args.backend);
44
+ const result = await backend.getContact(args.id);
45
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
46
+ });
47
+ server.tool("list_contacts", "Listet Kontakte auf mit optionalen Filtern. Lists contacts with optional filters.", contactSchemas.list_contacts.shape, async (args) => {
48
+ const backend = getBackend(args.backend);
49
+ const result = await backend.listContacts({
50
+ name: args.name,
51
+ email: args.email,
52
+ role: args.role,
53
+ limit: args.limit,
54
+ });
55
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
56
+ });
57
+ server.tool("update_contact", "Aktualisiert einen bestehenden Kontakt. Updates an existing contact.", contactSchemas.update_contact.shape, async (args) => {
58
+ const backend = getBackend(args.backend);
59
+ const { id, backend: _, ...updateFields } = args;
60
+ const result = await backend.updateContact(id, updateFields);
61
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
62
+ });
63
+ }
@@ -0,0 +1,116 @@
1
+ import { z } from "zod";
2
+ import type { IAccountingBackend } from "../backends/types.js";
3
+ export declare const invoiceSchemas: {
4
+ create_invoice: z.ZodObject<{
5
+ contactId: z.ZodString;
6
+ date: z.ZodString;
7
+ lineItems: z.ZodArray<z.ZodObject<{
8
+ name: z.ZodString;
9
+ quantity: z.ZodDefault<z.ZodNumber>;
10
+ unitPrice: z.ZodNumber;
11
+ taxRate: z.ZodDefault<z.ZodNumber>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ name: string;
14
+ quantity: number;
15
+ unitPrice: number;
16
+ taxRate: number;
17
+ }, {
18
+ name: string;
19
+ unitPrice: number;
20
+ quantity?: number | undefined;
21
+ taxRate?: number | undefined;
22
+ }>, "many">;
23
+ title: z.ZodOptional<z.ZodString>;
24
+ introduction: z.ZodOptional<z.ZodString>;
25
+ remark: z.ZodOptional<z.ZodString>;
26
+ currency: z.ZodDefault<z.ZodString>;
27
+ finalize: z.ZodDefault<z.ZodBoolean>;
28
+ backend: z.ZodOptional<z.ZodString>;
29
+ }, "strip", z.ZodTypeAny, {
30
+ date: string;
31
+ lineItems: {
32
+ name: string;
33
+ quantity: number;
34
+ unitPrice: number;
35
+ taxRate: number;
36
+ }[];
37
+ finalize: boolean;
38
+ contactId: string;
39
+ currency: string;
40
+ title?: string | undefined;
41
+ introduction?: string | undefined;
42
+ remark?: string | undefined;
43
+ backend?: string | undefined;
44
+ }, {
45
+ date: string;
46
+ lineItems: {
47
+ name: string;
48
+ unitPrice: number;
49
+ quantity?: number | undefined;
50
+ taxRate?: number | undefined;
51
+ }[];
52
+ contactId: string;
53
+ title?: string | undefined;
54
+ introduction?: string | undefined;
55
+ remark?: string | undefined;
56
+ finalize?: boolean | undefined;
57
+ currency?: string | undefined;
58
+ backend?: string | undefined;
59
+ }>;
60
+ get_invoice: z.ZodObject<{
61
+ id: z.ZodString;
62
+ backend: z.ZodOptional<z.ZodString>;
63
+ }, "strip", z.ZodTypeAny, {
64
+ id: string;
65
+ backend?: string | undefined;
66
+ }, {
67
+ id: string;
68
+ backend?: string | undefined;
69
+ }>;
70
+ list_invoices: z.ZodObject<{
71
+ status: z.ZodOptional<z.ZodEnum<["draft", "open", "paid", "overdue"]>>;
72
+ contactId: z.ZodOptional<z.ZodString>;
73
+ limit: z.ZodDefault<z.ZodNumber>;
74
+ backend: z.ZodOptional<z.ZodString>;
75
+ }, "strip", z.ZodTypeAny, {
76
+ limit: number;
77
+ status?: "draft" | "open" | "paid" | "overdue" | undefined;
78
+ contactId?: string | undefined;
79
+ backend?: string | undefined;
80
+ }, {
81
+ status?: "draft" | "open" | "paid" | "overdue" | undefined;
82
+ contactId?: string | undefined;
83
+ backend?: string | undefined;
84
+ limit?: number | undefined;
85
+ }>;
86
+ get_invoice_pdf: z.ZodObject<{
87
+ id: z.ZodString;
88
+ backend: z.ZodOptional<z.ZodString>;
89
+ }, "strip", z.ZodTypeAny, {
90
+ id: string;
91
+ backend?: string | undefined;
92
+ }, {
93
+ id: string;
94
+ backend?: string | undefined;
95
+ }>;
96
+ update_invoice: z.ZodObject<{
97
+ id: z.ZodString;
98
+ title: z.ZodOptional<z.ZodString>;
99
+ introduction: z.ZodOptional<z.ZodString>;
100
+ remark: z.ZodOptional<z.ZodString>;
101
+ backend: z.ZodOptional<z.ZodString>;
102
+ }, "strip", z.ZodTypeAny, {
103
+ id: string;
104
+ title?: string | undefined;
105
+ introduction?: string | undefined;
106
+ remark?: string | undefined;
107
+ backend?: string | undefined;
108
+ }, {
109
+ id: string;
110
+ title?: string | undefined;
111
+ introduction?: string | undefined;
112
+ remark?: string | undefined;
113
+ backend?: string | undefined;
114
+ }>;
115
+ };
116
+ export declare function registerInvoiceTools(server: any, getBackend: (name?: string) => IAccountingBackend): void;
@@ -0,0 +1,92 @@
1
+ import { z } from "zod";
2
+ const lineItemSchema = z.object({
3
+ name: z.string().describe("Positionsname"),
4
+ quantity: z.number().default(1).describe("Menge"),
5
+ unitPrice: z.number().describe("Einzelpreis netto"),
6
+ taxRate: z.number().default(19).describe("Steuersatz in Prozent"),
7
+ });
8
+ export const invoiceSchemas = {
9
+ create_invoice: z.object({
10
+ contactId: z.string().describe("Kontakt-ID des Rechnungsempfaengers"),
11
+ date: z.string().describe("Rechnungsdatum (ISO 8601, z.B. 2026-03-18)"),
12
+ lineItems: z.array(lineItemSchema).min(1).describe("Rechnungspositionen"),
13
+ title: z.string().optional().describe("Rechnungstitel"),
14
+ introduction: z.string().optional().describe("Einleitungstext"),
15
+ remark: z.string().optional().describe("Schlusstext"),
16
+ currency: z.string().default("EUR").describe("Waehrung"),
17
+ finalize: z.boolean().default(false).describe("Rechnung direkt finalisieren (nicht mehr editierbar)"),
18
+ backend: z.string().optional(),
19
+ }),
20
+ get_invoice: z.object({
21
+ id: z.string().describe("Rechnungs-ID"),
22
+ backend: z.string().optional(),
23
+ }),
24
+ list_invoices: z.object({
25
+ status: z.enum(["draft", "open", "paid", "overdue"]).optional().describe("Nach Status filtern"),
26
+ contactId: z.string().optional().describe("Nach Kontakt filtern"),
27
+ limit: z.number().default(25).describe("Maximale Anzahl"),
28
+ backend: z.string().optional(),
29
+ }),
30
+ get_invoice_pdf: z.object({
31
+ id: z.string().describe("Rechnungs-ID"),
32
+ backend: z.string().optional(),
33
+ }),
34
+ update_invoice: z.object({
35
+ id: z.string().describe("Rechnungs-ID"),
36
+ title: z.string().optional(),
37
+ introduction: z.string().optional(),
38
+ remark: z.string().optional(),
39
+ backend: z.string().optional(),
40
+ }),
41
+ };
42
+ export function registerInvoiceTools(server, getBackend) {
43
+ server.tool("create_invoice", "Erstellt eine neue Rechnung mit Positionen. Creates a new invoice with line items.", invoiceSchemas.create_invoice.shape, async (args) => {
44
+ const backend = getBackend(args.backend);
45
+ const result = await backend.createInvoice({
46
+ contactId: args.contactId,
47
+ date: args.date,
48
+ lineItems: args.lineItems,
49
+ title: args.title,
50
+ introduction: args.introduction,
51
+ remark: args.remark,
52
+ currency: args.currency,
53
+ finalize: args.finalize,
54
+ });
55
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
56
+ });
57
+ server.tool("get_invoice", "Ruft eine Rechnung per ID ab. Gets an invoice by ID.", invoiceSchemas.get_invoice.shape, async (args) => {
58
+ const backend = getBackend(args.backend);
59
+ const result = await backend.getInvoice(args.id);
60
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
61
+ });
62
+ server.tool("list_invoices", "Listet Rechnungen auf. Kann nach Status filtern (draft, open, paid, overdue). Lists invoices with optional status filter.", invoiceSchemas.list_invoices.shape, async (args) => {
63
+ const backend = getBackend(args.backend);
64
+ const result = await backend.listInvoices({
65
+ status: args.status,
66
+ contactId: args.contactId,
67
+ limit: args.limit,
68
+ });
69
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
70
+ });
71
+ server.tool("get_invoice_pdf", "Laedt eine Rechnung als PDF herunter (Base64-kodiert). Downloads an invoice as PDF (base64-encoded).", invoiceSchemas.get_invoice_pdf.shape, async (args) => {
72
+ const backend = getBackend(args.backend);
73
+ const { data, filename } = await backend.getInvoicePdf(args.id);
74
+ return {
75
+ content: [{
76
+ type: "text",
77
+ text: JSON.stringify({
78
+ filename,
79
+ size_bytes: data.length,
80
+ base64: data.toString("base64"),
81
+ hint: "PDF ist base64-kodiert. Zum Speichern: Buffer.from(base64, 'base64') oder als data-URI verwenden.",
82
+ }, null, 2),
83
+ }],
84
+ };
85
+ });
86
+ server.tool("update_invoice", "Aktualisiert eine bestehende Rechnung (nur Entwuerfe). Updates a draft invoice.", invoiceSchemas.update_invoice.shape, async (args) => {
87
+ const backend = getBackend(args.backend);
88
+ const { id, backend: _, ...updateFields } = args;
89
+ const result = await backend.updateInvoice(id, updateFields);
90
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
91
+ });
92
+ }
@@ -0,0 +1,2 @@
1
+ import type { IAccountingBackend } from "../backends/types.js";
2
+ export declare function registerQuotationTools(server: any, getBackend: (name?: string) => IAccountingBackend): void;