billingengine-mcp 0.1.0 → 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.
- package/README.md +5 -5
- package/dist/index.js +25 -3
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -4,16 +4,16 @@ Create customers and invoices, look up open invoices and record payments in
|
|
|
4
4
|
[BillingEngine](https://www.billingengine.com) by talking to Claude, Cursor or any other
|
|
5
5
|
[MCP](https://modelcontextprotocol.io) client:
|
|
6
6
|
|
|
7
|
-
> "Create an invoice for
|
|
7
|
+
> "Create an invoice for Acme Painting over 100 euros."
|
|
8
8
|
>
|
|
9
9
|
> "Which invoices are overdue?"
|
|
10
10
|
>
|
|
11
|
-
> "
|
|
11
|
+
> "Acme paid invoice 2610030028, record the payment."
|
|
12
12
|
|
|
13
13
|
BillingEngine is invoicing software for freelancers and small businesses with
|
|
14
|
-
[e-invoicing](https://www.billingengine.com/en/features) (XRechnung
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
[e-invoicing](https://www.billingengine.com/en/features) (EN 16931, XRechnung, ZUGFeRD / Factur-X).
|
|
15
|
+
The server is a thin layer over the [BillingEngine API](https://www.billingengine.com/en/api)
|
|
16
|
+
and runs on your computer.
|
|
17
17
|
|
|
18
18
|
## Requirements
|
|
19
19
|
|
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.1" }, { 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({
|
|
@@ -58,10 +58,12 @@ const invoiceFields = {
|
|
|
58
58
|
};
|
|
59
59
|
const READ = { readOnlyHint: true, openWorldHint: false };
|
|
60
60
|
const WRITE = { readOnlyHint: false, destructiveHint: false, openWorldHint: false };
|
|
61
|
+
const output = z.looseObject({});
|
|
61
62
|
async function respond(action) {
|
|
62
63
|
try {
|
|
63
64
|
const value = await action();
|
|
64
|
-
|
|
65
|
+
const structured = typeof value === "string" ? { message: value } : value;
|
|
66
|
+
return { content: [{ type: "text", text: JSON.stringify(structured, null, 2) }], structuredContent: structured };
|
|
65
67
|
}
|
|
66
68
|
catch (error) {
|
|
67
69
|
const message = error instanceof BillingEngineError ? error.message : `Unexpected error: ${error.message}`;
|
|
@@ -81,18 +83,21 @@ server.registerTool("find_customers", {
|
|
|
81
83
|
title: "Find customers",
|
|
82
84
|
description: "Lists customers, optionally filtered by a search text that matches company, names and email.",
|
|
83
85
|
inputSchema: { query: z.string().optional(), page },
|
|
86
|
+
outputSchema: output,
|
|
84
87
|
annotations: READ,
|
|
85
88
|
}, ({ query, page }) => respond(() => client.get("/customers", { query, page })));
|
|
86
89
|
server.registerTool("create_customer", {
|
|
87
90
|
title: "Create a customer",
|
|
88
91
|
description: "Creates a customer. Needs a company or a last name and a full address (line 1, postal code, city, country).",
|
|
89
92
|
inputSchema: { ...customerFields, address },
|
|
93
|
+
outputSchema: output,
|
|
90
94
|
annotations: WRITE,
|
|
91
95
|
}, ({ address, ...fields }) => respond(() => client.post("/customers", { ...fields, address_attributes: address })));
|
|
92
96
|
server.registerTool("update_customer", {
|
|
93
97
|
title: "Update a customer",
|
|
94
98
|
description: "Changes the given fields of a customer; fields that are left out stay as they are.",
|
|
95
99
|
inputSchema: { id: z.number().int(), ...customerFields, address: address.partial().optional() },
|
|
100
|
+
outputSchema: output,
|
|
96
101
|
annotations: { ...WRITE, idempotentHint: true },
|
|
97
102
|
}, ({ id, address, ...fields }) => respond(() => client.patch(`/customers/${id}`, { ...fields, ...(address && { address_attributes: address }) })));
|
|
98
103
|
server.registerTool("create_invoice", {
|
|
@@ -100,12 +105,14 @@ server.registerTool("create_invoice", {
|
|
|
100
105
|
description: "Creates a draft invoice for a customer. Number, language, texts and VAT treatment come from the account settings. " +
|
|
101
106
|
"The invoice is not sent; it counts towards the monthly invoice maximum of the plan.",
|
|
102
107
|
inputSchema: { customer_id: z.number().int(), items: z.array(item).min(1), ...invoiceFields },
|
|
108
|
+
outputSchema: output,
|
|
103
109
|
annotations: WRITE,
|
|
104
110
|
}, (input) => respond(async () => withInvoiceLinks(await client.post("/invoices", input))));
|
|
105
111
|
server.registerTool("update_invoice", {
|
|
106
112
|
title: "Update a draft invoice",
|
|
107
113
|
description: "Changes a draft invoice. Passing items replaces all existing items, so send the complete list. Sent invoices cannot be changed.",
|
|
108
114
|
inputSchema: { id: z.number().int(), items: z.array(item).min(1).optional(), ...invoiceFields },
|
|
115
|
+
outputSchema: output,
|
|
109
116
|
annotations: { ...WRITE, idempotentHint: true },
|
|
110
117
|
}, ({ id, ...changes }) => respond(async () => withInvoiceLinks(await client.patch(`/invoices/${id}`, changes))));
|
|
111
118
|
server.registerTool("list_invoices", {
|
|
@@ -120,12 +127,14 @@ server.registerTool("list_invoices", {
|
|
|
120
127
|
page,
|
|
121
128
|
per_page: z.number().int().min(1).max(100).optional(),
|
|
122
129
|
},
|
|
130
|
+
outputSchema: output,
|
|
123
131
|
annotations: READ,
|
|
124
132
|
}, (filters) => respond(async () => withInvoiceLinks(await client.get("/invoices", filters))));
|
|
125
133
|
server.registerTool("get_invoice", {
|
|
126
134
|
title: "Get an invoice",
|
|
127
135
|
description: "Returns one invoice with its items, amounts and payment status.",
|
|
128
136
|
inputSchema: { id: z.number().int() },
|
|
137
|
+
outputSchema: output,
|
|
129
138
|
annotations: READ,
|
|
130
139
|
}, ({ id }) => respond(async () => withInvoiceLinks(await client.get(`/invoices/${id}`))));
|
|
131
140
|
server.registerTool("download_invoice", {
|
|
@@ -137,6 +146,7 @@ server.registerTool("download_invoice", {
|
|
|
137
146
|
format: z.enum(["pdf", "xml"]).default("pdf"),
|
|
138
147
|
directory: z.string().optional().describe("Target folder, defaults to ~/Downloads"),
|
|
139
148
|
},
|
|
149
|
+
outputSchema: output,
|
|
140
150
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
141
151
|
}, ({ id, format, directory }) => respond(async () => {
|
|
142
152
|
const file = await client.getFile(`/invoices/${id}.${format}`);
|
|
@@ -155,12 +165,24 @@ server.registerTool("record_payment", {
|
|
|
155
165
|
payment_method: z.enum(["transfer", "cash"]).optional().describe("Defaults to transfer"),
|
|
156
166
|
date: date.optional().describe("Defaults to today"),
|
|
157
167
|
},
|
|
168
|
+
outputSchema: output,
|
|
158
169
|
annotations: WRITE,
|
|
159
|
-
}, (input) => respond(() =>
|
|
170
|
+
}, (input) => respond(async () => {
|
|
171
|
+
try {
|
|
172
|
+
return await client.post("/payments", input);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (error instanceof BillingEngineError && error.message.includes("Invoices is invalid")) {
|
|
176
|
+
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.`);
|
|
177
|
+
}
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}));
|
|
160
181
|
server.registerTool("list_payments", {
|
|
161
182
|
title: "List payments",
|
|
162
183
|
description: "Lists recorded payments, newest first, optionally only those for one invoice.",
|
|
163
184
|
inputSchema: { invoice_id: z.number().int().optional(), page },
|
|
185
|
+
outputSchema: output,
|
|
164
186
|
annotations: READ,
|
|
165
187
|
}, (filters) => respond(() => client.get("/payments", filters)));
|
|
166
188
|
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.1",
|
|
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
|
}
|