@kirjapro/mcp-server 0.6.3 → 0.8.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 (67) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +73 -9
  3. package/dist/index.js +1472 -657
  4. package/package.json +10 -4
  5. package/server.json +6 -2
  6. package/tool-fingerprints.json +55 -51
  7. package/dist/accounts.d.ts +0 -40
  8. package/dist/accounts.js +0 -45
  9. package/dist/auth.d.ts +0 -17
  10. package/dist/auth.js +0 -245
  11. package/dist/compliance/rates.d.ts +0 -53
  12. package/dist/compliance/rates.js +0 -57
  13. package/dist/database.types.d.ts +0 -6525
  14. package/dist/database.types.js +0 -20
  15. package/dist/index.d.ts +0 -2
  16. package/dist/lib/incomeStatementSynthesis.d.ts +0 -47
  17. package/dist/lib/incomeStatementSynthesis.js +0 -140
  18. package/dist/resources/company.d.ts +0 -2
  19. package/dist/resources/company.js +0 -75
  20. package/dist/resources/compliance.d.ts +0 -2
  21. package/dist/resources/compliance.js +0 -338
  22. package/dist/resources/index.d.ts +0 -2
  23. package/dist/resources/index.js +0 -10
  24. package/dist/resources/schema.d.ts +0 -2
  25. package/dist/resources/schema.js +0 -63
  26. package/dist/resources/security.d.ts +0 -2
  27. package/dist/resources/security.js +0 -33
  28. package/dist/schemas.d.ts +0 -12
  29. package/dist/schemas.js +0 -12
  30. package/dist/tools/accounts.d.ts +0 -2
  31. package/dist/tools/accounts.js +0 -48
  32. package/dist/tools/banking.d.ts +0 -2
  33. package/dist/tools/banking.js +0 -196
  34. package/dist/tools/bookings.d.ts +0 -2
  35. package/dist/tools/bookings.js +0 -294
  36. package/dist/tools/customers.d.ts +0 -2
  37. package/dist/tools/customers.js +0 -320
  38. package/dist/tools/financialNotes.d.ts +0 -2
  39. package/dist/tools/financialNotes.js +0 -255
  40. package/dist/tools/fingerprints.d.ts +0 -6
  41. package/dist/tools/fingerprints.js +0 -28
  42. package/dist/tools/fiscal-years.d.ts +0 -2
  43. package/dist/tools/fiscal-years.js +0 -35
  44. package/dist/tools/index.d.ts +0 -9
  45. package/dist/tools/index.js +0 -105
  46. package/dist/tools/invoices.d.ts +0 -2
  47. package/dist/tools/invoices.js +0 -489
  48. package/dist/tools/offers.d.ts +0 -2
  49. package/dist/tools/offers.js +0 -443
  50. package/dist/tools/received-invoices.d.ts +0 -2
  51. package/dist/tools/received-invoices.js +0 -134
  52. package/dist/tools/recurring-invoices.d.ts +0 -2
  53. package/dist/tools/recurring-invoices.js +0 -348
  54. package/dist/tools/reports.d.ts +0 -2
  55. package/dist/tools/reports.js +0 -869
  56. package/dist/tools/templates.d.ts +0 -89
  57. package/dist/tools/templates.js +0 -636
  58. package/dist/tools/transactions.d.ts +0 -2
  59. package/dist/tools/transactions.js +0 -597
  60. package/dist/tools/validation.d.ts +0 -26
  61. package/dist/tools/validation.js +0 -127
  62. package/dist/types.d.ts +0 -388
  63. package/dist/types.js +0 -34
  64. package/dist/utils.d.ts +0 -276
  65. package/dist/utils.js +0 -637
  66. package/dist/version.d.ts +0 -1
  67. package/dist/version.js +0 -3
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kirjapro/mcp-server",
3
- "version": "0.6.3",
4
- "description": "MCP server for Finnish accounting — exposes Kirjapro bookkeeping, invoicing, and reporting tools to Claude Code, Claude Desktop, and any MCP client.",
3
+ "version": "0.8.0",
4
+ "description": "MCP server for Finnish accounting — exposes Kirjapro bookkeeping, invoicing, and reporting tools to Claude, ChatGPT, Mistral, and other MCP clients.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "kirjapro-mcp": "dist/index.js"
@@ -30,7 +30,10 @@
30
30
  "finnish",
