visma-eaccounting-mcp-server 1.0.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 ADDED
@@ -0,0 +1,60 @@
1
+ # Visma eAccounting MCP Server
2
+
3
+ Connect AI agents to **Visma eAccounting** — used by businesses across Norway, Finland, Netherlands, and UK.
4
+
5
+ Manage invoices, customers, suppliers, accounting entries, orders, and more through the Model Context Protocol.
6
+
7
+ ## Tools (20)
8
+
9
+ | Category | Tools | Description |
10
+ |----------|-------|-------------|
11
+ | **Invoices** | `list_invoices`, `get_invoice`, `create_invoice`, `send_invoice`, `list_invoice_payments` | Create, send, and track invoices |
12
+ | **Customers** | `list_customers`, `get_customer`, `create_customer` | Manage your customer database |
13
+ | **Suppliers** | `list_supplier_invoices`, `get_supplier`, `create_supplier_invoice` | Track bills and expenses |
14
+ | **Accounting** | `list_vouchers`, `create_voucher`, `list_accounts`, `list_fiscal_years` | Journal entries and chart of accounts |
15
+ | **Reports** | `get_company_info`, `list_projects`, `list_cost_centers` | Company info and cost tracking |
16
+ | **Orders** | `list_orders`, `create_quote` | Sales orders and quotes |
17
+
18
+ ## Quick Start
19
+
20
+ ### 1. Get a Visma Access Token
21
+
22
+ Register as a partner at [developer.visma.com](https://developer.visma.com/api/eaccounting) and create an application to get your OAuth2 credentials.
23
+
24
+ ### 2. Configure in Claude Desktop
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "visma": {
30
+ "command": "npx",
31
+ "args": ["-y", "visma-eaccounting-mcp-server"],
32
+ "env": {
33
+ "VISMA_ACCESS_TOKEN": "your-access-token-here"
34
+ }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ### 3. Start Using
41
+
42
+ Ask Claude things like:
43
+ - "List all unpaid invoices"
44
+ - "Create an invoice for customer X with 5 hours consulting at 150 EUR/hr"
45
+ - "What supplier invoices are due this week?"
46
+ - "Show me the chart of accounts"
47
+
48
+ ## Environment Variables
49
+
50
+ | Variable | Required | Description |
51
+ |----------|----------|-------------|
52
+ | `VISMA_ACCESS_TOKEN` | Yes | OAuth2 access token from Visma |
53
+
54
+ ## API Reference
55
+
56
+ This server wraps the [Visma eAccounting API v2](https://developer.visma.com/api/eaccounting). Rate limit: 600 requests per minute.
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Visma eAccounting MCP Server
4
+ *
5
+ * Connects AI agents to Visma eAccounting — used across Norway, Finland,
6
+ * Netherlands, and UK. Manage invoices, customers, suppliers, and more.
7
+ */
8
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Visma eAccounting MCP Server
4
+ *
5
+ * Connects AI agents to Visma eAccounting — used across Norway, Finland,
6
+ * Netherlands, and UK. Manage invoices, customers, suppliers, and more.
7
+ */
8
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
+ import { z } from "zod";
11
+ import { VismaClient } from "./visma-client.js";
12
+ const server = new McpServer({
13
+ name: "visma-eaccounting-mcp-server",
14
+ version: "1.0.0",
15
+ });
16
+ function getClient() {
17
+ const token = process.env.VISMA_ACCESS_TOKEN;
18
+ if (!token) {
19
+ throw new Error("VISMA_ACCESS_TOKEN environment variable is required. " +
20
+ "Register as a partner at https://developer.visma.com to get credentials.");
21
+ }
22
+ return new VismaClient({ accessToken: token });
23
+ }
24
+ function jsonResponse(data) {
25
+ return {
26
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
27
+ };
28
+ }
29
+ // ── INVOICES (5 tools) ────────────────────────────────────────
30
+ server.tool("list_invoices", "List and filter customer invoices. Uses OData $filter syntax.", {
31
+ filter: z.string().optional().describe("OData filter (e.g. \"IsPaid eq false\")"),
32
+ top: z.string().optional().describe("Number of results to return"),
33
+ skip: z.string().optional().describe("Number of results to skip (pagination)"),
34
+ }, async ({ filter, top, skip }) => {
35
+ const client = getClient();
36
+ const result = await client.listCustomerInvoices({
37
+ $filter: filter,
38
+ $top: top,
39
+ $skip: skip,
40
+ });
41
+ return jsonResponse(result);
42
+ });
43
+ server.tool("get_invoice", "Get full details of a specific customer invoice.", {
44
+ id: z.string().describe("Invoice ID (GUID)"),
45
+ }, async ({ id }) => {
46
+ const client = getClient();
47
+ const result = await client.getCustomerInvoice(id);
48
+ return jsonResponse(result);
49
+ });
50
+ server.tool("create_invoice", "Create a new customer invoice with line items.", {
51
+ customerId: z.string().describe("Customer ID (GUID)"),
52
+ rows: z.array(z.object({
53
+ text: z.string().describe("Line item description"),
54
+ quantity: z.number().describe("Quantity"),
55
+ unitPrice: z.number().describe("Unit price"),
56
+ articleId: z.string().optional().describe("Article/product ID"),
57
+ accountNumber: z.number().optional().describe("Account number"),
58
+ })).describe("Invoice line items"),
59
+ dueDate: z.string().optional().describe("Due date (YYYY-MM-DD)"),
60
+ invoiceDate: z.string().optional().describe("Invoice date (YYYY-MM-DD)"),
61
+ currencyCode: z.string().optional().describe("Currency (e.g. NOK, EUR, SEK)"),
62
+ ourReference: z.string().optional().describe("Your reference"),
63
+ yourReference: z.string().optional().describe("Customer's reference"),
64
+ }, async ({ customerId, rows, dueDate, invoiceDate, currencyCode, ourReference, yourReference }) => {
65
+ const client = getClient();
66
+ const result = await client.createCustomerInvoice({
67
+ CustomerId: customerId,
68
+ Rows: rows.map((r) => ({
69
+ Text: r.text,
70
+ Quantity: r.quantity,
71
+ UnitPrice: r.unitPrice,
72
+ ArticleId: r.articleId,
73
+ AccountNumber: r.accountNumber,
74
+ })),
75
+ DueDate: dueDate,
76
+ InvoiceDate: invoiceDate,
77
+ CurrencyCode: currencyCode,
78
+ OurReference: ourReference,
79
+ YourReference: yourReference,
80
+ });
81
+ return jsonResponse(result);
82
+ });
83
+ server.tool("send_invoice", "Send a customer invoice via email.", {
84
+ id: z.string().describe("Invoice ID (GUID) to send"),
85
+ }, async ({ id }) => {
86
+ const client = getClient();
87
+ const result = await client.sendCustomerInvoice(id);
88
+ return jsonResponse(result);
89
+ });
90
+ server.tool("list_invoice_payments", "List customer invoice payments.", {
91
+ filter: z.string().optional().describe("OData filter"),
92
+ top: z.string().optional().describe("Number of results"),
93
+ skip: z.string().optional().describe("Skip for pagination"),
94
+ }, async ({ filter, top, skip }) => {
95
+ const client = getClient();
96
+ const result = await client.listCustomerInvoicePayments({
97
+ $filter: filter,
98
+ $top: top,
99
+ $skip: skip,
100
+ });
101
+ return jsonResponse(result);
102
+ });
103
+ // ── CUSTOMERS (3 tools) ───────────────────────────────────────
104
+ server.tool("list_customers", "Search and filter customers.", {
105
+ filter: z.string().optional().describe("OData filter (e.g. \"contains(Name,'Acme')\")"),
106
+ top: z.string().optional().describe("Number of results"),
107
+ skip: z.string().optional().describe("Skip for pagination"),
108
+ }, async ({ filter, top, skip }) => {
109
+ const client = getClient();
110
+ const result = await client.listCustomers({ $filter: filter, $top: top, $skip: skip });
111
+ return jsonResponse(result);
112
+ });
113
+ server.tool("get_customer", "Get full details of a specific customer.", {
114
+ id: z.string().describe("Customer ID (GUID)"),
115
+ }, async ({ id }) => {
116
+ const client = getClient();
117
+ const result = await client.getCustomer(id);
118
+ return jsonResponse(result);
119
+ });
120
+ server.tool("create_customer", "Add a new customer.", {
121
+ name: z.string().describe("Customer/company name"),
122
+ email: z.string().optional().describe("Email address"),
123
+ corporateIdentityNumber: z.string().optional().describe("Organisation number"),
124
+ phone: z.string().optional().describe("Phone number"),
125
+ address: z.string().optional().describe("Street address"),
126
+ zipCode: z.string().optional().describe("Postal code"),
127
+ city: z.string().optional().describe("City"),
128
+ countryCode: z.string().optional().describe("Country code (e.g. NO, FI, SE)"),
129
+ currencyCode: z.string().optional().describe("Default currency (e.g. NOK, EUR)"),
130
+ }, async ({ name, email, corporateIdentityNumber, phone, address, zipCode, city, countryCode, currencyCode }) => {
131
+ const client = getClient();
132
+ const result = await client.createCustomer({
133
+ Name: name,
134
+ EmailAddress: email,
135
+ CorporateIdentityNumber: corporateIdentityNumber,
136
+ Telephone: phone,
137
+ Address1: address,
138
+ ZipCode: zipCode,
139
+ City: city,
140
+ CountryCode: countryCode,
141
+ CurrencyCode: currencyCode,
142
+ });
143
+ return jsonResponse(result);
144
+ });
145
+ // ── SUPPLIERS & EXPENSES (3 tools) ────────────────────────────
146
+ server.tool("list_supplier_invoices", "List incoming supplier invoices (bills/expenses).", {
147
+ filter: z.string().optional().describe("OData filter"),
148
+ top: z.string().optional().describe("Number of results"),
149
+ skip: z.string().optional().describe("Skip for pagination"),
150
+ }, async ({ filter, top, skip }) => {
151
+ const client = getClient();
152
+ const result = await client.listSupplierInvoices({ $filter: filter, $top: top, $skip: skip });
153
+ return jsonResponse(result);
154
+ });
155
+ server.tool("get_supplier", "Get details of a specific supplier/vendor.", {
156
+ id: z.string().describe("Supplier ID (GUID)"),
157
+ }, async ({ id }) => {
158
+ const client = getClient();
159
+ const result = await client.getSupplier(id);
160
+ return jsonResponse(result);
161
+ });
162
+ server.tool("create_supplier_invoice", "Record a new supplier invoice (expense/bill).", {
163
+ supplierId: z.string().describe("Supplier ID (GUID)"),
164
+ rows: z.array(z.object({
165
+ accountNumber: z.number().describe("Account number"),
166
+ debitAmount: z.number().optional().describe("Debit amount"),
167
+ creditAmount: z.number().optional().describe("Credit amount"),
168
+ })).describe("Invoice accounting rows (must balance)"),
169
+ invoiceDate: z.string().optional().describe("Invoice date (YYYY-MM-DD)"),
170
+ dueDate: z.string().optional().describe("Due date (YYYY-MM-DD)"),
171
+ totalAmount: z.number().optional().describe("Total amount"),
172
+ }, async ({ supplierId, rows, invoiceDate, dueDate, totalAmount }) => {
173
+ const client = getClient();
174
+ const result = await client.createSupplierInvoice({
175
+ SupplierId: supplierId,
176
+ Rows: rows.map((r) => ({
177
+ AccountNumber: r.accountNumber,
178
+ DebitAmount: r.debitAmount,
179
+ CreditAmount: r.creditAmount,
180
+ })),
181
+ InvoiceDate: invoiceDate,
182
+ DueDate: dueDate,
183
+ TotalAmount: totalAmount,
184
+ });
185
+ return jsonResponse(result);
186
+ });
187
+ // ── ACCOUNTING (4 tools) ──────────────────────────────────────
188
+ server.tool("list_vouchers", "List journal entries/vouchers.", {
189
+ filter: z.string().optional().describe("OData filter"),
190
+ top: z.string().optional().describe("Number of results"),
191
+ skip: z.string().optional().describe("Skip for pagination"),
192
+ }, async ({ filter, top, skip }) => {
193
+ const client = getClient();
194
+ const result = await client.listVouchers({ $filter: filter, $top: top, $skip: skip });
195
+ return jsonResponse(result);
196
+ });
197
+ server.tool("create_voucher", "Create a manual journal entry. Rows must balance (total debit = total credit).", {
198
+ description: z.string().describe("Voucher description"),
199
+ voucherDate: z.string().describe("Date (YYYY-MM-DD)"),
200
+ rows: z.array(z.object({
201
+ accountNumber: z.number().describe("Account number"),
202
+ debitAmount: z.number().optional().describe("Debit amount"),
203
+ creditAmount: z.number().optional().describe("Credit amount"),
204
+ description: z.string().optional().describe("Row description"),
205
+ })).describe("Voucher rows (must balance)"),
206
+ }, async ({ description, voucherDate, rows }) => {
207
+ const client = getClient();
208
+ const result = await client.createVoucher({
209
+ Description: description,
210
+ VoucherDate: voucherDate,
211
+ Rows: rows.map((r) => ({
212
+ AccountNumber: r.accountNumber,
213
+ DebitAmount: r.debitAmount,
214
+ CreditAmount: r.creditAmount,
215
+ Description: r.description,
216
+ })),
217
+ });
218
+ return jsonResponse(result);
219
+ });
220
+ server.tool("list_accounts", "Get the chart of accounts.", {
221
+ filter: z.string().optional().describe("OData filter"),
222
+ top: z.string().optional().describe("Number of results"),
223
+ skip: z.string().optional().describe("Skip for pagination"),
224
+ }, async ({ filter, top, skip }) => {
225
+ const client = getClient();
226
+ const result = await client.listAccounts({ $filter: filter, $top: top, $skip: skip });
227
+ return jsonResponse(result);
228
+ });
229
+ server.tool("list_fiscal_years", "Get information about fiscal/accounting years.", {}, async () => {
230
+ const client = getClient();
231
+ const result = await client.listFiscalYears();
232
+ return jsonResponse(result);
233
+ });
234
+ // ── REPORTS & ANALYTICS (3 tools) ─────────────────────────────
235
+ server.tool("get_company_info", "Get company information and settings.", {}, async () => {
236
+ const client = getClient();
237
+ const result = await client.getCompanyInfo();
238
+ return jsonResponse(result);
239
+ });
240
+ server.tool("list_projects", "List all projects.", {
241
+ top: z.string().optional().describe("Number of results"),
242
+ skip: z.string().optional().describe("Skip for pagination"),
243
+ }, async ({ top, skip }) => {
244
+ const client = getClient();
245
+ const result = await client.listProjects({ $top: top, $skip: skip });
246
+ return jsonResponse(result);
247
+ });
248
+ server.tool("list_cost_centers", "List cost centers for expense allocation.", {
249
+ top: z.string().optional().describe("Number of results"),
250
+ skip: z.string().optional().describe("Skip for pagination"),
251
+ }, async ({ top, skip }) => {
252
+ const client = getClient();
253
+ const result = await client.listCostCenters({ $top: top, $skip: skip });
254
+ return jsonResponse(result);
255
+ });
256
+ // ── ORDERS & QUOTES (2 tools) ─────────────────────────────────
257
+ server.tool("list_orders", "List and filter sales orders.", {
258
+ filter: z.string().optional().describe("OData filter"),
259
+ top: z.string().optional().describe("Number of results"),
260
+ skip: z.string().optional().describe("Skip for pagination"),
261
+ }, async ({ filter, top, skip }) => {
262
+ const client = getClient();
263
+ const result = await client.listOrders({ $filter: filter, $top: top, $skip: skip });
264
+ return jsonResponse(result);
265
+ });
266
+ server.tool("create_quote", "Create a quote/offer for a customer.", {
267
+ customerId: z.string().describe("Customer ID (GUID)"),
268
+ rows: z.array(z.object({
269
+ text: z.string().describe("Line item description"),
270
+ quantity: z.number().describe("Quantity"),
271
+ unitPrice: z.number().describe("Unit price"),
272
+ articleId: z.string().optional().describe("Article ID"),
273
+ })).describe("Quote line items"),
274
+ quoteDate: z.string().optional().describe("Quote date (YYYY-MM-DD)"),
275
+ expirationDate: z.string().optional().describe("Expiry date (YYYY-MM-DD)"),
276
+ ourReference: z.string().optional().describe("Your reference"),
277
+ yourReference: z.string().optional().describe("Customer's reference"),
278
+ }, async ({ customerId, rows, quoteDate, expirationDate, ourReference, yourReference }) => {
279
+ const client = getClient();
280
+ const result = await client.createQuote({
281
+ CustomerId: customerId,
282
+ Rows: rows.map((r) => ({
283
+ Text: r.text,
284
+ Quantity: r.quantity,
285
+ UnitPrice: r.unitPrice,
286
+ ArticleId: r.articleId,
287
+ })),
288
+ QuoteDate: quoteDate,
289
+ ExpirationDate: expirationDate,
290
+ OurReference: ourReference,
291
+ YourReference: yourReference,
292
+ });
293
+ return jsonResponse(result);
294
+ });
295
+ // ── Start server ──────────────────────────────────────────────
296
+ async function main() {
297
+ const transport = new StdioServerTransport();
298
+ await server.connect(transport);
299
+ }
300
+ main().catch((error) => {
301
+ console.error("Server error:", error);
302
+ process.exit(1);
303
+ });
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Visma eAccounting REST API client.
3
+ * API docs: https://eaccountingapi.vismaonline.com/swagger/ui/index
4
+ * Production: https://eaccountingapi.vismaonline.com/v2
5
+ */
6
+ interface VismaConfig {
7
+ accessToken: string;
8
+ }
9
+ export declare class VismaClient {
10
+ private accessToken;
11
+ constructor(config: VismaConfig);
12
+ private request;
13
+ listCustomerInvoices(params?: {
14
+ $filter?: string;
15
+ $top?: string;
16
+ $skip?: string;
17
+ }): Promise<unknown>;
18
+ getCustomerInvoice(id: string): Promise<unknown>;
19
+ createCustomerInvoice(invoice: {
20
+ CustomerId: string;
21
+ Rows: Array<{
22
+ ArticleId?: string;
23
+ Text?: string;
24
+ Quantity?: number;
25
+ UnitPrice?: number;
26
+ AccountNumber?: number;
27
+ }>;
28
+ DueDate?: string;
29
+ InvoiceDate?: string;
30
+ CurrencyCode?: string;
31
+ YourReference?: string;
32
+ OurReference?: string;
33
+ }): Promise<unknown>;
34
+ sendCustomerInvoice(id: string): Promise<unknown>;
35
+ listCustomerInvoicePayments(params?: {
36
+ $filter?: string;
37
+ $top?: string;
38
+ $skip?: string;
39
+ }): Promise<unknown>;
40
+ listCustomers(params?: {
41
+ $filter?: string;
42
+ $top?: string;
43
+ $skip?: string;
44
+ }): Promise<unknown>;
45
+ getCustomer(id: string): Promise<unknown>;
46
+ createCustomer(customer: {
47
+ Name: string;
48
+ EmailAddress?: string;
49
+ CorporateIdentityNumber?: string;
50
+ Telephone?: string;
51
+ Address1?: string;
52
+ ZipCode?: string;
53
+ City?: string;
54
+ CountryCode?: string;
55
+ CurrencyCode?: string;
56
+ }): Promise<unknown>;
57
+ listSupplierInvoices(params?: {
58
+ $filter?: string;
59
+ $top?: string;
60
+ $skip?: string;
61
+ }): Promise<unknown>;
62
+ getSupplier(id: string): Promise<unknown>;
63
+ createSupplierInvoice(invoice: {
64
+ SupplierId: string;
65
+ InvoiceDate?: string;
66
+ DueDate?: string;
67
+ TotalAmount?: number;
68
+ Rows: Array<{
69
+ AccountNumber: number;
70
+ DebitAmount?: number;
71
+ CreditAmount?: number;
72
+ }>;
73
+ }): Promise<unknown>;
74
+ listVouchers(params?: {
75
+ $filter?: string;
76
+ $top?: string;
77
+ $skip?: string;
78
+ }): Promise<unknown>;
79
+ createVoucher(voucher: {
80
+ Description: string;
81
+ VoucherDate: string;
82
+ VoucherSeriesId?: string;
83
+ Rows: Array<{
84
+ AccountNumber: number;
85
+ DebitAmount?: number;
86
+ CreditAmount?: number;
87
+ Description?: string;
88
+ }>;
89
+ }): Promise<unknown>;
90
+ listAccounts(params?: {
91
+ $filter?: string;
92
+ $top?: string;
93
+ $skip?: string;
94
+ }): Promise<unknown>;
95
+ listFiscalYears(): Promise<unknown>;
96
+ getCompanyInfo(): Promise<unknown>;
97
+ listProjects(params?: {
98
+ $top?: string;
99
+ $skip?: string;
100
+ }): Promise<unknown>;
101
+ listCostCenters(params?: {
102
+ $top?: string;
103
+ $skip?: string;
104
+ }): Promise<unknown>;
105
+ listOrders(params?: {
106
+ $filter?: string;
107
+ $top?: string;
108
+ $skip?: string;
109
+ }): Promise<unknown>;
110
+ createQuote(quote: {
111
+ CustomerId: string;
112
+ Rows: Array<{
113
+ ArticleId?: string;
114
+ Text?: string;
115
+ Quantity?: number;
116
+ UnitPrice?: number;
117
+ }>;
118
+ QuoteDate?: string;
119
+ ExpirationDate?: string;
120
+ YourReference?: string;
121
+ OurReference?: string;
122
+ }): Promise<unknown>;
123
+ }
124
+ export {};
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Visma eAccounting REST API client.
3
+ * API docs: https://eaccountingapi.vismaonline.com/swagger/ui/index
4
+ * Production: https://eaccountingapi.vismaonline.com/v2
5
+ */
6
+ const BASE_URL = "https://eaccountingapi.vismaonline.com/v2";
7
+ export class VismaClient {
8
+ accessToken;
9
+ constructor(config) {
10
+ this.accessToken = config.accessToken;
11
+ }
12
+ async request(method, path, body, params) {
13
+ const url = new URL(`${BASE_URL}${path}`);
14
+ if (params) {
15
+ for (const [key, value] of Object.entries(params)) {
16
+ if (value !== undefined && value !== "") {
17
+ url.searchParams.set(key, value);
18
+ }
19
+ }
20
+ }
21
+ const headers = {
22
+ Authorization: `Bearer ${this.accessToken}`,
23
+ "Content-Type": "application/json",
24
+ Accept: "application/json",
25
+ };
26
+ const response = await fetch(url.toString(), {
27
+ method,
28
+ headers,
29
+ body: body ? JSON.stringify(body) : undefined,
30
+ });
31
+ if (!response.ok) {
32
+ const errorText = await response.text();
33
+ throw new Error(`Visma API error ${response.status}: ${errorText}`);
34
+ }
35
+ return response.json();
36
+ }
37
+ // ── Invoices ──────────────────────────────────────────────
38
+ async listCustomerInvoices(params) {
39
+ return this.request("GET", "/customerinvoices", undefined, params);
40
+ }
41
+ async getCustomerInvoice(id) {
42
+ return this.request("GET", `/customerinvoices/${id}`);
43
+ }
44
+ async createCustomerInvoice(invoice) {
45
+ return this.request("POST", "/customerinvoices", invoice);
46
+ }
47
+ async sendCustomerInvoice(id) {
48
+ return this.request("POST", `/customerinvoices/${id}/send`, { SendType: 1 } // 1 = Email
49
+ );
50
+ }
51
+ async listCustomerInvoicePayments(params) {
52
+ return this.request("GET", "/customerinvoicepayments", undefined, params);
53
+ }
54
+ // ── Customers ─────────────────────────────────────────────
55
+ async listCustomers(params) {
56
+ return this.request("GET", "/customers", undefined, params);
57
+ }
58
+ async getCustomer(id) {
59
+ return this.request("GET", `/customers/${id}`);
60
+ }
61
+ async createCustomer(customer) {
62
+ return this.request("POST", "/customers", customer);
63
+ }
64
+ // ── Suppliers & Expenses ──────────────────────────────────
65
+ async listSupplierInvoices(params) {
66
+ return this.request("GET", "/supplierinvoices", undefined, params);
67
+ }
68
+ async getSupplier(id) {
69
+ return this.request("GET", `/suppliers/${id}`);
70
+ }
71
+ async createSupplierInvoice(invoice) {
72
+ return this.request("POST", "/supplierinvoices", invoice);
73
+ }
74
+ // ── Accounting ────────────────────────────────────────────
75
+ async listVouchers(params) {
76
+ return this.request("GET", "/vouchers", undefined, params);
77
+ }
78
+ async createVoucher(voucher) {
79
+ return this.request("POST", "/vouchers", voucher);
80
+ }
81
+ async listAccounts(params) {
82
+ return this.request("GET", "/accounts", undefined, params);
83
+ }
84
+ async listFiscalYears() {
85
+ return this.request("GET", "/fiscalyears");
86
+ }
87
+ // ── Reports & Analytics ───────────────────────────────────
88
+ async getCompanyInfo() {
89
+ return this.request("GET", "/companysettings");
90
+ }
91
+ async listProjects(params) {
92
+ return this.request("GET", "/projects", undefined, params);
93
+ }
94
+ async listCostCenters(params) {
95
+ return this.request("GET", "/costcenters", undefined, params);
96
+ }
97
+ // ── Orders & Quotes ───────────────────────────────────────
98
+ async listOrders(params) {
99
+ return this.request("GET", "/orders", undefined, params);
100
+ }
101
+ async createQuote(quote) {
102
+ return this.request("POST", "/quotes", quote);
103
+ }
104
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "visma-eaccounting-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Visma eAccounting — manage invoices, customers, suppliers, and accounting via AI agents. Covers Norway, Finland, Netherlands, UK.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "visma-eaccounting-mcp-server": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js",
12
+ "dev": "tsc --watch"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "visma",
17
+ "eaccounting",
18
+ "accounting",
19
+ "nordic",
20
+ "invoices",
21
+ "bookkeeping",
22
+ "ai",
23
+ "model-context-protocol",
24
+ "norway",
25
+ "finland"
26
+ ],
27
+ "author": "Viggo Johansson",
28
+ "license": "MIT",
29
+ "type": "module",
30
+ "dependencies": {
31
+ "@modelcontextprotocol/sdk": "^1.29.0",
32
+ "zod": "^4.3.6"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^25.5.0",
36
+ "typescript": "^6.0.2"
37
+ }
38
+ }
package/src/index.ts ADDED
@@ -0,0 +1,439 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Visma eAccounting MCP Server
5
+ *
6
+ * Connects AI agents to Visma eAccounting — used across Norway, Finland,
7
+ * Netherlands, and UK. Manage invoices, customers, suppliers, and more.
8
+ */
9
+
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
+ import { z } from "zod";
13
+ import { VismaClient } from "./visma-client.js";
14
+
15
+ const server = new McpServer({
16
+ name: "visma-eaccounting-mcp-server",
17
+ version: "1.0.0",
18
+ });
19
+
20
+ function getClient(): VismaClient {
21
+ const token = process.env.VISMA_ACCESS_TOKEN;
22
+ if (!token) {
23
+ throw new Error(
24
+ "VISMA_ACCESS_TOKEN environment variable is required. " +
25
+ "Register as a partner at https://developer.visma.com to get credentials."
26
+ );
27
+ }
28
+ return new VismaClient({ accessToken: token });
29
+ }
30
+
31
+ function jsonResponse(data: unknown) {
32
+ return {
33
+ content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
34
+ };
35
+ }
36
+
37
+ // ── INVOICES (5 tools) ────────────────────────────────────────
38
+
39
+ server.tool(
40
+ "list_invoices",
41
+ "List and filter customer invoices. Uses OData $filter syntax.",
42
+ {
43
+ filter: z.string().optional().describe("OData filter (e.g. \"IsPaid eq false\")"),
44
+ top: z.string().optional().describe("Number of results to return"),
45
+ skip: z.string().optional().describe("Number of results to skip (pagination)"),
46
+ },
47
+ async ({ filter, top, skip }) => {
48
+ const client = getClient();
49
+ const result = await client.listCustomerInvoices({
50
+ $filter: filter,
51
+ $top: top,
52
+ $skip: skip,
53
+ });
54
+ return jsonResponse(result);
55
+ }
56
+ );
57
+
58
+ server.tool(
59
+ "get_invoice",
60
+ "Get full details of a specific customer invoice.",
61
+ {
62
+ id: z.string().describe("Invoice ID (GUID)"),
63
+ },
64
+ async ({ id }) => {
65
+ const client = getClient();
66
+ const result = await client.getCustomerInvoice(id);
67
+ return jsonResponse(result);
68
+ }
69
+ );
70
+
71
+ server.tool(
72
+ "create_invoice",
73
+ "Create a new customer invoice with line items.",
74
+ {
75
+ customerId: z.string().describe("Customer ID (GUID)"),
76
+ rows: z.array(z.object({
77
+ text: z.string().describe("Line item description"),
78
+ quantity: z.number().describe("Quantity"),
79
+ unitPrice: z.number().describe("Unit price"),
80
+ articleId: z.string().optional().describe("Article/product ID"),
81
+ accountNumber: z.number().optional().describe("Account number"),
82
+ })).describe("Invoice line items"),
83
+ dueDate: z.string().optional().describe("Due date (YYYY-MM-DD)"),
84
+ invoiceDate: z.string().optional().describe("Invoice date (YYYY-MM-DD)"),
85
+ currencyCode: z.string().optional().describe("Currency (e.g. NOK, EUR, SEK)"),
86
+ ourReference: z.string().optional().describe("Your reference"),
87
+ yourReference: z.string().optional().describe("Customer's reference"),
88
+ },
89
+ async ({ customerId, rows, dueDate, invoiceDate, currencyCode, ourReference, yourReference }) => {
90
+ const client = getClient();
91
+ const result = await client.createCustomerInvoice({
92
+ CustomerId: customerId,
93
+ Rows: rows.map((r) => ({
94
+ Text: r.text,
95
+ Quantity: r.quantity,
96
+ UnitPrice: r.unitPrice,
97
+ ArticleId: r.articleId,
98
+ AccountNumber: r.accountNumber,
99
+ })),
100
+ DueDate: dueDate,
101
+ InvoiceDate: invoiceDate,
102
+ CurrencyCode: currencyCode,
103
+ OurReference: ourReference,
104
+ YourReference: yourReference,
105
+ });
106
+ return jsonResponse(result);
107
+ }
108
+ );
109
+
110
+ server.tool(
111
+ "send_invoice",
112
+ "Send a customer invoice via email.",
113
+ {
114
+ id: z.string().describe("Invoice ID (GUID) to send"),
115
+ },
116
+ async ({ id }) => {
117
+ const client = getClient();
118
+ const result = await client.sendCustomerInvoice(id);
119
+ return jsonResponse(result);
120
+ }
121
+ );
122
+
123
+ server.tool(
124
+ "list_invoice_payments",
125
+ "List customer invoice payments.",
126
+ {
127
+ filter: z.string().optional().describe("OData filter"),
128
+ top: z.string().optional().describe("Number of results"),
129
+ skip: z.string().optional().describe("Skip for pagination"),
130
+ },
131
+ async ({ filter, top, skip }) => {
132
+ const client = getClient();
133
+ const result = await client.listCustomerInvoicePayments({
134
+ $filter: filter,
135
+ $top: top,
136
+ $skip: skip,
137
+ });
138
+ return jsonResponse(result);
139
+ }
140
+ );
141
+
142
+ // ── CUSTOMERS (3 tools) ───────────────────────────────────────
143
+
144
+ server.tool(
145
+ "list_customers",
146
+ "Search and filter customers.",
147
+ {
148
+ filter: z.string().optional().describe("OData filter (e.g. \"contains(Name,'Acme')\")"),
149
+ top: z.string().optional().describe("Number of results"),
150
+ skip: z.string().optional().describe("Skip for pagination"),
151
+ },
152
+ async ({ filter, top, skip }) => {
153
+ const client = getClient();
154
+ const result = await client.listCustomers({ $filter: filter, $top: top, $skip: skip });
155
+ return jsonResponse(result);
156
+ }
157
+ );
158
+
159
+ server.tool(
160
+ "get_customer",
161
+ "Get full details of a specific customer.",
162
+ {
163
+ id: z.string().describe("Customer ID (GUID)"),
164
+ },
165
+ async ({ id }) => {
166
+ const client = getClient();
167
+ const result = await client.getCustomer(id);
168
+ return jsonResponse(result);
169
+ }
170
+ );
171
+
172
+ server.tool(
173
+ "create_customer",
174
+ "Add a new customer.",
175
+ {
176
+ name: z.string().describe("Customer/company name"),
177
+ email: z.string().optional().describe("Email address"),
178
+ corporateIdentityNumber: z.string().optional().describe("Organisation number"),
179
+ phone: z.string().optional().describe("Phone number"),
180
+ address: z.string().optional().describe("Street address"),
181
+ zipCode: z.string().optional().describe("Postal code"),
182
+ city: z.string().optional().describe("City"),
183
+ countryCode: z.string().optional().describe("Country code (e.g. NO, FI, SE)"),
184
+ currencyCode: z.string().optional().describe("Default currency (e.g. NOK, EUR)"),
185
+ },
186
+ async ({ name, email, corporateIdentityNumber, phone, address, zipCode, city, countryCode, currencyCode }) => {
187
+ const client = getClient();
188
+ const result = await client.createCustomer({
189
+ Name: name,
190
+ EmailAddress: email,
191
+ CorporateIdentityNumber: corporateIdentityNumber,
192
+ Telephone: phone,
193
+ Address1: address,
194
+ ZipCode: zipCode,
195
+ City: city,
196
+ CountryCode: countryCode,
197
+ CurrencyCode: currencyCode,
198
+ });
199
+ return jsonResponse(result);
200
+ }
201
+ );
202
+
203
+ // ── SUPPLIERS & EXPENSES (3 tools) ────────────────────────────
204
+
205
+ server.tool(
206
+ "list_supplier_invoices",
207
+ "List incoming supplier invoices (bills/expenses).",
208
+ {
209
+ filter: z.string().optional().describe("OData filter"),
210
+ top: z.string().optional().describe("Number of results"),
211
+ skip: z.string().optional().describe("Skip for pagination"),
212
+ },
213
+ async ({ filter, top, skip }) => {
214
+ const client = getClient();
215
+ const result = await client.listSupplierInvoices({ $filter: filter, $top: top, $skip: skip });
216
+ return jsonResponse(result);
217
+ }
218
+ );
219
+
220
+ server.tool(
221
+ "get_supplier",
222
+ "Get details of a specific supplier/vendor.",
223
+ {
224
+ id: z.string().describe("Supplier ID (GUID)"),
225
+ },
226
+ async ({ id }) => {
227
+ const client = getClient();
228
+ const result = await client.getSupplier(id);
229
+ return jsonResponse(result);
230
+ }
231
+ );
232
+
233
+ server.tool(
234
+ "create_supplier_invoice",
235
+ "Record a new supplier invoice (expense/bill).",
236
+ {
237
+ supplierId: z.string().describe("Supplier ID (GUID)"),
238
+ rows: z.array(z.object({
239
+ accountNumber: z.number().describe("Account number"),
240
+ debitAmount: z.number().optional().describe("Debit amount"),
241
+ creditAmount: z.number().optional().describe("Credit amount"),
242
+ })).describe("Invoice accounting rows (must balance)"),
243
+ invoiceDate: z.string().optional().describe("Invoice date (YYYY-MM-DD)"),
244
+ dueDate: z.string().optional().describe("Due date (YYYY-MM-DD)"),
245
+ totalAmount: z.number().optional().describe("Total amount"),
246
+ },
247
+ async ({ supplierId, rows, invoiceDate, dueDate, totalAmount }) => {
248
+ const client = getClient();
249
+ const result = await client.createSupplierInvoice({
250
+ SupplierId: supplierId,
251
+ Rows: rows.map((r) => ({
252
+ AccountNumber: r.accountNumber,
253
+ DebitAmount: r.debitAmount,
254
+ CreditAmount: r.creditAmount,
255
+ })),
256
+ InvoiceDate: invoiceDate,
257
+ DueDate: dueDate,
258
+ TotalAmount: totalAmount,
259
+ });
260
+ return jsonResponse(result);
261
+ }
262
+ );
263
+
264
+ // ── ACCOUNTING (4 tools) ──────────────────────────────────────
265
+
266
+ server.tool(
267
+ "list_vouchers",
268
+ "List journal entries/vouchers.",
269
+ {
270
+ filter: z.string().optional().describe("OData filter"),
271
+ top: z.string().optional().describe("Number of results"),
272
+ skip: z.string().optional().describe("Skip for pagination"),
273
+ },
274
+ async ({ filter, top, skip }) => {
275
+ const client = getClient();
276
+ const result = await client.listVouchers({ $filter: filter, $top: top, $skip: skip });
277
+ return jsonResponse(result);
278
+ }
279
+ );
280
+
281
+ server.tool(
282
+ "create_voucher",
283
+ "Create a manual journal entry. Rows must balance (total debit = total credit).",
284
+ {
285
+ description: z.string().describe("Voucher description"),
286
+ voucherDate: z.string().describe("Date (YYYY-MM-DD)"),
287
+ rows: z.array(z.object({
288
+ accountNumber: z.number().describe("Account number"),
289
+ debitAmount: z.number().optional().describe("Debit amount"),
290
+ creditAmount: z.number().optional().describe("Credit amount"),
291
+ description: z.string().optional().describe("Row description"),
292
+ })).describe("Voucher rows (must balance)"),
293
+ },
294
+ async ({ description, voucherDate, rows }) => {
295
+ const client = getClient();
296
+ const result = await client.createVoucher({
297
+ Description: description,
298
+ VoucherDate: voucherDate,
299
+ Rows: rows.map((r) => ({
300
+ AccountNumber: r.accountNumber,
301
+ DebitAmount: r.debitAmount,
302
+ CreditAmount: r.creditAmount,
303
+ Description: r.description,
304
+ })),
305
+ });
306
+ return jsonResponse(result);
307
+ }
308
+ );
309
+
310
+ server.tool(
311
+ "list_accounts",
312
+ "Get the chart of accounts.",
313
+ {
314
+ filter: z.string().optional().describe("OData filter"),
315
+ top: z.string().optional().describe("Number of results"),
316
+ skip: z.string().optional().describe("Skip for pagination"),
317
+ },
318
+ async ({ filter, top, skip }) => {
319
+ const client = getClient();
320
+ const result = await client.listAccounts({ $filter: filter, $top: top, $skip: skip });
321
+ return jsonResponse(result);
322
+ }
323
+ );
324
+
325
+ server.tool(
326
+ "list_fiscal_years",
327
+ "Get information about fiscal/accounting years.",
328
+ {},
329
+ async () => {
330
+ const client = getClient();
331
+ const result = await client.listFiscalYears();
332
+ return jsonResponse(result);
333
+ }
334
+ );
335
+
336
+ // ── REPORTS & ANALYTICS (3 tools) ─────────────────────────────
337
+
338
+ server.tool(
339
+ "get_company_info",
340
+ "Get company information and settings.",
341
+ {},
342
+ async () => {
343
+ const client = getClient();
344
+ const result = await client.getCompanyInfo();
345
+ return jsonResponse(result);
346
+ }
347
+ );
348
+
349
+ server.tool(
350
+ "list_projects",
351
+ "List all projects.",
352
+ {
353
+ top: z.string().optional().describe("Number of results"),
354
+ skip: z.string().optional().describe("Skip for pagination"),
355
+ },
356
+ async ({ top, skip }) => {
357
+ const client = getClient();
358
+ const result = await client.listProjects({ $top: top, $skip: skip });
359
+ return jsonResponse(result);
360
+ }
361
+ );
362
+
363
+ server.tool(
364
+ "list_cost_centers",
365
+ "List cost centers for expense allocation.",
366
+ {
367
+ top: z.string().optional().describe("Number of results"),
368
+ skip: z.string().optional().describe("Skip for pagination"),
369
+ },
370
+ async ({ top, skip }) => {
371
+ const client = getClient();
372
+ const result = await client.listCostCenters({ $top: top, $skip: skip });
373
+ return jsonResponse(result);
374
+ }
375
+ );
376
+
377
+ // ── ORDERS & QUOTES (2 tools) ─────────────────────────────────
378
+
379
+ server.tool(
380
+ "list_orders",
381
+ "List and filter sales orders.",
382
+ {
383
+ filter: z.string().optional().describe("OData filter"),
384
+ top: z.string().optional().describe("Number of results"),
385
+ skip: z.string().optional().describe("Skip for pagination"),
386
+ },
387
+ async ({ filter, top, skip }) => {
388
+ const client = getClient();
389
+ const result = await client.listOrders({ $filter: filter, $top: top, $skip: skip });
390
+ return jsonResponse(result);
391
+ }
392
+ );
393
+
394
+ server.tool(
395
+ "create_quote",
396
+ "Create a quote/offer for a customer.",
397
+ {
398
+ customerId: z.string().describe("Customer ID (GUID)"),
399
+ rows: z.array(z.object({
400
+ text: z.string().describe("Line item description"),
401
+ quantity: z.number().describe("Quantity"),
402
+ unitPrice: z.number().describe("Unit price"),
403
+ articleId: z.string().optional().describe("Article ID"),
404
+ })).describe("Quote line items"),
405
+ quoteDate: z.string().optional().describe("Quote date (YYYY-MM-DD)"),
406
+ expirationDate: z.string().optional().describe("Expiry date (YYYY-MM-DD)"),
407
+ ourReference: z.string().optional().describe("Your reference"),
408
+ yourReference: z.string().optional().describe("Customer's reference"),
409
+ },
410
+ async ({ customerId, rows, quoteDate, expirationDate, ourReference, yourReference }) => {
411
+ const client = getClient();
412
+ const result = await client.createQuote({
413
+ CustomerId: customerId,
414
+ Rows: rows.map((r) => ({
415
+ Text: r.text,
416
+ Quantity: r.quantity,
417
+ UnitPrice: r.unitPrice,
418
+ ArticleId: r.articleId,
419
+ })),
420
+ QuoteDate: quoteDate,
421
+ ExpirationDate: expirationDate,
422
+ OurReference: ourReference,
423
+ YourReference: yourReference,
424
+ });
425
+ return jsonResponse(result);
426
+ }
427
+ );
428
+
429
+ // ── Start server ──────────────────────────────────────────────
430
+
431
+ async function main() {
432
+ const transport = new StdioServerTransport();
433
+ await server.connect(transport);
434
+ }
435
+
436
+ main().catch((error) => {
437
+ console.error("Server error:", error);
438
+ process.exit(1);
439
+ });
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Visma eAccounting REST API client.
3
+ * API docs: https://eaccountingapi.vismaonline.com/swagger/ui/index
4
+ * Production: https://eaccountingapi.vismaonline.com/v2
5
+ */
6
+
7
+ const BASE_URL = "https://eaccountingapi.vismaonline.com/v2";
8
+
9
+ interface VismaConfig {
10
+ accessToken: string;
11
+ }
12
+
13
+ export class VismaClient {
14
+ private accessToken: string;
15
+
16
+ constructor(config: VismaConfig) {
17
+ this.accessToken = config.accessToken;
18
+ }
19
+
20
+ private async request<T>(
21
+ method: string,
22
+ path: string,
23
+ body?: unknown,
24
+ params?: Record<string, string>
25
+ ): Promise<T> {
26
+ const url = new URL(`${BASE_URL}${path}`);
27
+ if (params) {
28
+ for (const [key, value] of Object.entries(params)) {
29
+ if (value !== undefined && value !== "") {
30
+ url.searchParams.set(key, value);
31
+ }
32
+ }
33
+ }
34
+
35
+ const headers: Record<string, string> = {
36
+ Authorization: `Bearer ${this.accessToken}`,
37
+ "Content-Type": "application/json",
38
+ Accept: "application/json",
39
+ };
40
+
41
+ const response = await fetch(url.toString(), {
42
+ method,
43
+ headers,
44
+ body: body ? JSON.stringify(body) : undefined,
45
+ });
46
+
47
+ if (!response.ok) {
48
+ const errorText = await response.text();
49
+ throw new Error(`Visma API error ${response.status}: ${errorText}`);
50
+ }
51
+
52
+ return response.json() as Promise<T>;
53
+ }
54
+
55
+ // ── Invoices ──────────────────────────────────────────────
56
+
57
+ async listCustomerInvoices(params?: {
58
+ $filter?: string;
59
+ $top?: string;
60
+ $skip?: string;
61
+ }) {
62
+ return this.request<unknown>("GET", "/customerinvoices", undefined, params);
63
+ }
64
+
65
+ async getCustomerInvoice(id: string) {
66
+ return this.request<unknown>("GET", `/customerinvoices/${id}`);
67
+ }
68
+
69
+ async createCustomerInvoice(invoice: {
70
+ CustomerId: string;
71
+ Rows: Array<{
72
+ ArticleId?: string;
73
+ Text?: string;
74
+ Quantity?: number;
75
+ UnitPrice?: number;
76
+ AccountNumber?: number;
77
+ }>;
78
+ DueDate?: string;
79
+ InvoiceDate?: string;
80
+ CurrencyCode?: string;
81
+ YourReference?: string;
82
+ OurReference?: string;
83
+ }) {
84
+ return this.request<unknown>("POST", "/customerinvoices", invoice);
85
+ }
86
+
87
+ async sendCustomerInvoice(id: string) {
88
+ return this.request<unknown>(
89
+ "POST",
90
+ `/customerinvoices/${id}/send`,
91
+ { SendType: 1 } // 1 = Email
92
+ );
93
+ }
94
+
95
+ async listCustomerInvoicePayments(params?: {
96
+ $filter?: string;
97
+ $top?: string;
98
+ $skip?: string;
99
+ }) {
100
+ return this.request<unknown>(
101
+ "GET",
102
+ "/customerinvoicepayments",
103
+ undefined,
104
+ params
105
+ );
106
+ }
107
+
108
+ // ── Customers ─────────────────────────────────────────────
109
+
110
+ async listCustomers(params?: {
111
+ $filter?: string;
112
+ $top?: string;
113
+ $skip?: string;
114
+ }) {
115
+ return this.request<unknown>("GET", "/customers", undefined, params);
116
+ }
117
+
118
+ async getCustomer(id: string) {
119
+ return this.request<unknown>("GET", `/customers/${id}`);
120
+ }
121
+
122
+ async createCustomer(customer: {
123
+ Name: string;
124
+ EmailAddress?: string;
125
+ CorporateIdentityNumber?: string;
126
+ Telephone?: string;
127
+ Address1?: string;
128
+ ZipCode?: string;
129
+ City?: string;
130
+ CountryCode?: string;
131
+ CurrencyCode?: string;
132
+ }) {
133
+ return this.request<unknown>("POST", "/customers", customer);
134
+ }
135
+
136
+ // ── Suppliers & Expenses ──────────────────────────────────
137
+
138
+ async listSupplierInvoices(params?: {
139
+ $filter?: string;
140
+ $top?: string;
141
+ $skip?: string;
142
+ }) {
143
+ return this.request<unknown>(
144
+ "GET",
145
+ "/supplierinvoices",
146
+ undefined,
147
+ params
148
+ );
149
+ }
150
+
151
+ async getSupplier(id: string) {
152
+ return this.request<unknown>("GET", `/suppliers/${id}`);
153
+ }
154
+
155
+ async createSupplierInvoice(invoice: {
156
+ SupplierId: string;
157
+ InvoiceDate?: string;
158
+ DueDate?: string;
159
+ TotalAmount?: number;
160
+ Rows: Array<{
161
+ AccountNumber: number;
162
+ DebitAmount?: number;
163
+ CreditAmount?: number;
164
+ }>;
165
+ }) {
166
+ return this.request<unknown>("POST", "/supplierinvoices", invoice);
167
+ }
168
+
169
+ // ── Accounting ────────────────────────────────────────────
170
+
171
+ async listVouchers(params?: {
172
+ $filter?: string;
173
+ $top?: string;
174
+ $skip?: string;
175
+ }) {
176
+ return this.request<unknown>("GET", "/vouchers", undefined, params);
177
+ }
178
+
179
+ async createVoucher(voucher: {
180
+ Description: string;
181
+ VoucherDate: string;
182
+ VoucherSeriesId?: string;
183
+ Rows: Array<{
184
+ AccountNumber: number;
185
+ DebitAmount?: number;
186
+ CreditAmount?: number;
187
+ Description?: string;
188
+ }>;
189
+ }) {
190
+ return this.request<unknown>("POST", "/vouchers", voucher);
191
+ }
192
+
193
+ async listAccounts(params?: {
194
+ $filter?: string;
195
+ $top?: string;
196
+ $skip?: string;
197
+ }) {
198
+ return this.request<unknown>("GET", "/accounts", undefined, params);
199
+ }
200
+
201
+ async listFiscalYears() {
202
+ return this.request<unknown>("GET", "/fiscalyears");
203
+ }
204
+
205
+ // ── Reports & Analytics ───────────────────────────────────
206
+
207
+ async getCompanyInfo() {
208
+ return this.request<unknown>("GET", "/companysettings");
209
+ }
210
+
211
+ async listProjects(params?: { $top?: string; $skip?: string }) {
212
+ return this.request<unknown>("GET", "/projects", undefined, params);
213
+ }
214
+
215
+ async listCostCenters(params?: { $top?: string; $skip?: string }) {
216
+ return this.request<unknown>("GET", "/costcenters", undefined, params);
217
+ }
218
+
219
+ // ── Orders & Quotes ───────────────────────────────────────
220
+
221
+ async listOrders(params?: {
222
+ $filter?: string;
223
+ $top?: string;
224
+ $skip?: string;
225
+ }) {
226
+ return this.request<unknown>("GET", "/orders", undefined, params);
227
+ }
228
+
229
+ async createQuote(quote: {
230
+ CustomerId: string;
231
+ Rows: Array<{
232
+ ArticleId?: string;
233
+ Text?: string;
234
+ Quantity?: number;
235
+ UnitPrice?: number;
236
+ }>;
237
+ QuoteDate?: string;
238
+ ExpirationDate?: string;
239
+ YourReference?: string;
240
+ OurReference?: string;
241
+ }) {
242
+ return this.request<unknown>("POST", "/quotes", quote);
243
+ }
244
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true,
12
+ "types": ["node"]
13
+ },
14
+ "include": ["src/**/*"]
15
+ }