eeconomic-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.
@@ -0,0 +1,94 @@
1
+ /**
2
+ * e-conomic REST API client (Danish accounting by Visma).
3
+ * API docs: https://restapi.e-conomic.com
4
+ * Auth: Two headers — X-AppSecretToken + X-AgreementGrantToken
5
+ */
6
+ export declare class EconomicClient {
7
+ private appToken;
8
+ private grantToken;
9
+ constructor(appToken: string, grantToken: string);
10
+ private request;
11
+ listDraftInvoices(params?: {
12
+ pagesize?: string;
13
+ skippages?: string;
14
+ }): Promise<unknown>;
15
+ getDraftInvoice(id: string): Promise<unknown>;
16
+ createDraftInvoice(invoice: {
17
+ date: string;
18
+ currency: string;
19
+ customer: {
20
+ customerNumber: number;
21
+ };
22
+ layout: {
23
+ layoutNumber: number;
24
+ };
25
+ lines: Array<{
26
+ description: string;
27
+ quantity: number;
28
+ unitNetPrice: number;
29
+ product?: {
30
+ productNumber: string;
31
+ };
32
+ }>;
33
+ }): Promise<unknown>;
34
+ bookDraftInvoice(id: string): Promise<unknown>;
35
+ listBookedInvoices(params?: {
36
+ pagesize?: string;
37
+ skippages?: string;
38
+ }): Promise<unknown>;
39
+ listCustomers(params?: {
40
+ pagesize?: string;
41
+ skippages?: string;
42
+ }): Promise<unknown>;
43
+ getCustomer(customerNumber: string): Promise<unknown>;
44
+ createCustomer(customer: {
45
+ name: string;
46
+ currency: string;
47
+ customerGroup: {
48
+ customerGroupNumber: number;
49
+ };
50
+ vatZone: {
51
+ vatZoneNumber: number;
52
+ };
53
+ paymentTerms: {
54
+ paymentTermsNumber: number;
55
+ };
56
+ email?: string;
57
+ corporateIdentificationNumber?: string;
58
+ address?: string;
59
+ zip?: string;
60
+ city?: string;
61
+ country?: string;
62
+ }): Promise<unknown>;
63
+ listSuppliers(params?: {
64
+ pagesize?: string;
65
+ skippages?: string;
66
+ }): Promise<unknown>;
67
+ getSupplier(supplierNumber: string): Promise<unknown>;
68
+ listAccounts(params?: {
69
+ pagesize?: string;
70
+ skippages?: string;
71
+ }): Promise<unknown>;
72
+ listJournals(): Promise<unknown>;
73
+ createJournalEntry(journalNumber: string, entry: {
74
+ entries: Array<{
75
+ accountNumber: number;
76
+ amount: number;
77
+ date: string;
78
+ text?: string;
79
+ }>;
80
+ }): Promise<unknown>;
81
+ listProducts(params?: {
82
+ pagesize?: string;
83
+ skippages?: string;
84
+ }): Promise<unknown>;
85
+ createProduct(product: {
86
+ name: string;
87
+ productNumber: string;
88
+ salesPrice: number;
89
+ productGroup: {
90
+ productGroupNumber: number;
91
+ };
92
+ }): Promise<unknown>;
93
+ getCompanyInfo(): Promise<unknown>;
94
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * e-conomic REST API client (Danish accounting by Visma).
3
+ * API docs: https://restapi.e-conomic.com
4
+ * Auth: Two headers — X-AppSecretToken + X-AgreementGrantToken
5
+ */
6
+ const BASE_URL = "https://restapi.e-conomic.com";
7
+ export class EconomicClient {
8
+ appToken;
9
+ grantToken;
10
+ constructor(appToken, grantToken) {
11
+ this.appToken = appToken;
12
+ this.grantToken = grantToken;
13
+ }
14
+ async request(method, path, body, params) {
15
+ const url = new URL(`${BASE_URL}${path}`);
16
+ if (params)
17
+ for (const [k, v] of Object.entries(params)) {
18
+ if (v)
19
+ url.searchParams.set(k, v);
20
+ }
21
+ const res = await fetch(url.toString(), {
22
+ method, headers: { "X-AppSecretToken": this.appToken, "X-AgreementGrantToken": this.grantToken, "Content-Type": "application/json", Accept: "application/json" },
23
+ body: body ? JSON.stringify(body) : undefined,
24
+ });
25
+ if (!res.ok)
26
+ throw new Error(`e-conomic API error ${res.status}: ${await res.text()}`);
27
+ return res.json();
28
+ }
29
+ // Invoices
30
+ async listDraftInvoices(params) { return this.request("GET", "/invoices/drafts", undefined, params); }
31
+ async getDraftInvoice(id) { return this.request("GET", `/invoices/drafts/${id}`); }
32
+ async createDraftInvoice(invoice) {
33
+ return this.request("POST", "/invoices/drafts", invoice);
34
+ }
35
+ async bookDraftInvoice(id) { return this.request("POST", `/invoices/drafts/${id}/book`); }
36
+ async listBookedInvoices(params) { return this.request("GET", "/invoices/booked", undefined, params); }
37
+ // Customers
38
+ async listCustomers(params) { return this.request("GET", "/customers", undefined, params); }
39
+ async getCustomer(customerNumber) { return this.request("GET", `/customers/${customerNumber}`); }
40
+ async createCustomer(customer) {
41
+ return this.request("POST", "/customers", customer);
42
+ }
43
+ // Suppliers
44
+ async listSuppliers(params) { return this.request("GET", "/suppliers", undefined, params); }
45
+ async getSupplier(supplierNumber) { return this.request("GET", `/suppliers/${supplierNumber}`); }
46
+ // Accounting
47
+ async listAccounts(params) { return this.request("GET", "/accounts", undefined, params); }
48
+ async listJournals() { return this.request("GET", "/journals"); }
49
+ async createJournalEntry(journalNumber, entry) {
50
+ return this.request("POST", `/journals/${journalNumber}/entries`, entry);
51
+ }
52
+ // Products
53
+ async listProducts(params) { return this.request("GET", "/products", undefined, params); }
54
+ async createProduct(product) {
55
+ return this.request("POST", "/products", product);
56
+ }
57
+ // Company
58
+ async getCompanyInfo() { return this.request("GET", "/self"); }
59
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
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 { z } from "zod";
5
+ import { EconomicClient } from "./eeconomic-client.js";
6
+ const server = new McpServer({ name: "eeconomic-mcp-server", version: "1.0.0" });
7
+ function getClient() {
8
+ const app = process.env.ECONOMIC_APP_TOKEN;
9
+ const grant = process.env.ECONOMIC_GRANT_TOKEN;
10
+ if (!app || !grant)
11
+ throw new Error("ECONOMIC_APP_TOKEN and ECONOMIC_GRANT_TOKEN are required. Get them from e-conomic → Settings → Apps.");
12
+ return new EconomicClient(app, grant);
13
+ }
14
+ function json(data) { return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; }
15
+ server.tool("list_draft_invoices", "List draft invoices.", { pagesize: z.string().optional(), skippages: z.string().optional() }, async (p) => json(await getClient().listDraftInvoices(p)));
16
+ server.tool("get_draft_invoice", "Get draft invoice details.", { id: z.string().describe("Draft invoice number") }, async ({ id }) => json(await getClient().getDraftInvoice(id)));
17
+ server.tool("create_draft_invoice", "Create a draft invoice.", {
18
+ customerNumber: z.number().describe("Customer number"), date: z.string().describe("Invoice date YYYY-MM-DD"), currency: z.string().describe("Currency code (e.g. DKK)"),
19
+ layoutNumber: z.number().describe("Layout number"), lines: z.array(z.object({ description: z.string(), quantity: z.number(), unitNetPrice: z.number(), productNumber: z.string().optional() })),
20
+ }, async ({ customerNumber, date, currency, layoutNumber, lines }) => json(await getClient().createDraftInvoice({ date, currency, customer: { customerNumber }, layout: { layoutNumber }, lines: lines.map(l => ({ ...l, product: l.productNumber ? { productNumber: l.productNumber } : undefined })) })));
21
+ server.tool("book_invoice", "Book (finalize) a draft invoice.", { id: z.string().describe("Draft invoice number") }, async ({ id }) => json(await getClient().bookDraftInvoice(id)));
22
+ server.tool("list_booked_invoices", "List booked/sent invoices.", { pagesize: z.string().optional(), skippages: z.string().optional() }, async (p) => json(await getClient().listBookedInvoices(p)));
23
+ server.tool("list_customers", "List customers.", { pagesize: z.string().optional(), skippages: z.string().optional() }, async (p) => json(await getClient().listCustomers(p)));
24
+ server.tool("get_customer", "Get customer details.", { customerNumber: z.string().describe("Customer number") }, async ({ customerNumber }) => json(await getClient().getCustomer(customerNumber)));
25
+ server.tool("create_customer", "Create a new customer.", {
26
+ name: z.string(), currency: z.string().describe("e.g. DKK"), customerGroupNumber: z.number(), vatZoneNumber: z.number(), paymentTermsNumber: z.number(),
27
+ email: z.string().optional(), corporateId: z.string().optional().describe("CVR number"), address: z.string().optional(), zip: z.string().optional(), city: z.string().optional(), country: z.string().optional(),
28
+ }, async ({ name, currency, customerGroupNumber, vatZoneNumber, paymentTermsNumber, email, corporateId, address, zip, city, country }) => json(await getClient().createCustomer({ name, currency, customerGroup: { customerGroupNumber }, vatZone: { vatZoneNumber }, paymentTerms: { paymentTermsNumber }, email, corporateIdentificationNumber: corporateId, address, zip, city, country })));
29
+ server.tool("list_suppliers", "List suppliers.", { pagesize: z.string().optional() }, async (p) => json(await getClient().listSuppliers(p)));
30
+ server.tool("get_supplier", "Get supplier details.", { supplierNumber: z.string() }, async ({ supplierNumber }) => json(await getClient().getSupplier(supplierNumber)));
31
+ server.tool("list_accounts", "Get chart of accounts (kontoplan).", { pagesize: z.string().optional() }, async (p) => json(await getClient().listAccounts(p)));
32
+ server.tool("list_journals", "List available journals.", {}, async () => json(await getClient().listJournals()));
33
+ server.tool("create_journal_entry", "Post a journal entry.", {
34
+ journalNumber: z.string().describe("Journal number"), entries: z.array(z.object({ accountNumber: z.number(), amount: z.number().describe("Positive=debit, negative=credit"), date: z.string(), text: z.string().optional() })),
35
+ }, async ({ journalNumber, entries }) => json(await getClient().createJournalEntry(journalNumber, { entries })));
36
+ server.tool("list_products", "List products.", { pagesize: z.string().optional() }, async (p) => json(await getClient().listProducts(p)));
37
+ server.tool("create_product", "Create a product.", {
38
+ name: z.string(), productNumber: z.string(), salesPrice: z.number(), productGroupNumber: z.number(),
39
+ }, async ({ name, productNumber, salesPrice, productGroupNumber }) => json(await getClient().createProduct({ name, productNumber, salesPrice, productGroup: { productGroupNumber } })));
40
+ server.tool("get_company_info", "Get company/agreement info.", {}, async () => json(await getClient().getCompanyInfo()));
41
+ async function main() { await server.connect(new StdioServerTransport()); }
42
+ main().catch((e) => { console.error("Server error:", e); process.exit(1); });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "eeconomic-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for e-conomic by Visma — Denmark's leading accounting platform. Manage invoices, customers, accounts, and more via AI agents.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "eeconomic-mcp-server": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js"
12
+ },
13
+ "keywords": [
14
+ "mcp",
15
+ "e-conomic",
16
+ "economic",
17
+ "accounting",
18
+ "denmark",
19
+ "danish",
20
+ "regnskab",
21
+ "invoices",
22
+ "visma",
23
+ "model-context-protocol"
24
+ ],
25
+ "author": "Viggo Johansson",
26
+ "license": "MIT",
27
+ "type": "module",
28
+ "dependencies": {
29
+ "@modelcontextprotocol/sdk": "^1.29.0",
30
+ "zod": "^4.3.6"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^25.5.0",
34
+ "typescript": "^6.0.2"
35
+ }
36
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * e-conomic REST API client (Danish accounting by Visma).
3
+ * API docs: https://restapi.e-conomic.com
4
+ * Auth: Two headers — X-AppSecretToken + X-AgreementGrantToken
5
+ */
6
+
7
+ const BASE_URL = "https://restapi.e-conomic.com";
8
+
9
+ export class EconomicClient {
10
+ constructor(private appToken: string, private grantToken: string) {}
11
+
12
+ private async request<T>(method: string, path: string, body?: unknown, params?: Record<string, string>): Promise<T> {
13
+ const url = new URL(`${BASE_URL}${path}`);
14
+ if (params) for (const [k, v] of Object.entries(params)) { if (v) url.searchParams.set(k, v); }
15
+ const res = await fetch(url.toString(), {
16
+ method, headers: { "X-AppSecretToken": this.appToken, "X-AgreementGrantToken": this.grantToken, "Content-Type": "application/json", Accept: "application/json" },
17
+ body: body ? JSON.stringify(body) : undefined,
18
+ });
19
+ if (!res.ok) throw new Error(`e-conomic API error ${res.status}: ${await res.text()}`);
20
+ return res.json() as Promise<T>;
21
+ }
22
+
23
+ // Invoices
24
+ async listDraftInvoices(params?: { pagesize?: string; skippages?: string }) { return this.request<unknown>("GET", "/invoices/drafts", undefined, params); }
25
+ async getDraftInvoice(id: string) { return this.request<unknown>("GET", `/invoices/drafts/${id}`); }
26
+ async createDraftInvoice(invoice: { date: string; currency: string; customer: { customerNumber: number }; layout: { layoutNumber: number }; lines: Array<{ description: string; quantity: number; unitNetPrice: number; product?: { productNumber: string } }> }) {
27
+ return this.request<unknown>("POST", "/invoices/drafts", invoice);
28
+ }
29
+ async bookDraftInvoice(id: string) { return this.request<unknown>("POST", `/invoices/drafts/${id}/book`); }
30
+ async listBookedInvoices(params?: { pagesize?: string; skippages?: string }) { return this.request<unknown>("GET", "/invoices/booked", undefined, params); }
31
+
32
+ // Customers
33
+ async listCustomers(params?: { pagesize?: string; skippages?: string }) { return this.request<unknown>("GET", "/customers", undefined, params); }
34
+ async getCustomer(customerNumber: string) { return this.request<unknown>("GET", `/customers/${customerNumber}`); }
35
+ async createCustomer(customer: { name: string; currency: string; customerGroup: { customerGroupNumber: number }; vatZone: { vatZoneNumber: number }; paymentTerms: { paymentTermsNumber: number }; email?: string; corporateIdentificationNumber?: string; address?: string; zip?: string; city?: string; country?: string }) {
36
+ return this.request<unknown>("POST", "/customers", customer);
37
+ }
38
+
39
+ // Suppliers
40
+ async listSuppliers(params?: { pagesize?: string; skippages?: string }) { return this.request<unknown>("GET", "/suppliers", undefined, params); }
41
+ async getSupplier(supplierNumber: string) { return this.request<unknown>("GET", `/suppliers/${supplierNumber}`); }
42
+
43
+ // Accounting
44
+ async listAccounts(params?: { pagesize?: string; skippages?: string }) { return this.request<unknown>("GET", "/accounts", undefined, params); }
45
+ async listJournals() { return this.request<unknown>("GET", "/journals"); }
46
+ async createJournalEntry(journalNumber: string, entry: { entries: Array<{ accountNumber: number; amount: number; date: string; text?: string }> }) {
47
+ return this.request<unknown>("POST", `/journals/${journalNumber}/entries`, entry);
48
+ }
49
+
50
+ // Products
51
+ async listProducts(params?: { pagesize?: string; skippages?: string }) { return this.request<unknown>("GET", "/products", undefined, params); }
52
+ async createProduct(product: { name: string; productNumber: string; salesPrice: number; productGroup: { productGroupNumber: number } }) {
53
+ return this.request<unknown>("POST", "/products", product);
54
+ }
55
+
56
+ // Company
57
+ async getCompanyInfo() { return this.request<unknown>("GET", "/self"); }
58
+ }
package/src/index.ts ADDED
@@ -0,0 +1,47 @@
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 { z } from "zod";
5
+ import { EconomicClient } from "./eeconomic-client.js";
6
+
7
+ const server = new McpServer({ name: "eeconomic-mcp-server", version: "1.0.0" });
8
+
9
+ function getClient(): EconomicClient {
10
+ const app = process.env.ECONOMIC_APP_TOKEN;
11
+ const grant = process.env.ECONOMIC_GRANT_TOKEN;
12
+ if (!app || !grant) throw new Error("ECONOMIC_APP_TOKEN and ECONOMIC_GRANT_TOKEN are required. Get them from e-conomic → Settings → Apps.");
13
+ return new EconomicClient(app, grant);
14
+ }
15
+ function json(data: unknown) { return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] }; }
16
+
17
+ server.tool("list_draft_invoices", "List draft invoices.", { pagesize: z.string().optional(), skippages: z.string().optional() }, async (p) => json(await getClient().listDraftInvoices(p)));
18
+ server.tool("get_draft_invoice", "Get draft invoice details.", { id: z.string().describe("Draft invoice number") }, async ({ id }) => json(await getClient().getDraftInvoice(id)));
19
+ server.tool("create_draft_invoice", "Create a draft invoice.", {
20
+ customerNumber: z.number().describe("Customer number"), date: z.string().describe("Invoice date YYYY-MM-DD"), currency: z.string().describe("Currency code (e.g. DKK)"),
21
+ layoutNumber: z.number().describe("Layout number"), lines: z.array(z.object({ description: z.string(), quantity: z.number(), unitNetPrice: z.number(), productNumber: z.string().optional() })),
22
+ }, async ({ customerNumber, date, currency, layoutNumber, lines }) => json(await getClient().createDraftInvoice({ date, currency, customer: { customerNumber }, layout: { layoutNumber }, lines: lines.map(l => ({ ...l, product: l.productNumber ? { productNumber: l.productNumber } : undefined })) })));
23
+ server.tool("book_invoice", "Book (finalize) a draft invoice.", { id: z.string().describe("Draft invoice number") }, async ({ id }) => json(await getClient().bookDraftInvoice(id)));
24
+ server.tool("list_booked_invoices", "List booked/sent invoices.", { pagesize: z.string().optional(), skippages: z.string().optional() }, async (p) => json(await getClient().listBookedInvoices(p)));
25
+
26
+ server.tool("list_customers", "List customers.", { pagesize: z.string().optional(), skippages: z.string().optional() }, async (p) => json(await getClient().listCustomers(p)));
27
+ server.tool("get_customer", "Get customer details.", { customerNumber: z.string().describe("Customer number") }, async ({ customerNumber }) => json(await getClient().getCustomer(customerNumber)));
28
+ server.tool("create_customer", "Create a new customer.", {
29
+ name: z.string(), currency: z.string().describe("e.g. DKK"), customerGroupNumber: z.number(), vatZoneNumber: z.number(), paymentTermsNumber: z.number(),
30
+ email: z.string().optional(), corporateId: z.string().optional().describe("CVR number"), address: z.string().optional(), zip: z.string().optional(), city: z.string().optional(), country: z.string().optional(),
31
+ }, async ({ name, currency, customerGroupNumber, vatZoneNumber, paymentTermsNumber, email, corporateId, address, zip, city, country }) => json(await getClient().createCustomer({ name, currency, customerGroup: { customerGroupNumber }, vatZone: { vatZoneNumber }, paymentTerms: { paymentTermsNumber }, email, corporateIdentificationNumber: corporateId, address, zip, city, country })));
32
+
33
+ server.tool("list_suppliers", "List suppliers.", { pagesize: z.string().optional() }, async (p) => json(await getClient().listSuppliers(p)));
34
+ server.tool("get_supplier", "Get supplier details.", { supplierNumber: z.string() }, async ({ supplierNumber }) => json(await getClient().getSupplier(supplierNumber)));
35
+ server.tool("list_accounts", "Get chart of accounts (kontoplan).", { pagesize: z.string().optional() }, async (p) => json(await getClient().listAccounts(p)));
36
+ server.tool("list_journals", "List available journals.", {}, async () => json(await getClient().listJournals()));
37
+ server.tool("create_journal_entry", "Post a journal entry.", {
38
+ journalNumber: z.string().describe("Journal number"), entries: z.array(z.object({ accountNumber: z.number(), amount: z.number().describe("Positive=debit, negative=credit"), date: z.string(), text: z.string().optional() })),
39
+ }, async ({ journalNumber, entries }) => json(await getClient().createJournalEntry(journalNumber, { entries })));
40
+ server.tool("list_products", "List products.", { pagesize: z.string().optional() }, async (p) => json(await getClient().listProducts(p)));
41
+ server.tool("create_product", "Create a product.", {
42
+ name: z.string(), productNumber: z.string(), salesPrice: z.number(), productGroupNumber: z.number(),
43
+ }, async ({ name, productNumber, salesPrice, productGroupNumber }) => json(await getClient().createProduct({ name, productNumber, salesPrice, productGroup: { productGroupNumber } })));
44
+ server.tool("get_company_info", "Get company/agreement info.", {}, async () => json(await getClient().getCompanyInfo()));
45
+
46
+ async function main() { await server.connect(new StdioServerTransport()); }
47
+ main().catch((e) => { console.error("Server error:", e); process.exit(1); });
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
+ }