@kirjapro/mcp-server 0.8.2 → 0.8.5

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 (66) hide show
  1. package/CHANGELOG.md +58 -3
  2. package/dist/index.js +144 -142
  3. package/package.json +1 -1
  4. package/server.json +1 -1
  5. package/tool-fingerprints.json +4 -4
  6. package/dist/accounts.d.ts +0 -40
  7. package/dist/accounts.js +0 -45
  8. package/dist/auth.d.ts +0 -17
  9. package/dist/auth.js +0 -245
  10. package/dist/compliance/rates.d.ts +0 -53
  11. package/dist/compliance/rates.js +0 -57
  12. package/dist/database.types.d.ts +0 -6525
  13. package/dist/database.types.js +0 -20
  14. package/dist/index.d.ts +0 -2
  15. package/dist/lib/incomeStatementSynthesis.d.ts +0 -47
  16. package/dist/lib/incomeStatementSynthesis.js +0 -140
  17. package/dist/resources/company.d.ts +0 -2
  18. package/dist/resources/company.js +0 -75
  19. package/dist/resources/compliance.d.ts +0 -2
  20. package/dist/resources/compliance.js +0 -338
  21. package/dist/resources/index.d.ts +0 -2
  22. package/dist/resources/index.js +0 -10
  23. package/dist/resources/schema.d.ts +0 -2
  24. package/dist/resources/schema.js +0 -63
  25. package/dist/resources/security.d.ts +0 -2
  26. package/dist/resources/security.js +0 -33
  27. package/dist/schemas.d.ts +0 -12
  28. package/dist/schemas.js +0 -12
  29. package/dist/tools/accounts.d.ts +0 -2
  30. package/dist/tools/accounts.js +0 -48
  31. package/dist/tools/banking.d.ts +0 -2
  32. package/dist/tools/banking.js +0 -196
  33. package/dist/tools/bookings.d.ts +0 -2
  34. package/dist/tools/bookings.js +0 -294
  35. package/dist/tools/customers.d.ts +0 -2
  36. package/dist/tools/customers.js +0 -320
  37. package/dist/tools/financialNotes.d.ts +0 -2
  38. package/dist/tools/financialNotes.js +0 -255
  39. package/dist/tools/fingerprints.d.ts +0 -6
  40. package/dist/tools/fingerprints.js +0 -28
  41. package/dist/tools/fiscal-years.d.ts +0 -2
  42. package/dist/tools/fiscal-years.js +0 -35
  43. package/dist/tools/index.d.ts +0 -9
  44. package/dist/tools/index.js +0 -105
  45. package/dist/tools/invoices.d.ts +0 -2
  46. package/dist/tools/invoices.js +0 -489
  47. package/dist/tools/offers.d.ts +0 -2
  48. package/dist/tools/offers.js +0 -443
  49. package/dist/tools/received-invoices.d.ts +0 -2
  50. package/dist/tools/received-invoices.js +0 -134
  51. package/dist/tools/recurring-invoices.d.ts +0 -2
  52. package/dist/tools/recurring-invoices.js +0 -348
  53. package/dist/tools/reports.d.ts +0 -2
  54. package/dist/tools/reports.js +0 -869
  55. package/dist/tools/templates.d.ts +0 -89
  56. package/dist/tools/templates.js +0 -636
  57. package/dist/tools/transactions.d.ts +0 -2
  58. package/dist/tools/transactions.js +0 -597
  59. package/dist/tools/validation.d.ts +0 -26
  60. package/dist/tools/validation.js +0 -127
  61. package/dist/types.d.ts +0 -388
  62. package/dist/types.js +0 -34
  63. package/dist/utils.d.ts +0 -276
  64. package/dist/utils.js +0 -637
  65. package/dist/version.d.ts +0 -1
  66. package/dist/version.js +0 -3
