uol-mcp 0.0.0-stage → 0.4.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/LICENSE +21 -0
- package/README.cs.md +118 -0
- package/README.md +258 -2
- package/dist/config.js +241 -0
- package/dist/config.js.map +1 -0
- package/dist/domain/audit.js +23 -0
- package/dist/domain/audit.js.map +1 -0
- package/dist/domain/bank.js +39 -0
- package/dist/domain/bank.js.map +1 -0
- package/dist/domain/company.js +12 -0
- package/dist/domain/company.js.map +1 -0
- package/dist/domain/contact-names.js +46 -0
- package/dist/domain/contact-names.js.map +1 -0
- package/dist/domain/files.js +17 -0
- package/dist/domain/files.js.map +1 -0
- package/dist/domain/ledger.js +92 -0
- package/dist/domain/ledger.js.map +1 -0
- package/dist/domain/paging.js +17 -0
- package/dist/domain/paging.js.map +1 -0
- package/dist/domain/receivables.js +109 -0
- package/dist/domain/receivables.js.map +1 -0
- package/dist/domain/uploads.js +57 -0
- package/dist/domain/uploads.js.map +1 -0
- package/dist/domain/vat.js +109 -0
- package/dist/domain/vat.js.map +1 -0
- package/dist/http.js +162 -0
- package/dist/http.js.map +1 -0
- package/dist/index.js +128 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions.js +16 -0
- package/dist/instructions.js.map +1 -0
- package/dist/prompts.js +60 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.js +57 -0
- package/dist/resources.js.map +1 -0
- package/dist/server.js +103 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/accounts.js +33 -0
- package/dist/tools/accounts.js.map +1 -0
- package/dist/tools/banking.js +105 -0
- package/dist/tools/banking.js.map +1 -0
- package/dist/tools/company.js +96 -0
- package/dist/tools/company.js.map +1 -0
- package/dist/tools/contacts.js +131 -0
- package/dist/tools/contacts.js.map +1 -0
- package/dist/tools/context.js +82 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/destructive.js +96 -0
- package/dist/tools/destructive.js.map +1 -0
- package/dist/tools/documents.js +124 -0
- package/dist/tools/documents.js.map +1 -0
- package/dist/tools/files.js +20 -0
- package/dist/tools/files.js.map +1 -0
- package/dist/tools/insights.js +247 -0
- package/dist/tools/insights.js.map +1 -0
- package/dist/tools/invoices.js +329 -0
- package/dist/tools/invoices.js.map +1 -0
- package/dist/tools/ledger.js +138 -0
- package/dist/tools/ledger.js.map +1 -0
- package/dist/tools/raw.js +47 -0
- package/dist/tools/raw.js.map +1 -0
- package/dist/tools/receivables.js +84 -0
- package/dist/tools/receivables.js.map +1 -0
- package/dist/tools/schemas.js +41 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools/warehouse.js +181 -0
- package/dist/tools/warehouse.js.map +1 -0
- package/dist/tools/write-support.js +34 -0
- package/dist/tools/write-support.js.map +1 -0
- package/dist/tools/writes-documents.js +117 -0
- package/dist/tools/writes-documents.js.map +1 -0
- package/dist/tools/writes-records.js +324 -0
- package/dist/tools/writes-records.js.map +1 -0
- package/dist/tools/writes.js +291 -0
- package/dist/tools/writes.js.map +1 -0
- package/dist/uol/client.js +49 -0
- package/dist/uol/client.js.map +1 -0
- package/dist/uol/errors.js +50 -0
- package/dist/uol/errors.js.map +1 -0
- package/dist/uol/http.js +213 -0
- package/dist/uol/http.js.map +1 -0
- package/dist/uol/normalize.js +117 -0
- package/dist/uol/normalize.js.map +1 -0
- package/dist/uol/rate-limit.js +55 -0
- package/dist/uol/rate-limit.js.map +1 -0
- package/package.json +55 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jakub Begera
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.cs.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# uol-mcp — návod
|
|
2
|
+
|
|
3
|
+
Napojení [UOL Účetnictví](https://www.uol.cz) na AI asistenty (Claude, Cursor a další klienty
|
|
4
|
+
s podporou [MCP](https://modelcontextprotocol.io)). Asistent pak umí odpovídat nad vaším účetnictvím:
|
|
5
|
+
vydané a přijaté faktury, kdo vám dluží, nahrané doklady, kontakty.
|
|
6
|
+
|
|
7
|
+
> **Neoficiální projekt.** Není spojený s UOL a.s. Komunikuje s veřejným
|
|
8
|
+
> [UOL REST API](https://api.uol.cz/) pomocí vašeho vlastního API tokenu.
|
|
9
|
+
|
|
10
|
+
[English](README.md)
|
|
11
|
+
|
|
12
|
+
**Stav:** verze 0.4, ve výchozím stavu pouze čtení, zápis lze zapnout. Zatím není na npm; do prvního
|
|
13
|
+
vydání se spouští [ze zdrojového kódu](README.md#from-source).
|
|
14
|
+
|
|
15
|
+
## Co umí
|
|
16
|
+
|
|
17
|
+
- **Přehled firmy jedním dotazem:** tento a minulý měsíc, DPH, pohledávky, doklady ke zpracování
|
|
18
|
+
a zůstatky na účtech.
|
|
19
|
+
- **Tržby, náklady a zisk:** po měsících z hlavní knihy, stejně jako dashboard UOL, včetně
|
|
20
|
+
největších nákladových a výnosových účtů.
|
|
21
|
+
- **DPH:** za zdaňovací období, k zaplacení nebo nadměrný odpočet, termín podání; u neuzavřeného
|
|
22
|
+
období jako odhad z faktur.
|
|
23
|
+
- **Zůstatky na účtech:** podle účtů a měn (UOL eviduje měsíční počáteční stavy, ne aktuální zůstatek).
|
|
24
|
+
- **Faktury:** hledání vydaných i přijatých faktur, detail s položkami a DPH, stav úhrady
|
|
25
|
+
u vydaných faktur.
|
|
26
|
+
- **Pohledávky:** kdo vám dluží, kolik a jak dlouho po splatnosti, součty podle měn.
|
|
27
|
+
- **Export:** PDF nebo ISDOC vydané faktury uložené na disk.
|
|
28
|
+
- **Kontakty:** odběratelé a dodavatelé podle názvu, IČO nebo DIČ, s adresami a bankovními účty.
|
|
29
|
+
- **Nahrané doklady:** co čeká na zpracování, stažení původního souboru.
|
|
30
|
+
- **Číselníky:** měny, střediska, zakázky, ceníky, vlastní bankovní účty a další.
|
|
31
|
+
|
|
32
|
+
Připravené postupy (v klientech jako příkazy s lomítkem): `monthly_review` (měsíční přehled),
|
|
33
|
+
`vat_check` (kontrola DPH), `overdue_followup` (návrhy upomínek, nic neodesílá) a `process_inbox`
|
|
34
|
+
(projde nahrané doklady).
|
|
35
|
+
|
|
36
|
+
Příklady otázek:
|
|
37
|
+
|
|
38
|
+
- „Jak si letos stojíme?“
|
|
39
|
+
- „Kolik zaplatím DPH za tento kvartál?“
|
|
40
|
+
- „Na čem nejvíc utrácíme?“
|
|
41
|
+
- „Kdo mi dluží víc než 30 dní?“
|
|
42
|
+
- „Je faktura 2026000123 zaplacená?“
|
|
43
|
+
- „Kolik jsme letos fakturovali firmě Alza?“
|
|
44
|
+
- „Stáhni mi PDF poslední vydané faktury.“
|
|
45
|
+
- „Které nahrané doklady ještě účetní nezpracovala?“
|
|
46
|
+
|
|
47
|
+
## Jak získat API token
|
|
48
|
+
|
|
49
|
+
1. Otevřete v UOL stránku API tokenů (adresa `/api_tokens` na vaší instanci, např.
|
|
50
|
+
`https://firma.ucetnictvi.uol.cz/api_tokens`) a vytvořte token.
|
|
51
|
+
2. Uživatel musí mít oprávnění **REST API**. Doporučujeme samostatného uživatele jen s právy,
|
|
52
|
+
která má asistent mít.
|
|
53
|
+
|
|
54
|
+
Název instance (tenant) je první část vaší adresy UOL: `https://firma.ucetnictvi.uol.cz` → `firma`.
|
|
55
|
+
|
|
56
|
+
Chcete si to nejdřív vyzkoušet? S `UOL_TENANT=demo` se použije veřejná DEMO firma od UOL,
|
|
57
|
+
token není potřeba.
|
|
58
|
+
|
|
59
|
+
## Nastavení v Claude Desktop
|
|
60
|
+
|
|
61
|
+
Nastavení → Developer → Edit Config a doplňte:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"uol": {
|
|
67
|
+
"command": "npx",
|
|
68
|
+
"args": ["-y", "uol-mcp"],
|
|
69
|
+
"env": {
|
|
70
|
+
"UOL_TENANT": "firma",
|
|
71
|
+
"UOL_EMAIL": "vas@email.cz",
|
|
72
|
+
"UOL_TOKEN": "vas-token"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Po restartu Claude Desktop zkuste: „Jaké jsou údaje naší firmy v UOL?“
|
|
80
|
+
|
|
81
|
+
Ostatní klienty (Claude Code, Cursor, VS Code), Docker, HTTP server a všechny proměnné prostředí
|
|
82
|
+
popisuje [anglické README](README.md#configuration).
|
|
83
|
+
|
|
84
|
+
## Zápis (volitelný)
|
|
85
|
+
|
|
86
|
+
S `UOL_ACCESS=write` (v Claude Desktop rozšíření pole „Access“ = `write`) asistent umí navíc:
|
|
87
|
+
|
|
88
|
+
- **nahrát doklad** (PDF) účetní do UOL, a to jen ze složek Stažené soubory, Plocha a Dokumenty;
|
|
89
|
+
- **vystavit fakturu** existujícímu kontaktu, standardně jako koncept;
|
|
90
|
+
- **upravit nebo vystavit koncept faktury**; už vystavenou fakturu UOL měnit nedovolí;
|
|
91
|
+
- **zaevidovat přijatou fakturu**, **založit nebo upravit kontakt** (s kontrolou duplicitního IČO);
|
|
92
|
+
- **vytvořit upomínku** k neuhrazeným fakturám jednoho odběratele (nic se neodesílá);
|
|
93
|
+
- **zpracovat nahrané doklady**: označit jako zpracované, spárovat s fakturou, přidat štítek.
|
|
94
|
+
|
|
95
|
+
S `UOL_ACCESS=full` navíc smazání konceptu faktury, storno nebo vrácení vystavené faktury do konceptu
|
|
96
|
+
a obecný zápis do libovolného endpointu.
|
|
97
|
+
|
|
98
|
+
Před každým zápisem si asistent nechá vše ukázat nanečisto a klient vás požádá o souhlas.
|
|
99
|
+
Opakované vytvoření stejné faktury nevznikne dvakrát.
|
|
100
|
+
|
|
101
|
+
## Více firem (pro účetní)
|
|
102
|
+
|
|
103
|
+
Jeden server obslouží víc klientských firem: v souboru `UOL_PROFILES_FILE` je seznam účtů (tenant,
|
|
104
|
+
e-mail, token nebo název proměnné s tokenem). Asistent pak u každého dotazu ví, které firmy se týká,
|
|
105
|
+
třeba „Kdo dluží firmě Novák?“. Formát souboru popisuje [anglické README](README.md#several-companies).
|
|
106
|
+
|
|
107
|
+
## Soukromí a bezpečnost
|
|
108
|
+
|
|
109
|
+
- Bez `UOL_ACCESS=write` server data jen čte. Text uvnitř faktur, poznámek a dokladů je pro
|
|
110
|
+
asistenta jen data: před schválením zápisu si zkontrolujte, co navrhuje.
|
|
111
|
+
- Vše, co nástroje vrátí, dostane AI model, který váš klient používá. Účetnictví obsahuje osobní
|
|
112
|
+
údaje kontaktů a zaměstnanců; zvolte si takový tarif, s jehož podmínkami souhlasíte.
|
|
113
|
+
- Přihlašovací údaje se posílají jen na `https://*.uol.cz` a nikdy se nezapisují do logů.
|
|
114
|
+
- Stažené soubory se ukládají jen do složky `UOL_DOWNLOAD_DIR` (výchozí `~/Downloads/uol-mcp`).
|
|
115
|
+
|
|
116
|
+
## Licence
|
|
117
|
+
|
|
118
|
+
[MIT](LICENSE)
|
package/README.md
CHANGED
|
@@ -1,3 +1,259 @@
|
|
|
1
|
-
#
|
|
1
|
+
# uol-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[Model Context Protocol](https://modelcontextprotocol.io) server for
|
|
4
|
+
[UOL Účetnictví](https://www.uol.cz) — ask Claude, Cursor or any other MCP client about your
|
|
5
|
+
bookkeeping: issued and received invoices, who owes you money, uploaded documents, contacts.
|
|
6
|
+
|
|
7
|
+
> **Unofficial.** Not affiliated with or endorsed by UOL a.s. It talks to the public
|
|
8
|
+
> [UOL REST API](https://api.uol.cz/) with your own API token.
|
|
9
|
+
|
|
10
|
+
[Česky](README.cs.md)
|
|
11
|
+
|
|
12
|
+
**Status:** v0.4 — read-only by default, opt-in writes. Not on npm yet; run it [from source](#from-source) until the first release.
|
|
13
|
+
|
|
14
|
+
## What it can do
|
|
15
|
+
|
|
16
|
+
| Tool | Answers |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `get_business_snapshot` | How the company is doing, in one call: this month vs last, VAT, receivables, documents to process, bank |
|
|
19
|
+
| `get_financial_overview` | Revenue, expenses and profit per month from the general ledger, largest accounts |
|
|
20
|
+
| `get_vat_overview` | VAT per filing period: to pay or excess deduction, due date, final or estimate |
|
|
21
|
+
| `get_bank_balances` | Bank balances per account and currency (UOL keeps monthly snapshots) |
|
|
22
|
+
| `get_company` | Company name, IČO, DIČ, address, VAT filing period; doubles as a connection check |
|
|
23
|
+
| `search_sales_invoices`, `get_sales_invoice` | Issued invoices with line items, VAT and payment status |
|
|
24
|
+
| `search_purchase_invoices`, `get_purchase_invoice` | Received invoices |
|
|
25
|
+
| `list_receivables` | Who owes you, how much, how long overdue — totals per currency |
|
|
26
|
+
| `export_sales_invoice` | PDF or ISDOC of an issued invoice, saved to disk (ISDOC is also returned as XML) |
|
|
27
|
+
| `search_contacts`, `get_contact` | Customers and suppliers by name, IČO or DIČ, with addresses and bank accounts |
|
|
28
|
+
| `list_uploaded_documents`, `get_uploaded_document`, `download_uploaded_document` | Documents uploaded for the accountant, their state and files |
|
|
29
|
+
| `list_codebook` | Currencies, departments, contracts, price lists, own bank accounts, … |
|
|
30
|
+
| `api_get` | Read-only GET of any other `/v1/…` endpoint |
|
|
31
|
+
|
|
32
|
+
Example questions: *"Jak si letos stojíme?"*, *"Kolik zaplatím DPH za tento kvartál?"*,
|
|
33
|
+
*"Kdo mi dluží víc než 30 dní?"*, *"Je faktura 2026000123 zaplacená?"*,
|
|
34
|
+
*"Na čem nejvíc utrácíme?"*, *"Stáhni mi PDF poslední vydané faktury."*
|
|
35
|
+
|
|
36
|
+
More read tools are available as opt-in toolsets (`UOL_TOOLSETS`): `ledger` (general-ledger postings, internal
|
|
37
|
+
documents), `banking` (bank movements, petty-cash documents) and `warehouse` (products, stock levels, orders).
|
|
38
|
+
|
|
39
|
+
Ready-made prompts (slash commands in most clients): `monthly_review`, `vat_check`,
|
|
40
|
+
`overdue_followup` (drafts reminders, sends nothing), `process_inbox`. The `uol://guide` resource
|
|
41
|
+
explains the domain to the model.
|
|
42
|
+
|
|
43
|
+
The server knows the API's quirks: invoice status `valid` means *draft*, payment status exists only
|
|
44
|
+
for issued invoices, credit notes come with positive amounts, revenue and expenses have to be read
|
|
45
|
+
from the ledger (excluding year-end closing entries), purchase invoices ignore issue-date filters,
|
|
46
|
+
files have three different download shapes, and the rate limit (30 requests / 10 s) is respected
|
|
47
|
+
client-side. See [docs/DESIGN.md](docs/DESIGN.md).
|
|
48
|
+
|
|
49
|
+
## Getting an API token
|
|
50
|
+
|
|
51
|
+
1. Open the API tokens page of your UOL tenant (path `/api_tokens`, e.g.
|
|
52
|
+
`https://firma.ucetnictvi.uol.cz/api_tokens`) and create a token.
|
|
53
|
+
2. The UOL user needs the **REST API** permission. A dedicated user with only the rights you want
|
|
54
|
+
the assistant to have is a good idea.
|
|
55
|
+
|
|
56
|
+
Your tenant is the first part of your UOL address: `https://firma.ucetnictvi.uol.cz` → `firma`.
|
|
57
|
+
|
|
58
|
+
Want to try it first? `UOL_TENANT=demo` uses UOL's public DEMO company, no token needed.
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
|
|
62
|
+
| Variable | Meaning |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `UOL_TENANT` | `demo`, your tenant name (`firma`), a host prefix (`firma.sandbox`) or the full `https://….uol.cz` URL |
|
|
65
|
+
| `UOL_EMAIL` | E-mail you log into UOL with |
|
|
66
|
+
| `UOL_TOKEN` | API token |
|
|
67
|
+
| `UOL_DOWNLOAD_DIR` | Where exported and downloaded files go (default `~/Downloads/uol-mcp`) |
|
|
68
|
+
| `UOL_TOOLSETS` | Comma-separated: `core`, `insights` (these two are the default), `ledger`, `banking`, `warehouse`, or `all` |
|
|
69
|
+
| `UOL_ACCESS` | `read` (default), `write` or `full` — see [Writing](#writing-opt-in) |
|
|
70
|
+
| `UOL_PROFILES_FILE` | Several companies in one server — see [Several companies](#several-companies) |
|
|
71
|
+
| `UOL_UPLOAD_DIRS` | Folders `upload_document` may read from (default `~/Downloads`, `~/Desktop`, `~/Documents`; `:`-separated, `;` on Windows) |
|
|
72
|
+
| `UOL_AUDIT_LOG` | File that records every write as a JSON line |
|
|
73
|
+
| `UOL_DEBUG` | `1` logs requests to stderr (never credentials) |
|
|
74
|
+
|
|
75
|
+
### Claude Desktop
|
|
76
|
+
|
|
77
|
+
Settings → Developer → Edit Config, then add:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"uol": {
|
|
83
|
+
"command": "npx",
|
|
84
|
+
"args": ["-y", "uol-mcp"],
|
|
85
|
+
"env": {
|
|
86
|
+
"UOL_TENANT": "firma",
|
|
87
|
+
"UOL_EMAIL": "you@example.com",
|
|
88
|
+
"UOL_TOKEN": "your-token"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Claude Code
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
claude mcp add uol -e UOL_TENANT=firma -e UOL_EMAIL=you@example.com -e UOL_TOKEN=your-token -- npx -y uol-mcp
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Cursor / VS Code
|
|
102
|
+
|
|
103
|
+
Cursor (`.cursor/mcp.json`) uses the same `mcpServers` block as Claude Desktop. VS Code
|
|
104
|
+
(`.vscode/mcp.json`):
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"servers": {
|
|
109
|
+
"uol": {
|
|
110
|
+
"type": "stdio",
|
|
111
|
+
"command": "npx",
|
|
112
|
+
"args": ["-y", "uol-mcp"],
|
|
113
|
+
"env": { "UOL_TENANT": "firma", "UOL_EMAIL": "you@example.com", "UOL_TOKEN": "your-token" }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Docker
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
docker run -i --rm -e UOL_TENANT=firma -e UOL_EMAIL=you@example.com -e UOL_TOKEN=your-token -v "$HOME/Downloads/uol-mcp:/data" ghcr.io/jakubbegera/uol-mcp
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Exported and downloaded files are written to `/data` inside the container; the `-v` mount keeps them.
|
|
126
|
+
In an MCP client config, use `docker` as the command and the rest as `args`.
|
|
127
|
+
|
|
128
|
+
### Claude Desktop extension
|
|
129
|
+
|
|
130
|
+
Each release ships `uol-mcp.mcpb`. Open it to install, then fill in tenant, e-mail and token in the
|
|
131
|
+
form; the token is stored as a secret. `bash scripts/build-mcpb.sh` builds the bundle from source.
|
|
132
|
+
|
|
133
|
+
### HTTP server (self-hosting)
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
MCP_TRANSPORT=http MCP_AUTH_TOKEN=change-me UOL_TENANT=firma UOL_EMAIL=you@example.com UOL_TOKEN=your-token npx -y uol-mcp
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The endpoint is `http://127.0.0.1:3000/mcp` (`HOST`, `PORT` to change; `/health` for probes).
|
|
140
|
+
Two modes:
|
|
141
|
+
|
|
142
|
+
- **One tenant**: UOL credentials in the server's env. Clients must send `Authorization: Bearer
|
|
143
|
+
<MCP_AUTH_TOKEN>`; the server refuses to start without the token.
|
|
144
|
+
- **Pass-through**: no UOL credentials in the env. Each client sends its own `X-UOL-Tenant`,
|
|
145
|
+
`X-UOL-Email` and `X-UOL-Token` headers with every request; the server stores nothing.
|
|
146
|
+
`MCP_AUTH_TOKEN` is optional.
|
|
147
|
+
|
|
148
|
+
Over HTTP, exported invoices and downloaded documents come back inside the tool result instead of
|
|
149
|
+
being saved on the server. In Docker, bind to all interfaces and publish the port:
|
|
150
|
+
`docker run -p 3000:3000 -e MCP_TRANSPORT=http -e HOST=0.0.0.0 -e MCP_ALLOWED_HOSTS=mcp.example.com …`.
|
|
151
|
+
|
|
152
|
+
### Check the setup
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
UOL_TENANT=firma UOL_EMAIL=you@example.com UOL_TOKEN=your-token npx -y uol-mcp doctor
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Writing (opt-in)
|
|
159
|
+
|
|
160
|
+
With `UOL_ACCESS=write` these tools appear:
|
|
161
|
+
|
|
162
|
+
| Tool | Does |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `upload_document` | Uploads a PDF to UOL's document inbox for the accountant (only from `UOL_UPLOAD_DIRS`) |
|
|
165
|
+
| `create_sales_invoice` | Creates an issued invoice for an existing contact — a draft unless `issue: true` |
|
|
166
|
+
| `update_sales_invoice` | Changes or issues a draft invoice (UOL doesn't allow editing issued ones) |
|
|
167
|
+
| `create_purchase_invoice` | Books a received invoice from an existing supplier |
|
|
168
|
+
| `create_contact`, `update_contact` | Adds or changes a customer / supplier (refuses a duplicate IČO) |
|
|
169
|
+
| `create_payment_demand` | Creates a payment demand (upomínka) for unpaid invoices of one customer — never sends it |
|
|
170
|
+
| `mark_document_processed`, `pair_uploaded_document`, `tag_uploaded_document`, `untag_uploaded_document` | The accountant's inbox: state, link to the booked record, tags |
|
|
171
|
+
|
|
172
|
+
`UOL_ACCESS=full` adds `delete_sales_invoice` (drafts only), `repair_sales_invoice` (storno or back to
|
|
173
|
+
draft for issued invoices) and `api_request` (a raw write to any endpoint).
|
|
174
|
+
|
|
175
|
+
Invoice lines need a UOL product (`list_codebook` with `kind: products` lists them). Every write
|
|
176
|
+
supports `dry_run` (shows exactly what would be sent), creates are safe to retry
|
|
177
|
+
(idempotency key), and the tools are marked so clients ask you before running them. Set
|
|
178
|
+
`UOL_AUDIT_LOG` to keep a record of every write.
|
|
179
|
+
|
|
180
|
+
## Several companies
|
|
181
|
+
|
|
182
|
+
Accountants with several clients on UOL can serve them all from one server. List the accounts in
|
|
183
|
+
`UOL_ACCOUNTS` and give each one its own variables (name upper-cased, `-` becomes `_`):
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
"env": {
|
|
187
|
+
"UOL_ACCOUNTS": "firma,novak",
|
|
188
|
+
"UOL_ACCOUNT_FIRMA_TENANT": "firma",
|
|
189
|
+
"UOL_ACCOUNT_FIRMA_EMAIL": "ja@firma.cz",
|
|
190
|
+
"UOL_ACCOUNT_FIRMA_TOKEN": "op://Private/UOL firma/credential",
|
|
191
|
+
"UOL_ACCOUNT_NOVAK_TENANT": "novak",
|
|
192
|
+
"UOL_ACCOUNT_NOVAK_EMAIL": "ja@firma.cz",
|
|
193
|
+
"UOL_ACCOUNT_NOVAK_TOKEN": "op://Private/UOL novak/credential",
|
|
194
|
+
"UOL_ACCOUNT_NOVAK_LABEL": "Novák s.r.o."
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The first account is the default (or set `UOL_DEFAULT_ACCOUNT`). With the 1Password CLI, start the
|
|
199
|
+
server as `op run -- node …/dist/index.js` and the `op://` references are replaced by the tokens at
|
|
200
|
+
startup — nothing secret sits in the config. `uol-mcp doctor` checks every account.
|
|
201
|
+
|
|
202
|
+
Alternatively, point `UOL_PROFILES_FILE` at a JSON file:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"default": "firma",
|
|
207
|
+
"accounts": {
|
|
208
|
+
"firma": { "tenant": "firma", "email": "ja@firma.cz", "token_env": "UOL_TOKEN_FIRMA" },
|
|
209
|
+
"novak": { "tenant": "novak", "email": "ja@firma.cz", "token_env": "UOL_TOKEN_NOVAK", "label": "Novák s.r.o." }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`token_env` names an environment variable holding the token, so the file needs no secrets (an inline
|
|
215
|
+
`token` works too; keep such a file `chmod 600`). The server then offers `list_accounts`, and every tool
|
|
216
|
+
takes an `account` argument — *"Kdo dluží firmě Novák?"* goes to the right tenant. Each account keeps
|
|
217
|
+
its own rate limit.
|
|
218
|
+
|
|
219
|
+
## Privacy and safety
|
|
220
|
+
|
|
221
|
+
- Read-only unless you set `UOL_ACCESS=write`. Text inside invoices, notes and documents is treated
|
|
222
|
+
as data: review what the assistant proposes to write before you approve it.
|
|
223
|
+
- Whatever the tools return goes to the AI model your client uses. Accounting data contains personal
|
|
224
|
+
data of your contacts and employees — use a plan whose terms you are comfortable with.
|
|
225
|
+
- Credentials are used only for `https://*.uol.cz` and are never written to logs or tool output.
|
|
226
|
+
- Downloaded files are saved under `UOL_DOWNLOAD_DIR` only.
|
|
227
|
+
|
|
228
|
+
## From source
|
|
229
|
+
|
|
230
|
+
Requires Node.js 22+.
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
git clone https://github.com/jakubbegera/uol-mcp.git
|
|
234
|
+
cd uol-mcp
|
|
235
|
+
npm install
|
|
236
|
+
npm run build
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Then point your client at `node /path/to/uol-mcp/dist/index.js` instead of `npx -y uol-mcp`.
|
|
240
|
+
|
|
241
|
+
## Development
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
npm test # unit + MCP-level tests against recorded DEMO responses
|
|
245
|
+
npm run typecheck
|
|
246
|
+
npm run smoke # calls every tool against the live DEMO tenant
|
|
247
|
+
npm run record-fixtures # refreshes test/fixtures/demo from the DEMO tenant
|
|
248
|
+
bash scripts/build-mcpb.sh # builds build/uol-mcp.mcpb
|
|
249
|
+
docker build -t uol-mcp .
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Releases are cut by pushing a `v*` tag; `.github/workflows/release.yml` publishes to npm, GHCR,
|
|
253
|
+
the GitHub release (MCPB bundle) and the MCP Registry.
|
|
254
|
+
|
|
255
|
+
Design notes, API facts and the roadmap are in [docs/DESIGN.md](docs/DESIGN.md).
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
[MIT](LICENSE)
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { homedir, tmpdir } from 'node:os';
|
|
3
|
+
import { delimiter, join } from 'node:path';
|
|
4
|
+
/** Public DEMO tenant published in the UOL OpenAPI spec. */
|
|
5
|
+
export const DEMO = {
|
|
6
|
+
baseUrl: 'https://test.demo.uol.cz/api',
|
|
7
|
+
email: 'demo@ucetnictvi-on-line.cz',
|
|
8
|
+
token: '3R1Cc_wzcSuq02Wsh1-uww',
|
|
9
|
+
};
|
|
10
|
+
export const TOOLSETS = ['core', 'insights', 'ledger', 'banking', 'warehouse'];
|
|
11
|
+
/** What most users need; the rest is opt-in to keep the tool list short for the model. */
|
|
12
|
+
export const DEFAULT_TOOLSETS = ['core', 'insights'];
|
|
13
|
+
/** `read` — no writes; `write` — create / update / upload; `full` — adds destructive tools. */
|
|
14
|
+
export const ACCESS_LEVELS = ['read', 'write', 'full'];
|
|
15
|
+
export function defaultUploadDirs() {
|
|
16
|
+
return ['Downloads', 'Desktop', 'Documents'].map((d) => join(homedir(), d));
|
|
17
|
+
}
|
|
18
|
+
/** `UOL_TOOLSETS=core,ledger` / `all`; unset means [DEFAULT_TOOLSETS]; unknown names are reported, not fatal. */
|
|
19
|
+
export function parseToolsets(raw) {
|
|
20
|
+
const names = (raw ?? '')
|
|
21
|
+
.split(',')
|
|
22
|
+
.map((s) => s.trim().toLowerCase())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
if (names.length === 0)
|
|
25
|
+
return { toolsets: new Set(DEFAULT_TOOLSETS), unknown: [] };
|
|
26
|
+
if (names.includes('all'))
|
|
27
|
+
return { toolsets: new Set(TOOLSETS), unknown: [] };
|
|
28
|
+
const known = names.filter((n) => TOOLSETS.includes(n));
|
|
29
|
+
return { toolsets: new Set(known), unknown: names.filter((n) => !known.includes(n)) };
|
|
30
|
+
}
|
|
31
|
+
export class ConfigError extends Error {
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Turns whatever the user typed as a tenant into the API root.
|
|
35
|
+
*
|
|
36
|
+
* Accepted forms:
|
|
37
|
+
* - `demo` → the public DEMO tenant
|
|
38
|
+
* - `firma` → `https://firma.ucetnictvi.uol.cz/api` (production is the common case)
|
|
39
|
+
* - `firma.ucetnictvi`, `firma.sandbox` → `https://<prefix>.uol.cz/api`
|
|
40
|
+
* - a full URL with or without `/api` or `/api/v1`
|
|
41
|
+
*
|
|
42
|
+
* Only `https://*.uol.cz` hosts are allowed, so a typo or a hostile config can't
|
|
43
|
+
* make the server send the user's Basic-auth credentials somewhere else.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveTenant(input, name = 'UOL_TENANT') {
|
|
46
|
+
const raw = input.trim();
|
|
47
|
+
if (raw === '')
|
|
48
|
+
throw new ConfigError(`${name} is empty`);
|
|
49
|
+
if (raw.toLowerCase() === 'demo')
|
|
50
|
+
return { baseUrl: DEMO.baseUrl, isDemo: true };
|
|
51
|
+
let host;
|
|
52
|
+
if (/^[a-z]+:\/\//i.test(raw)) {
|
|
53
|
+
let url;
|
|
54
|
+
try {
|
|
55
|
+
url = new URL(raw);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw new ConfigError(`${name} is not a valid URL: ${raw}`);
|
|
59
|
+
}
|
|
60
|
+
if (url.protocol !== 'https:')
|
|
61
|
+
throw new ConfigError(`${name} must use https: ${raw}`);
|
|
62
|
+
host = url.hostname.toLowerCase();
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const prefix = raw.toLowerCase().replace(/\.uol\.cz$/, '');
|
|
66
|
+
if (!/^[a-z0-9-]+(\.[a-z0-9-]+)*$/.test(prefix)) {
|
|
67
|
+
throw new ConfigError(`${name} has unexpected characters: ${raw}`);
|
|
68
|
+
}
|
|
69
|
+
host = prefix.includes('.') ? `${prefix}.uol.cz` : `${prefix}.ucetnictvi.uol.cz`;
|
|
70
|
+
}
|
|
71
|
+
if (!host.endsWith('.uol.cz')) {
|
|
72
|
+
throw new ConfigError(`${name} must point to a *.uol.cz host, got ${host}`);
|
|
73
|
+
}
|
|
74
|
+
const baseUrl = `https://${host}/api`;
|
|
75
|
+
return { baseUrl, isDemo: baseUrl === DEMO.baseUrl };
|
|
76
|
+
}
|
|
77
|
+
function defaultDownloadDir() {
|
|
78
|
+
const downloads = join(homedir(), 'Downloads');
|
|
79
|
+
return existsSync(downloads) ? join(downloads, 'uol-mcp') : join(tmpdir(), 'uol-mcp');
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Trimmed value, or undefined when blank. Hosts that template the env (MCPB's
|
|
83
|
+
* `${user_config.email}`) may pass the placeholder through when the user
|
|
84
|
+
* leaves an optional field empty, so an unexpanded placeholder counts as blank.
|
|
85
|
+
*/
|
|
86
|
+
function value(raw) {
|
|
87
|
+
const v = raw?.trim();
|
|
88
|
+
return v && !/^\$\{[^}]*\}$/.test(v) ? v : undefined;
|
|
89
|
+
}
|
|
90
|
+
export function loadConfig(env = process.env) {
|
|
91
|
+
const { toolsets, unknown } = parseToolsets(value(env.UOL_TOOLSETS));
|
|
92
|
+
const warnings = unknown.length > 0 ? [`ignoring unknown UOL_TOOLSETS entries: ${unknown.join(', ')} (known: ${TOOLSETS.join(', ')}, all)`] : [];
|
|
93
|
+
const rawAccess = value(env.UOL_ACCESS)?.toLowerCase() ?? 'read';
|
|
94
|
+
const access = ACCESS_LEVELS.includes(rawAccess) ? rawAccess : 'read';
|
|
95
|
+
if (access !== rawAccess)
|
|
96
|
+
warnings.push(`unknown UOL_ACCESS "${rawAccess}", using read (known: ${ACCESS_LEVELS.join(', ')})`);
|
|
97
|
+
const uploadDirs = value(env.UOL_UPLOAD_DIRS)
|
|
98
|
+
?.split(delimiter)
|
|
99
|
+
.map((d) => d.trim())
|
|
100
|
+
.filter(Boolean);
|
|
101
|
+
const config = {
|
|
102
|
+
toolsets,
|
|
103
|
+
access,
|
|
104
|
+
uploadDirs: uploadDirs && uploadDirs.length > 0 ? uploadDirs : defaultUploadDirs(),
|
|
105
|
+
auditLog: value(env.UOL_AUDIT_LOG),
|
|
106
|
+
warnings,
|
|
107
|
+
downloadDir: value(env.UOL_DOWNLOAD_DIR) ?? defaultDownloadDir(),
|
|
108
|
+
requestTimeoutMs: Number(env.UOL_TIMEOUT_MS) > 0 ? Number(env.UOL_TIMEOUT_MS) : 30_000,
|
|
109
|
+
debug: env.UOL_DEBUG === '1' || env.UOL_DEBUG === 'true',
|
|
110
|
+
};
|
|
111
|
+
const tenant = value(env.UOL_TENANT);
|
|
112
|
+
const email = value(env.UOL_EMAIL);
|
|
113
|
+
const token = value(env.UOL_TOKEN);
|
|
114
|
+
const profilesFile = value(env.UOL_PROFILES_FILE);
|
|
115
|
+
const accountList = value(env.UOL_ACCOUNTS);
|
|
116
|
+
try {
|
|
117
|
+
if (profilesFile || accountList) {
|
|
118
|
+
if (profilesFile && accountList)
|
|
119
|
+
throw new ConfigError('Set either UOL_ACCOUNTS or UOL_PROFILES_FILE, not both');
|
|
120
|
+
const source = accountList ? 'UOL_ACCOUNTS' : 'UOL_PROFILES_FILE';
|
|
121
|
+
if (tenant || email || token)
|
|
122
|
+
warnings.push(`${source} is set; ignoring UOL_TENANT / UOL_EMAIL / UOL_TOKEN`);
|
|
123
|
+
const profiles = accountList ? accountsFromEnv(accountList, env) : loadProfiles(profilesFile, env, warnings);
|
|
124
|
+
config.accounts = profiles.accounts;
|
|
125
|
+
config.defaultAccount = profiles.defaultAccount;
|
|
126
|
+
config.credentials = profiles.accounts.find((a) => a.name === profiles.defaultAccount).credentials;
|
|
127
|
+
return config;
|
|
128
|
+
}
|
|
129
|
+
if (!tenant && !email && !token)
|
|
130
|
+
return config;
|
|
131
|
+
config.credentials = credentialsFor(tenant, email, token, 'UOL_TENANT', 'UOL_EMAIL and UOL_TOKEN');
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (!(error instanceof ConfigError))
|
|
135
|
+
throw error;
|
|
136
|
+
config.configError = error.message;
|
|
137
|
+
}
|
|
138
|
+
return config;
|
|
139
|
+
}
|
|
140
|
+
function credentialsFor(tenant, email, token, tenantName, pairName) {
|
|
141
|
+
if (!tenant)
|
|
142
|
+
throw new ConfigError(`${tenantName} is missing`);
|
|
143
|
+
const { baseUrl, isDemo } = resolveTenant(tenant, tenantName);
|
|
144
|
+
if (isDemo && !email && !token)
|
|
145
|
+
return { baseUrl, email: DEMO.email, token: DEMO.token, isDemo };
|
|
146
|
+
if (!email || !token)
|
|
147
|
+
throw new ConfigError(`${pairName} must both be set`);
|
|
148
|
+
return { baseUrl, email, token, isDemo };
|
|
149
|
+
}
|
|
150
|
+
const ACCOUNT_NAME = /^[a-z0-9][a-z0-9_-]{0,39}$/;
|
|
151
|
+
/**
|
|
152
|
+
* Several tenants straight from the environment — for hosts whose config is
|
|
153
|
+
* an env block (Claude Desktop), and so `op run` can resolve each token from
|
|
154
|
+
* an `op://` reference:
|
|
155
|
+
*
|
|
156
|
+
* ```
|
|
157
|
+
* UOL_ACCOUNTS=firma,novak (first one is the default, or UOL_DEFAULT_ACCOUNT)
|
|
158
|
+
* UOL_ACCOUNT_FIRMA_TENANT=firma
|
|
159
|
+
* UOL_ACCOUNT_FIRMA_EMAIL=ja@firma.cz
|
|
160
|
+
* UOL_ACCOUNT_FIRMA_TOKEN=op://…
|
|
161
|
+
* UOL_ACCOUNT_FIRMA_LABEL=Firma s.r.o. (optional)
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* The variable prefix is the account name upper-cased, with `-` as `_`.
|
|
165
|
+
*/
|
|
166
|
+
export function accountsFromEnv(list, env) {
|
|
167
|
+
const names = list
|
|
168
|
+
.split(',')
|
|
169
|
+
.map((n) => n.trim().toLowerCase())
|
|
170
|
+
.filter(Boolean);
|
|
171
|
+
if (names.length === 0)
|
|
172
|
+
throw new ConfigError('UOL_ACCOUNTS lists no account');
|
|
173
|
+
const accounts = names.map((name) => {
|
|
174
|
+
if (!ACCOUNT_NAME.test(name)) {
|
|
175
|
+
throw new ConfigError(`UOL_ACCOUNTS: account name "${name}" must be lowercase letters, digits, - or _`);
|
|
176
|
+
}
|
|
177
|
+
const prefix = `UOL_ACCOUNT_${name.toUpperCase().replace(/-/g, '_')}_`;
|
|
178
|
+
const credentials = credentialsFor(value(env[`${prefix}TENANT`]), value(env[`${prefix}EMAIL`]), value(env[`${prefix}TOKEN`]), `${prefix}TENANT`, `${prefix}EMAIL and ${prefix}TOKEN`);
|
|
179
|
+
return { name, label: value(env[`${prefix}LABEL`]), credentials };
|
|
180
|
+
});
|
|
181
|
+
const defaultAccount = value(env.UOL_DEFAULT_ACCOUNT)?.toLowerCase() ?? names[0];
|
|
182
|
+
if (!names.includes(defaultAccount)) {
|
|
183
|
+
throw new ConfigError(`UOL_DEFAULT_ACCOUNT "${defaultAccount}" is not listed in UOL_ACCOUNTS`);
|
|
184
|
+
}
|
|
185
|
+
return { accounts, defaultAccount };
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Reads UOL_PROFILES_FILE — several tenants for accountants:
|
|
189
|
+
*
|
|
190
|
+
* ```json
|
|
191
|
+
* { "default": "firma",
|
|
192
|
+
* "accounts": {
|
|
193
|
+
* "firma": { "tenant": "firma", "email": "ja@firma.cz", "token_env": "UOL_TOKEN_FIRMA" },
|
|
194
|
+
* "novak": { "tenant": "novak", "email": "ja@firma.cz", "token": "…", "label": "Novák s.r.o." },
|
|
195
|
+
* "demo": { "tenant": "demo" } } }
|
|
196
|
+
* ```
|
|
197
|
+
*
|
|
198
|
+
* `token_env` names an environment variable, so tokens don't have to live in
|
|
199
|
+
* the file. A file with inline tokens that others can read is reported.
|
|
200
|
+
*/
|
|
201
|
+
export function loadProfiles(path, env, warnings) {
|
|
202
|
+
let parsed;
|
|
203
|
+
try {
|
|
204
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
throw new ConfigError(`UOL_PROFILES_FILE ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
208
|
+
}
|
|
209
|
+
const root = (parsed ?? {});
|
|
210
|
+
const entries = Object.entries(root.accounts ?? {});
|
|
211
|
+
if (entries.length === 0)
|
|
212
|
+
throw new ConfigError(`UOL_PROFILES_FILE ${path} has no "accounts"`);
|
|
213
|
+
let inlineTokens = false;
|
|
214
|
+
const accounts = entries.map(([name, raw]) => {
|
|
215
|
+
if (!ACCOUNT_NAME.test(name)) {
|
|
216
|
+
throw new ConfigError(`UOL_PROFILES_FILE: account name "${name}" must be lowercase letters, digits, - or _`);
|
|
217
|
+
}
|
|
218
|
+
const text = (key) => (typeof raw[key] === 'string' ? value(raw[key]) : undefined);
|
|
219
|
+
const tokenEnv = text('token_env');
|
|
220
|
+
if (text('token'))
|
|
221
|
+
inlineTokens = true;
|
|
222
|
+
const token = text('token') ?? (tokenEnv ? value(env[tokenEnv]) : undefined);
|
|
223
|
+
if (tokenEnv && !token)
|
|
224
|
+
throw new ConfigError(`UOL_PROFILES_FILE: account "${name}" reads its token from ${tokenEnv}, which is not set`);
|
|
225
|
+
const credentials = credentialsFor(text('tenant'), text('email'), token, `account "${name}": tenant`, `account "${name}": email and token`);
|
|
226
|
+
return { name, label: text('label'), credentials };
|
|
227
|
+
});
|
|
228
|
+
const defaultAccount = typeof root.default === 'string' ? root.default : accounts[0].name;
|
|
229
|
+
if (!accounts.some((a) => a.name === defaultAccount)) {
|
|
230
|
+
throw new ConfigError(`UOL_PROFILES_FILE: default account "${defaultAccount}" is not among the accounts`);
|
|
231
|
+
}
|
|
232
|
+
if (inlineTokens && process.platform !== 'win32' && (statSync(path).mode & 0o077) !== 0) {
|
|
233
|
+
warnings.push(`${path} contains API tokens and is readable by other users; run chmod 600 on it or use token_env`);
|
|
234
|
+
}
|
|
235
|
+
return { accounts, defaultAccount };
|
|
236
|
+
}
|
|
237
|
+
export const SETUP_HINT = 'Set UOL_TENANT (e.g. "firma" for https://firma.ucetnictvi.uol.cz, or "demo"), ' +
|
|
238
|
+
'UOL_EMAIL and UOL_TOKEN, or UOL_ACCOUNTS with UOL_ACCOUNT_<NAME>_TENANT / _EMAIL / _TOKEN ' +
|
|
239
|
+
'for several companies (create the token on your tenant at /api_tokens). ' +
|
|
240
|
+
'The UOL user needs the "REST API" permission.';
|
|
241
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|