@kirjapro/mcp-server 0.6.3 → 0.7.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/CHANGELOG.md +19 -0
- package/README.md +28 -6
- package/dist/index.js +195 -47
- package/package.json +10 -4
- package/server.json +2 -2
- package/tool-fingerprints.json +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-07-16
|
|
4
|
+
|
|
5
|
+
Adds invoice delivery as a first-class MCP workflow.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`kirjapro_send_invoice`** sends a draft invoice through an explicit `email` or `einvoice` channel. The tool calls the authenticated `dispatch-invoice` Edge Function, keeps provider credentials server-side, and returns the delivery identifier.
|
|
10
|
+
- Invoice create → send guidance now uses the delivery tool instead of redirecting users to the web UI.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- The default `core` surface now contains 27 tools; the complete catalog remains 52 tools.
|
|
15
|
+
- Package, server manifest, and MCPB versions are aligned at 0.7.0.
|
|
16
|
+
- Client setup now documents native local `stdio` use with Mistral Vibe and ChatGPT access through OpenAI Secure MCP Tunnel. Mistral Le Chat is explicitly marked as requiring a separately hosted remote endpoint.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Server instructions no longer list e-invoice sending as web-only now that `kirjapro_send_invoice` supports the explicit `einvoice` channel.
|
|
21
|
+
|
|
3
22
|
## [0.6.1] - 2026-05-29
|
|
4
23
|
|
|
5
24
|
Hardening release from a full server audit (12 dimensions, adversarially verified). No new tools; tightens input validation, closes two security gaps, and corrects compliance guidance + tool descriptions. `tool-fingerprints.json` regenerated for the description/input-schema changes.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @kirjapro/mcp-server
|
|
2
2
|
|
|
3
|
-
MCP server for Finnish accounting. Exposes Kirjapro bookkeeping, invoicing, and reporting tools to Claude Code, Claude Desktop, and
|
|
3
|
+
MCP server for Finnish accounting. Exposes Kirjapro bookkeeping, invoicing, and reporting tools to Claude Code, Claude Desktop, Mistral Vibe, ChatGPT through Secure MCP Tunnel, and other MCP clients.
|
|
4
4
|
|
|
5
5
|
**Full documentation:** [kirjapro.fi/docs/mcp](https://kirjapro.fi/docs/mcp/)
|
|
6
6
|
|
|
@@ -72,9 +72,30 @@ Add to `claude_desktop_config.json`:
|
|
|
72
72
|
|
|
73
73
|
Restart Claude Desktop after saving.
|
|
74
74
|
|
|
75
|
+
#### Mistral Vibe
|
|
76
|
+
|
|
77
|
+
Add to `~/.vibe/config.toml`:
|
|
78
|
+
|
|
79
|
+
```toml
|
|
80
|
+
[[mcp_servers]]
|
|
81
|
+
name = "kirjapro"
|
|
82
|
+
transport = "stdio"
|
|
83
|
+
command = "npx"
|
|
84
|
+
args = ["-y", "@kirjapro/mcp-server"]
|
|
85
|
+
env = { KIRJAPRO_API_KEY = "kp_your_key_here" }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Restart Vibe and verify the connection from `/mcp`.
|
|
89
|
+
|
|
90
|
+
#### ChatGPT
|
|
91
|
+
|
|
92
|
+
ChatGPT requires a remote MCP connection and cannot launch this local `stdio` package directly. Use [OpenAI Secure MCP Tunnel](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels) to bridge ChatGPT to `npx -y @kirjapro/mcp-server`. Full write/modify support currently requires ChatGPT Business or Enterprise/Edu; see the [full setup guide](https://kirjapro.fi/docs/mcp/) for the tunnel commands and plan limitations.
|
|
93
|
+
|
|
94
|
+
Mistral Le Chat likewise requires a remote MCP server URL. The local npm package works directly with Mistral Vibe; Le Chat support requires a separately hosted remote endpoint.
|
|
95
|
+
|
|
75
96
|
#### Other clients
|
|
76
97
|
|
|
77
|
-
See [full setup guide](https://kirjapro.fi/docs/mcp/) for Cursor, VS Code / Copilot, Windsurf, and generic stdio.
|
|
98
|
+
See [full setup guide](https://kirjapro.fi/docs/mcp/) for ChatGPT, Mistral Vibe, Cursor, VS Code / Copilot, Windsurf, and generic stdio.
|
|
78
99
|
|
|
79
100
|
### 3. Verify
|
|
80
101
|
|
|
@@ -86,6 +107,7 @@ A few prompts to feel out the daily workflows the agent handles cleanly:
|
|
|
86
107
|
|
|
87
108
|
- *"Show my dashboard"* — financial snapshot of the current fiscal year
|
|
88
109
|
- *"Create an invoice for Acme Oy, 5000 € for consulting in March"* — full lifecycle from customer lookup to draft invoice
|
|
110
|
+
- *"Send invoice #42 by email"* — server-rendered PDF delivery with an explicit channel
|
|
89
111
|
- *"Draft a contract for Acme Oy and email it to john@acme.fi, 3-month retainer at 5000 € / month"* — `tarjous`/`sopimus` create + customer-visible email link
|
|
90
112
|
- *"Record that invoice #42 was paid yesterday"* — payment recording + auto status transition
|
|
91
113
|
- *"What's my VAT for this quarter?"* — `kirjapro_get_alv_summary` with OmaVero field codes
|
|
@@ -94,7 +116,7 @@ A few prompts to feel out the daily workflows the agent handles cleanly:
|
|
|
94
116
|
|
|
95
117
|
## What's included
|
|
96
118
|
|
|
97
|
-
**
|
|
119
|
+
**27 tools** in the default `core` surface, focused on the daily-use workflows: read your books, invoice flow, transactions + banking, contract create / send / convert, Finnish validators. The full 52-tool catalog (booking shortcuts, recurring invoices, templates, batch tools, etc.) is available via opt-in:
|
|
98
120
|
|
|
99
121
|
```jsonc
|
|
100
122
|
{
|
|
@@ -104,14 +126,14 @@ A few prompts to feel out the daily workflows the agent handles cleanly:
|
|
|
104
126
|
"args": ["-y", "@kirjapro/mcp-server"],
|
|
105
127
|
"env": {
|
|
106
128
|
"KIRJAPRO_API_KEY": "kp_your_key_here",
|
|
107
|
-
"KIRJAPRO_MCP_SURFACE": "full" // 52 tools instead of
|
|
129
|
+
"KIRJAPRO_MCP_SURFACE": "full" // 52 tools instead of 27
|
|
108
130
|
}
|
|
109
131
|
}
|
|
110
132
|
}
|
|
111
133
|
}
|
|
112
134
|
```
|
|
113
135
|
|
|
114
|
-
**Why default to
|
|
136
|
+
**Why default to 27 instead of 52:** 2025-2026 SOTA research on tool catalog scaling (Anthropic, OpenAI, MCP authors) shows tool-selection accuracy degrades between 25-50 tools per catalog. A focused surface produces better first-call selections for the daily workflows. As real usage data informs which deferred tools matter most, they'll be promoted into core.
|
|
115
137
|
|
|
116
138
|
**Payroll is intentionally not exposed.** Kirjapro does not calculate or book salaries. Direct users to a dedicated payroll service (such as [Palkkaus.fi](https://www.palkkaus.fi) or [Palkka.fi](https://www.palkka.fi)) for the calculation — these services hold the regulated responsibility for Tulorekisteri-ilmoitus, TyEL/TVR raportointi, and ennakonpidätys-tilitys. Once the payslip is calculated externally, book it manually via `kirjapro_create_transaction`.
|
|
117
139
|
|
|
@@ -119,7 +141,7 @@ A few prompts to feel out the daily workflows the agent handles cleanly:
|
|
|
119
141
|
|---|---|
|
|
120
142
|
| Read your books | `list_fiscal_years`, `list_accounts`, `get_dashboard`, `get_income_statement`, `get_balance_sheet`, `get_alv_summary`, `get_ar_aging` |
|
|
121
143
|
| Customers | `list_customers`, `get_customer`, `create_customer`, `lookup_business` (PRH) |
|
|
122
|
-
| Invoice lifecycle | `list_invoices`, `get_invoice`, `create_invoice`, `record_payment`, `update_invoice_status` |
|
|
144
|
+
| Invoice lifecycle | `list_invoices`, `get_invoice`, `create_invoice`, `send_invoice`, `record_payment`, `update_invoice_status` |
|
|
123
145
|
| Transactions + banking | `list_transactions`, `create_transaction`, `list_bank_transactions`, `match_bank_transaction` |
|
|
124
146
|
| Contracts | `create_offer` (`tarjous` / `sopimus`), `send_offer` (email link), `convert_offer_to_invoice` |
|
|
125
147
|
| Validators (free) | `validate_business_id`, `validate_iban`, `generate_reference` |
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
8
|
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
-
|
|
9
|
+
try {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
} catch (e) {
|
|
12
|
+
throw mod = 0, e;
|
|
13
|
+
}
|
|
10
14
|
};
|
|
11
15
|
var __export = (target, all) => {
|
|
12
16
|
for (var name in all)
|
|
@@ -3105,6 +3109,9 @@ var require_utils = __commonJS({
|
|
|
3105
3109
|
"use strict";
|
|
3106
3110
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3107
3111
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
3112
|
+
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
3113
|
+
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
3114
|
+
var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
|
|
3108
3115
|
function stringArrayToHexStripped(input) {
|
|
3109
3116
|
let acc = "";
|
|
3110
3117
|
let code = 0;
|
|
@@ -3297,27 +3304,77 @@ var require_utils = __commonJS({
|
|
|
3297
3304
|
}
|
|
3298
3305
|
return output.join("");
|
|
3299
3306
|
}
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3307
|
+
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3308
|
+
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3309
|
+
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
3310
|
+
function reescapeHostDelimiters(host, isIP) {
|
|
3311
|
+
const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
3312
|
+
re.lastIndex = 0;
|
|
3313
|
+
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
3314
|
+
}
|
|
3315
|
+
function normalizePercentEncoding(input, decodeUnreserved = false) {
|
|
3316
|
+
if (input.indexOf("%") === -1) {
|
|
3317
|
+
return input;
|
|
3310
3318
|
}
|
|
3311
|
-
|
|
3312
|
-
|
|
3319
|
+
let output = "";
|
|
3320
|
+
for (let i = 0; i < input.length; i++) {
|
|
3321
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3322
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3323
|
+
if (isHexPair(hex3)) {
|
|
3324
|
+
const normalizedHex = hex3.toUpperCase();
|
|
3325
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3326
|
+
if (decodeUnreserved && isUnreserved(decoded)) {
|
|
3327
|
+
output += decoded;
|
|
3328
|
+
} else {
|
|
3329
|
+
output += "%" + normalizedHex;
|
|
3330
|
+
}
|
|
3331
|
+
i += 2;
|
|
3332
|
+
continue;
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
output += input[i];
|
|
3313
3336
|
}
|
|
3314
|
-
|
|
3315
|
-
|
|
3337
|
+
return output;
|
|
3338
|
+
}
|
|
3339
|
+
function normalizePathEncoding(input) {
|
|
3340
|
+
let output = "";
|
|
3341
|
+
for (let i = 0; i < input.length; i++) {
|
|
3342
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3343
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3344
|
+
if (isHexPair(hex3)) {
|
|
3345
|
+
const normalizedHex = hex3.toUpperCase();
|
|
3346
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3347
|
+
if (decoded !== "." && isUnreserved(decoded)) {
|
|
3348
|
+
output += decoded;
|
|
3349
|
+
} else {
|
|
3350
|
+
output += "%" + normalizedHex;
|
|
3351
|
+
}
|
|
3352
|
+
i += 2;
|
|
3353
|
+
continue;
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
if (isPathCharacter(input[i])) {
|
|
3357
|
+
output += input[i];
|
|
3358
|
+
} else {
|
|
3359
|
+
output += escape(input[i]);
|
|
3360
|
+
}
|
|
3316
3361
|
}
|
|
3317
|
-
|
|
3318
|
-
|
|
3362
|
+
return output;
|
|
3363
|
+
}
|
|
3364
|
+
function escapePreservingEscapes(input) {
|
|
3365
|
+
let output = "";
|
|
3366
|
+
for (let i = 0; i < input.length; i++) {
|
|
3367
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3368
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3369
|
+
if (isHexPair(hex3)) {
|
|
3370
|
+
output += "%" + hex3.toUpperCase();
|
|
3371
|
+
i += 2;
|
|
3372
|
+
continue;
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
output += escape(input[i]);
|
|
3319
3376
|
}
|
|
3320
|
-
return
|
|
3377
|
+
return output;
|
|
3321
3378
|
}
|
|
3322
3379
|
function recomposeAuthority(component) {
|
|
3323
3380
|
const uriTokens = [];
|
|
@@ -3332,7 +3389,7 @@ var require_utils = __commonJS({
|
|
|
3332
3389
|
if (ipV6res.isIPV6 === true) {
|
|
3333
3390
|
host = `[${ipV6res.escapedHost}]`;
|
|
3334
3391
|
} else {
|
|
3335
|
-
host =
|
|
3392
|
+
host = reescapeHostDelimiters(host, false);
|
|
3336
3393
|
}
|
|
3337
3394
|
}
|
|
3338
3395
|
uriTokens.push(host);
|
|
@@ -3346,7 +3403,10 @@ var require_utils = __commonJS({
|
|
|
3346
3403
|
module.exports = {
|
|
3347
3404
|
nonSimpleDomain,
|
|
3348
3405
|
recomposeAuthority,
|
|
3349
|
-
|
|
3406
|
+
reescapeHostDelimiters,
|
|
3407
|
+
normalizePercentEncoding,
|
|
3408
|
+
normalizePathEncoding,
|
|
3409
|
+
escapePreservingEscapes,
|
|
3350
3410
|
removeDotSegments,
|
|
3351
3411
|
isIPv4,
|
|
3352
3412
|
isUUID,
|
|
@@ -3570,12 +3630,12 @@ var require_schemes = __commonJS({
|
|
|
3570
3630
|
var require_fast_uri = __commonJS({
|
|
3571
3631
|
"node_modules/fast-uri/index.js"(exports, module) {
|
|
3572
3632
|
"use strict";
|
|
3573
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority,
|
|
3633
|
+
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3574
3634
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3575
3635
|
function normalize(uri, options) {
|
|
3576
3636
|
if (typeof uri === "string") {
|
|
3577
3637
|
uri = /** @type {T} */
|
|
3578
|
-
|
|
3638
|
+
normalizeString(uri, options);
|
|
3579
3639
|
} else if (typeof uri === "object") {
|
|
3580
3640
|
uri = /** @type {T} */
|
|
3581
3641
|
parse3(serialize(uri, options), options);
|
|
@@ -3642,19 +3702,9 @@ var require_fast_uri = __commonJS({
|
|
|
3642
3702
|
return target;
|
|
3643
3703
|
}
|
|
3644
3704
|
function equal(uriA, uriB, options) {
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
} else if (typeof uriA === "object") {
|
|
3649
|
-
uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
|
|
3650
|
-
}
|
|
3651
|
-
if (typeof uriB === "string") {
|
|
3652
|
-
uriB = unescape(uriB);
|
|
3653
|
-
uriB = serialize(normalizeComponentEncoding(parse3(uriB, options), true), { ...options, skipEscape: true });
|
|
3654
|
-
} else if (typeof uriB === "object") {
|
|
3655
|
-
uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
|
|
3656
|
-
}
|
|
3657
|
-
return uriA.toLowerCase() === uriB.toLowerCase();
|
|
3705
|
+
const normalizedA = normalizeComparableURI(uriA, options);
|
|
3706
|
+
const normalizedB = normalizeComparableURI(uriB, options);
|
|
3707
|
+
return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
|
|
3658
3708
|
}
|
|
3659
3709
|
function serialize(cmpts, opts) {
|
|
3660
3710
|
const component = {
|
|
@@ -3679,12 +3729,12 @@ var require_fast_uri = __commonJS({
|
|
|
3679
3729
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
|
|
3680
3730
|
if (component.path !== void 0) {
|
|
3681
3731
|
if (!options.skipEscape) {
|
|
3682
|
-
component.path =
|
|
3732
|
+
component.path = escapePreservingEscapes(component.path);
|
|
3683
3733
|
if (component.scheme !== void 0) {
|
|
3684
3734
|
component.path = component.path.split("%3A").join(":");
|
|
3685
3735
|
}
|
|
3686
3736
|
} else {
|
|
3687
|
-
component.path =
|
|
3737
|
+
component.path = normalizePercentEncoding(component.path);
|
|
3688
3738
|
}
|
|
3689
3739
|
}
|
|
3690
3740
|
if (options.reference !== "suffix" && component.scheme) {
|
|
@@ -3719,7 +3769,17 @@ var require_fast_uri = __commonJS({
|
|
|
3719
3769
|
return uriTokens.join("");
|
|
3720
3770
|
}
|
|
3721
3771
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3722
|
-
|
|
3772
|
+
var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
3773
|
+
function getParseError(parsed, matches) {
|
|
3774
|
+
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3775
|
+
return 'URI path must start with "/" when authority is present.';
|
|
3776
|
+
}
|
|
3777
|
+
if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
|
|
3778
|
+
return "URI port is malformed.";
|
|
3779
|
+
}
|
|
3780
|
+
return void 0;
|
|
3781
|
+
}
|
|
3782
|
+
function parseWithStatus(uri, opts) {
|
|
3723
3783
|
const options = Object.assign({}, opts);
|
|
3724
3784
|
const parsed = {
|
|
3725
3785
|
scheme: void 0,
|
|
@@ -3730,6 +3790,7 @@ var require_fast_uri = __commonJS({
|
|
|
3730
3790
|
query: void 0,
|
|
3731
3791
|
fragment: void 0
|
|
3732
3792
|
};
|
|
3793
|
+
let malformedAuthorityOrPort = false;
|
|
3733
3794
|
let isIP = false;
|
|
3734
3795
|
if (options.reference === "suffix") {
|
|
3735
3796
|
if (options.scheme) {
|
|
@@ -3738,6 +3799,11 @@ var require_fast_uri = __commonJS({
|
|
|
3738
3799
|
uri = "//" + uri;
|
|
3739
3800
|
}
|
|
3740
3801
|
}
|
|
3802
|
+
const authorityMatch = uri.match(AUTHORITY_PREFIX);
|
|
3803
|
+
if (authorityMatch !== null && authorityMatch[1].indexOf("\\") !== -1) {
|
|
3804
|
+
parsed.error = "URI authority must not contain a literal backslash.";
|
|
3805
|
+
malformedAuthorityOrPort = true;
|
|
3806
|
+
}
|
|
3741
3807
|
const matches = uri.match(URI_PARSE);
|
|
3742
3808
|
if (matches) {
|
|
3743
3809
|
parsed.scheme = matches[1];
|
|
@@ -3750,6 +3816,11 @@ var require_fast_uri = __commonJS({
|
|
|
3750
3816
|
if (isNaN(parsed.port)) {
|
|
3751
3817
|
parsed.port = matches[5];
|
|
3752
3818
|
}
|
|
3819
|
+
const parseError = getParseError(parsed, matches);
|
|
3820
|
+
if (parseError !== void 0) {
|
|
3821
|
+
parsed.error = parsed.error || parseError;
|
|
3822
|
+
malformedAuthorityOrPort = true;
|
|
3823
|
+
}
|
|
3753
3824
|
if (parsed.host) {
|
|
3754
3825
|
const ipv4result = isIPv4(parsed.host);
|
|
3755
3826
|
if (ipv4result === false) {
|
|
@@ -3776,7 +3847,7 @@ var require_fast_uri = __commonJS({
|
|
|
3776
3847
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
|
|
3777
3848
|
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
3778
3849
|
try {
|
|
3779
|
-
parsed.host = URL
|
|
3850
|
+
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
3780
3851
|
} catch (e) {
|
|
3781
3852
|
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
3782
3853
|
}
|
|
@@ -3788,14 +3859,18 @@ var require_fast_uri = __commonJS({
|
|
|
3788
3859
|
parsed.scheme = unescape(parsed.scheme);
|
|
3789
3860
|
}
|
|
3790
3861
|
if (parsed.host !== void 0) {
|
|
3791
|
-
parsed.host = unescape(parsed.host);
|
|
3862
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
|
|
3792
3863
|
}
|
|
3793
3864
|
}
|
|
3794
3865
|
if (parsed.path) {
|
|
3795
|
-
parsed.path =
|
|
3866
|
+
parsed.path = normalizePathEncoding(parsed.path);
|
|
3796
3867
|
}
|
|
3797
3868
|
if (parsed.fragment) {
|
|
3798
|
-
|
|
3869
|
+
try {
|
|
3870
|
+
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
|
|
3871
|
+
} catch {
|
|
3872
|
+
parsed.error = parsed.error || "URI malformed";
|
|
3873
|
+
}
|
|
3799
3874
|
}
|
|
3800
3875
|
}
|
|
3801
3876
|
if (schemeHandler && schemeHandler.parse) {
|
|
@@ -3804,7 +3879,29 @@ var require_fast_uri = __commonJS({
|
|
|
3804
3879
|
} else {
|
|
3805
3880
|
parsed.error = parsed.error || "URI can not be parsed.";
|
|
3806
3881
|
}
|
|
3807
|
-
return parsed;
|
|
3882
|
+
return { parsed, malformedAuthorityOrPort };
|
|
3883
|
+
}
|
|
3884
|
+
function parse3(uri, opts) {
|
|
3885
|
+
return parseWithStatus(uri, opts).parsed;
|
|
3886
|
+
}
|
|
3887
|
+
function normalizeString(uri, opts) {
|
|
3888
|
+
return normalizeStringWithStatus(uri, opts).normalized;
|
|
3889
|
+
}
|
|
3890
|
+
function normalizeStringWithStatus(uri, opts) {
|
|
3891
|
+
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
|
|
3892
|
+
return {
|
|
3893
|
+
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
3894
|
+
malformedAuthorityOrPort
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
function normalizeComparableURI(uri, opts) {
|
|
3898
|
+
if (typeof uri === "string") {
|
|
3899
|
+
const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
|
|
3900
|
+
return malformedAuthorityOrPort ? void 0 : normalized;
|
|
3901
|
+
}
|
|
3902
|
+
if (typeof uri === "object") {
|
|
3903
|
+
return serialize(uri, opts);
|
|
3904
|
+
}
|
|
3808
3905
|
}
|
|
3809
3906
|
var fastUri = {
|
|
3810
3907
|
SCHEMES,
|
|
@@ -32324,6 +32421,57 @@ function registerInvoiceTools(server2) {
|
|
|
32324
32421
|
);
|
|
32325
32422
|
}
|
|
32326
32423
|
);
|
|
32424
|
+
server2.registerTool(
|
|
32425
|
+
"kirjapro_send_invoice",
|
|
32426
|
+
{
|
|
32427
|
+
description: "Deliver a draft sales invoice through email or Finnish e-invoice. The server generates the branded PDF/Finvoice payload, performs idempotency checks, finalizes the invoice as sent, and books the sales journal entry. Use email when the customer has an email address; use einvoice only when Maventa and the customer's e-invoice address are configured.",
|
|
32428
|
+
inputSchema: {
|
|
32429
|
+
invoiceId: external_exports.string().uuid().describe("Draft invoice UUID"),
|
|
32430
|
+
channel: external_exports.enum(["email", "einvoice"]).describe("Delivery channel: email or einvoice"),
|
|
32431
|
+
idempotencyKey: IDEMPOTENCY_KEY_SCHEMA
|
|
32432
|
+
},
|
|
32433
|
+
annotations: WRITE_ANNOTATIONS
|
|
32434
|
+
},
|
|
32435
|
+
async (params) => {
|
|
32436
|
+
const supabase2 = getSupabase();
|
|
32437
|
+
const companyId2 = getCompanyId();
|
|
32438
|
+
return withIdempotency(
|
|
32439
|
+
supabase2,
|
|
32440
|
+
companyId2,
|
|
32441
|
+
"kirjapro_send_invoice",
|
|
32442
|
+
params.idempotencyKey,
|
|
32443
|
+
params,
|
|
32444
|
+
async () => {
|
|
32445
|
+
const { data: invoice, error: invoiceError } = await supabase2.from("invoices").select("id, invoice_number, status").eq("id", params.invoiceId).eq("company_id", companyId2).maybeSingle();
|
|
32446
|
+
if (invoiceError || !invoice) {
|
|
32447
|
+
return toErrorContent(`Invoice ${params.invoiceId} not found.`);
|
|
32448
|
+
}
|
|
32449
|
+
if (invoice.status !== "draft") {
|
|
32450
|
+
return toErrorContent(
|
|
32451
|
+
`Invoice ${invoice.invoice_number} cannot be delivered from status "${invoice.status}". Only draft invoices can be sent.`
|
|
32452
|
+
);
|
|
32453
|
+
}
|
|
32454
|
+
const { data, error: error51 } = await supabase2.functions.invoke("dispatch-invoice", {
|
|
32455
|
+
body: { invoiceId: invoice.id, channel: params.channel }
|
|
32456
|
+
});
|
|
32457
|
+
if (error51 || !data?.success) {
|
|
32458
|
+
return toErrorContent(
|
|
32459
|
+
data?.error || error51?.message || `Failed to deliver invoice ${invoice.invoice_number}.`
|
|
32460
|
+
);
|
|
32461
|
+
}
|
|
32462
|
+
return toResult({
|
|
32463
|
+
message: `Invoice ${invoice.invoice_number} delivered via ${params.channel}`,
|
|
32464
|
+
invoice_id: invoice.id,
|
|
32465
|
+
invoice_number: invoice.invoice_number,
|
|
32466
|
+
channel: data.channel,
|
|
32467
|
+
transmission_id: data.transmissionId ?? null,
|
|
32468
|
+
message_id: data.messageId ?? null,
|
|
32469
|
+
status: "sent"
|
|
32470
|
+
});
|
|
32471
|
+
}
|
|
32472
|
+
);
|
|
32473
|
+
}
|
|
32474
|
+
);
|
|
32327
32475
|
server2.registerTool(
|
|
32328
32476
|
"kirjapro_record_payment",
|
|
32329
32477
|
{
|
|
@@ -36139,6 +36287,7 @@ var CORE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
36139
36287
|
"kirjapro_list_invoices",
|
|
36140
36288
|
"kirjapro_get_invoice",
|
|
36141
36289
|
"kirjapro_create_invoice",
|
|
36290
|
+
"kirjapro_send_invoice",
|
|
36142
36291
|
"kirjapro_record_payment",
|
|
36143
36292
|
"kirjapro_update_invoice_status",
|
|
36144
36293
|
// Transactions + banking
|
|
@@ -36162,7 +36311,7 @@ function resolveSurface(explicit) {
|
|
|
36162
36311
|
if (env === "core") return "core";
|
|
36163
36312
|
if (env && env !== "core") {
|
|
36164
36313
|
console.error(
|
|
36165
|
-
`[kirjapro-mcp] Unknown KIRJAPRO_MCP_SURFACE="${env}" \u2014 falling back to "core". Valid values: "core" (default,
|
|
36314
|
+
`[kirjapro-mcp] Unknown KIRJAPRO_MCP_SURFACE="${env}" \u2014 falling back to "core". Valid values: "core" (default, 27 tools), "full" (52 tools).`
|
|
36166
36315
|
);
|
|
36167
36316
|
}
|
|
36168
36317
|
return "core";
|
|
@@ -36720,7 +36869,7 @@ function registerResources(server2) {
|
|
|
36720
36869
|
}
|
|
36721
36870
|
|
|
36722
36871
|
// src/version.ts
|
|
36723
|
-
var VERSION = "0.
|
|
36872
|
+
var VERSION = "0.7.0";
|
|
36724
36873
|
|
|
36725
36874
|
// src/index.ts
|
|
36726
36875
|
var INSTRUCTIONS_COMMON = `
|
|
@@ -36750,7 +36899,6 @@ These tasks have no MCP equivalent in any surface mode. If a user asks for them,
|
|
|
36750
36899
|
|
|
36751
36900
|
- **Document attachments / receipt OCR** \u2014 file uploads are web-only
|
|
36752
36901
|
- **Bank account configuration** \u2014 Enable Banking connect, CAMT.053 import
|
|
36753
|
-
- **E-invoice sending** \u2014 Finvoice via Maventa is web-driven
|
|
36754
36902
|
- **Multi-company switching** \u2014 one API key is bound to one company
|
|
36755
36903
|
- **Filling out year-end notes (liitetiedot)** \u2014 the form is web-only
|
|
36756
36904
|
|
|
@@ -36759,7 +36907,7 @@ Do not invent a "closest match" tool for these tasks. A confidently wrong action
|
|
|
36759
36907
|
## Tool Ordering & Workflows
|
|
36760
36908
|
|
|
36761
36909
|
### Invoice Lifecycle
|
|
36762
|
-
\`kirjapro_create_customer\` (if new) \u2192 \`kirjapro_create_invoice\` (draft) \u2192 \`
|
|
36910
|
+
\`kirjapro_create_customer\` (if new) \u2192 \`kirjapro_create_invoice\` (draft) \u2192 \`kirjapro_send_invoice\` (email/e-invoice) \u2192 \`kirjapro_record_payment\` or \`kirjapro_match_bank_transaction\` \u2192 status becomes paid.
|
|
36763
36911
|
|
|
36764
36912
|
### Offer / Contract \u2192 Invoice
|
|
36765
36913
|
\`kirjapro_create_offer\` (type: \`tarjous\` or \`sopimus\`) \u2192 \`kirjapro_send_offer\` (emails the customer a public-token link). The customer's accept/reject click on the email link sets the offer status server-side \u2014 the agent does NOT need to manually update status. Once the user tells you the offer is accepted, call \`kirjapro_convert_offer_to_invoice\` to create a draft invoice, then follow the invoice lifecycle.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirjapro/mcp-server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Finnish accounting — exposes Kirjapro bookkeeping, invoicing, and reporting tools to Claude
|
|
3
|
+
"version": "0.7.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.
|
|
5
|
+
"version": "0.7.0",
|
|
6
6
|
"homepage": "https://kirjapro.fi",
|
|
7
7
|
"repository": "https://github.com/Jaollo/kirja-pro",
|
|
8
8
|
"license": "MIT",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"required": true
|
|
17
17
|
},
|
|
18
18
|
"KIRJAPRO_MCP_SURFACE": {
|
|
19
|
-
"description": "Tool surface: \"core\" (default,
|
|
19
|
+
"description": "Tool surface: \"core\" (default, 27 daily-use tools) or \"full\" (all 52 tools).",
|
|
20
20
|
"required": false
|
|
21
21
|
}
|
|
22
22
|
}
|
package/tool-fingerprints.json
CHANGED