@@ -1,348 +0,0 @@
1
- import { z } from 'zod';
2
- import { getSupabase, getCompanyId } from '../auth.js';
3
- import { clampPagination, paginate, toResult, toErrorContent, toDryRunResponse, sanitizeDbError, invokeEdgeFunction, tagCustomer, withIdempotency, IDEMPOTENCY_KEY_SCHEMA, ISO_DATE_SCHEMA, DRY_RUN_SCHEMA, READ_ONLY_ANNOTATIONS, WRITE_ANNOTATIONS, DESTRUCTIVE_ANNOTATIONS } from '../utils.js';
4
- import { PAGINATED_OUTPUT_SHAPE } from '../schemas.js';
5
- const RECURRING_INVOICE_DETAIL_OUTPUT_SHAPE = {
6
- id: z.string(),
7
- name: z.string(),
8
- frequency: z.string(),
9
- next_date: z.string().nullable(),
10
- is_active: z.boolean(),
11
- template_data: z.record(z.string(), z.unknown()).nullable(),
12
- customer: z.record(z.string(), z.unknown()).nullable(),
13
- recent_logs: z.array(z.record(z.string(), z.unknown())),
14
- };
15
- export function registerRecurringInvoiceTools(server) {
16
- // ---- LIST ----
17
- server.registerTool('kirjapro_list_recurring_invoices', {
18
- description: 'List recurring invoice templates with pagination. Shows frequency, next generation date, active status, and customer info.',
19
- inputSchema: {
20
- limit: z.number().min(1).max(200).default(50).describe('Max results (default 50)'),
21
- offset: z.number().min(0).default(0).describe('Skip first N results'),
22
- activeOnly: z.boolean().default(true).describe('Only show active templates (default true)'),
23
- },
24
- outputSchema: PAGINATED_OUTPUT_SHAPE,
25
- annotations: READ_ONLY_ANNOTATIONS,
26
- }, async (params) => {
27
- const supabase = getSupabase();
28
- const companyId = getCompanyId();
29
- const { limit, offset } = clampPagination(params.limit, params.offset);
30
- let query = supabase
31
- .from('recurring_invoices')
32
- .select('id, name, frequency, next_date, end_date, is_active, auto_send, last_generated_at, times_generated, created_at, customer:customers(id, name)', { count: 'exact' })
33
- .eq('company_id', companyId)
34
- .order('next_date');
35
- if (params.activeOnly) {
36
- query = query.eq('is_active', true);
37
- }
38
- query = query.range(offset, offset + limit - 1);
39
- const { data, count, error } = await query;
40
- if (error) {
41
- return toErrorContent(sanitizeDbError("Failed to list recurring invoices", error));
42
- }
43
- const tagged = (data || []).map((row) => ({
44
- ...row,
45
- customer: tagCustomer(row.customer),
46
- }));
47
- return toResult(paginate(tagged, count || 0, offset, limit));
48
- });
49
- // ---- GET ----
50
- server.registerTool('kirjapro_get_recurring_invoice', {
51
- description: 'Get a single recurring invoice template with full details including template data (lines, notes, payment terms) and generation history.',
52
- inputSchema: {
53
- recurringInvoiceId: z.string().uuid().describe('Recurring invoice UUID'),
54
- },
55
- outputSchema: RECURRING_INVOICE_DETAIL_OUTPUT_SHAPE,
56
- annotations: READ_ONLY_ANNOTATIONS,
57
- }, async (params) => {
58
- const supabase = getSupabase();
59
- const companyId = getCompanyId();
60
- // Explicit column allowlist — no select('*'). Adding a new column to
61
- // recurring_invoices must be a conscious decision here, since template_data
62
- // is a free-form JSONB blob the agent could act on.
63
- const { data, error } = await supabase
64
- .from('recurring_invoices')
65
- .select('id, company_id, customer_id, name, frequency, next_date, end_date, is_active, auto_send, template_data, last_generated_at, times_generated, created_at, updated_at, customer:customers(id, name, business_id, email)')
66
- .eq('id', params.recurringInvoiceId)
67
- .eq('company_id', companyId)
68
- .single();
69
- if (error || !data) {
70
- return toErrorContent(`Recurring invoice ${params.recurringInvoiceId} not found. Use kirjapro_list_recurring_invoices to find IDs.`);
71
- }
72
- // Fetch recent generation logs
73
- const { data: logs } = await supabase
74
- .from('recurring_invoice_logs')
75
- .select('id, status, scheduled_date, invoice_number, error_message, created_at')
76
- .eq('recurring_invoice_id', params.recurringInvoiceId)
77
- .eq('company_id', companyId)
78
- .order('created_at', { ascending: false })
79
- .limit(10);
80
- return toResult({
81
- ...data,
82
- customer: tagCustomer(data
83
- .customer),
84
- recent_logs: logs || [],
85
- });
86
- });
87
- // ---- CREATE ----
88
- server.registerTool('kirjapro_create_recurring_invoice', {
89
- description: 'Create a new recurring invoice template. Specify customer, frequency, start date, and invoice line items. Invoices will be generated automatically on the schedule.',
90
- inputSchema: {
91
- customerId: z.string().uuid().describe('Customer UUID'),
92
- name: z.string().min(1).max(200).describe('Template name (e.g. "Monthly hosting fee")'),
93
- frequency: z.enum(['weekly', 'monthly', 'quarterly', 'yearly']).describe('Generation frequency'),
94
- nextDate: ISO_DATE_SCHEMA.describe('First generation date (YYYY-MM-DD)'),
95
- endDate: ISO_DATE_SCHEMA.optional().describe('Stop generating after this date'),
96
- autoSend: z.boolean().default(false).describe('Auto-send generated invoices (default false)'),
97
- lines: z.array(z.object({
98
- description: z.string().describe('Line item description'),
99
- quantity: z.number().positive().describe('Quantity'),
100
- unit: z.string().default('kpl').describe('Unit (default "kpl")'),
101
- unitPrice: z
102
- .number()
103
- .finite()
104
- .min(-1_000_000_000)
105
- .max(1_000_000_000)
106
- .describe('Unit price in EUR (excl. VAT). Can be negative for credit lines.'),
107
- vatRate: z.enum(['25.5', '13.5', '10', '0']).describe('VAT rate'),
108
- })).min(1).describe('Invoice line items'),
109
- notes: z.string().optional().describe('Invoice notes'),
110
- paymentTermDays: z.number().min(0).max(365).default(14).describe('Payment term in days'),
111
- idempotencyKey: IDEMPOTENCY_KEY_SCHEMA,
112
- },
113
- annotations: WRITE_ANNOTATIONS,
114
- }, async (params) => {
115
- const supabase = getSupabase();
116
- const companyId = getCompanyId();
117
- return withIdempotency(supabase, companyId, 'kirjapro_create_recurring_invoice', params.idempotencyKey, params, async () => {
118
- // Verify customer exists
119
- const { data: customer, error: custError } = await supabase
120
- .from('customers')
121
- .select('id, name')
122
- .eq('id', params.customerId)
123
- .eq('company_id', companyId)
124
- .maybeSingle();
125
- if (custError || !customer) {
126
- return toErrorContent(`Customer ${params.customerId} not found. Use kirjapro_list_customers to find customer IDs.`);
127
- }
128
- const templateData = {
129
- lines: params.lines.map((l, i) => ({
130
- line_number: i + 1,
131
- description: l.description,
132
- quantity: l.quantity,
133
- unit: l.unit,
134
- unit_price: l.unitPrice,
135
- vat_rate: parseFloat(l.vatRate),
136
- })),
137
- notes: params.notes || null,
138
- payment_terms_days: params.paymentTermDays,
139
- };
140
- const { data, error } = await supabase
141
- .from('recurring_invoices')
142
- .insert({
143
- company_id: companyId,
144
- customer_id: params.customerId,
145
- name: params.name,
146
- frequency: params.frequency,
147
- next_date: params.nextDate,
148
- end_date: params.endDate || null,
149
- is_active: true,
150
- auto_send: params.autoSend,
151
- template_data: templateData,
152
- })
153
- .select('id, name, frequency, next_date, end_date, is_active, auto_send, created_at, updated_at')
154
- .single();
155
- if (error) {
156
- return toErrorContent(sanitizeDbError("Failed to create recurring invoice", error));
157
- }
158
- return toResult({
159
- message: `Recurring invoice "${params.name}" created for ${customer.name} (${params.frequency}, next: ${params.nextDate})`,
160
- recurring_invoice: data,
161
- });
162
- });
163
- });
164
- // ---- UPDATE ----
165
- server.registerTool('kirjapro_update_recurring_invoice', {
166
- description: 'Update a recurring invoice template. Only provided fields are changed. Use this to pause/resume (isActive), change schedule, or update line items.',
167
- inputSchema: {
168
- recurringInvoiceId: z.string().uuid().describe('Recurring invoice UUID'),
169
- name: z.string().min(1).max(200).optional().describe('Template name'),
170
- frequency: z.enum(['weekly', 'monthly', 'quarterly', 'yearly']).optional().describe('Generation frequency'),
171
- nextDate: ISO_DATE_SCHEMA.optional().describe('Next generation date'),
172
- endDate: ISO_DATE_SCHEMA.optional().nullable().describe('End date (null to remove)'),
173
- isActive: z.boolean().optional().describe('Active status (false to pause)'),
174
- autoSend: z.boolean().optional().describe('Auto-send generated invoices'),
175
- lines: z.array(z.object({
176
- description: z.string().describe('Line item description'),
177
- quantity: z.number().positive().describe('Quantity'),
178
- unit: z.string().default('kpl').describe('Unit'),
179
- unitPrice: z
180
- .number()
181
- .finite()
182
- .min(-1_000_000_000)
183
- .max(1_000_000_000)
184
- .describe('Unit price in EUR (excl. VAT). Can be negative for credit lines.'),
185
- vatRate: z.enum(['25.5', '13.5', '10', '0']).describe('VAT rate'),
186
- })).min(1).optional().describe('Replace all invoice lines'),
187
- notes: z.string().optional().nullable().describe('Invoice notes (null to remove)'),
188
- paymentTermDays: z.number().min(0).max(365).optional().describe('Payment term in days'),
189
- idempotencyKey: IDEMPOTENCY_KEY_SCHEMA,
190
- },
191
- annotations: WRITE_ANNOTATIONS,
192
- }, async (params) => {
193
- const supabase = getSupabase();
194
- const companyId = getCompanyId();
195
- // Verify exists
196
- const { data: existing, error: fetchError } = await supabase
197
- .from('recurring_invoices')
198
- .select('id, name, template_data, next_date, end_date')
199
- .eq('id', params.recurringInvoiceId)
200
- .eq('company_id', companyId)
201
- .single();
202
- if (fetchError || !existing) {
203
- return toErrorContent(`Recurring invoice ${params.recurringInvoiceId} not found. Use kirjapro_list_recurring_invoices to find IDs.`);
204
- }
205
- // Cross-field date validation: the effective nextDate must not be after
206
- // the effective endDate. Skipping this lets the caller create a schedule
207
- // that will never fire (or immediately auto-deactivate on the next cron
208
- // sweep), silently wasting an idempotency slot.
209
- const effectiveNext = params.nextDate !== undefined ? params.nextDate : existing.next_date;
210
- const effectiveEnd = params.endDate !== undefined ? params.endDate : existing.end_date;
211
- if (effectiveEnd && effectiveNext && effectiveEnd < effectiveNext) {
212
- return toErrorContent(`endDate (${effectiveEnd}) must not be earlier than nextDate (${effectiveNext}). ` +
213
- `Either clear endDate (set to null) or advance nextDate first.`);
214
- }
215
- const updates = {};
216
- if (params.name !== undefined)
217
- updates.name = params.name;
218
- if (params.frequency !== undefined)
219
- updates.frequency = params.frequency;
220
- if (params.nextDate !== undefined)
221
- updates.next_date = params.nextDate;
222
- if (params.endDate !== undefined)
223
- updates.end_date = params.endDate;
224
- if (params.isActive !== undefined)
225
- updates.is_active = params.isActive;
226
- if (params.autoSend !== undefined)
227
- updates.auto_send = params.autoSend;
228
- // Rebuild template_data if lines or notes/payment terms changed
229
- if (params.lines !== undefined || params.notes !== undefined || params.paymentTermDays !== undefined) {
230
- const currentTemplate = (existing.template_data || {});
231
- const newTemplate = { ...currentTemplate };
232
- if (params.lines !== undefined) {
233
- newTemplate.lines = params.lines.map((l, i) => ({
234
- line_number: i + 1,
235
- description: l.description,
236
- quantity: l.quantity,
237
- unit: l.unit,
238
- unit_price: l.unitPrice,
239
- vat_rate: parseFloat(l.vatRate),
240
- }));
241
- }
242
- if (params.notes !== undefined)
243
- newTemplate.notes = params.notes;
244
- if (params.paymentTermDays !== undefined)
245
- newTemplate.payment_terms_days = params.paymentTermDays;
246
- updates.template_data = newTemplate;
247
- }
248
- if (Object.keys(updates).length === 0) {
249
- return toErrorContent('No fields to update. Provide at least one field to change.');
250
- }
251
- return withIdempotency(supabase, companyId, 'kirjapro_update_recurring_invoice', params.idempotencyKey, params, async () => {
252
- const { data, error } = await supabase
253
- .from('recurring_invoices')
254
- .update(updates)
255
- .eq('id', params.recurringInvoiceId)
256
- .eq('company_id', companyId)
257
- .select('id, name, frequency, next_date, end_date, is_active, auto_send, created_at, updated_at')
258
- .single();
259
- if (error) {
260
- return toErrorContent(sanitizeDbError("Failed to update recurring invoice", error));
261
- }
262
- return toResult({
263
- message: `Recurring invoice "${data.name}" updated`,
264
- recurring_invoice: data,
265
- fields_changed: Object.keys(updates),
266
- });
267
- });
268
- });
269
- // ---- DELETE ----
270
- server.registerTool('kirjapro_delete_recurring_invoice', {
271
- description: 'Delete a recurring invoice template. Previously generated invoices are NOT affected.',
272
- inputSchema: {
273
- recurringInvoiceId: z.string().uuid().describe('Recurring invoice UUID'),
274
- idempotencyKey: IDEMPOTENCY_KEY_SCHEMA,
275
- dryRun: DRY_RUN_SCHEMA,
276
- },
277
- annotations: DESTRUCTIVE_ANNOTATIONS,
278
- }, async (params) => {
279
- const supabase = getSupabase();
280
- const companyId = getCompanyId();
281
- // Verify exists
282
- const { data: existing, error: fetchError } = await supabase
283
- .from('recurring_invoices')
284
- .select('id, name')
285
- .eq('id', params.recurringInvoiceId)
286
- .eq('company_id', companyId)
287
- .single();
288
- if (fetchError || !existing) {
289
- return toErrorContent(`Recurring invoice ${params.recurringInvoiceId} not found. Use kirjapro_list_recurring_invoices to find IDs.`);
290
- }
291
- if (params.dryRun) {
292
- return toDryRunResponse('kirjapro_delete_recurring_invoice', {
293
- action: 'delete_recurring_invoice',
294
- name: existing.name,
295
- note: 'Previously generated invoices will be unaffected.',
296
- });
297
- }
298
- return withIdempotency(supabase, companyId, 'kirjapro_delete_recurring_invoice', params.idempotencyKey, params, async () => {
299
- const { error } = await supabase
300
- .from('recurring_invoices')
301
- .delete()
302
- .eq('id', params.recurringInvoiceId)
303
- .eq('company_id', companyId);
304
- if (error) {
305
- return toErrorContent(sanitizeDbError("Failed to delete recurring invoice", error));
306
- }
307
- return toResult({
308
- message: `Recurring invoice "${existing.name}" deleted. Previously generated invoices are unaffected.`,
309
- });
310
- });
311
- });
312
- // ---- TRIGGER ----
313
- server.registerTool('kirjapro_trigger_recurring_invoice', {
314
- description: 'Manually trigger generation of a recurring invoice now, regardless of its next scheduled date. Returns the generated invoice.',
315
- inputSchema: {
316
- recurringInvoiceId: z.string().uuid().describe('Recurring invoice UUID'),
317
- idempotencyKey: IDEMPOTENCY_KEY_SCHEMA,
318
- },
319
- annotations: WRITE_ANNOTATIONS,
320
- }, async (params) => {
321
- const supabase = getSupabase();
322
- const companyId = getCompanyId();
323
- return withIdempotency(supabase, companyId, 'kirjapro_trigger_recurring_invoice', params.idempotencyKey, params, async () => {
324
- // Verify exists and is active
325
- const { data: existing, error: fetchError } = await supabase
326
- .from('recurring_invoices')
327
- .select('id, name, is_active')
328
- .eq('id', params.recurringInvoiceId)
329
- .eq('company_id', companyId)
330
- .single();
331
- if (fetchError || !existing) {
332
- return toErrorContent(`Recurring invoice ${params.recurringInvoiceId} not found. Use kirjapro_list_recurring_invoices to find IDs.`);
333
- }
334
- if (!existing.is_active) {
335
- return toErrorContent(`Recurring invoice "${existing.name}" is paused. Activate it first with kirjapro_update_recurring_invoice.`);
336
- }
337
- const result = await invokeEdgeFunction(supabase, 'generate-recurring-invoices', { recurring_invoice_id: params.recurringInvoiceId, company_id: companyId });
338
- if (result.error !== null) {
339
- return toErrorContent(result.error);
340
- }
341
- return toResult({
342
- message: `Invoice generated from "${existing.name}"`,
343
- invoice_id: result.data.invoice_id,
344
- invoice_number: result.data.invoice_number,
345
- });
346
- });
347
- });
348
- }
@@ -1,2 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare function registerReportTools(server: McpServer): void;