e-arveldaja-mcp 0.2.0 → 0.3.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.
Files changed (40) hide show
  1. package/.claude/commands/book-invoice.md +17 -1
  2. package/README.md +8 -0
  3. package/dist/annotations.d.ts +43 -0
  4. package/dist/annotations.js +14 -0
  5. package/dist/api/base-resource.d.ts +1 -2
  6. package/dist/api/base-resource.js +5 -3
  7. package/dist/api/clients.api.js +1 -1
  8. package/dist/api/journals.api.d.ts +1 -0
  9. package/dist/api/journals.api.js +5 -1
  10. package/dist/api/products.api.js +1 -1
  11. package/dist/api/purchase-invoices.api.js +8 -8
  12. package/dist/api/sale-invoices.api.js +1 -1
  13. package/dist/api/transactions.api.js +1 -1
  14. package/dist/file-validation.js +14 -6
  15. package/dist/index.js +61 -48
  16. package/dist/logger.d.ts +4 -0
  17. package/dist/logger.js +9 -0
  18. package/dist/money.d.ts +2 -0
  19. package/dist/money.js +2 -0
  20. package/dist/prompts.d.ts +2 -0
  21. package/dist/prompts.js +456 -0
  22. package/dist/resources/dynamic-resources.d.ts +3 -0
  23. package/dist/resources/dynamic-resources.js +63 -0
  24. package/dist/tool-error.d.ts +2 -0
  25. package/dist/tool-error.js +7 -0
  26. package/dist/tools/account-balance.js +30 -23
  27. package/dist/tools/aging-analysis.js +8 -6
  28. package/dist/tools/bank-reconciliation.js +6 -6
  29. package/dist/tools/crud-tools.d.ts +2 -0
  30. package/dist/tools/crud-tools.js +64 -58
  31. package/dist/tools/document-audit.js +3 -2
  32. package/dist/tools/estonian-tax.js +17 -15
  33. package/dist/tools/financial-statements.js +26 -24
  34. package/dist/tools/lightyear-investments.js +29 -27
  35. package/dist/tools/pdf-workflow.js +13 -23
  36. package/dist/tools/purchase-vat-defaults.js +4 -3
  37. package/dist/tools/recurring-invoices.js +3 -1
  38. package/dist/tools/wise-import.js +4 -3
  39. package/package.json +1 -1
  40. package/workflows/book-invoice.md +17 -1
@@ -71,6 +71,21 @@ If no past invoices exist, call `list_purchase_articles` and choose the most app
71
71
  - 49 (Consultation/Konsultatsioon, acct 5340)
72
72
  - 62 (Other operating/Muud tegevuskulud, acct 5990)
73
73
 