31
31
  "kirjanpito",
32
32
  "bookkeeping",
33
- "claude"
33
+ "claude",
34
+ "chatgpt",
35
+ "openai",
36
+ "mistral"
34
37
  ],
35
38
  "license": "MIT",
36
39
  "repository": {
@@ -74,5 +77,8 @@
74
77
  "server.json",
75
78
  "CHANGELOG.md",
76
79
  "tool-fingerprints.json"
77
- ]
80
+ ],
81
+ "allowScripts": {
82
+ "esbuild@0.28.1": true
83
+ }
78
84
  }
package/server.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@kirjapro/mcp-server",
3
3
  "displayName": "Kirjapro - Finnish Accounting",
4
4
  "description": "Finnish double-entry bookkeeping, invoicing, and financial reporting for growing businesses. Create transactions, generate invoices with VAT calculation, produce income statements and balance sheets, and validate Finnish business identifiers.",
5
- "version": "0.6.1",
5
+ "version": "0.8.0",
6
6
  "homepage": "https://kirjapro.fi",
7
7
  "repository": "https://github.com/Jaollo/kirja-pro",
8
8
  "license": "MIT",
@@ -16,7 +16,11 @@
16
16
  "required": true
17
17
  },
18
18
  "KIRJAPRO_MCP_SURFACE": {
19
- "description": "Tool surface: \"core\" (default, 26 daily-use tools) or \"full\" (all 52 tools).",
19
+ "description": "Tool surface: \"core\" (default, 27 daily-use tools) or \"full\" (all 52 tools).",
20
+ "required": false
21
+ },
22
+ "KIRJAPRO_TELEMETRY": {
23
+ "description": "First-party operational metadata attributed to the company/API key (default on). Set to false to opt out; arguments and results are never sent.",
20
24
  "required": false
21
25
  }
22
26
  }
