procountor-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,59 @@
1
+ # Procountor MCP Server
2
+
3
+ Connect AI agents to **Procountor** — Finland's leading financial management and accounting platform.
4
+
5
+ Manage invoices, business partners, ledger entries, products, and more through the Model Context Protocol.
6
+
7
+ ## Tools (19)
8
+
9
+ | Category | Tools | Description |
10
+ |----------|-------|-------------|
11
+ | **Invoices** | `list_invoices`, `get_invoice`, `create_sales_invoice`, `send_invoice`, `list_payments` | Create, send, and track invoices |
12
+ | **Partners** | `list_business_partners`, `get_business_partner`, `create_business_partner` | Manage customers and suppliers |
13
+ | **Accounting** | `list_ledger_receipts`, `create_journal_entry`, `list_chart_of_accounts`, `list_fiscal_years` | Journal entries and tilikartta |
14
+ | **Company** | `get_company_info`, `list_dimensions` | Company info and tracking dimensions |
15
+ | **Products** | `list_products`, `create_product` | Product and service catalog |
16
+
17
+ ## Quick Start
18
+
19
+ ### 1. Get Procountor API Credentials
20
+
21
+ Register at [dev.procountor.com](https://dev.procountor.com/) and create an application. Free test environment available.
22
+
23
+ ### 2. Configure in Claude Desktop
24
+
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "procountor": {
29
+ "command": "npx",
30
+ "args": ["-y", "procountor-mcp-server"],
31
+ "env": {
32
+ "PROCOUNTOR_ACCESS_TOKEN": "your-access-token-here"
33
+ }
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ### 3. Start Using
40
+
41
+ Ask Claude things like:
42
+ - "Näytä kaikki maksamattomat laskut" (List all unpaid invoices)
43
+ - "Luo myyntilasku asiakkaalle Oy Firma Ab" (Create a sales invoice)
44
+ - "Mitä ostolaskuja on erääntymässä?" (What purchase invoices are due?)
45
+ - "Show me the chart of accounts"
46
+
47
+ ## Environment Variables
48
+
49
+ | Variable | Required | Description |
50
+ |----------|----------|-------------|
51
+ | `PROCOUNTOR_ACCESS_TOKEN` | Yes | OAuth2 access token from Procountor |
52
+
53
+ ## API Reference
54
+
55
+ This server wraps the [Procountor REST API](https://dev.procountor.com/api-reference/). Free test environment available for development.
56
+
57
+ ## License
58
+
59
+ MIT
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Procountor MCP Server
4
+ *
5
+ * Connects AI agents to Procountor — Finland's leading financial management
6
+ * platform. Manage invoices, business partners, ledger entries, and more.
7
+ */
8
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Procountor MCP Server
4
+ *
5
+ * Connects AI agents to Procountor — Finland's leading financial management
6
+ * platform. Manage invoices, business partners, ledger entries, 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 { ProcountorClient } from "./procountor-client.js";
12
+ const server = new McpServer({
13
+ name: "procountor-mcp-server",
14
+ version: "1.0.0",
15
+ });
16
+ function getClient() {
17
+ const token = process.env.PROCOUNTOR_ACCESS_TOKEN;
18
+ if (!token) {
19
+ throw new Error("PROCOUNTOR_ACCESS_TOKEN environment variable is required. " +
20
+ "Register at https://dev.procountor.com to get API credentials.");
21
+ }
22
+ return new ProcountorClient({ 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 invoices (sales, purchase, travel, etc.).", {
31
+ type: z.string().optional().describe("Type: SALES_INVOICE, PURCHASE_INVOICE, TRAVEL_INVOICE, BILL_OF_CHARGES"),
32
+ status: z.string().optional().describe("Status: EMPTY, UNFINISHED, NOT_SENT, SENT, PAID, etc."),
33
+ startDate: z.string().optional().describe("Start date (YYYY-MM-DD)"),
34
+ endDate: z.string().optional().describe("End date (YYYY-MM-DD)"),
35
+ size: z.string().optional().describe("Results per page (max 200)"),
36
+ }, async ({ type, status, startDate, endDate, size }) => {
37
+ const client = getClient();
38
+ const result = await client.listInvoices({ type, status, startDate, endDate, size });
39
+ return jsonResponse(result);
40
+ });
41
+ server.tool("get_invoice", "Get full details of a specific invoice.", {
42
+ id: z.string().describe("Invoice ID"),
43
+ }, async ({ id }) => {
44
+ const client = getClient();
45
+ const result = await client.getInvoice(id);
46
+ return jsonResponse(result);
47
+ });
48
+ server.tool("create_sales_invoice", "Create a new sales invoice.", {
49
+ customerName: z.string().describe("Customer/company name"),
50
+ customerBusinessId: z.string().optional().describe("Customer business ID (Y-tunnus)"),
51
+ rows: z.array(z.object({
52
+ product: z.string().describe("Product/service name"),
53
+ quantity: z.number().describe("Quantity"),
54
+ unitPrice: z.number().describe("Unit price"),
55
+ vatPercent: z.number().optional().describe("VAT percentage (default 25.5 in Finland)"),
56
+ unit: z.string().optional().describe("Unit (e.g. kpl, h, pv)"),
57
+ })).describe("Invoice line items"),
58
+ dueDate: z.string().optional().describe("Due date (YYYY-MM-DD)"),
59
+ date: z.string().optional().describe("Invoice date (YYYY-MM-DD)"),
60
+ currency: z.string().optional().describe("Currency (default EUR)"),
61
+ notes: z.string().optional().describe("Notes/comments"),
62
+ }, async ({ customerName, customerBusinessId, rows, dueDate, date, currency, notes }) => {
63
+ const client = getClient();
64
+ const result = await client.createInvoice({
65
+ type: "SALES_INVOICE",
66
+ counterParty: {
67
+ name: customerName,
68
+ identifier: customerBusinessId,
69
+ },
70
+ invoiceRows: rows.map((r) => ({
71
+ product: r.product,
72
+ quantity: r.quantity,
73
+ unitPrice: r.unitPrice,
74
+ vatPercent: r.vatPercent,
75
+ unit: r.unit,
76
+ })),
77
+ dueDate,
78
+ date,
79
+ currency,
80
+ notes,
81
+ });
82
+ return jsonResponse(result);
83
+ });
84
+ server.tool("send_invoice", "Send an invoice via email or e-invoice.", {
85
+ id: z.string().describe("Invoice ID to send"),
86
+ method: z.string().optional().describe("Send method: EMAIL (default), EINVOICE, LETTER"),
87
+ }, async ({ id, method }) => {
88
+ const client = getClient();
89
+ const result = await client.sendInvoice(id, method);
90
+ return jsonResponse(result);
91
+ });
92
+ server.tool("list_payments", "List payments and payment status.", {
93
+ startDate: z.string().optional().describe("Start date (YYYY-MM-DD)"),
94
+ endDate: z.string().optional().describe("End date (YYYY-MM-DD)"),
95
+ size: z.string().optional().describe("Results per page"),
96
+ }, async ({ startDate, endDate, size }) => {
97
+ const client = getClient();
98
+ const result = await client.listPayments({ startDate, endDate, size });
99
+ return jsonResponse(result);
100
+ });
101
+ // ── BUSINESS PARTNERS — Customers & Suppliers (3 tools) ───────
102
+ server.tool("list_business_partners", "List customers, suppliers, or all business partners.", {
103
+ name: z.string().optional().describe("Search by name"),
104
+ type: z.string().optional().describe("Filter: CUSTOMER, SUPPLIER (omit for all)"),
105
+ size: z.string().optional().describe("Results per page"),
106
+ }, async ({ name, type, size }) => {
107
+ const client = getClient();
108
+ const result = await client.listBusinessPartners({ name, type, size });
109
+ return jsonResponse(result);
110
+ });
111
+ server.tool("get_business_partner", "Get full details of a customer or supplier.", {
112
+ id: z.string().describe("Business partner ID"),
113
+ }, async ({ id }) => {
114
+ const client = getClient();
115
+ const result = await client.getBusinessPartner(id);
116
+ return jsonResponse(result);
117
+ });
118
+ server.tool("create_business_partner", "Add a new customer or supplier.", {
119
+ name: z.string().describe("Company/person name"),
120
+ type: z.string().describe("Type: CUSTOMER or SUPPLIER"),
121
+ email: z.string().optional().describe("Email address"),
122
+ phone: z.string().optional().describe("Phone number"),
123
+ businessId: z.string().optional().describe("Business ID (Y-tunnus)"),
124
+ street: z.string().optional().describe("Street address"),
125
+ zip: z.string().optional().describe("Postal code"),
126
+ city: z.string().optional().describe("City"),
127
+ country: z.string().optional().describe("Country code (e.g. FI, SE)"),
128
+ currency: z.string().optional().describe("Default currency (e.g. EUR)"),
129
+ }, async ({ name, type, email, phone, businessId, street, zip, city, country, currency }) => {
130
+ const client = getClient();
131
+ const result = await client.createBusinessPartner({
132
+ name,
133
+ type,
134
+ email,
135
+ phone,
136
+ businessId,
137
+ billingAddress: street ? { street, zip, city, country } : undefined,
138
+ currency,
139
+ });
140
+ return jsonResponse(result);
141
+ });
142
+ // ── ACCOUNTING (4 tools) ──────────────────────────────────────
143
+ server.tool("list_ledger_receipts", "List journal entries/ledger receipts.", {
144
+ startDate: z.string().optional().describe("Start date (YYYY-MM-DD)"),
145
+ endDate: z.string().optional().describe("End date (YYYY-MM-DD)"),
146
+ size: z.string().optional().describe("Results per page"),
147
+ }, async ({ startDate, endDate, size }) => {
148
+ const client = getClient();
149
+ const result = await client.listLedgerReceipts({ startDate, endDate, size });
150
+ return jsonResponse(result);
151
+ });
152
+ server.tool("create_journal_entry", "Create a manual journal entry. Transactions must balance.", {
153
+ name: z.string().describe("Entry description"),
154
+ receiptDate: z.string().describe("Date (YYYY-MM-DD)"),
155
+ transactions: z.array(z.object({
156
+ accountNumber: z.string().describe("Account number"),
157
+ debit: z.number().describe("Amount (positive = debit, negative = credit)"),
158
+ description: z.string().optional().describe("Transaction description"),
159
+ })).describe("Transactions (must sum to zero)"),
160
+ }, async ({ name, receiptDate, transactions }) => {
161
+ const client = getClient();
162
+ const result = await client.createLedgerReceipt({
163
+ type: "JOURNAL",
164
+ receiptDate,
165
+ name,
166
+ transactions: transactions.map((t) => ({
167
+ accountNumber: t.accountNumber,
168
+ debit: t.debit,
169
+ description: t.description,
170
+ })),
171
+ });
172
+ return jsonResponse(result);
173
+ });
174
+ server.tool("list_chart_of_accounts", "Get the chart of accounts (tilikartta).", {
175
+ size: z.string().optional().describe("Results per page"),
176
+ }, async ({ size }) => {
177
+ const client = getClient();
178
+ const result = await client.listChartOfAccounts({ size });
179
+ return jsonResponse(result);
180
+ });
181
+ server.tool("list_fiscal_years", "Get information about fiscal/accounting years (tilikaudet).", {}, async () => {
182
+ const client = getClient();
183
+ const result = await client.listFiscalYears();
184
+ return jsonResponse(result);
185
+ });
186
+ // ── COMPANY & REPORTS (2 tools) ───────────────────────────────
187
+ server.tool("get_company_info", "Get company information and settings.", {}, async () => {
188
+ const client = getClient();
189
+ const result = await client.getCompanyInfo();
190
+ return jsonResponse(result);
191
+ });
192
+ server.tool("list_dimensions", "List tracking dimensions (cost centers, projects, etc.).", {
193
+ size: z.string().optional().describe("Results per page"),
194
+ }, async ({ size }) => {
195
+ const client = getClient();
196
+ const result = await client.listDimensions({ size });
197
+ return jsonResponse(result);
198
+ });
199
+ // ── PRODUCTS (2 tools) ────────────────────────────────────────
200
+ server.tool("list_products", "List products and services.", {
201
+ type: z.string().optional().describe("Filter by type"),
202
+ size: z.string().optional().describe("Results per page"),
203
+ }, async ({ type, size }) => {
204
+ const client = getClient();
205
+ const result = await client.listProducts({ type, size });
206
+ return jsonResponse(result);
207
+ });
208
+ server.tool("create_product", "Add a new product or service.", {
209
+ name: z.string().describe("Product/service name"),
210
+ type: z.string().describe("Type: PRODUCT or SERVICE"),
211
+ price: z.number().optional().describe("Default price"),
212
+ unit: z.string().optional().describe("Unit (e.g. kpl, h)"),
213
+ vatPercent: z.number().optional().describe("VAT percentage"),
214
+ }, async ({ name, type, price, unit, vatPercent }) => {
215
+ const client = getClient();
216
+ const result = await client.createProduct({ name, type, price, unit, vatPercent });
217
+ return jsonResponse(result);
218
+ });
219
+ // ── Start server ──────────────────────────────────────────────
220
+ async function main() {
221
+ const transport = new StdioServerTransport();
222
+ await server.connect(transport);
223
+ }
224
+ main().catch((error) => {
225
+ console.error("Server error:", error);
226
+ process.exit(1);
227
+ });
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Procountor REST API client.
3
+ * API docs: https://dev.procountor.com/api-reference/
4
+ * Production: https://api.procountor.com/api
5
+ */
6
+ interface ProcountorConfig {
7
+ accessToken: string;
8
+ }
9
+ export declare class ProcountorClient {
10
+ private accessToken;
11
+ constructor(config: ProcountorConfig);
12
+ private request;
13
+ listInvoices(params?: {
14
+ status?: string;
15
+ startDate?: string;
16
+ endDate?: string;
17
+ previousId?: string;
18
+ orderById?: string;
19
+ size?: string;
20
+ type?: string;
21
+ }): Promise<unknown>;
22
+ getInvoice(id: string): Promise<unknown>;
23
+ createInvoice(invoice: {
24
+ type: string;
25
+ partnerId?: number;
26
+ counterParty: {
27
+ contactPersonName?: string;
28
+ identifier?: string;
29
+ name: string;
30
+ taxCode?: string;
31
+ };
32
+ billingAddress?: {
33
+ name?: string;
34
+ street?: string;
35
+ zip?: string;
36
+ city?: string;
37
+ country?: string;
38
+ };
39
+ invoiceRows: Array<{
40
+ product?: string;
41
+ productCode?: string;
42
+ quantity?: number;
43
+ unit?: string;
44
+ unitPrice?: number;
45
+ vatPercent?: number;
46
+ comment?: string;
47
+ }>;
48
+ date?: string;
49
+ dueDate?: string;
50
+ currency?: string;
51
+ notes?: string;
52
+ }): Promise<unknown>;
53
+ sendInvoice(id: string, method?: string): Promise<unknown>;
54
+ listPayments(params?: {
55
+ previousId?: string;
56
+ size?: string;
57
+ startDate?: string;
58
+ endDate?: string;
59
+ }): Promise<unknown>;
60
+ listBusinessPartners(params?: {
61
+ name?: string;
62
+ type?: string;
63
+ previousId?: string;
64
+ size?: string;
65
+ }): Promise<unknown>;
66
+ getBusinessPartner(id: string): Promise<unknown>;
67
+ createBusinessPartner(partner: {
68
+ name: string;
69
+ type: string;
70
+ contactPersonName?: string;
71
+ email?: string;
72
+ phone?: string;
73
+ businessId?: string;
74
+ billingAddress?: {
75
+ name?: string;
76
+ street?: string;
77
+ zip?: string;
78
+ city?: string;
79
+ country?: string;
80
+ };
81
+ currency?: string;
82
+ }): Promise<unknown>;
83
+ listLedgerReceipts(params?: {
84
+ startDate?: string;
85
+ endDate?: string;
86
+ previousId?: string;
87
+ size?: string;
88
+ }): Promise<unknown>;
89
+ createLedgerReceipt(receipt: {
90
+ type: string;
91
+ receiptDate: string;
92
+ name?: string;
93
+ transactions: Array<{
94
+ accountNumber: string;
95
+ debit: number;
96
+ description?: string;
97
+ }>;
98
+ }): Promise<unknown>;
99
+ listChartOfAccounts(params?: {
100
+ previousId?: string;
101
+ size?: string;
102
+ }): Promise<unknown>;
103
+ listFiscalYears(): Promise<unknown>;
104
+ getCompanyInfo(): Promise<unknown>;
105
+ listDimensions(params?: {
106
+ previousId?: string;
107
+ size?: string;
108
+ }): Promise<unknown>;
109
+ listProducts(params?: {
110
+ previousId?: string;
111
+ size?: string;
112
+ type?: string;
113
+ }): Promise<unknown>;
114
+ createProduct(product: {
115
+ name: string;
116
+ type: string;
117
+ price?: number;
118
+ unit?: string;
119
+ vatPercent?: number;
120
+ }): Promise<unknown>;
121
+ }
122
+ export {};
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Procountor REST API client.
3
+ * API docs: https://dev.procountor.com/api-reference/
4
+ * Production: https://api.procountor.com/api
5
+ */
6
+ const BASE_URL = "https://api.procountor.com/api";
7
+ export class ProcountorClient {
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(`Procountor API error ${response.status}: ${errorText}`);
34
+ }
35
+ return response.json();
36
+ }
37
+ // ── Invoices ──────────────────────────────────────────────
38
+ async listInvoices(params) {
39
+ return this.request("GET", "/invoices", undefined, params);
40
+ }
41
+ async getInvoice(id) {
42
+ return this.request("GET", `/invoices/${id}`);
43
+ }
44
+ async createInvoice(invoice) {
45
+ return this.request("POST", "/invoices", invoice);
46
+ }
47
+ async sendInvoice(id, method = "EMAIL") {
48
+ return this.request("PUT", `/invoices/${id}/send`, {
49
+ method,
50
+ });
51
+ }
52
+ async listPayments(params) {
53
+ return this.request("GET", "/payments", undefined, params);
54
+ }
55
+ // ── Business Partners (Customers/Suppliers) ───────────────
56
+ async listBusinessPartners(params) {
57
+ return this.request("GET", "/businesspartners", undefined, params);
58
+ }
59
+ async getBusinessPartner(id) {
60
+ return this.request("GET", `/businesspartners/${id}`);
61
+ }
62
+ async createBusinessPartner(partner) {
63
+ return this.request("POST", "/businesspartners", partner);
64
+ }
65
+ // ── Ledger / Accounting ───────────────────────────────────
66
+ async listLedgerReceipts(params) {
67
+ return this.request("GET", "/ledgerreceipts", undefined, params);
68
+ }
69
+ async createLedgerReceipt(receipt) {
70
+ return this.request("POST", "/ledgerreceipts", receipt);
71
+ }
72
+ async listChartOfAccounts(params) {
73
+ return this.request("GET", "/coa", undefined, params);
74
+ }
75
+ async listFiscalYears() {
76
+ return this.request("GET", "/fiscalyears");
77
+ }
78
+ // ── Company & Reports ─────────────────────────────────────
79
+ async getCompanyInfo() {
80
+ return this.request("GET", "/company");
81
+ }
82
+ async listDimensions(params) {
83
+ return this.request("GET", "/dimensions", undefined, params);
84
+ }
85
+ // ── Products ──────────────────────────────────────────────
86
+ async listProducts(params) {
87
+ return this.request("GET", "/products", undefined, params);
88
+ }
89
+ async createProduct(product) {
90
+ return this.request("POST", "/products", product);
91
+ }
92
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "procountor-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Procountor — Finnish accounting and financial management via AI agents. Invoices, customers, ledger, and more.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "procountor-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
+ "procountor",
17
+ "accounting",
18
+ "finland",
19
+ "finnish",
20
+ "invoices",
21
+ "bookkeeping",
22
+ "ai",
23
+ "model-context-protocol",
24
+ "taloushallinto"
25
+ ],
26
+ "author": "Viggo Johansson",
27
+ "license": "MIT",
28
+ "type": "module",
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.29.0",
31
+ "zod": "^4.3.6"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^25.5.0",
35
+ "typescript": "^6.0.2"
36
+ }
37
+ }
package/src/index.ts ADDED
@@ -0,0 +1,338 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Procountor MCP Server
5
+ *
6
+ * Connects AI agents to Procountor — Finland's leading financial management
7
+ * platform. Manage invoices, business partners, ledger entries, 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 { ProcountorClient } from "./procountor-client.js";
14
+
15
+ const server = new McpServer({
16
+ name: "procountor-mcp-server",
17
+ version: "1.0.0",
18
+ });
19
+
20
+ function getClient(): ProcountorClient {
21
+ const token = process.env.PROCOUNTOR_ACCESS_TOKEN;
22
+ if (!token) {
23
+ throw new Error(
24
+ "PROCOUNTOR_ACCESS_TOKEN environment variable is required. " +
25
+ "Register at https://dev.procountor.com to get API credentials."
26
+ );
27
+ }
28
+ return new ProcountorClient({ 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 invoices (sales, purchase, travel, etc.).",
42
+ {
43
+ type: z.string().optional().describe("Type: SALES_INVOICE, PURCHASE_INVOICE, TRAVEL_INVOICE, BILL_OF_CHARGES"),
44
+ status: z.string().optional().describe("Status: EMPTY, UNFINISHED, NOT_SENT, SENT, PAID, etc."),
45
+ startDate: z.string().optional().describe("Start date (YYYY-MM-DD)"),
46
+ endDate: z.string().optional().describe("End date (YYYY-MM-DD)"),
47
+ size: z.string().optional().describe("Results per page (max 200)"),
48
+ },
49
+ async ({ type, status, startDate, endDate, size }) => {
50
+ const client = getClient();
51
+ const result = await client.listInvoices({ type, status, startDate, endDate, size });
52
+ return jsonResponse(result);
53
+ }
54
+ );
55
+
56
+ server.tool(
57
+ "get_invoice",
58
+ "Get full details of a specific invoice.",
59
+ {
60
+ id: z.string().describe("Invoice ID"),
61
+ },
62
+ async ({ id }) => {
63
+ const client = getClient();
64
+ const result = await client.getInvoice(id);
65
+ return jsonResponse(result);
66
+ }
67
+ );
68
+
69
+ server.tool(
70
+ "create_sales_invoice",
71
+ "Create a new sales invoice.",
72
+ {
73
+ customerName: z.string().describe("Customer/company name"),
74
+ customerBusinessId: z.string().optional().describe("Customer business ID (Y-tunnus)"),
75
+ rows: z.array(z.object({
76
+ product: z.string().describe("Product/service name"),
77
+ quantity: z.number().describe("Quantity"),
78
+ unitPrice: z.number().describe("Unit price"),
79
+ vatPercent: z.number().optional().describe("VAT percentage (default 25.5 in Finland)"),
80
+ unit: z.string().optional().describe("Unit (e.g. kpl, h, pv)"),
81
+ })).describe("Invoice line items"),
82
+ dueDate: z.string().optional().describe("Due date (YYYY-MM-DD)"),
83
+ date: z.string().optional().describe("Invoice date (YYYY-MM-DD)"),
84
+ currency: z.string().optional().describe("Currency (default EUR)"),
85
+ notes: z.string().optional().describe("Notes/comments"),
86
+ },
87
+ async ({ customerName, customerBusinessId, rows, dueDate, date, currency, notes }) => {
88
+ const client = getClient();
89
+ const result = await client.createInvoice({
90
+ type: "SALES_INVOICE",
91
+ counterParty: {
92
+ name: customerName,
93
+ identifier: customerBusinessId,
94
+ },
95
+ invoiceRows: rows.map((r) => ({
96
+ product: r.product,
97
+ quantity: r.quantity,
98
+ unitPrice: r.unitPrice,
99
+ vatPercent: r.vatPercent,
100
+ unit: r.unit,
101
+ })),
102
+ dueDate,
103
+ date,
104
+ currency,
105
+ notes,
106
+ });
107
+ return jsonResponse(result);
108
+ }
109
+ );
110
+
111
+ server.tool(
112
+ "send_invoice",
113
+ "Send an invoice via email or e-invoice.",
114
+ {
115
+ id: z.string().describe("Invoice ID to send"),
116
+ method: z.string().optional().describe("Send method: EMAIL (default), EINVOICE, LETTER"),
117
+ },
118
+ async ({ id, method }) => {
119
+ const client = getClient();
120
+ const result = await client.sendInvoice(id, method);
121
+ return jsonResponse(result);
122
+ }
123
+ );
124
+
125
+ server.tool(
126
+ "list_payments",
127
+ "List payments and payment status.",
128
+ {
129
+ startDate: z.string().optional().describe("Start date (YYYY-MM-DD)"),
130
+ endDate: z.string().optional().describe("End date (YYYY-MM-DD)"),
131
+ size: z.string().optional().describe("Results per page"),
132
+ },
133
+ async ({ startDate, endDate, size }) => {
134
+ const client = getClient();
135
+ const result = await client.listPayments({ startDate, endDate, size });
136
+ return jsonResponse(result);
137
+ }
138
+ );
139
+
140
+ // ── BUSINESS PARTNERS — Customers & Suppliers (3 tools) ───────
141
+
142
+ server.tool(
143
+ "list_business_partners",
144
+ "List customers, suppliers, or all business partners.",
145
+ {
146
+ name: z.string().optional().describe("Search by name"),
147
+ type: z.string().optional().describe("Filter: CUSTOMER, SUPPLIER (omit for all)"),
148
+ size: z.string().optional().describe("Results per page"),
149
+ },
150
+ async ({ name, type, size }) => {
151
+ const client = getClient();
152
+ const result = await client.listBusinessPartners({ name, type, size });
153
+ return jsonResponse(result);
154
+ }
155
+ );
156
+
157
+ server.tool(
158
+ "get_business_partner",
159
+ "Get full details of a customer or supplier.",
160
+ {
161
+ id: z.string().describe("Business partner ID"),
162
+ },
163
+ async ({ id }) => {
164
+ const client = getClient();
165
+ const result = await client.getBusinessPartner(id);
166
+ return jsonResponse(result);
167
+ }
168
+ );
169
+
170
+ server.tool(
171
+ "create_business_partner",
172
+ "Add a new customer or supplier.",
173
+ {
174
+ name: z.string().describe("Company/person name"),
175
+ type: z.string().describe("Type: CUSTOMER or SUPPLIER"),
176
+ email: z.string().optional().describe("Email address"),
177
+ phone: z.string().optional().describe("Phone number"),
178
+ businessId: z.string().optional().describe("Business ID (Y-tunnus)"),
179
+ street: z.string().optional().describe("Street address"),
180
+ zip: z.string().optional().describe("Postal code"),
181
+ city: z.string().optional().describe("City"),
182
+ country: z.string().optional().describe("Country code (e.g. FI, SE)"),
183
+ currency: z.string().optional().describe("Default currency (e.g. EUR)"),
184
+ },
185
+ async ({ name, type, email, phone, businessId, street, zip, city, country, currency }) => {
186
+ const client = getClient();
187
+ const result = await client.createBusinessPartner({
188
+ name,
189
+ type,
190
+ email,
191
+ phone,
192
+ businessId,
193
+ billingAddress: street ? { street, zip, city, country } : undefined,
194
+ currency,
195
+ });
196
+ return jsonResponse(result);
197
+ }
198
+ );
199
+
200
+ // ── ACCOUNTING (4 tools) ──────────────────────────────────────
201
+
202
+ server.tool(
203
+ "list_ledger_receipts",
204
+ "List journal entries/ledger receipts.",
205
+ {
206
+ startDate: z.string().optional().describe("Start date (YYYY-MM-DD)"),
207
+ endDate: z.string().optional().describe("End date (YYYY-MM-DD)"),
208
+ size: z.string().optional().describe("Results per page"),
209
+ },
210
+ async ({ startDate, endDate, size }) => {
211
+ const client = getClient();
212
+ const result = await client.listLedgerReceipts({ startDate, endDate, size });
213
+ return jsonResponse(result);
214
+ }
215
+ );
216
+
217
+ server.tool(
218
+ "create_journal_entry",
219
+ "Create a manual journal entry. Transactions must balance.",
220
+ {
221
+ name: z.string().describe("Entry description"),
222
+ receiptDate: z.string().describe("Date (YYYY-MM-DD)"),
223
+ transactions: z.array(z.object({
224
+ accountNumber: z.string().describe("Account number"),
225
+ debit: z.number().describe("Amount (positive = debit, negative = credit)"),
226
+ description: z.string().optional().describe("Transaction description"),
227
+ })).describe("Transactions (must sum to zero)"),
228
+ },
229
+ async ({ name, receiptDate, transactions }) => {
230
+ const client = getClient();
231
+ const result = await client.createLedgerReceipt({
232
+ type: "JOURNAL",
233
+ receiptDate,
234
+ name,
235
+ transactions: transactions.map((t) => ({
236
+ accountNumber: t.accountNumber,
237
+ debit: t.debit,
238
+ description: t.description,
239
+ })),
240
+ });
241
+ return jsonResponse(result);
242
+ }
243
+ );
244
+
245
+ server.tool(
246
+ "list_chart_of_accounts",
247
+ "Get the chart of accounts (tilikartta).",
248
+ {
249
+ size: z.string().optional().describe("Results per page"),
250
+ },
251
+ async ({ size }) => {
252
+ const client = getClient();
253
+ const result = await client.listChartOfAccounts({ size });
254
+ return jsonResponse(result);
255
+ }
256
+ );
257
+
258
+ server.tool(
259
+ "list_fiscal_years",
260
+ "Get information about fiscal/accounting years (tilikaudet).",
261
+ {},
262
+ async () => {
263
+ const client = getClient();
264
+ const result = await client.listFiscalYears();
265
+ return jsonResponse(result);
266
+ }
267
+ );
268
+
269
+ // ── COMPANY & REPORTS (2 tools) ───────────────────────────────
270
+
271
+ server.tool(
272
+ "get_company_info",
273
+ "Get company information and settings.",
274
+ {},
275
+ async () => {
276
+ const client = getClient();
277
+ const result = await client.getCompanyInfo();
278
+ return jsonResponse(result);
279
+ }
280
+ );
281
+
282
+ server.tool(
283
+ "list_dimensions",
284
+ "List tracking dimensions (cost centers, projects, etc.).",
285
+ {
286
+ size: z.string().optional().describe("Results per page"),
287
+ },
288
+ async ({ size }) => {
289
+ const client = getClient();
290
+ const result = await client.listDimensions({ size });
291
+ return jsonResponse(result);
292
+ }
293
+ );
294
+
295
+ // ── PRODUCTS (2 tools) ────────────────────────────────────────
296
+
297
+ server.tool(
298
+ "list_products",
299
+ "List products and services.",
300
+ {
301
+ type: z.string().optional().describe("Filter by type"),
302
+ size: z.string().optional().describe("Results per page"),
303
+ },
304
+ async ({ type, size }) => {
305
+ const client = getClient();
306
+ const result = await client.listProducts({ type, size });
307
+ return jsonResponse(result);
308
+ }
309
+ );
310
+
311
+ server.tool(
312
+ "create_product",
313
+ "Add a new product or service.",
314
+ {
315
+ name: z.string().describe("Product/service name"),
316
+ type: z.string().describe("Type: PRODUCT or SERVICE"),
317
+ price: z.number().optional().describe("Default price"),
318
+ unit: z.string().optional().describe("Unit (e.g. kpl, h)"),
319
+ vatPercent: z.number().optional().describe("VAT percentage"),
320
+ },
321
+ async ({ name, type, price, unit, vatPercent }) => {
322
+ const client = getClient();
323
+ const result = await client.createProduct({ name, type, price, unit, vatPercent });
324
+ return jsonResponse(result);
325
+ }
326
+ );
327
+
328
+ // ── Start server ──────────────────────────────────────────────
329
+
330
+ async function main() {
331
+ const transport = new StdioServerTransport();
332
+ await server.connect(transport);
333
+ }
334
+
335
+ main().catch((error) => {
336
+ console.error("Server error:", error);
337
+ process.exit(1);
338
+ });
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Procountor REST API client.
3
+ * API docs: https://dev.procountor.com/api-reference/
4
+ * Production: https://api.procountor.com/api
5
+ */
6
+
7
+ const BASE_URL = "https://api.procountor.com/api";
8
+
9
+ interface ProcountorConfig {
10
+ accessToken: string;
11
+ }
12
+
13
+ export class ProcountorClient {
14
+ private accessToken: string;
15
+
16
+ constructor(config: ProcountorConfig) {
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(`Procountor API error ${response.status}: ${errorText}`);
50
+ }
51
+
52
+ return response.json() as Promise<T>;
53
+ }
54
+
55
+ // ── Invoices ──────────────────────────────────────────────
56
+
57
+ async listInvoices(params?: {
58
+ status?: string;
59
+ startDate?: string;
60
+ endDate?: string;
61
+ previousId?: string;
62
+ orderById?: string;
63
+ size?: string;
64
+ type?: string;
65
+ }) {
66
+ return this.request<unknown>("GET", "/invoices", undefined, params);
67
+ }
68
+
69
+ async getInvoice(id: string) {
70
+ return this.request<unknown>("GET", `/invoices/${id}`);
71
+ }
72
+
73
+ async createInvoice(invoice: {
74
+ type: string; // SALES_INVOICE, PURCHASE_INVOICE, etc.
75
+ partnerId?: number;
76
+ counterParty: {
77
+ contactPersonName?: string;
78
+ identifier?: string;
79
+ name: string;
80
+ taxCode?: string;
81
+ };
82
+ billingAddress?: {
83
+ name?: string;
84
+ street?: string;
85
+ zip?: string;
86
+ city?: string;
87
+ country?: string;
88
+ };
89
+ invoiceRows: Array<{
90
+ product?: string;
91
+ productCode?: string;
92
+ quantity?: number;
93
+ unit?: string;
94
+ unitPrice?: number;
95
+ vatPercent?: number;
96
+ comment?: string;
97
+ }>;
98
+ date?: string;
99
+ dueDate?: string;
100
+ currency?: string;
101
+ notes?: string;
102
+ }) {
103
+ return this.request<unknown>("POST", "/invoices", invoice);
104
+ }
105
+
106
+ async sendInvoice(id: string, method: string = "EMAIL") {
107
+ return this.request<unknown>("PUT", `/invoices/${id}/send`, {
108
+ method,
109
+ });
110
+ }
111
+
112
+ async listPayments(params?: {
113
+ previousId?: string;
114
+ size?: string;
115
+ startDate?: string;
116
+ endDate?: string;
117
+ }) {
118
+ return this.request<unknown>("GET", "/payments", undefined, params);
119
+ }
120
+
121
+ // ── Business Partners (Customers/Suppliers) ───────────────
122
+
123
+ async listBusinessPartners(params?: {
124
+ name?: string;
125
+ type?: string;
126
+ previousId?: string;
127
+ size?: string;
128
+ }) {
129
+ return this.request<unknown>(
130
+ "GET",
131
+ "/businesspartners",
132
+ undefined,
133
+ params
134
+ );
135
+ }
136
+
137
+ async getBusinessPartner(id: string) {
138
+ return this.request<unknown>("GET", `/businesspartners/${id}`);
139
+ }
140
+
141
+ async createBusinessPartner(partner: {
142
+ name: string;
143
+ type: string; // CUSTOMER, SUPPLIER
144
+ contactPersonName?: string;
145
+ email?: string;
146
+ phone?: string;
147
+ businessId?: string;
148
+ billingAddress?: {
149
+ name?: string;
150
+ street?: string;
151
+ zip?: string;
152
+ city?: string;
153
+ country?: string;
154
+ };
155
+ currency?: string;
156
+ }) {
157
+ return this.request<unknown>("POST", "/businesspartners", partner);
158
+ }
159
+
160
+ // ── Ledger / Accounting ───────────────────────────────────
161
+
162
+ async listLedgerReceipts(params?: {
163
+ startDate?: string;
164
+ endDate?: string;
165
+ previousId?: string;
166
+ size?: string;
167
+ }) {
168
+ return this.request<unknown>(
169
+ "GET",
170
+ "/ledgerreceipts",
171
+ undefined,
172
+ params
173
+ );
174
+ }
175
+
176
+ async createLedgerReceipt(receipt: {
177
+ type: string; // JOURNAL
178
+ receiptDate: string;
179
+ name?: string;
180
+ transactions: Array<{
181
+ accountNumber: string;
182
+ debit: number;
183
+ description?: string;
184
+ }>;
185
+ }) {
186
+ return this.request<unknown>("POST", "/ledgerreceipts", receipt);
187
+ }
188
+
189
+ async listChartOfAccounts(params?: {
190
+ previousId?: string;
191
+ size?: string;
192
+ }) {
193
+ return this.request<unknown>(
194
+ "GET",
195
+ "/coa",
196
+ undefined,
197
+ params
198
+ );
199
+ }
200
+
201
+ async listFiscalYears() {
202
+ return this.request<unknown>("GET", "/fiscalyears");
203
+ }
204
+
205
+ // ── Company & Reports ─────────────────────────────────────
206
+
207
+ async getCompanyInfo() {
208
+ return this.request<unknown>("GET", "/company");
209
+ }
210
+
211
+ async listDimensions(params?: { previousId?: string; size?: string }) {
212
+ return this.request<unknown>("GET", "/dimensions", undefined, params);
213
+ }
214
+
215
+ // ── Products ──────────────────────────────────────────────
216
+
217
+ async listProducts(params?: {
218
+ previousId?: string;
219
+ size?: string;
220
+ type?: string;
221
+ }) {
222
+ return this.request<unknown>("GET", "/products", undefined, params);
223
+ }
224
+
225
+ async createProduct(product: {
226
+ name: string;
227
+ type: string;
228
+ price?: number;
229
+ unit?: string;
230
+ vatPercent?: number;
231
+ }) {
232
+ return this.request<unknown>("POST", "/products", product);
233
+ }
234
+ }
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
+ }