74
+ ### Step 5b: Determine reverse charge VAT (pöördkäibemaks)
75
+
76
+ ALWAYS check if reverse charge applies. Set `reversed_vat_id: 1` on items when:
77
+ - Supplier is **outside Estonia** (EU or non-EU) AND provides services
78
+ - Invoice mentions "reverse charge", "Article 196", "pöördkäibemaks", or has 0% VAT with a foreign supplier
79
+ - Supplier country is NOT Estonia (check cl_code_country, VAT number prefix, or address)
80
+
81
+ When reverse charge applies:
82
+ - `vat_rate_dropdown`: "0"
83
+ - `reversed_vat_id`: 1
84
+
85
+ When supplier is Estonian with regular VAT:
86
+ - `vat_rate_dropdown`: the VAT rate (e.g. "24")
87
+ - `reversed_vat_id`: null (don't set)
88
+
74
89
  ### Step 6: Create the purchase invoice
75
90
 
76
91
  Call `create_purchase_invoice_from_pdf`:
@@ -84,7 +99,8 @@ Call `create_purchase_invoice_from_pdf`:
84
99
  - cl_purchase_articles_id: from step 5
85
100
  - purchase_accounts_id: from step 5
86
101
  - total_net_price: net amount
87
- - vat_rate_dropdown: VAT rate as string (e.g. "24")
102
+ - vat_rate_dropdown: VAT rate as string (e.g. "24", or "0" for reverse charge)
103
+ - reversed_vat_id: 1 if reverse charge applies (see step 5b)
88
104
  - amount: quantity
89
105
  - vat_price: EXACT total VAT from the original invoice
90
106
  - gross_price: EXACT total gross from the original invoice
package/README.md CHANGED
@@ -138,6 +138,14 @@ Download your Lightyear account statement CSV and capital gains report, then:
138
138
 
139
139
  The assistant will parse the trades, pair foreign currency conversions, calculate capital gains from the FIFO report, and create journal entries with the correct securities accounts.
140
140
 
141
+ ### Import Wise bank transactions
142
+
143
+ Download your Wise transaction history CSV (Account → Statements → CSV), then:
144
+
145
+ > "Import my Wise transactions from transaction-history.csv into e-arveldaja"
146
+
147
+ The assistant will parse the CSV, create bank transactions with correct amounts, and separate Wise fees into their own entries for proper expense accounting. Supports EUR and foreign currency card payments (USD etc.).
148
+
141
149
  ### Generate financial reports
142
150
 
143
151
  > "Generate a P&L and balance sheet as of 28.02.2026"
@@ -0,0 +1,43 @@
1
+ /** MCP tool annotation presets for e-arveldaja tools. */
2
+ /** Read-only data retrieval — safe to auto-approve. */
3
+ export declare const readOnly: {
4
+ readonly readOnlyHint: true;
5
+ readonly destructiveHint: false;
6
+ readonly idempotentHint: true;
7
+ readonly openWorldHint: true;
8
+ };
9
+ /** Creates a new record (draft). Not destructive but not idempotent. */
10
+ export declare const create: {
11
+ readonly readOnlyHint: false;
12
+ readonly destructiveHint: false;
13
+ readonly idempotentHint: false;
14
+ readonly openWorldHint: true;
15
+ };
16
+ /** Updates an existing record. Reversible. */
17
+ export declare const mutate: {
18
+ readonly readOnlyHint: false;
19
+ readonly destructiveHint: false;
20
+ readonly idempotentHint: true;
21
+ readonly openWorldHint: true;
22
+ };
23
+ /** Irreversible action: confirm, delete. Requires user confirmation. */
24
+ export declare const destructive: {
25
+ readonly readOnlyHint: false;
26
+ readonly destructiveHint: true;
27
+ readonly idempotentHint: true;
28
+ readonly openWorldHint: true;
29
+ };
30
+ /** Sends data externally (email, e-invoice). Irreversible and not idempotent. */
31
+ export declare const send: {
32
+ readonly readOnlyHint: false;
33
+ readonly destructiveHint: true;
34
+ readonly idempotentHint: false;
35
+ readonly openWorldHint: true;
36
+ };
37
+ /** Batch operation that modifies multiple records. Not idempotent. */
38
+ export declare const batch: {
39
+ readonly readOnlyHint: false;
40
+ readonly destructiveHint: true;
41
+ readonly idempotentHint: false;
42
+ readonly openWorldHint: true;
43
+ };
@@ -0,0 +1,14 @@
1
+ /** MCP tool annotation presets for e-arveldaja tools. */
2
+ const base = { openWorldHint: true };
3
+ /** Read-only data retrieval — safe to auto-approve. */
4
+ export const readOnly = { ...base, readOnlyHint: true, destructiveHint: false, idempotentHint: true };
5
+ /** Creates a new record (draft). Not destructive but not idempotent. */
6
+ export const create = { ...base, readOnlyHint: false, destructiveHint: false, idempotentHint: false };
7
+ /** Updates an existing record. Reversible. */
8
+ export const mutate = { ...base, readOnlyHint: false, destructiveHint: false, idempotentHint: true };
9
+ /** Irreversible action: confirm, delete. Requires user confirmation. */
10
+ export const destructive = { ...base, readOnlyHint: false, destructiveHint: true, idempotentHint: true };
11
+ /** Sends data externally (email, e-invoice). Irreversible and not idempotent. */
12
+ export const send = { ...base, readOnlyHint: false, destructiveHint: true, idempotentHint: false };
13
+ /** Batch operation that modifies multiple records. Not idempotent. */
14
+ export const batch = { ...base, readOnlyHint: false, destructiveHint: true, idempotentHint: false };
@@ -9,8 +9,7 @@ export interface ListParams {
9
9
  export declare class BaseResource<T> {
10
10
  protected client: HttpClient;
11
11
  protected basePath: string;
12
- protected idParam: string;
13
- constructor(client: HttpClient, basePath: string, idParam: string);
12
+ constructor(client: HttpClient, basePath: string);
14
13
  protected cacheKey(key: string): string;
15
14
  protected invalidateCache(pattern?: string): void;
16
15
  list(params?: ListParams): Promise<PaginatedResponse<T>>;
@@ -1,13 +1,12 @@
1
1
  import { Cache } from "../cache.js";
2
+ import { log } from "../logger.js";
2
3
  export const cache = new Cache(300);
3
4
  export class BaseResource {
4
5
  client;
5
6
  basePath;
6
- idParam;
7
- constructor(client, basePath, idParam) {
7
+ constructor(client, basePath) {
8
8
  this.client = client;
9
9
  this.basePath = basePath;
10
- this.idParam = idParam;
11
10
  }
12
11
  cacheKey(key) {
13
12
  return `${this.client.cacheNamespace}:${key}`;
@@ -36,6 +35,9 @@ export class BaseResource {
36
35
  const response = await this.list({ ...params, page });
37
36
  allItems.push(...response.items);
38
37
  totalPages = response.total_pages;
38
+ if (totalPages > 1 && page === 1) {
39
+ log("info", `${this.basePath}: fetching ${totalPages} pages...`);
40
+ }
39
41
  page++;
40
42
  } while (page <= totalPages);
41
43
  return allItems;
@@ -1,7 +1,7 @@
1
1
  import { BaseResource } from "./base-resource.js";
2
2
  export class ClientsApi extends BaseResource {
3
3
  constructor(client) {
4
- super(client, "/clients", "clients_id");
4
+ super(client, "/clients");
5
5
  }
6
6
  async deactivate(id) {
7
7
  this.invalidateCache();
@@ -10,6 +10,7 @@ export declare class JournalsApi extends BaseResource<Journal> {
10
10
  */
11
11
  listAllWithPostings(): Promise<Journal[]>;
12
12
  confirm(id: number): Promise<ApiResponse>;
13
+ invalidate(id: number): Promise<ApiResponse>;
13
14
  getDocument(id: number): Promise<ApiFile>;
14
15
  uploadDocument(id: number, name: string, contents: string): Promise<ApiResponse>;
15
16
  deleteDocument(id: number): Promise<ApiResponse>;
@@ -1,7 +1,7 @@
1
1
  import { BaseResource, cache } from "./base-resource.js";
2
2
  export class JournalsApi extends BaseResource {
3
3
  constructor(client) {
4
- super(client, "/journals", "journals_id");
4
+ super(client, "/journals");
5
5
  }
6
6
  /**
7
7
  * Load all journals with postings guaranteed to be populated.
@@ -38,6 +38,10 @@ export class JournalsApi extends BaseResource {
38
38
  this.invalidateCache();
39
39
  return this.client.patch(`/journals/${id}/register`, {});
40
40
  }
41
+ async invalidate(id) {
42
+ this.invalidateCache();
43
+ return this.client.patch(`/journals/${id}/invalidate`, {});
44
+ }
41
45
  async getDocument(id) {
42
46
  return this.client.get(`/journals/${id}/document_user`);
43
47
  }
@@ -1,7 +1,7 @@
1
1
  import { BaseResource } from "./base-resource.js";
2
2
  export class ProductsApi extends BaseResource {
3
3
  constructor(client) {
4
- super(client, "/products", "products_id");
4
+ super(client, "/products");
5
5
  }
6
6
  async deactivate(id) {
7
7
  this.invalidateCache();
@@ -1,5 +1,5 @@
1
1
  import { BaseResource } from "./base-resource.js";
2
- const roundMoney = (v) => Math.round(v * 100) / 100;
2
+ import { roundMoney } from "../money.js";
3
3
  /**
4
4
  * For non-VAT (mitte-KMD) companies: set project_no_vat_gross_price on items
5
5
  * so the API computes item-level vat_amount for informational tracking.
@@ -31,7 +31,7 @@ function normalizeItemsForNonVat(items, isVatRegistered, grossPrice) {
31
31
  }
32
32
  export class PurchaseInvoicesApi extends BaseResource {
33
33
  constructor(client) {
34
- super(client, "/purchase_invoices", "purchase_invoices_id");
34
+ super(client, "/purchase_invoices");
35
35
  }
36
36
  /**
37
37
  * Create a purchase invoice and set invoice-level totals.
@@ -55,8 +55,8 @@ export class PurchaseInvoicesApi extends BaseResource {
55
55
  // Read back to get item-level VAT computed by API
56
56
  const invoice = await this.get(id);
57
57
  const items = invoice.items;
58
- const itemVat = items ? Math.round(items.reduce((s, i) => s + (i.vat_amount ?? 0), 0) * 100) / 100 : 0;
59
- const itemNet = items ? Math.round(items.reduce((s, i) => s + (i.total_net_price ?? 0), 0) * 100) / 100 : 0;
58
+ const itemVat = items ? roundMoney(items.reduce((s, i) => s + (i.vat_amount ?? 0), 0)) : 0;
59
+ const itemNet = items ? roundMoney(items.reduce((s, i) => s + (i.total_net_price ?? 0), 0)) : 0;
60
60
  // Invoice-level VAT: explicit value wins for VAT-registered companies.
61
61
  // Non-KMD companies must keep invoice-level vat_price at 0 even if item VAT is tracked.
62
62
  const vat = isVatRegistered
@@ -65,7 +65,7 @@ export class PurchaseInvoicesApi extends BaseResource {
65
65
  // Invoice-level gross: explicit value wins, otherwise net + actual item VAT
66
66
  const gross = grossPrice !== undefined
67
67
  ? grossPrice
68
- : Math.round((itemNet + itemVat) * 100) / 100;
68
+ : roundMoney(itemNet + itemVat);
69
69
  if (vat > 0 || gross > 0) {
70
70
  await this.update(id, { vat_price: vat, gross_price: gross, items: invoice.items });
71
71
  this.invalidateCache();
@@ -80,10 +80,10 @@ export class PurchaseInvoicesApi extends BaseResource {
80
80
  const invoice = await this.get(id);
81
81
  const items = invoice.items;
82
82
  if (items) {
83
- const itemVat = Math.round(items.reduce((s, i) => s + (i.vat_amount ?? 0), 0) * 100) / 100;
84
- const net = Math.round(items.reduce((s, i) => s + (i.total_net_price ?? 0), 0) * 100) / 100;
83
+ const itemVat = roundMoney(items.reduce((s, i) => s + (i.vat_amount ?? 0), 0));
84
+ const net = roundMoney(items.reduce((s, i) => s + (i.total_net_price ?? 0), 0));
85
85
  const vat = isVatRegistered ? itemVat : 0;
86
- const gross = Math.round((net + itemVat) * 100) / 100;
86
+ const gross = roundMoney(net + itemVat);
87
87
  const currentGross = invoice.gross_price;
88
88
  const shouldRepair = !currentGross || Math.abs(currentGross - gross) > 0.02;
89
89
  if (shouldRepair) {
@@ -1,7 +1,7 @@
1
1
  import { BaseResource } from "./base-resource.js";
2
2
  export class SaleInvoicesApi extends BaseResource {
3
3
  constructor(client) {
4
- super(client, "/sale_invoices", "sale_invoices_id");
4
+ super(client, "/sale_invoices");
5
5
  }
6
6
  async confirm(id) {
7
7
  this.invalidateCache();
@@ -1,7 +1,7 @@
1
1
  import { BaseResource } from "./base-resource.js";
2
2
  export class TransactionsApi extends BaseResource {
3
3
  constructor(client) {
4
- super(client, "/transactions", "transactions_id");
4
+ super(client, "/transactions");
5
5
  }
6
6
  /**
7
7
  * Confirm a transaction with distribution rows.
@@ -1,17 +1,25 @@
1
1
  import { stat, realpath } from "fs/promises";
2
2
  import { resolve, extname, isAbsolute } from "path";
3
- import { existsSync } from "fs";
3
+ import { existsSync, realpathSync } from "fs";
4
4
  import { homedir } from "os";
5
5
  /**
6
6
  * Allowed root directories for file reads. Configurable via EARVELDAJA_ALLOWED_PATHS
7
7
  * (colon-separated list). Defaults to $HOME and /tmp.
8
+ * Roots are resolved through symlinks so that the check works even if
9
+ * e.g. /tmp is a symlink to /private/tmp (macOS).
8
10
  */
9
11
  function getAllowedRoots() {
10
- const envPaths = process.env.EARVELDAJA_ALLOWED_PATHS;
11
- if (envPaths) {
12
- return envPaths.split(":").map(p => resolve(p));
13
- }
14
- return [homedir(), "/tmp"];
12
+ const raw = process.env.EARVELDAJA_ALLOWED_PATHS
13
+ ? process.env.EARVELDAJA_ALLOWED_PATHS.split(":").map(p => resolve(p))
14
+ : [homedir(), "/tmp"];
15
+ return raw.map(root => {
16
+ try {
17
+ return realpathSync(root);
18
+ }
19
+ catch {
20
+ return root;
21
+ }
22
+ });
15
23
  }
16
24
  /**
17
25
  * Get the project root (directory containing package.json).
package/dist/index.js CHANGED
@@ -25,6 +25,11 @@ import { registerDocumentAuditTools } from "./tools/document-audit.js";
25
25
  import { registerLightyearTools } from "./tools/lightyear-investments.js";
26
26
  import { registerWiseImportTools } from "./tools/wise-import.js";
27
27
  import { registerResources } from "./resources/static-resources.js";
28
+ import { registerDynamicResources } from "./resources/dynamic-resources.js";
29
+ import { registerPrompts } from "./prompts.js";
30
+ import { toolError } from "./tool-error.js";
31
+ import { setLogger } from "./logger.js";
32
+ import { readOnly, mutate } from "./annotations.js";
28
33
  function buildApiContext(httpClient) {
29
34
  return {
30
35
  clients: new ClientsApi(httpClient),
@@ -87,7 +92,7 @@ async function main() {
87
92
  const api = createScopedApiContext(connectionState, connectionContexts, invocationStorage);
88
93
  const server = new McpServer({
89
94
  name: "e-arveldaja",
90
- version: "1.0.0",
95
+ version: "0.3.0",
91
96
  description: "EXPERIMENTAL, UNOFFICIAL MCP server for the Estonian e-arveldaja (e-Financials) API. " +
92
97
  "NOT affiliated with or endorsed by RIK. Use entirely at your own risk — " +
93
98
  "this software interacts with live financial data and can create, modify, and delete accounting records. " +
@@ -98,7 +103,7 @@ async function main() {
98
103
  });
99
104
  // --- Multi-account tools ---
100
105
  server.tool("list_connections", "List all available e-arveldaja connections (API key files). " +
101
- "Shows which connection is currently active.", {}, async () => {
106
+ "Shows which connection is currently active.", {}, readOnly, async () => {
102
107
  const connections = allConfigs.map((nc, i) => ({
103
108
  index: i,
104
109
  name: nc.name,
@@ -121,7 +126,7 @@ async function main() {
121
126
  "Clears cached data atomically. Use list_connections to see available indices. " +
122
127
  "In-flight tool calls will fail fast and should be retried on the intended connection.", {
123
128
  index: z.number().describe("Connection index from list_connections"),
124
- }, async ({ index }) => {
129
+ }, mutate, async ({ index }) => {
125
130
  if (index < 0 || index >= allConfigs.length) {
126
131
  return {
127
132
  content: [{
@@ -160,60 +165,68 @@ async function main() {
160
165
  }],
161
166
  };
162
167
  });
163
- const originalTool = server.tool.bind(server);
164
- const wrapToolHandler = (handler) => {
165
- if (typeof handler !== "function") {
166
- return handler;
167
- }
168
- return async (...handlerArgs) => {
168
+ // Wrap server.tool to pin each handler to a connection snapshot via AsyncLocalStorage.
169
+ // The wrapper captures the active connection at invocation time, runs the handler
170
+ // inside that snapshot, and asserts the connection hasn't changed on return.
171
+ function wrapHandler(handler) {
172
+ return (async (...args) => {
169
173
  const snapshot = captureSnapshot(connectionState);
170
- return invocationStorage.run(snapshot, async () => {
171
- const result = await handler(...handlerArgs);
172
- assertSnapshotCurrent(connectionState, snapshot);
173
- return result;
174
- });
175
- };
176
- };
177
- server.tool = ((...toolArgs) => {
178
- if (toolArgs.length === 4) {
179
- const [name, descriptionOrParamsSchema, paramsSchemaOrAnnotations, handler] = toolArgs;
180
- return originalTool(name, descriptionOrParamsSchema, paramsSchemaOrAnnotations, wrapToolHandler(handler));
181
- }
182
- if (toolArgs.length === 3) {
183
- const [name, descriptionOrParamsSchema, handler] = toolArgs;
184
- return originalTool(name, descriptionOrParamsSchema, wrapToolHandler(handler));
185
- }
186
- if (toolArgs.length === 5) {
187
- const [name, description, paramsSchema, annotations, handler] = toolArgs;
188
- return originalTool(name, description, paramsSchema, annotations, wrapToolHandler(handler));
189
- }
190
- if (toolArgs.length === 2) {
191
- const [name, handler] = toolArgs;
192
- return originalTool(name, wrapToolHandler(handler));
193
- }
194
- return originalTool(...toolArgs);
174
+ try {
175
+ return await invocationStorage.run(snapshot, async () => {
176
+ const result = await handler(...args);
177
+ assertSnapshotCurrent(connectionState, snapshot);
178
+ return result;
179
+ });
180
+ }
181
+ catch (error) {
182
+ return toolError(error);
183
+ }
184
+ });
185
+ }
186
+ // Create a proxy that intercepts server.tool() calls and wraps the last function argument.
187
+ // This is forward-compatible with any overload signature the SDK may add.
188
+ const scopedServer = new Proxy(server, {
189
+ get(target, prop, receiver) {
190
+ if (prop !== "tool")
191
+ return Reflect.get(target, prop, receiver);
192
+ return (...toolArgs) => {
193
+ // The handler is always the last argument and always a function
194
+ const lastIdx = toolArgs.length - 1;
195
+ if (lastIdx >= 0 && typeof toolArgs[lastIdx] === "function") {
196
+ toolArgs[lastIdx] = wrapHandler(toolArgs[lastIdx]);
197
+ }
198
+ return target.tool(...toolArgs);
199
+ };
200
+ },
195
201
  });
196
- // Register all tools
197
- registerCrudTools(server, api);
198
- registerAccountBalanceTools(server, api);
199
- registerPdfWorkflowTools(server, api);
200
- registerBankReconciliationTools(server, api);
201
- registerFinancialStatementTools(server, api);
202
- registerAgingTools(server, api);
203
- registerRecurringInvoiceTools(server, api);
204
- registerEstonianTaxTools(server, api);
205
- registerDocumentAuditTools(server, api);
206
- registerLightyearTools(server, api);
207
- registerWiseImportTools(server, api);
202
+ // Register all tools (via scopedServer so handlers get connection-pinned)
203
+ registerCrudTools(scopedServer, api);
204
+ registerAccountBalanceTools(scopedServer, api);
205
+ registerPdfWorkflowTools(scopedServer, api);
206
+ registerBankReconciliationTools(scopedServer, api);
207
+ registerFinancialStatementTools(scopedServer, api);
208
+ registerAgingTools(scopedServer, api);
209
+ registerRecurringInvoiceTools(scopedServer, api);
210
+ registerEstonianTaxTools(scopedServer, api);
211
+ registerDocumentAuditTools(scopedServer, api);
212
+ registerLightyearTools(scopedServer, api);
213
+ registerWiseImportTools(scopedServer, api);
208
214
  // Register resources
209
215
  registerResources(server, api);
216
+ registerDynamicResources(server, api);
217
+ // Register prompts
218
+ registerPrompts(server);
210
219
  // Start server
211
220
  const transport = new StdioServerTransport();
212
221
  await server.connect(transport);
222
+ // Route log output through MCP logging protocol
223
+ setLogger((level, message) => {
224
+ server.sendLoggingMessage({ level, data: message });
225
+ });
213
226
  const names = allConfigs.map(c => c.name).join(", ");
214
- console.error(`e-arveldaja MCP server started (${allConfigs.length} connection(s): ${names})`);
227
+ process.stderr.write(`e-arveldaja MCP server started (${allConfigs.length} connection(s): ${names})\n`);
215
228
  }
216
229
  main().catch((err) => {
217
- console.error("Fatal error:", err);
230
+ process.stderr.write(`Fatal error: ${err instanceof Error ? err.message : String(err)}\n`);
218
231
  process.exit(1);
219
232
  });
@@ -0,0 +1,4 @@
1
+ type LogFn = (level: "debug" | "info" | "warning" | "error", message: string) => void;
2
+ export declare function setLogger(fn: LogFn): void;
3
+ export declare function log(level: "debug" | "info" | "warning" | "error", message: string): void;
4
+ export {};
package/dist/logger.js ADDED
@@ -0,0 +1,9 @@
1
+ let _log = (_level, message) => {
2
+ process.stderr.write(`${message}\n`);
3
+ };
4
+ export function setLogger(fn) {
5
+ _log = fn;
6
+ }
7
+ export function log(level, message) {
8
+ _log(level, message);
9
+ }
@@ -0,0 +1,2 @@
1
+ /** Round to 2 decimal places (cents). Use for all monetary arithmetic. */
2
+ export declare const roundMoney: (v: number) => number;
package/dist/money.js ADDED
@@ -0,0 +1,2 @@
1
+ /** Round to 2 decimal places (cents). Use for all monetary arithmetic. */
2
+ export const roundMoney = (v) => Math.round(v * 100) / 100;
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerPrompts(server: McpServer): void;