@@ -2,207 +2,211 @@
2
2
  "fingerprints": [
3
3
  {
4
4
  "name": "kirjapro_book_purchase",
5
- "hash": "ddb30ea742fd64ef"
5
+ "hash": "21897b2fd6bd8809"
6
6
  },
7
7
  {
8
8
  "name": "kirjapro_book_sale",
9
- "hash": "9ce00f1687fc23e0"
9
+ "hash": "a7ad4daef0e9947b"
10
10
  },
11
11
  {
12
12
  "name": "kirjapro_book_vat_settlement",
13
- "hash": "f40e4169493e0557"
13
+ "hash": "48ab541acbce9e66"
14
14
  },
15
15
  {
16
16
  "name": "kirjapro_convert_offer_to_invoice",
17
- "hash": "04dab36268da64df"
17
+ "hash": "d4873b388fdd1e16"
18
18
  },
19
19
  {
20
20
  "name": "kirjapro_create_customer",
21
- "hash": "7b616c8ca38f3eb4"
21
+ "hash": "d382f2e3ea84fe5b"
22
22
  },
23
23
  {
24
24
  "name": "kirjapro_create_from_template",
25
- "hash": "d5544fb7b65148b4"
25
+ "hash": "62d7241dbdba511c"
26
26
  },
27
27
  {
28
28
  "name": "kirjapro_create_from_template_batch",
29
- "hash": "a6290b31dc0a430e"
29
+ "hash": "f10cad13dc519c7c"
30
30
  },
31
31
  {
32
32
  "name": "kirjapro_create_invoice",
33
- "hash": "e9845043b62de019"
33
+ "hash": "94cdf7ec101a6b74"
34
34
  },
35
35
  {
36
36
  "name": "kirjapro_create_offer",
37
- "hash": "8233acaf2cb9c212"
37
+ "hash": "c86cde0b4bff6e46"
38
38
  },
39
39
  {
40
40
  "name": "kirjapro_create_recurring_invoice",
41
- "hash": "1b600c2f29000b48"
41
+ "hash": "49732c922c66ea55"
42
42
  },
43
43
  {
44
44
  "name": "kirjapro_create_transaction",
45
- "hash": "c54ff7f1258036cf"
45
+ "hash": "88b052da9babfc50"
46
46
  },
47
47
  {
48
48
  "name": "kirjapro_create_transactions_batch",
49
- "hash": "35c6a2e1b60f0ec0"
49
+ "hash": "da853c41adf6e4dd"
50
50
  },
51
51
  {
52
52
  "name": "kirjapro_delete_invoice",
53
- "hash": "e9b3dca7fb361763"
53
+ "hash": "5fe36a25590a8494"
54
54
  },
55
55
  {
56
56
  "name": "kirjapro_delete_offer",
57
- "hash": "aada845caa19d45f"
57
+ "hash": "9134fb798c745d2d"
58
58
  },
59
59
  {
60
60
  "name": "kirjapro_delete_recurring_invoice",
61
- "hash": "9555352655837d73"
61
+ "hash": "ebc063d2d2551dfd"
62
62
  },
63
63
  {
64
64
  "name": "kirjapro_generate_reference",
65
- "hash": "0e616015d5f5a4f2"
65
+ "hash": "1a7d5bf6cd61e3f4"
66
66
  },
67
67
  {
68
68
  "name": "kirjapro_get_alv_summary",
69
- "hash": "f36b009641a17c59"
69
+ "hash": "139628cea5a3df31"
70
70
  },
71
71
  {
72
72
  "name": "kirjapro_get_ar_aging",
73
- "hash": "9601f79d7db1a35b"
73
+ "hash": "a464f8e3e9efbd1f"
74
74
  },
75
75
  {
76
76
  "name": "kirjapro_get_balance_sheet",
77
- "hash": "56a22899912ffd12"
77
+ "hash": "aed695790d4da869"
78
78
  },
79
79
  {
80
80
  "name": "kirjapro_get_customer",
81
- "hash": "39c9a6f9b0446f19"
81
+ "hash": "cdd2e39e19f61c8d"
82
82
  },
83
83
  {
84
84
  "name": "kirjapro_get_dashboard",
85
- "hash": "c83a87bcdc6523f8"
85
+ "hash": "3d1f3f399e400628"
86
86
  },
87
87
  {
88
88
  "name": "kirjapro_get_financial_notes",
89
- "hash": "477d0c8c04cfd0b2"
89
+ "hash": "316891e91e4a8698"
90
90
  },
91
91
  {
92
92
  "name": "kirjapro_get_general_ledger",
93
- "hash": "bada080775580e51"
93
+ "hash": "fe253675298e02f6"
94
94
  },
95
95
  {
96
96
  "name": "kirjapro_get_income_statement",
97
- "hash": "b1b464406991a361"
97
+ "hash": "73686bc7a3aa90e8"
98
98
  },
99
99
  {
100
100
  "name": "kirjapro_get_invoice",
101
- "hash": "9f8f69f9f5646def"
101
+ "hash": "2bb3ae27a2136c28"
102
102
  },
103
103
  {
104
104
  "name": "kirjapro_get_offer",
105
- "hash": "3fea3e2c4fd188ec"
105
+ "hash": "bfe46a1864c4d14f"
106
106
  },
107
107
  {
108
108
  "name": "kirjapro_get_received_invoice",
109
- "hash": "b0ba869440b8318b"
109
+ "hash": "2f811b393288346a"
110
110
  },
111
111
  {
112
112
  "name": "kirjapro_get_recurring_invoice",
113
- "hash": "16b2137a601da075"
113
+ "hash": "829312eba65633d4"
114
114
  },
115
115
  {
116
116
  "name": "kirjapro_get_transaction",
117
- "hash": "a1d566d6590bd988"
117
+ "hash": "93acf150d5b45774"
118
118
  },
119
119
  {
120
120
  "name": "kirjapro_list_accounts",
121
- "hash": "d6f1b1de2ce6ff72"
121
+ "hash": "9e6a405ee8cff0b0"
122
122
  },
123
123
  {
124
124
  "name": "kirjapro_list_bank_accounts",
125
- "hash": "073893513a866469"
125
+ "hash": "f2d180b545f08c9d"
126
126
  },
127
127
  {
128
128
  "name": "kirjapro_list_bank_transactions",
129
- "hash": "25f103b3515af680"
129
+ "hash": "30ca863fb3d5d31d"
130
130
  },
131
131
  {
132
132
  "name": "kirjapro_list_customers",
133
- "hash": "10e3851faf9980a8"
133
+ "hash": "c62ec9d70a7f8a20"
134
134
  },
135
135
  {
136
136
  "name": "kirjapro_list_fiscal_years",
137
- "hash": "ee5c509c5d88c0e4"
137
+ "hash": "c705a269c962276a"
138
138
  },
139
139
  {
140
140
  "name": "kirjapro_list_invoices",
141
- "hash": "a74c83969ca428af"
141
+ "hash": "30146c7b6bcea7c2"
142
142
  },
143
143
  {
144
144
  "name": "kirjapro_list_offers",
145
- "hash": "31679473cc8afb1b"
145
+ "hash": "eae55636427f4cf0"
146
146
  },
147
147
  {
148
148
  "name": "kirjapro_list_received_invoices",
149
- "hash": "a9caf9458f2aab6c"
149
+ "hash": "02c27793894292dc"
150
150
  },
151
151
  {
152
152
  "name": "kirjapro_list_recurring_invoices",
153
- "hash": "8f0baf7557842360"
153
+ "hash": "38c9d669f87f939f"
154
154
  },
155
155
  {
156
156
  "name": "kirjapro_list_templates",
157
- "hash": "dd3d7e1221e657a3"
157
+ "hash": "97a385984b65329e"
158
158
  },
159
159
  {
160
160
  "name": "kirjapro_list_transactions",
161
- "hash": "acaa9235ab2d4b07"
161
+ "hash": "657aa2665aeaceb6"
162
162
  },
163
163
  {
164
164
  "name": "kirjapro_lookup_business",
165
- "hash": "7371b3ee63037037"
165
+ "hash": "a2bfa16d727293c1"
166
166
  },
167
167
  {
168
168
  "name": "kirjapro_match_bank_transaction",
169
- "hash": "c3ada52caa379518"
169
+ "hash": "d2ea52636cb0bdb3"
170
170
  },
171
171
  {
172
172
  "name": "kirjapro_record_payment",
173
- "hash": "f889803ddba97cba"
173
+ "hash": "be17f36d07cf7b41"
174
174
  },
175
175
  {
176
176
  "name": "kirjapro_search_transactions",
177
- "hash": "2c6afe63e538e0e8"
177
+ "hash": "9813b26329bfbeba"
178
+ },
179
+ {
180
+ "name": "kirjapro_send_invoice",
181
+ "hash": "ec584d23c747d458"
178
182
  },
179
183
  {
180
184
  "name": "kirjapro_send_offer",
181
- "hash": "363ba364cb4cba81"
185
+ "hash": "e515ef53ccb2d0b6"
182
186
  },
183
187
  {
184
188
  "name": "kirjapro_update_customer",
185
- "hash": "f381091d34a3723d"
189
+ "hash": "1e7215f8860013d9"
186
190
  },
187
191
  {
188
192
  "name": "kirjapro_update_invoice_status",
189
- "hash": "188f0fdacf8b2ba0"
193
+ "hash": "a459597647665a70"
190
194
  },
191
195
  {
192
196
  "name": "kirjapro_update_offer_status",
193
- "hash": "7eec2e13f8e8f866"
197
+ "hash": "8e59d492424854ea"
194
198
  },
195
199
  {
196
200
  "name": "kirjapro_update_recurring_invoice",
197
- "hash": "ffc77d2d6f71ac77"
201
+ "hash": "44b516ba8a070796"
198
202
  },
199
203
  {
200
204
  "name": "kirjapro_validate_business_id",
201
- "hash": "f6c099222e793332"
205
+ "hash": "529251f6d37c64f5"
202
206
  },
203
207
  {
204
208
  "name": "kirjapro_validate_iban",
205
- "hash": "b1422a07075b34e9"
209
+ "hash": "d0acc26c1d1e2ba3"
206
210
  }
207
211
  ]
