billingengine-mcp 0.1.0 → 0.1.2
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 +7 -5
- package/dist/index.js +30 -7
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
# BillingEngine MCP server
|
|
2
2
|
|
|
3
|
+
[](https://allmcps.com/mcp/billingengine-mcp?verify=e69e154f-3c18-4a25-b8a9-42bfcd9c6182)
|
|
4
|
+
|
|
3
5
|
Create customers and invoices, look up open invoices and record payments in
|
|
4
6
|
[BillingEngine](https://www.billingengine.com) by talking to Claude, Cursor or any other
|
|
5
7
|
[MCP](https://modelcontextprotocol.io) client:
|
|
6
8
|
|
|
7
|
-
> "Create an invoice for
|
|
9
|
+
> "Create an invoice for Acme Painting over 100 euros."
|
|
8
10
|
>
|
|
9
11
|
> "Which invoices are overdue?"
|
|
10
12
|
>
|
|
11
|
-
> "
|
|
13
|
+
> "Acme paid invoice 2610030028, record the payment."
|
|
12
14
|
|
|
13
15
|
BillingEngine is invoicing software for freelancers and small businesses with
|
|
14
|
-
[e-invoicing](https://www.billingengine.com/en/features) (XRechnung
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
[e-invoicing](https://www.billingengine.com/en/features) (EN 16931, XRechnung, ZUGFeRD / Factur-X).
|
|
17
|
+
The server is a thin layer over the [BillingEngine API](https://www.billingengine.com/en/api)
|
|
18
|
+
and runs on your computer.
|
|
17
19
|
|
|
18
20
|
## Requirements
|
|
19
21
|
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const INSTRUCTIONS = [
|
|
|
20
20
|
"Invoices are created as drafts and are not sent: tell the user to review and send them in BillingEngine, using the app_url in the result.",
|
|
21
21
|
"Nothing can be deleted through this server.",
|
|
22
22
|
].join(" ");
|
|
23
|
-
const server = new McpServer({ name: "billingengine", version: "0.1.
|
|
23
|
+
const server = new McpServer({ name: "billingengine", version: "0.1.2" }, { instructions: INSTRUCTIONS });
|
|
24
24
|
const date = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Use YYYY-MM-DD");
|
|
25
25
|
const page = z.number().int().min(1).optional().describe("Page of the list, starting at 1");
|
|
26
26
|
const address = z.object({
|
|
@@ -51,17 +51,20 @@ const item = z.object({
|
|
|
51
51
|
const invoiceFields = {
|
|
52
52
|
date: date.optional().describe("Invoice date, defaults to today"),
|
|
53
53
|
due_days: z.number().int().min(0).optional().describe("Days until payment is due"),
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
opening_text: z.string().optional().describe("Text above the items, defaults to the account's text"),
|
|
55
|
+
closing_text: z.string().optional().describe("Text below the items, defaults to the account's text"),
|
|
56
56
|
service_period_start_date: date.optional(),
|
|
57
57
|
service_period_end_date: date.optional(),
|
|
58
58
|
};
|
|
59
59
|
const READ = { readOnlyHint: true, openWorldHint: false };
|
|
60
60
|
const WRITE = { readOnlyHint: false, destructiveHint: false, openWorldHint: false };
|
|
61
|
+
const UPDATE = { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false };
|
|
62
|
+
const output = z.looseObject({});
|
|
61
63
|
async function respond(action) {
|
|
62
64
|
try {
|
|
63
65
|
const value = await action();
|
|
64
|
-
|
|
66
|
+
const structured = typeof value === "string" ? { message: value } : value;
|
|
67
|
+
return { content: [{ type: "text", text: JSON.stringify(structured, null, 2) }], structuredContent: structured };
|
|
65
68
|
}
|
|
66
69
|
catch (error) {
|
|
67
70
|
const message = error instanceof BillingEngineError ? error.message : `Unexpected error: ${error.message}`;
|
|
@@ -81,32 +84,37 @@ server.registerTool("find_customers", {
|
|
|
81
84
|
title: "Find customers",
|
|
82
85
|
description: "Lists customers, optionally filtered by a search text that matches company, names and email.",
|
|
83
86
|
inputSchema: { query: z.string().optional(), page },
|
|
87
|
+
outputSchema: output,
|
|
84
88
|
annotations: READ,
|
|
85
89
|
}, ({ query, page }) => respond(() => client.get("/customers", { query, page })));
|
|
86
90
|
server.registerTool("create_customer", {
|
|
87
91
|
title: "Create a customer",
|
|
88
92
|
description: "Creates a customer. Needs a company or a last name and a full address (line 1, postal code, city, country).",
|
|
89
93
|
inputSchema: { ...customerFields, address },
|
|
94
|
+
outputSchema: output,
|
|
90
95
|
annotations: WRITE,
|
|
91
96
|
}, ({ address, ...fields }) => respond(() => client.post("/customers", { ...fields, address_attributes: address })));
|
|
92
97
|
server.registerTool("update_customer", {
|
|
93
98
|
title: "Update a customer",
|
|
94
99
|
description: "Changes the given fields of a customer; fields that are left out stay as they are.",
|
|
95
100
|
inputSchema: { id: z.number().int(), ...customerFields, address: address.partial().optional() },
|
|
96
|
-
|
|
101
|
+
outputSchema: output,
|
|
102
|
+
annotations: UPDATE,
|
|
97
103
|
}, ({ id, address, ...fields }) => respond(() => client.patch(`/customers/${id}`, { ...fields, ...(address && { address_attributes: address }) })));
|
|
98
104
|
server.registerTool("create_invoice", {
|
|
99
105
|
title: "Create a draft invoice",
|
|
100
106
|
description: "Creates a draft invoice for a customer. Number, language, texts and VAT treatment come from the account settings. " +
|
|
101
107
|
"The invoice is not sent; it counts towards the monthly invoice maximum of the plan.",
|
|
102
108
|
inputSchema: { customer_id: z.number().int(), items: z.array(item).min(1), ...invoiceFields },
|
|
109
|
+
outputSchema: output,
|
|
103
110
|
annotations: WRITE,
|
|
104
111
|
}, (input) => respond(async () => withInvoiceLinks(await client.post("/invoices", input))));
|
|
105
112
|
server.registerTool("update_invoice", {
|
|
106
113
|
title: "Update a draft invoice",
|
|
107
114
|
description: "Changes a draft invoice. Passing items replaces all existing items, so send the complete list. Sent invoices cannot be changed.",
|
|
108
115
|
inputSchema: { id: z.number().int(), items: z.array(item).min(1).optional(), ...invoiceFields },
|
|
109
|
-
|
|
116
|
+
outputSchema: output,
|
|
117
|
+
annotations: UPDATE,
|
|
110
118
|
}, ({ id, ...changes }) => respond(async () => withInvoiceLinks(await client.patch(`/invoices/${id}`, changes))));
|
|
111
119
|
server.registerTool("list_invoices", {
|
|
112
120
|
title: "List invoices",
|
|
@@ -120,12 +128,14 @@ server.registerTool("list_invoices", {
|
|
|
120
128
|
page,
|
|
121
129
|
per_page: z.number().int().min(1).max(100).optional(),
|
|
122
130
|
},
|
|
131
|
+
outputSchema: output,
|
|
123
132
|
annotations: READ,
|
|
124
133
|
}, (filters) => respond(async () => withInvoiceLinks(await client.get("/invoices", filters))));
|
|
125
134
|
server.registerTool("get_invoice", {
|
|
126
135
|
title: "Get an invoice",
|
|
127
136
|
description: "Returns one invoice with its items, amounts and payment status.",
|
|
128
137
|
inputSchema: { id: z.number().int() },
|
|
138
|
+
outputSchema: output,
|
|
129
139
|
annotations: READ,
|
|
130
140
|
}, ({ id }) => respond(async () => withInvoiceLinks(await client.get(`/invoices/${id}`))));
|
|
131
141
|
server.registerTool("download_invoice", {
|
|
@@ -137,6 +147,7 @@ server.registerTool("download_invoice", {
|
|
|
137
147
|
format: z.enum(["pdf", "xml"]).default("pdf"),
|
|
138
148
|
directory: z.string().optional().describe("Target folder, defaults to ~/Downloads"),
|
|
139
149
|
},
|
|
150
|
+
outputSchema: output,
|
|
140
151
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
141
152
|
}, ({ id, format, directory }) => respond(async () => {
|
|
142
153
|
const file = await client.getFile(`/invoices/${id}.${format}`);
|
|
@@ -155,12 +166,24 @@ server.registerTool("record_payment", {
|
|
|
155
166
|
payment_method: z.enum(["transfer", "cash"]).optional().describe("Defaults to transfer"),
|
|
156
167
|
date: date.optional().describe("Defaults to today"),
|
|
157
168
|
},
|
|
169
|
+
outputSchema: output,
|
|
158
170
|
annotations: WRITE,
|
|
159
|
-
}, (input) => respond(() =>
|
|
171
|
+
}, (input) => respond(async () => {
|
|
172
|
+
try {
|
|
173
|
+
return await client.post("/payments", input);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (error instanceof BillingEngineError && error.message.includes("Invoices is invalid")) {
|
|
177
|
+
throw new BillingEngineError(`${error.message} Payments can only be recorded for sent, open invoices of the same customer and tax rate; drafts cannot be paid.`);
|
|
178
|
+
}
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}));
|
|
160
182
|
server.registerTool("list_payments", {
|
|
161
183
|
title: "List payments",
|
|
162
184
|
description: "Lists recorded payments, newest first, optionally only those for one invoice.",
|
|
163
185
|
inputSchema: { invoice_id: z.number().int().optional(), page },
|
|
186
|
+
outputSchema: output,
|
|
164
187
|
annotations: READ,
|
|
165
188
|
}, (filters) => respond(() => client.get("/payments", filters)));
|
|
166
189
|
await server.connect(new StdioServerTransport());
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "billingengine-mcp",
|
|
3
3
|
"mcpName": "com.billingengine/mcp",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"description": "MCP server for BillingEngine: create customers and invoices, look up invoices and record payments from Claude, Cursor and other MCP clients.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mcp",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"xrechnung",
|
|
14
14
|
"zugferd"
|
|
15
15
|
],
|
|
16
|
-
"homepage": "https://www.billingengine.com/mcp",
|
|
16
|
+
"homepage": "https://www.billingengine.com/en/mcp",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
19
|
"url": "git+https://github.com/billingengine/billingengine-mcp.git"
|
|
@@ -37,14 +37,18 @@
|
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsc && chmod +x dist/index.js",
|
|
40
|
-
"prepublishOnly": "npm run build"
|
|
40
|
+
"prepublishOnly": "npm run build",
|
|
41
|
+
"bundle": "npm run build && esbuild dist/index.js --bundle --platform=node --format=esm --target=node20 --outfile=mcpb/server/index.js --banner:js=\"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);\" && mcpb pack mcpb billingengine.mcpb",
|
|
42
|
+
"bundle:smithery": "npm run bundle && node scripts/smithery-bundle.mjs"
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
45
|
"@modelcontextprotocol/sdk": "^1.32.0",
|
|
44
46
|
"zod": "^4.6.5"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
49
|
+
"@anthropic-ai/mcpb": "^2.1.2",
|
|
47
50
|
"@types/node": "^26.6.4",
|
|
51
|
+
"esbuild": "^0.28.2",
|
|
48
52
|
"typescript": "^7.0.2"
|
|
49
53
|
}
|
|
50
54
|
}
|