@ekwo-ai/mcp 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -4
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +27 -1
- package/dist/backend.js.map +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +1 -1
- package/dist/server.d.ts +9 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +118 -7
- package/dist/server.js.map +1 -1
- package/dist/start.d.ts +8 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +13 -1
- package/dist/start.js.map +1 -1
- package/dist/tools/modules.d.ts +3 -1
- package/dist/tools/modules.d.ts.map +1 -1
- package/dist/tools/modules.js +86 -1
- package/dist/tools/modules.js.map +1 -1
- package/dist/tools/read.d.ts +26 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +62 -6
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +32 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +64 -3
- package/dist/tools/write.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -128,6 +128,7 @@ alongside the server; the recommended route needs no driver at all.
|
|
|
128
128
|
| `EKWO_ACT_AS_USER_ID` | Required with `EKWO_DB_URL`: the `auth.users` id to act for |
|
|
129
129
|
| `EKWO_NO_REGISTER_INVITE` | Optional. `1` leaves the invitation to register out of the instructions |
|
|
130
130
|
| `EKWO_EINVOICE_DIRECTORY` | Optional. The folder electronic invoices are sent to by `einvoicing_issue` with `send`. Unset, files are issued and kept, and sending is refused as `no_transport` |
|
|
131
|
+
| `EKWO_FISCAL_SYSTEM` | Optional. The invoicing system `einvoicing_fiscalise` chains fiscal records under, as JSON: `name`, `id`, `version`, `installation`, `producerName`, `producerTaxId`, and the booleans a regime may ask (`verifactuOnly`, `multipleTaxpayers`, `multipleTaxpayersNow`). Unset, fiscalising is refused as `no_fiscal_system`; malformed, the server does not start |
|
|
131
132
|
|
|
132
133
|
### Started with nothing set
|
|
133
134
|
|
|
@@ -214,6 +215,7 @@ Every write names its company explicitly.
|
|
|
214
215
|
| `get_company` | Financial years, lock dates, journals, default accounts |
|
|
215
216
|
| `list_accounts` | The accounts a company works with, by code prefix, type or name. `include_all` for the whole chart |
|
|
216
217
|
| `search_contacts` | Customers and suppliers, by name, type or VAT number |
|
|
218
|
+
| `suggest_duplicate_contacts` | The pairs of contacts that are probably one party, strongest first: the contact to keep, a score, and the evidence — the same VAT or registration number, the same bank account, the same words of the name. Writes nothing; `merge_contacts` joins a pair the user confirms |
|
|
217
219
|
| `search_products` | The catalogue: code, unit, price, account and tax of what is sold and bought |
|
|
218
220
|
| `list_documents` | Invoices, credit notes and quotes, filtered |
|
|
219
221
|
| `get_document` | One document with its lines and the entry it produced |
|
|
@@ -222,13 +224,18 @@ Every write names its company explicitly.
|
|
|
222
224
|
| `list_bank_transactions` | Statement lines, pending by default |
|
|
223
225
|
| `trial_balance` | Opening, movements and closing per account |
|
|
224
226
|
| `general_ledger` | Every posted line of an account, with a running balance |
|
|
227
|
+
| `suggest_line_contacts` | The contacts an open receivable or payable line that names nobody could be, best first, each with its evidence and how many contacts that evidence reached. Writes nothing |
|
|
228
|
+
| `suggest_item_combinations` | For each open receivable or payable line of one counterparty, the lines of the other side that settle it: an exact pair, several lines to the cent, or within the company's difference tolerance, with the difference. Writes nothing |
|
|
229
|
+
| `matching_difference_options` | The reasons a difference may be booked for, with the account the pack gives for each, and the company's tolerance |
|
|
225
230
|
| `aged_balance` | What is still owed, bucketed by age, read from the ledger |
|
|
226
231
|
| `vat_return` | The boxes for a period, summed from the ledger |
|
|
227
232
|
| `ec_sales_list` | The recapitulative statement of European Union supplies: one line per customer VAT number and per nature |
|
|
228
233
|
| `portfolio_upcoming_filings` | *Portfolio* = the companies you may read: for an accounting firm, its clients ([`docs/firms.md`](../../docs/firms.md)). What falls due between two dates in every company you hold `filings.read` on. One row per company at least: a pack that names no deadline is listed without a date, and says so |
|
|
229
234
|
| `portfolio_filings_touched_since` | Declarations that have gone and whose period received entries afterwards, across the same companies, with the company named |
|
|
230
235
|
| `list_statements` / `financial_statement` | The schemes a company can be presented on, and one statement |
|
|
231
|
-
| `financial_analysis` | Revenue, net result, equity, total and current assets, cash, receivables, payables and the other aggregates of an analysis, each with the accounts that make it, as the country pack defines them
|
|
236
|
+
| `financial_analysis` | Revenue, net result, equity, total and current assets, cash, receivables, payables and the other aggregates of an analysis, each with the accounts that make it, as the country pack defines them, or read by account type through the generic framework where it defines none — `source` says which ([`docs/analysis.md`](../../docs/analysis.md)) |
|
|
237
|
+
| `list_analytic_axes` | The analytic axes of a company — a cost centre, a project, a site — and the values of each, which may nest, with `required_on`, the lines that must carry the axis whole at posting |
|
|
238
|
+
| `analytic_report` | The income, expense, result and fixed-asset movement of each value of one axis over a period, from the posted ledger, the whole income statement included, a parent with the subtotal of the values below it, `unassigned`, the part no share of the axis reaches, and `total` ([decision 0076](../../docs/decisions/0076-an-analytic-value-is-entered-on-the-line.md)) |
|
|
232
239
|
| `generate_fec` | The French FEC as text, with its checks and its filename, for a company that keeps French books |
|
|
233
240
|
| `read_audit_log` | Who changed what and when: the configuration of a company, and the acts that change a state. Append-only for every client; only the database writes it |
|
|
234
241
|
| `get_preferences` | What you prefer, and the language chain to read labels with |
|
|
@@ -237,6 +244,7 @@ Every write names its company explicitly.
|
|
|
237
244
|
| `describe_pack` | Which country packs this installation holds: their version, how much anyone has read them, the register of texts each was built from — title, official publisher, link and the day it was opened — and, for a proven pack, its evidence: per scope, the kind of filing, the year, what was reproduced and what was not tested |
|
|
238
245
|
| `status` | Schema version, instance, connection, companies |
|
|
239
246
|
| `create_contact` | A customer, supplier or other third party |
|
|
247
|
+
| `merge_contacts` | Joins two contacts that are one party: everything naming the absorbed one, posted lines and issued documents included, names the kept one, whose empty fields are filled; the absorbed one is retired. Refused by name for two different VAT or registration numbers, a contact merged before, or a matching that would span two parties |
|
|
240
248
|
| `create_product` | A catalogue row: code, name, unit, price, account, tax |
|
|
241
249
|
| `update_product` | Changes one, or retires it with `active: false` |
|
|
242
250
|
| `pin_accounts` | Adds accounts to the working chart a company sees first, or takes one back out with `pinned: false` |
|
|
@@ -246,6 +254,10 @@ Every write names its company explicitly.
|
|
|
246
254
|
| `cancel_document` | Undoes a posted invoice, and says how in `undone_by`: back to `draft` where its country's `posted_edit_policy` allows it and nothing about it has left (`unpost_document()`), otherwise a `credit_note` that names it, posted and matched against it, and the invoice cancelled (`cancel_document()`), with `why` the draft was ruled out. A credit note is dated on the invoice's day while that period is open; otherwise the caller gives a date. A date, or `credit_note: true`, asks for the credit note |
|
|
247
255
|
| `reverse_entry` | Undoes a posted entry keyed by hand: its mirror, posted under the next number and matched against it. Same rule for the date |
|
|
248
256
|
| `record_payment` | Books money in or out and matches it against open invoices — or, with `document_id`, against that document alone, which then names the contact and the direction. With `client_ref`, recording twice records once |
|
|
257
|
+
| `set_line_contact` | Names the contact of such a line, once, from nothing to a contact of the company, and learns the label as a motif. Refused by name for a line that names somebody, is matched, is not on a receivable or payable account, or a contact of another company |
|
|
258
|
+
| `leave_line_unnamed` / `undo_leave_line_unnamed` | Leaves such a line without a contact on purpose, with the reason, so it is no longer suggested; or takes that back |
|
|
259
|
+
| `reconcile_many` | Matches several lines of one counterparty and one account together, their open debits and credits equal to the cent, under one letter |
|
|
260
|
+
| `reconcile_with_difference` | The same with a small difference, booked on the account the pack gives for the reason (`rounding`, `exchange_difference`) within the company's tolerance, then matched |
|
|
249
261
|
| `reconcile` / `unreconcile` | Matches two ledger lines, or undoes one matching |
|
|
250
262
|
| `create_bank_account` | Registers an account from its identifier, in the scheme the company's country uses (an IBAN, a routing number and an account number, a sort code and an account number…), and wires it to the bank journal. Running it twice with the same identifier creates nothing |
|
|
251
263
|
| `create_bank_transaction` | One statement line by hand, for an installation with no feed |
|
|
@@ -254,7 +266,8 @@ Every write names its company explicitly.
|
|
|
254
266
|
| `lock_period` | Moves the accounting and VAT lock dates. Needs `company.write`. |
|
|
255
267
|
| `opening_balance` | The trial balance of whatever kept the books before, as the opening entry — of the first day of the year, or of `opening_date`, the day inside it the books were taken over. `items` are the invoices and credit notes still open that day: each a posted document with its number, payment reference and due date, which statement matching finds like an invoice issued here; the items of an account must add up to its line |
|
|
256
268
|
| `import_books` | Books kept elsewhere — a FEC, an export of journal items, a journal report, a trial balance — whole or not at all, with `open_items` beside a trial balance. `dry_run: true` first: the correspondence proposed for every account and journal — `exact` only for the same code the files do not contradict, otherwise `suggested` with its reason, or `none` — what has no answer, and the import rehearsed by the database and taken back. Then again with the completed `mapping`, or `accept_suggestions` once the user has read every suggestion; nothing is posted while one is unconfirmed. Every entry through `post_entry()`; no tax; the same files twice refused, saying when and what. The files travel as text, 256 KiB at most: beyond, the tool answers with the `ekwo import` command that reads them from the disk. `ekwo import` is the same function |
|
|
257
|
-
| `close_fiscal_year` / `reopen_fiscal_year` | Closes a year the way the country pack says
|
|
269
|
+
| `close_fiscal_year` / `reopen_fiscal_year` | Closes a year the way the country pack says — with `final_close`, whether the country also requires a final close and by when — or reopens it to correct it, whatever later years hold; a later closed year is reopened first |
|
|
270
|
+
| `close_fiscal_year_finally` | The final close of a closed year: nobody reopens it afterwards, the operator included. Only on the user's explicit request |
|
|
258
271
|
| `create_company` | A company on a country pack, with its chart and its first financial year. An instance-level act |
|
|
259
272
|
| `update_company_profile` | What a company says about itself on its documents |
|
|
260
273
|
| `set_preferences` | Your own language, timezone, formats and default company |
|
|
@@ -272,7 +285,7 @@ whole chart, and every write tool still accepts any account of it.
|
|
|
272
285
|
|
|
273
286
|
`post_document`, `cancel_document`, `reverse_entry`, `record_payment`,
|
|
274
287
|
`update_document_lines`, `unreconcile`, `settle_from_statement`, `lock_period`, `opening_balance`, `import_books`,
|
|
275
|
-
`close_fiscal_year`, `reopen_fiscal_year`, `revoke_invitation`, `remove_member`,
|
|
288
|
+
`close_fiscal_year`, `reopen_fiscal_year`, `close_fiscal_year_finally`, `revoke_invitation`, `remove_member`,
|
|
276
289
|
`set_member_role`, `revoke_api_key` and `revoke_share` are annotated destructive in the protocol, so a client can ask
|
|
277
290
|
before calling them.
|
|
278
291
|
|
|
@@ -288,7 +301,8 @@ startup to know which: `fixed_assets_list`, `fixed_assets_create`,
|
|
|
288
301
|
`fixed_assets_schedule`, `fixed_assets_run_depreciation`,
|
|
289
302
|
`fixed_assets_dispose`, `budgets_list`, `budgets_upsert_lines`,
|
|
290
303
|
`budgets_variance`, `einvoicing_validate`, `einvoicing_issue`,
|
|
291
|
-
`einvoicing_status`, `einvoicing_list`, `
|
|
304
|
+
`einvoicing_status`, `einvoicing_list`, `einvoicing_fiscalise`,
|
|
305
|
+
`einvoicing_fiscalisation_status`, `einvoicing_clearance`, `tax_estimate`, `tax_parameters`,
|
|
292
306
|
`tax_declare_parameter`, `tax_adjustments`, `tax_declare_adjustment`,
|
|
293
307
|
`tax_losses`, `tax_record_computation`, `tax_computations`. A module may
|
|
294
308
|
offer prompts too, under the same prefix: `tax_review_year`. A module that is
|
|
@@ -301,6 +315,10 @@ transport a host hands to `buildServer()` as `einvoiceTransport`. No
|
|
|
301
315
|
credential of a network ever reaches a tool. See
|
|
302
316
|
[`modules/einvoicing`](../../modules/einvoicing/README.md).
|
|
303
317
|
|
|
318
|
+
The fiscalisation tools chain, for a company whose pack declares a regime,
|
|
319
|
+
the record of each posted sale under the invoicing system the server is
|
|
320
|
+
given in `EKWO_FISCAL_SYSTEM` — never one a model describes.
|
|
321
|
+
|
|
304
322
|
**Resources.** `ekwo://companies/{id}/chart` is the whole chart of accounts;
|
|
305
323
|
`ekwo://companies/{id}/taxes` is every tax with the ledger account and the
|
|
306
324
|
declaration box each of its postings feeds.
|
package/dist/backend.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACL,UAAU,EACV,UAAU,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,KAAK,OAAO,EACZ,KAAK,MAAM,EACX,KAAK,KAAK,EACV,KAAK,GAAG,EACR,KAAK,WAAW,EAChB,KAAK,KAAK,EACX,MAAM,eAAe,CAAC;AAKvB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AACxD,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAEhE,yEAAyE;AACzE,eAAO,MAAM,YAAY,mBAAa,CAAC;AACvC,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACL,UAAU,EACV,UAAU,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,KAAK,OAAO,EACZ,KAAK,MAAM,EACX,KAAK,KAAK,EACV,KAAK,GAAG,EACR,KAAK,WAAW,EAChB,KAAK,KAAK,EACX,MAAM,eAAe,CAAC;AAKvB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AACxD,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAEhE,yEAAyE;AACzE,eAAO,MAAM,YAAY,mBAAa,CAAC;AACvC,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC;AA6HtC,4EAA4E;AAC5E,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAcrD"}
|
package/dist/backend.js
CHANGED
|
@@ -56,6 +56,8 @@ const HINTS = {
|
|
|
56
56
|
entry_is_a_reversal: 'This entry undoes another one. A reversal is not reversed; post the original again as a new entry if it was right after all.',
|
|
57
57
|
entry_already_reversed: 'A posted reversal already names this entry. Read it back rather than reversing twice.',
|
|
58
58
|
entry_of_a_document: 'A document wrote this entry, and it is undone with the document: cancel_document.',
|
|
59
|
+
fiscal_year_finally_closed: 'The financial year is under a final close: nobody reopens it or changes it. Book the correction in an open year.',
|
|
60
|
+
later_fiscal_year_closed: 'A later financial year is closed. Reopen the later one first, then this one — latest first.',
|
|
59
61
|
entry_of_a_year_end: 'The entries of a close are undone by reopen_fiscal_year; an opening is corrected by an ordinary entry of the year.',
|
|
60
62
|
entry_belongs_elsewhere: 'Something else wrote this entry — a payment, a bank transaction, a module, a declaration or a matching — and it is undone there.',
|
|
61
63
|
entry_matched: 'The entry is matched. Undo the matching with unreconcile first, and only when the user says so: it records money that moved.',
|
|
@@ -93,7 +95,7 @@ const HINTS = {
|
|
|
93
95
|
reconcile_nothing_left: 'Both lines are already fully matched.',
|
|
94
96
|
account_not_reconcilable: 'That account is not reconcilable, so nothing on it can be matched.',
|
|
95
97
|
settlement_unbalanced: 'The statement line does not equal the items it settles and its extra lines, to the cent; the difference is in the message. Read the amounts again with the user — a fee or a charge missing, or an item settled for the wrong amount — and never add a line to make it balance on your own.',
|
|
96
|
-
account_takes_no_entries: 'An extra line goes on an account that takes a charge or an income. A customer or supplier account is settled by naming its open items, not by an extra line.',
|
|
98
|
+
account_takes_no_entries: 'An extra line, or a difference, goes on an account that takes a charge or an income. A customer or supplier account is settled by naming its open items, not by an extra line.',
|
|
97
99
|
extra_line_sign: 'A charge bearing a tax is positive. A charge the provider refunded is a credit note of theirs, not an extra line.',
|
|
98
100
|
settlement_over_open: 'An amount named is more than that item has open. List the open items again; what is already settled cannot be settled twice.',
|
|
99
101
|
unknown_document: 'No document with that id is visible to you.',
|
|
@@ -101,6 +103,30 @@ const HINTS = {
|
|
|
101
103
|
unknown_entry_line: 'No ledger line with that id is visible to you.',
|
|
102
104
|
unknown_reconciliation: 'No matching with that id is visible to you.',
|
|
103
105
|
unknown_payment: 'No payment with that id is visible to you.',
|
|
106
|
+
unknown_line: 'No ledger line with that id is visible to you. suggest_line_contacts lists the lines that can be named.',
|
|
107
|
+
line_has_contact: 'The line already names a contact, and keeps it. A posted counterparty is corrected by reversing the entry, and only when the user says so.',
|
|
108
|
+
line_matched: 'The line is matched, and keeps the counterparty it was matched as. Undo the matching with unreconcile first, and only when the user says so.',
|
|
109
|
+
line_not_third_party: 'Only a line on a receivable or payable account is named this way. suggest_line_contacts lists the ones that can be.',
|
|
110
|
+
line_not_posted: 'The line belongs to a draft, which takes its contact like any other column and is posted with it.',
|
|
111
|
+
same_contact: 'A merge joins two contacts; the same id was given twice.',
|
|
112
|
+
already_merged: 'That contact was merged before. Merge into the contact that holds its history, which the message names.',
|
|
113
|
+
different_identifiers: 'The two contacts carry different VAT or registration numbers, so they are two parties. Ask the user; if one number is wrong, correct it first.',
|
|
114
|
+
matching_across_parties: 'A matching of the absorbed contact would end up settling the items of two parties. Show the user the matching the message names; undo it with unreconcile only when they say so.',
|
|
115
|
+
contact_merged: 'That contact was merged into another, which holds its history now. Use the contact the message names.',
|
|
116
|
+
no_lines_named: 'A matching names at least two lines, a debit and a credit.',
|
|
117
|
+
duplicate_line: 'Each line is named once.',
|
|
118
|
+
reconcile_company_mismatch: 'The lines belong to two companies.',
|
|
119
|
+
reconcile_contact_mismatch: 'The lines name two counterparties, and a matching settles the items of one. A branch and its parent are one party.',
|
|
120
|
+
reconcile_foreign_currency: 'A line is stated in another currency than the company\'s. Match it pair by pair with reconcile, which books the exchange difference.',
|
|
121
|
+
line_not_open: 'A line has nothing left open. List the open items again.',
|
|
122
|
+
reconcile_unbalanced: 'The debits and the credits differ; the amounts are in the message. Show the user the difference: reconcile_with_difference books it with a reason they choose, within the company tolerance. Never add a line yourself.',
|
|
123
|
+
no_difference: 'The lines balance to the cent: reconcile_many matches them.',
|
|
124
|
+
difference_above_tolerance: 'The difference is more than the company books by itself (matching_difference_options gives the tolerance). Look at the lines again with the user: an item missing, or a payment that is not for these invoices.',
|
|
125
|
+
unknown_difference_reason: 'matching_difference_options lists the reasons this company can book a difference for.',
|
|
126
|
+
no_account_for_reason: 'The pack or the company names no account for that reason on that side. Pick another reason with the user, or have the account set up.',
|
|
127
|
+
reason_required: 'Say why the line names nobody: the reason is what the next person reads.',
|
|
128
|
+
line_left_unnamed: 'The line is already left unnamed. undo_leave_line_unnamed takes that back.',
|
|
129
|
+
line_not_left_unnamed: 'The line was not left unnamed.',
|
|
104
130
|
};
|
|
105
131
|
/**
|
|
106
132
|
* Check constraints that a client can legitimately provoke, in the words a
|
package/dist/backend.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACL,UAAU,EACV,UAAU,EACV,UAAU,EACV,SAAS,EACT,SAAS,GAOV,MAAM,eAAe,CAAC;AAEvB,8EAA8E;AAC9E,8EAA8E;AAC9E,+EAA+E;AAC/E,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AAGxD,yEAAyE;AACzE,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;AAGvC;;;;;;GAMG;AACH,MAAM,KAAK,GAA2B;IACpC,aAAa,EAAE,0HAA0H;IACzI,iBAAiB,EAAE,0GAA0G;IAC7H,kBAAkB,EAAE,sFAAsF;IAC1G,gBAAgB,EAAE,mEAAmE;IACrF,WAAW,EAAE,yBAAyB;IACtC,cAAc,EAAE,iEAAiE;IACjF,uBAAuB,EAAE,mFAAmF;IAC5G,uBAAuB,EAAE,sFAAsF;IAC/G,kBAAkB,EAAE,wCAAwC;IAC5D,YAAY,EAAE,gLAAgL;IAC9L,oBAAoB,EAAE,0OAA0O;IAChQ,uBAAuB,EAAE,iKAAiK;IAC1L,iBAAiB,EAAE,8JAA8J;IACjL,eAAe,EAAE,wTAAwT;IACzU,yBAAyB,EAAE,kHAAkH;IAC7I,aAAa,EAAE,yHAAyH;IACxI,oBAAoB,EAAE,4GAA4G;IAClI,sBAAsB,EAAE,qKAAqK;IAC7L,iBAAiB,EAAE,8IAA8I;IACjK,wBAAwB,EAAE,+IAA+I;IACzK,gCAAgC,EAAE,mKAAmK;IACrM,0BAA0B,EAAE,oKAAoK;IAChM,oBAAoB,EAAE,+KAA+K;IACrM,wBAAwB,EAAE,iDAAiD;IAC3E,gBAAgB,EAAE,qEAAqE;IACvF,mBAAmB,EAAE,8HAA8H;IACnJ,sBAAsB,EAAE,uFAAuF;IAC/G,mBAAmB,EAAE,mFAAmF;IACxG,mBAAmB,EAAE,oHAAoH;IACzI,uBAAuB,EAAE,kIAAkI;IAC3J,aAAa,EAAE,8HAA8H;IAC7I,mBAAmB,EAAE,iEAAiE;IACtF,0BAA0B,EAAE,qCAAqC;IACjE,yBAAyB,EAAE,mGAAmG;IAC9H,yBAAyB,EAAE,wFAAwF;IACnH,aAAa,EAAE,gKAAgK;IAC/K,mBAAmB,EAAE,kJAAkJ;IACvK,oBAAoB,EAAE,mKAAmK;IACzL,6BAA6B,EAAE,gHAAgH;IAC/I,+BAA+B,EAAE,iJAAiJ;IAClL,iCAAiC,EAAE,+FAA+F;IAClI,wBAAwB,EAAE,2EAA2E;IACrG,uBAAuB,EAAE,+HAA+H;IACxJ,gBAAgB,EAAE,2FAA2F;IAC7G,2BAA2B,EAAE,iFAAiF;IAC9G,sBAAsB,EAAE,yFAAyF;IACjH,UAAU,EAAE,gFAAgF;IAC5F,oBAAoB,EAAE,yMAAyM;IAC/N,oBAAoB,EAAE,kLAAkL;IACxM,0BAA0B,EAAE,wGAAwG;IACpI,yBAAyB,EAAE,8JAA8J;IACzL,kBAAkB,EAAE,yIAAyI;IAC7J,2BAA2B,EAAE,2GAA2G;IACxI,qBAAqB,EAAE,gEAAgE;IACvF,eAAe,EAAE,qIAAqI;IACtJ,eAAe,EAAE,wKAAwK;IACzL,sBAAsB,EAAE,oCAAoC;IAC5D,iBAAiB,EAAE,uCAAuC;IAC1D,mBAAmB,EAAE,wEAAwE;IAC7F,0BAA0B,EAAE,0CAA0C;IACtE,oBAAoB,EAAE,iEAAiE;IACvF,qBAAqB,EAAE,kEAAkE;IACzF,sBAAsB,EAAE,uCAAuC;IAC/D,wBAAwB,EAAE,oEAAoE;IAC9F,qBAAqB,EAAE,6RAA6R;IACpT,wBAAwB,EAAE,
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { type Backend } from './backend.js';
|
|
16
16
|
/** The oldest schema this server speaks to. */
|
|
17
|
-
export declare const SCHEMA_MIN = "0.
|
|
17
|
+
export declare const SCHEMA_MIN = "0.15.0";
|
|
18
18
|
/** The version the migrations of that database define. */
|
|
19
19
|
export declare function installedSchemaVersion(backend: Backend): Promise<string | undefined>;
|
|
20
20
|
/**
|
package/dist/schema.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { schemaIsAtLeast } from '@ekwo-ai/core';
|
|
16
16
|
import { EkwoMcpError } from './backend.js';
|
|
17
17
|
/** The oldest schema this server speaks to. */
|
|
18
|
-
export const SCHEMA_MIN = '0.
|
|
18
|
+
export const SCHEMA_MIN = '0.15.0';
|
|
19
19
|
/** The version the migrations of that database define. */
|
|
20
20
|
export async function installedSchemaVersion(backend) {
|
|
21
21
|
try {
|
package/dist/server.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
11
11
|
import { type Backend } from './backend.js';
|
|
12
|
-
import { type EinvoiceTransport, type LogoFetch } from '@ekwo-ai/core';
|
|
12
|
+
import { type EinvoiceTransport, type FiscalSystem, type LogoFetch } from '@ekwo-ai/core';
|
|
13
13
|
export declare const SERVER_NAME = "@ekwo-ai/mcp";
|
|
14
14
|
/**
|
|
15
15
|
* What this server tells a client it is, in the MCP handshake.
|
|
@@ -19,7 +19,7 @@ export declare const SERVER_NAME = "@ekwo-ai/mcp";
|
|
|
19
19
|
* `tests/mcp/surface.test.ts` keeps the two equal, so a release that bumps one
|
|
20
20
|
* and forgets the other fails the build.
|
|
21
21
|
*/
|
|
22
|
-
export declare const SERVER_VERSION = "0.
|
|
22
|
+
export declare const SERVER_VERSION = "0.15.0";
|
|
23
23
|
export interface ServerOptions {
|
|
24
24
|
/**
|
|
25
25
|
* The modules this installation carries, from `public.modules`. Their tools
|
|
@@ -44,6 +44,13 @@ export interface ServerOptions {
|
|
|
44
44
|
* and sending is refused as no_transport.
|
|
45
45
|
*/
|
|
46
46
|
einvoiceTransport?: EinvoiceTransport;
|
|
47
|
+
/**
|
|
48
|
+
* The invoicing system fiscal records are chained by, for
|
|
49
|
+
* `einvoicing_fiscalise`: its name, identifier, version, installation and
|
|
50
|
+
* producer. Described by whoever runs the server — `start.ts` reads
|
|
51
|
+
* `EKWO_FISCAL_SYSTEM` — and never invented.
|
|
52
|
+
*/
|
|
53
|
+
fiscalSystem?: FiscalSystem;
|
|
47
54
|
/**
|
|
48
55
|
* How `render_invoice_pdf` fetches the logo a company names by URL. Left
|
|
49
56
|
* out, the global `fetch`; a test passes its own. Only http and https, and
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAGtF,OAAO,EAAgB,KAAK,OAAO,EAAY,MAAM,cAAc,CAAC;AACpE,OAAO,EAAgC,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAoB,MAAM,yCAAyC,CAAC;AAGtF,OAAO,EAAgB,KAAK,OAAO,EAAY,MAAM,cAAc,CAAC;AACpE,OAAO,EAAgC,KAAK,iBAAiB,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAKxH,eAAO,MAAM,WAAW,iBAAiB,CAAC;AAC1C;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,WAAW,CAAC;AA+BvC,MAAM,WAAW,aAAa;IAC5B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,QAMX,CAAC;AAEf,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,OAAO,GAAE,aAAkB,GAAG,MAAM,CAEnE;AA8BD,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,aAAkB,GAAG,SAAS,CAslCpF"}
|
package/dist/server.js
CHANGED
|
@@ -23,7 +23,7 @@ export const SERVER_NAME = '@ekwo-ai/mcp';
|
|
|
23
23
|
* `tests/mcp/surface.test.ts` keeps the two equal, so a release that bumps one
|
|
24
24
|
* and forgets the other fails the build.
|
|
25
25
|
*/
|
|
26
|
-
export const SERVER_VERSION = '0.
|
|
26
|
+
export const SERVER_VERSION = '0.15.0';
|
|
27
27
|
/** Everything a tool returns: JSON, pretty-printed, as one text block. */
|
|
28
28
|
function ok(payload) {
|
|
29
29
|
return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
|
|
@@ -63,7 +63,7 @@ async function guard(run) {
|
|
|
63
63
|
export const INSTRUCTIONS = [
|
|
64
64
|
'Ekwo OS keeps double-entry books in the user\'s own Postgres. You act as that user: everything you can see and change is what row level security lets them see and change. Amounts are decimal strings ("1210.00"), dates are ISO (2026-06-15), identifiers are uuids.',
|
|
65
65
|
'A first session, in order. (1) list_companies: every other tool takes a company_id from it; an empty list means this user has not been invited to a company yet, and status says who you are acting as. (2) get_company: the financial years and whether each is closed, the lock dates, the journals and the role accounts — a date you book on falls in an open year, after the lock date. (3) list_accounts and the resource ekwo://companies/{companyId}/taxes: a document line names its account and its tax by code, never by rate, because several taxes share a rate; describe_pack says which country pack those codes come from and how far it has been reviewed. (4) search_contacts, then create_contact only if nobody matches, then create_document: a draft with the totals the database computed. Show it to the user. (5) post_document once the user has said yes to that document; record_payment when the money moves.',
|
|
66
|
-
'Reading a tax return: vat_return gives the boxes of a period with their names and amounts, summed from what was posted, and the boxes computed from other boxes; a draft is in no box. Name a report_code only where the country files several declarations. It prepares figures and files nothing — the prompt prepare_vat_return walks through checking them against the ledger. The reports beside it are trial_balance, general_ledger, aged_balance, financial_statement (list_statements first)
|
|
66
|
+
'Reading a tax return: vat_return gives the boxes of a period with their names and amounts, summed from what was posted, and the boxes computed from other boxes; a draft is in no box. Name a report_code only where the country files several declarations. It prepares figures and files nothing — the prompt prepare_vat_return walks through checking them against the ledger. The reports beside it are trial_balance, general_ledger, aged_balance, financial_statement (list_statements first), financial_analysis, the figures a ratio is computed from, and analytic_report, the result of each project, cost centre or site of an analytic axis.',
|
|
67
67
|
'Correcting: a posted entry is never deleted or edited. A posted invoice is undone by cancel_document — back to draft where its country allows it and nothing has left, by the credit note that names it otherwise, and it says which — and an entry keyed by hand by reverse_entry. A paid document is unmatched first with unreconcile, and only when the user says so. Ask the user before every post, cancel, reversal, payment or lock.',
|
|
68
68
|
'When the database refuses, the message starts with a name — period_locked, entry_unbalanced, document_total_mismatch, reversal_date_needed — followed by a sentence saying what to do next. Report it to the user and follow that sentence rather than working around it.',
|
|
69
69
|
].join('\n\n');
|
|
@@ -71,8 +71,35 @@ export const INSTRUCTIONS = [
|
|
|
71
71
|
export function instructionsFor(options = {}) {
|
|
72
72
|
return options.inviteToRegister === true ? `${INSTRUCTIONS}\n\n${relayableInvitation()}` : INSTRUCTIONS;
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The same tool, with its title where every reader looks for it.
|
|
76
|
+
*
|
|
77
|
+
* The protocol lets a tool carry its human-readable name in two places: a
|
|
78
|
+
* top-level `title`, which the SDK's `registerTool` takes, and
|
|
79
|
+
* `annotations.title`, which some directories and clients are the only ones
|
|
80
|
+
* to read. A tool declared with one and not the other is listed by its
|
|
81
|
+
* machine name there. So the one title given below is copied into the
|
|
82
|
+
* annotations, and a title already set there is left as it is.
|
|
83
|
+
*/
|
|
84
|
+
function titled(config) {
|
|
85
|
+
if (config.title === undefined || config.annotations?.title !== undefined)
|
|
86
|
+
return config;
|
|
87
|
+
return { ...config, annotations: { ...config.annotations, title: config.title } };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A server on which no tool can be registered without `annotations.title`.
|
|
91
|
+
*
|
|
92
|
+
* Done once, on the server, rather than at each of the registrations below:
|
|
93
|
+
* a tool added later goes through the same door, and `tests/mcp/surface.test.ts`
|
|
94
|
+
* reads the list a client sees to make sure none slipped past.
|
|
95
|
+
*/
|
|
96
|
+
function withTitledTools(server) {
|
|
97
|
+
const register = server.registerTool.bind(server);
|
|
98
|
+
server.registerTool = ((name, config, callback) => register(name, titled(config), callback));
|
|
99
|
+
return server;
|
|
100
|
+
}
|
|
74
101
|
export function buildServer(backend, options = {}) {
|
|
75
|
-
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION }, { instructions: instructionsFor(options) });
|
|
102
|
+
const server = withTitledTools(new McpServer({ name: SERVER_NAME, version: SERVER_VERSION }, { instructions: instructionsFor(options) }));
|
|
76
103
|
// -------------------------------------------------------------------- read
|
|
77
104
|
server.registerTool('list_companies', {
|
|
78
105
|
title: 'List companies',
|
|
@@ -98,6 +125,12 @@ export function buildServer(backend, options = {}) {
|
|
|
98
125
|
inputSchema: read.SearchContactsInput.shape,
|
|
99
126
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
100
127
|
}, async (args) => guard(() => read.searchContacts(backend, args)));
|
|
128
|
+
server.registerTool('suggest_duplicate_contacts', {
|
|
129
|
+
title: 'Find the contacts that are probably one party',
|
|
130
|
+
description: 'The pairs of active contacts of a company that are probably one party — two spellings of one customer, two imports of one supplier — strongest first. Each pair names the contact to keep (the one more ledger lines and documents name, then the older) and the one to absorb, a score from 0 to 1, the kinds of evidence (vat_number, registration_number, account: the same number or bank account, score 1; name: the same significant words in any order, or every word of one in the other, 0.950 for the same name, 0.900 otherwise, 0.600 when those words reach several contacts) and the evidence in sentences. Two contacts with different VAT or registration numbers, a contact and its own parent, and inactive or merged contacts are never proposed. It writes nothing: show the user both contacts and call merge_contacts only on a pair they confirm, with the contact they choose to keep.',
|
|
131
|
+
inputSchema: read.SuggestDuplicateContactsInput.shape,
|
|
132
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
133
|
+
}, async (args) => guard(() => read.suggestDuplicateContacts(backend, args)));
|
|
101
134
|
server.registerTool('list_documents', {
|
|
102
135
|
title: 'List documents',
|
|
103
136
|
description: 'Invoices, credit notes and quotes of a company, filtered by type, state, settlement state, contact or date range. `state` is the document (draft, posted, cancelled) and `payment_state` is the settlement (not_paid, partially_paid, paid) — two different questions. Returns headers and totals, not lines; use get_document for those.',
|
|
@@ -175,6 +208,24 @@ export function buildServer(backend, options = {}) {
|
|
|
175
208
|
inputSchema: read.GeneralLedgerInput.shape,
|
|
176
209
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
177
210
|
}, async (args) => guard(() => read.generalLedger(backend, args)));
|
|
211
|
+
server.registerTool('suggest_line_contacts', {
|
|
212
|
+
title: 'Suggest the contact of lines that name nobody',
|
|
213
|
+
description: 'For each open line on a receivable or payable account that names no contact — typically the open items of books imported line by line — the contacts it could be, best first, each with a score, the method (a learned motif, the number of a document of the company quoted in the label, the description or the reference of the entry, the words of a contact\'s name), the evidence in a sentence, and alternatives: how many contacts the same evidence reached. More than 1 is a question for the user, not an answer. It writes nothing; set_line_contact names a line once the user has chosen.',
|
|
214
|
+
inputSchema: read.SuggestLineContactsInput.shape,
|
|
215
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
216
|
+
}, async (args) => guard(() => read.suggestLineContacts(backend, args)));
|
|
217
|
+
server.registerTool('suggest_item_combinations', {
|
|
218
|
+
title: 'Suggest which open items settle each other',
|
|
219
|
+
description: 'For each open line on a receivable or payable account of one counterparty (a branch and its parent are one party), the lines of the other side on the same account that could settle it, up to three proposals a line: an exact pair, several lines that add up to it to the cent ("this payment settles these three invoices", or one invoice paid in instalments), and — only when nothing is exact — a pair or several lines within the company\'s difference tolerance, with the difference (the line less the total). Each proposal has a stable proposal_id, a kind (pair, combination), the lines and a sentence. It writes nothing: show the user, then reconcile_many for a difference of 0.00, or reconcile_with_difference once they have seen the difference and chosen a reason.',
|
|
220
|
+
inputSchema: read.SuggestItemCombinationsInput.shape,
|
|
221
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
222
|
+
}, async (args) => guard(() => read.suggestItemCombinations(backend, args)));
|
|
223
|
+
server.registerTool('matching_difference_options', {
|
|
224
|
+
title: 'The reasons and the tolerance for booking a difference',
|
|
225
|
+
description: 'What reconcile_with_difference accepts for this company: the largest difference it books (in units of the currency and as an amount) and the reasons — rounding, exchange_difference — each with the account the pack gives, the side it answers (charge, income, either) and whether it is available. No pack carries a role for bank charges or payment discounts, so they are not reasons.',
|
|
226
|
+
inputSchema: read.MatchingDifferenceOptionsInput.shape,
|
|
227
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
228
|
+
}, async (args) => guard(() => read.matchingDifferenceOptions(backend, args)));
|
|
178
229
|
server.registerTool('aged_balance', {
|
|
179
230
|
title: 'Aged balance',
|
|
180
231
|
description: 'What customers still owe (or what is still owed to suppliers), bucketed by how overdue it is. It reads unmatched ledger lines, not invoices, so it ties back to the balance sheet: an invoice counts as settled only once its payment has been matched.',
|
|
@@ -219,10 +270,22 @@ export function buildServer(backend, options = {}) {
|
|
|
219
270
|
}, async (args) => guard(() => read.financialStatement(backend, args)));
|
|
220
271
|
server.registerTool('financial_analysis', {
|
|
221
272
|
title: 'Financial analysis',
|
|
222
|
-
description: "The figures an analysis of a company is computed from — revenue, other operating income, purchases, staff costs, depreciation, the financial result, income tax, the net result, equity, total and current assets, cash, trade receivables, current liabilities, trade payables, long-term debt — each with the accounts that make it. The movements are read over the period and the balances at its end; equity includes the result of the years not closed yet. Which accounts make each figure is written in the company's country pack and in no tool:
|
|
273
|
+
description: "The figures an analysis of a company is computed from — revenue, other operating income, purchases, staff costs, depreciation, the financial result, income tax, the net result, equity, total and current assets, cash, trade receivables, current liabilities, trade payables, long-term debt — each with the accounts that make it. The movements are read over the period and the balances at its end; equity includes the result of the years not closed yet. Which accounts make each figure is written in the company's country pack and in no tool. Each row carries source: pack when the country pack defines the analysis; framework when it defines none and the figures are read by account type through the generic framework, which leaves out staff costs, the financial result and income tax because no account type tells them apart — say those are not read rather than zero. The figures are never to be guessed from account codes. Ratios — a margin, a current ratio, days of sales outstanding — are worked out from these, not stored. It prepares figures; it files nothing.",
|
|
223
274
|
inputSchema: read.FinancialAnalysisInput.shape,
|
|
224
275
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
225
276
|
}, async (args) => guard(() => read.financialAnalysis(backend, args)));
|
|
277
|
+
server.registerTool('list_analytic_axes', {
|
|
278
|
+
title: 'Analytic axes',
|
|
279
|
+
description: 'The analytic axes of a company — a cost centre, a project, a site, a business unit — each with its values, which may nest under a parent_id, and required_on: none, or expense, income or result — the lines that must carry the axis whole, or posting refuses them (analytic_axis_required). A value says what a cost, a revenue or a fixed asset is for; a product says what was sold or bought. A retired value takes no new share. Read it before analytic_report, which takes an axis by its code.',
|
|
280
|
+
inputSchema: read.ListAnalyticAxesInput.shape,
|
|
281
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
282
|
+
}, async (args) => guard(() => read.listAnalyticAxes(backend, args)));
|
|
283
|
+
server.registerTool('analytic_report', {
|
|
284
|
+
title: 'Result by analytic value',
|
|
285
|
+
description: 'The result of each value of one analytic axis over a period, read from the posted ledger: the income, the expense and their difference per value, in the company\'s currency, from the whole income statement — salaries, rent and depreciation included, wherever they were split — and fixed_assets, the net movement of the fixed-asset accounts, beside the result and never in it. A value with others below it holds their subtotal; its own amount alone is in the direct_ columns, and level is its depth. unassigned is the part no share of the axis reaches and total the whole period: the direct_ amounts and unassigned add up to total, so say how large unassigned is rather than presenting the values as the whole, and never add a parent to its children. The margin of each product is a different question, read from the invoices. It prepares figures; it files nothing.',
|
|
286
|
+
inputSchema: read.AnalyticReportInput.shape,
|
|
287
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
288
|
+
}, async (args) => guard(() => read.analyticReport(backend, args)));
|
|
226
289
|
server.registerTool('generate_fec', {
|
|
227
290
|
title: 'Generate the French FEC',
|
|
228
291
|
description: 'The Fichier des Écritures Comptables for a period: the eighteen columns of the arrêté du 29 juillet 2013, as text, with the checks a tax inspector applies first (mandatory fields, one side per line, entries balancing) and the filename the format wants. The file comes back in the answer; writing it to disk is the caller\'s business.',
|
|
@@ -272,6 +335,12 @@ export function buildServer(backend, options = {}) {
|
|
|
272
335
|
inputSchema: write.CreateContactInput.shape,
|
|
273
336
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
274
337
|
}, async (args) => guard(() => write.createContact(backend, args)));
|
|
338
|
+
server.registerTool('merge_contacts', {
|
|
339
|
+
title: 'Merge two contacts of one company',
|
|
340
|
+
description: 'One party created twice — two spellings of one customer, two imports of one supplier: everything that names the absorbed contact names the kept one afterwards, in one transaction — posted ledger lines and issued documents included, statement lines, payments, branches, files and learned motifs — and the kept contact\'s empty fields (VAT number, IBAN, address, email…) are filled from the absorbed one without overwriting any. The absorbed contact is retired, never deleted, and no row may name it again. No amount, account, date or tax box moves, and there is no unmerge. Refused by name: unknown_contact (also a contact of another company), same_contact, already_merged, different_identifiers (two different VAT or registration numbers are two parties), matching_across_parties, not_allowed. suggest_duplicate_contacts proposes the pairs. Show the user the two contacts side by side with search_contacts and ask which one to keep before calling it; never decide that two contacts are one yourself.',
|
|
341
|
+
inputSchema: write.MergeContactsInput.shape,
|
|
342
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
343
|
+
}, async (args) => guard(() => write.mergeContacts(backend, args)));
|
|
275
344
|
server.registerTool('create_document', {
|
|
276
345
|
title: 'Create a draft invoice',
|
|
277
346
|
description: 'Creates a draft invoice, credit note or quote with its lines, and returns it with the totals the database computed — never with totals you supplied. A line may name a product_code, which fills in its text, price, unit, account and tax; anything the line carries wins over that. With no product and no account_code, the account falls back to the company default and then to its country model. A tax is different: a line with none is booked as a base with no VAT box, which is not the same as 0 %. Nothing is in the ledger yet. Next: show the draft to the user; update_document_lines changes it, and post_document books it once they agree.',
|
|
@@ -320,6 +389,36 @@ export function buildServer(backend, options = {}) {
|
|
|
320
389
|
inputSchema: write.UnreconcileInput.shape,
|
|
321
390
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
322
391
|
}, async (args) => guard(() => write.unreconcile(backend, args)));
|
|
392
|
+
server.registerTool('set_line_contact', {
|
|
393
|
+
title: 'Name the contact of a ledger line',
|
|
394
|
+
description: 'Names the contact of an open line on a receivable or payable account that names nobody — the open items of books imported line by line — once, from nothing to a contact of the same company, and learns the label as a motif of that contact for the next suggestion. Refused by name: unknown_line, line_not_posted, line_has_contact (a posted counterparty is corrected by reversing the entry), line_matched (unmatch it first, and only when the user says so), line_not_third_party, unknown_contact. Changes no amount, account or tax box. Show the user the candidates of suggest_line_contacts and ask which one before calling it; never pick one yourself.',
|
|
395
|
+
inputSchema: write.SetLineContactInput.shape,
|
|
396
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
397
|
+
}, async (args) => guard(() => write.setLineContact(backend, args)));
|
|
398
|
+
server.registerTool('leave_line_unnamed', {
|
|
399
|
+
title: 'Leave a ledger line without a contact',
|
|
400
|
+
description: 'Records that an open line on a receivable or payable account names nobody on purpose — a cash difference, a one-off refund — with the user\'s reason: suggest_line_contacts no longer proposes it. The line does not move and stays an open item. Refused by name: unknown_line, not_allowed, reason_required, reason_too_long, line_not_posted, line_has_contact, line_not_third_party, line_left_unnamed. Ask the user before calling it.',
|
|
401
|
+
inputSchema: write.LeaveLineUnnamedInput.shape,
|
|
402
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
403
|
+
}, async (args) => guard(() => write.leaveLineUnnamed(backend, args)));
|
|
404
|
+
server.registerTool('undo_leave_line_unnamed', {
|
|
405
|
+
title: 'Take back leaving a line without a contact',
|
|
406
|
+
description: 'Takes back leave_line_unnamed: the line is proposed a contact again. Refused by name: unknown_line, not_allowed, line_not_left_unnamed.',
|
|
407
|
+
inputSchema: write.UndoLeaveLineUnnamedInput.shape,
|
|
408
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
409
|
+
}, async (args) => guard(() => write.undoLeaveLineUnnamed(backend, args)));
|
|
410
|
+
server.registerTool('reconcile_many', {
|
|
411
|
+
title: 'Match several ledger lines together',
|
|
412
|
+
description: 'Matches several lines together in one call — one payment against several invoices, one invoice against several instalments — when their open debits and credits are equal to the cent: posted lines of one company, one reconcilable account, one counterparty (a line naming nobody is attributed by the matching), in the company\'s currency. Paired oldest first through reconcile, under one letter. Refused by name, and never settled in part: reconcile_unbalanced gives the difference. Show the user the lines and ask before calling it.',
|
|
413
|
+
inputSchema: write.ReconcileManyInput.shape,
|
|
414
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
415
|
+
}, async (args) => guard(() => write.reconcileMany(backend, args)));
|
|
416
|
+
server.registerTool('reconcile_with_difference', {
|
|
417
|
+
title: 'Match ledger lines and book a small difference',
|
|
418
|
+
description: 'Matches the same set of lines as reconcile_many when their totals differ by no more than the company\'s tolerance: posts an entry that books the difference on the same account and counterparty against the account the pack gives for the reason (matching_difference_options: rounding, exchange_difference), then matches everything under one letter. Refused by name: difference_above_tolerance, no_difference, unknown_difference_reason, no_account_for_reason, and every refusal of reconcile_many. The entry is posted; unreconcile of the matching of the difference line takes it back. Show the user the difference and the account, and let them choose the reason, before calling it.',
|
|
419
|
+
inputSchema: write.ReconcileWithDifferenceInput.shape,
|
|
420
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
421
|
+
}, async (args) => guard(() => write.reconcileWithDifference(backend, args)));
|
|
323
422
|
server.registerTool('settle_from_statement', {
|
|
324
423
|
title: 'Settle a bank statement line',
|
|
325
424
|
description: 'Books a pending statement line as the payment it is and matches it against the documents or open items named, in one entry: the bank, the customer or supplier account, and the extra lines the money is net of — the fee a payment provider kept from a payout, the charges of a bank, each on the account and with the tax the user names. A payout of 97.00 for an invoice of 100.00 settles the invoice in full with an extra line of 3.00 on a fee account. amounts says how much settles each item; left out, oldest first. The line has to balance to the cent (settlement_unbalanced otherwise, with the difference). Extra lines alone settle a bank charge. One counterparty per line. The entry cannot be unposted. Ask the user before calling it, and for the fee account and its tax.',
|
|
@@ -442,16 +541,22 @@ export function buildServer(backend, options = {}) {
|
|
|
442
541
|
}, async (args) => guard(() => write.openingBalance(backend, args)));
|
|
443
542
|
server.registerTool('close_fiscal_year', {
|
|
444
543
|
title: 'Close a fiscal year',
|
|
445
|
-
description: 'Closes a year: the result of the year leaves the income statement the way the country model says — straight to retained earnings, into a current-year result account, or through the appropriation accounts — every income and expense account goes back to zero, and the year stops accepting entries. Refused while the year holds a draft entry, while an earlier year with entries is open, or when a later year is already closed. It never writes what a general meeting decides to do with the result. Always ask the user before calling it.',
|
|
544
|
+
description: 'Closes a year: the result of the year leaves the income statement the way the country model says — straight to retained earnings, into a current-year result account, or through the appropriation accounts — every income and expense account goes back to zero, and the year stops accepting entries. The close can be undone with reopen_fiscal_year; final_close in the answer says whether the country also requires a final close, which cannot, and by when. Refused while the year holds a draft entry, while an earlier year with entries is open, or when a later year is already closed. It never writes what a general meeting decides to do with the result. Always ask the user before calling it.',
|
|
446
545
|
inputSchema: write.CloseFiscalYearInput.shape,
|
|
447
546
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
448
547
|
}, async (args) => guard(() => write.closeFiscalYear(backend, args)));
|
|
449
548
|
server.registerTool('reopen_fiscal_year', {
|
|
450
549
|
title: 'Re-open a closed fiscal year',
|
|
451
|
-
description: 'Undoes
|
|
550
|
+
description: 'Undoes an ordinary close, to correct the year: the entries it wrote are reversed, never deleted, and the year accepts entries again. Entries already posted in later years do not prevent it and are not touched — they read this year from the ledger, so closing it again carries the corrected result to them. Refused for a year under a final close (fiscal_year_finally_closed), for everybody, and while a later year is closed (later_fiscal_year_closed): reopen that one first. Always ask the user before calling it.',
|
|
452
551
|
inputSchema: write.ReopenFiscalYearInput.shape,
|
|
453
552
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
454
553
|
}, async (args) => guard(() => write.reopenFiscalYear(backend, args)));
|
|
554
|
+
server.registerTool('close_fiscal_year_finally', {
|
|
555
|
+
title: 'Close a fiscal year for good',
|
|
556
|
+
description: 'The final close: a year already closed is frozen, and from then on nobody can reopen it or change it — not the owner, not an accountant, not the operator. Refused on an open year (fiscal_year_not_closed) and while a closed year before it is not finally closed. Whether the country requires it, and by when, is final_close in the answer of close_fiscal_year. Irreversible: call it only when the user has asked for this specific year to be closed for good.',
|
|
557
|
+
inputSchema: write.CloseFiscalYearFinallyInput.shape,
|
|
558
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
559
|
+
}, async (args) => guard(() => write.closeFiscalYearFinally(backend, args)));
|
|
455
560
|
// --------------------------------------------------------------- resources
|
|
456
561
|
server.registerResource('chart-of-accounts', new ResourceTemplate('ekwo://companies/{companyId}/chart', { list: undefined }), {
|
|
457
562
|
title: 'Chart of accounts',
|
|
@@ -596,9 +701,15 @@ export function buildServer(backend, options = {}) {
|
|
|
596
701
|
title: tool.title,
|
|
597
702
|
description: tool.description,
|
|
598
703
|
inputSchema: tool.input.shape,
|
|
599
|
-
|
|
704
|
+
// A module tool that is not read-only writes the books — an asset
|
|
705
|
+
// recorded, a depreciation booked, budget lines replaced — and says
|
|
706
|
+
// so, the way the socle's writes above do.
|
|
707
|
+
annotations: tool.readOnly
|
|
708
|
+
? { readOnlyHint: true, openWorldHint: false }
|
|
709
|
+
: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
600
710
|
}, async (args) => guard(() => tool.run(backend, args, {
|
|
601
711
|
...(options.einvoiceTransport === undefined ? {} : { einvoiceTransport: options.einvoiceTransport }),
|
|
712
|
+
...(options.fiscalSystem === undefined ? {} : { fiscalSystem: options.fiscalSystem }),
|
|
602
713
|
})));
|
|
603
714
|
}
|
|
604
715
|
for (const prompt of toolset.prompts ?? []) {
|