208
212
  }
@@ -1,40 +0,0 @@
1
- /**
2
- * Standard Finnish Chart of Accounts (YSE 2017) — commonly used account constants.
3
- *
4
- * MIRROR of src/lib/accounts.ts — kept in sync via mcp-server/src/accounts.test.ts.
5
- * Do not modify without updating the source file.
6
- */
7
- export declare const ACCOUNTS: {
8
- readonly MYYNTISAAMISET: "2210";
9
- readonly ALV_SAAMISET: "2320";
10
- readonly KASSA: "2910";
11
- readonly PANKKITILI: "2920";
12
- readonly OSTOVELAT: "4130";
13
- readonly ALV_VELKA: "4210";
14
- readonly ENNAKONPIDATYSVELKA: "4220";
15
- readonly TYEL_VELKA: "4240";
16
- readonly SOTU_VELKA: "4230";
17
- readonly TTV_VELKA: "4250";
18
- readonly PALKKAVELKA: "4420";
19
- readonly MYYNTI_25_5: "5010";
20
- readonly MYYNTI_13_5: "5020";
21
- readonly MYYNTI_10: "5030";
22
- readonly MYYNTI_0: "5040";
23
- readonly EU_TAVARAT: "5050";
24
- readonly EU_PALVELUT: "5060";
25
- readonly VIENTI: "5070";
26
- readonly OSTOT: "6010";
27
- readonly OSTOT_PALVELUT: "6030";
28
- readonly PALKAT: "7010";
29
- readonly ELAKEKULUT: "7100";
30
- readonly SOSIAALITURVAMAKSUT: "7210";
31
- readonly TOIMITILAVUOKRAT: "7420";
32
- readonly SAHKO: "7510";
33
- readonly POLTTOAINEET: "7610";
34
- readonly EDUSTUSKULUT: "7810";
35
- readonly PUHELIN: "7951";
36
- readonly TOIMISTOTARVIKKEET: "7952";
37
- readonly OHJELMISTOT: "7954";
38
- readonly PANKKIKULUT: "7957";
39
- readonly VAKUUTUKSET: "7970";
40
- };
package/dist/accounts.js DELETED
@@ -1,45 +0,0 @@
1
- /**
2
- * Standard Finnish Chart of Accounts (YSE 2017) — commonly used account constants.
3
- *
4
- * MIRROR of src/lib/accounts.ts — kept in sync via mcp-server/src/accounts.test.ts.
5
- * Do not modify without updating the source file.
6
- */
7
- export const ACCOUNTS = {
8
- // ── Assets (2xxx) ──
9
- MYYNTISAAMISET: '2210',
10
- ALV_SAAMISET: '2320',
11
- KASSA: '2910',
12
- PANKKITILI: '2920',
13
- // ── Liabilities (4xxx) ──
14
- OSTOVELAT: '4130',
15
- ALV_VELKA: '4210',
16
- ENNAKONPIDATYSVELKA: '4220',
17
- TYEL_VELKA: '4240',
18
- SOTU_VELKA: '4230',
19
- TTV_VELKA: '4250',
20
- PALKKAVELKA: '4420',
21
- // ── Revenue (5xxx) ──
22
- MYYNTI_25_5: '5010',
23
- MYYNTI_13_5: '5020',
24
- MYYNTI_10: '5030',
25
- MYYNTI_0: '5040',
26
- EU_TAVARAT: '5050',
27
- EU_PALVELUT: '5060',
28
- VIENTI: '5070',
29
- // ── Materials & Services (6xxx) ──
30
- OSTOT: '6010',
31
- OSTOT_PALVELUT: '6030',
32
- // ── Expenses (7xxx) ──
33
- PALKAT: '7010',
34
- ELAKEKULUT: '7100',
35
- SOSIAALITURVAMAKSUT: '7210',
36
- TOIMITILAVUOKRAT: '7420',
37
- SAHKO: '7510',
38
- POLTTOAINEET: '7610',
39
- EDUSTUSKULUT: '7810',
40
- PUHELIN: '7951',
41
- TOIMISTOTARVIKKEET: '7952',
42
- OHJELMISTOT: '7954',
43
- PANKKIKULUT: '7957',
44
- VAKUUTUKSET: '7970',
45
- };
package/dist/auth.d.ts DELETED
@@ -1,17 +0,0 @@
1
- import { SupabaseClient } from '@supabase/supabase-js';
2
- import type { Database } from './database.types.js';
3
- /**
4
- * Cancel any pending token refresh and release the event loop.
5
- * Call from `transport.onclose` so the MCP process exits cleanly.
6
- */
7
- export declare function stopTokenRefresh(): void;
8
- /**
9
- * Initialize authenticated Supabase client.
10
- *
11
- * Supports two auth modes:
12
- * 1. API key (recommended): Set KIRJAPRO_API_KEY
13
- * 2. Legacy (development): Set SUPABASE_URL + SUPABASE_ACCESS_TOKEN + KIRJAPRO_COMPANY_ID
14
- */
15
- export declare function initAuth(): Promise<void>;
16
- export declare function getSupabase(): SupabaseClient<Database>;
17
- export declare function getCompanyId(): string;
package/dist/auth.js DELETED
@@ -1,245 +0,0 @@
1
- import { createClient } from '@supabase/supabase-js';
2
- const SUPABASE_PROJECT_URL = 'https://tifewzsimmyoiepblhld.supabase.co';
3
- let supabase = null;
4
- let companyId = null;
5
- let refreshTimer = null;
6
- /**
7
- * Authenticate via API key by calling the mcp-auth edge function.
8
- * Returns the auth response containing a short-lived JWT.
9
- */
10
- async function authenticateWithApiKey(apiKey) {
11
- const url = `${SUPABASE_PROJECT_URL}/functions/v1/mcp-auth`;
12
- const response = await fetch(url, {
13
- method: 'POST',
14
- headers: { 'Content-Type': 'application/json' },
15
- body: JSON.stringify({ api_key: apiKey }),
16
- });
17
- if (!response.ok) {
18
- const body = await response.json().catch(() => ({ error: 'Unknown error' }));
19
- throw new Error(`API key authentication failed (${response.status}): ${body.error || 'Unknown error'}`);
20
- }
21
- return response.json();
22
- }
23
- /**
24
- * Create a Supabase client from an mcp-auth response.
25
- */
26
- function createClientFromAuth(auth) {
27
- return createClient(auth.supabase_url, auth.anon_key, {
28
- auth: {
29
- persistSession: false,
30
- autoRefreshToken: false,
31
- },
32
- global: {
33
- headers: {
34
- Authorization: `Bearer ${auth.access_token}`,
35
- },
36
- },
37
- });
38
- }
39
- /**
40
- * Schedule automatic token refresh before the JWT expires.
41
- *
42
- * Hardening (see audit M2):
43
- * - Jitter 75–90% of TTL: multiple concurrent agents sharing one API key
44
- * don't hit `mcp-auth` in lockstep (10 req/min rate limit).
45
- * - Recursive `setTimeout`: re-arms with the *new* `expires_in` after every
46
- * successful refresh (can't do that with `setInterval`).
47
- * - Exponential backoff (capped at 60s, jittered) on refresh failure.
48
- * - `.unref()`: doesn't keep the event loop alive if stdio transport closes.
49
- */
50
- function scheduleTokenRefresh(apiKey, expiresIn) {
51
- if (refreshTimer) {
52
- clearTimeout(refreshTimer);
53
- }
54
- const ttlMs = expiresIn * 1000;
55
- // Refresh at 75–90% of TTL, clamped to [60s, ttl-60s]
56
- const target = ttlMs * (0.75 + Math.random() * 0.15);
57
- const refreshMs = Math.max(60_000, Math.min(target, ttlMs - 60_000));
58
- const tick = async (attempt) => {
59
- try {
60
- console.error('[kirjapro-mcp] Refreshing API key token...');
61
- const auth = await authenticateWithApiKey(apiKey);
62
- supabase = createClientFromAuth(auth);
63
- companyId = auth.company_id;
64
- console.error('[kirjapro-mcp] Token refreshed successfully');
65
- scheduleTokenRefresh(apiKey, auth.expires_in);
66
- }
67
- catch (error) {
68
- console.error('[kirjapro-mcp] Token refresh failed:', error.message);
69
- // Exponential backoff with jitter: 1s, 2s, 4s, …, capped at 60s.
70
- const backoff = Math.min(60_000, 2 ** attempt * 1000) + Math.random() * 1000;
71
- refreshTimer = setTimeout(() => {
72
- void tick(attempt + 1);
73
- }, backoff);
74
- refreshTimer.unref?.();
75
- }
76
- };
77
- refreshTimer = setTimeout(() => {
78
- void tick(0);
79
- }, refreshMs);
80
- refreshTimer.unref?.();
81
- }
82
- /**
83
- * Cancel any pending token refresh and release the event loop.
84
- * Call from `transport.onclose` so the MCP process exits cleanly.
85
- */
86
- export function stopTokenRefresh() {
87
- if (refreshTimer) {
88
- clearTimeout(refreshTimer);
89
- refreshTimer = null;
90
- }
91
- }
92
- /**
93
- * Initialize using KIRJAPRO_API_KEY (recommended).
94
- * Calls the mcp-auth edge function to exchange the API key for a JWT.
95
- */
96
- async function initApiKeyAuth(apiKey) {
97
- const auth = await authenticateWithApiKey(apiKey);
98
- supabase = createClientFromAuth(auth);
99
- companyId = auth.company_id;
100
- // Validate the connection by fetching company info
101
- const { data: company, error: companyError } = await supabase
102
- .from('companies')
103
- .select('id, name')
104
- .eq('id', companyId)
105
- .single();
106
- if (companyError || !company) {
107
- throw new Error(`Cannot access company ${companyId}. ${companyError?.message || ''}`);
108
- }
109
- console.error(`[kirjapro-mcp] Authenticated via API key | Company: ${company.name}`);
110
- // Schedule automatic token refresh
111
- scheduleTokenRefresh(apiKey, auth.expires_in);
112
- }
113
- /**
114
- * Initialize using legacy env vars (for development).
115
- * Requires SUPABASE_URL, SUPABASE_ACCESS_TOKEN, KIRJAPRO_COMPANY_ID.
116
- */
117
- async function initLegacyAuth() {
118
- const supabaseUrl = process.env.SUPABASE_URL;
119
- const supabaseAccessToken = process.env.SUPABASE_ACCESS_TOKEN;
120
- const kirjaproCompanyId = process.env.KIRJAPRO_COMPANY_ID;
121
- if (!supabaseUrl) {
122
- throw new Error('SUPABASE_URL environment variable is required.');
123
- }
124
- if (!supabaseAccessToken) {
125
- throw new Error('SUPABASE_ACCESS_TOKEN environment variable is required.');
126
- }
127
- if (!kirjaproCompanyId) {
128
- throw new Error('KIRJAPRO_COMPANY_ID environment variable is required.');
129
- }
130
- // Use anon key if available, otherwise fall back to access token (works but semantically wrong)
131
- const anonKey = process.env.SUPABASE_ANON_KEY || supabaseAccessToken;
132
- supabase = createClient(supabaseUrl, anonKey, {
133
- auth: {
134
- persistSession: false,
135
- autoRefreshToken: false,
136
- },
137
- global: {
138
- headers: {
139
- Authorization: `Bearer ${supabaseAccessToken}`,
140
- },
141
- },
142
- });
143
- // Validate the token
144
- const { data: userData, error: authError } = await supabase.auth.getUser();
145
- if (authError || !userData.user) {
146
- throw new Error('Failed to authenticate with Supabase. ' +
147
- 'Token may be expired. Refresh your access token and restart the MCP server. ' +
148
- (authError?.message || ''));
149
- }
150
- // Verify company access
151
- const { data: company, error: companyError } = await supabase
152
- .from('companies')
153
- .select('id, name')
154
- .eq('id', kirjaproCompanyId)
155
- .single();
156
- if (companyError || !company) {
157
- throw new Error(`Cannot access company ${kirjaproCompanyId}. ` +
158
- 'Verify the KIRJAPRO_COMPANY_ID is correct and your user has access. ' +
159
- (companyError?.message || ''));
160
- }
161
- // Check subscription tier for API access (subscriptions are user-keyed)
162
- const { data: subscription } = await supabase
163
- .from('subscriptions')
164
- .select('tier')
165
- .eq('user_id', userData.user.id)
166
- .single();
167
- const tier = subscription?.tier || 'free';
168
- // Keep in sync with feature-gates.ts (hasApiAccess) + supabase/functions/mcp-auth.
169
- // 'growth' (Kasvu) is a paid tier with API access — DT-073.
170
- const apiTiers = ['entrepreneur', 'growth', 'company', 'custom'];
171
- if (!apiTiers.includes(tier)) {
172
- throw new Error(`API access requires a Yrittäjä, Kasvu, Tilitoimisto, or Räätälöity subscription. ` +
173
- `Current tier: ${tier}. Upgrade at https://kirjapro.fi/settings/billing`);
174
- }
175
- companyId = kirjaproCompanyId;
176
- console.error(`[kirjapro-mcp] Authenticated | Company: ${company.name} | Tier: ${tier}`);
177
- // Parse JWT exp and schedule an expiry warning — legacy auth has no
178
- // refresh path, so warn the user before the session silently dies.
179
- try {
180
- const payload = JSON.parse(Buffer.from(supabaseAccessToken.split('.')[1], 'base64url').toString());
181
- if (typeof payload.exp === 'number') {
182
- const expiresInMs = payload.exp * 1000 - Date.now();
183
- if (expiresInMs > 0 && expiresInMs < 24 * 60 * 60 * 1000) {
184
- const warnMs = Math.max(0, expiresInMs - 5 * 60 * 1000);
185
- const timer = setTimeout(() => {
186
- console.error('[kirjapro-mcp] WARNING: Legacy auth token expires in ~5 minutes. ' +
187
- 'Restart with a fresh token or switch to KIRJAPRO_API_KEY for automatic refresh.');
188
- }, warnMs);
189
- timer.unref?.();
190
- console.error(`[kirjapro-mcp] Token expires in ${Math.round(expiresInMs / 60_000)} minutes. ` +
191
- 'Consider switching to KIRJAPRO_API_KEY for automatic refresh.');
192
- }
193
- }
194
- }
195
- catch {
196
- // Not a JWT or unparseable — service_role keys don't expire
197
- }
198
- }
199
- /**
200
- * Initialize authenticated Supabase client.
201
- *
202
- * Supports two auth modes:
203
- * 1. API key (recommended): Set KIRJAPRO_API_KEY
204
- * 2. Legacy (development): Set SUPABASE_URL + SUPABASE_ACCESS_TOKEN + KIRJAPRO_COMPANY_ID
205
- */
206
- export async function initAuth() {
207
- const apiKey = process.env.KIRJAPRO_API_KEY;
208
- if (apiKey) {
209
- if (!apiKey.startsWith('kp_')) {
210
- throw new Error('Invalid KIRJAPRO_API_KEY format. Keys start with "kp_". ' +
211
- 'Generate one at https://kirjapro.fi/settings/api-keys');
212
- }
213
- await initApiKeyAuth(apiKey);
214
- return;
215
- }
216
- // Legacy env vars present?
217
- if (process.env.SUPABASE_URL && process.env.SUPABASE_ACCESS_TOKEN && process.env.KIRJAPRO_COMPANY_ID) {
218
- if (process.env.ALLOW_LEGACY_AUTH !== 'true') {
219
- throw new Error('Legacy auth (SUPABASE_URL + SUPABASE_ACCESS_TOKEN + KIRJAPRO_COMPANY_ID) is disabled.\n\n' +
220
- 'Legacy auth bypasses the mcp-auth exchange — no audit log, no IP trail, no key rotation signal.\n' +
221
- 'Switch to KIRJAPRO_API_KEY: generate a key at https://kirjapro.fi/settings/api-keys.\n\n' +
222
- 'If you must use legacy auth temporarily (e.g. local development against a dev project),\n' +
223
- 'set ALLOW_LEGACY_AUTH=true to opt in. This path will be removed in a future release.');
224
- }
225
- console.error('[kirjapro-mcp] WARNING: Using legacy auth. This bypasses audit logging and will be removed ' +
226
- 'in a future release. Migrate to KIRJAPRO_API_KEY: https://kirjapro.fi/settings/api-keys');
227
- await initLegacyAuth();
228
- return;
229
- }
230
- throw new Error('Missing authentication configuration.\n\n' +
231
- 'Set KIRJAPRO_API_KEY (recommended):\n' +
232
- ' Generate an API key at https://kirjapro.fi/settings/api-keys');
233
- }
234
- export function getSupabase() {
235
- if (!supabase) {
236
- throw new Error('Supabase client not initialized. Call initAuth() first.');
237
- }
238
- return supabase;
239
- }
240
- export function getCompanyId() {
241
- if (!companyId) {
242
- throw new Error('Company ID not set. Call initAuth() first.');
243
- }
244
- return companyId;
245
- }