@codemill-solutions/yuki-mcp 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 CodeMill Solutions B.V.
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.md ADDED
@@ -0,0 +1,240 @@
1
+ # yuki-mcp
2
+
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that connects AI agents to [Yuki](https://www.yukiworks.nl) accounting via Yuki's SOAP API.
4
+
5
+ Built with Node.js, TypeScript, and [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk).
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @codemill-solutions/yuki-mcp
13
+ ```
14
+
15
+ Then add it to your MCP host configuration (e.g. `claude_desktop_config.json`):
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "yuki": {
21
+ "command": "node",
22
+ "args": ["node_modules/@codemill-solutions/yuki-mcp/dist/index.js"],
23
+ "env": {
24
+ "YUKI_API_KEY": "your-api-key-here",
25
+ "YUKI_DOMAIN_ID": "your-administration-guid-here"
26
+ }
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Prerequisites
35
+
36
+ - Node.js 20+
37
+ - A Yuki account with API access enabled
38
+ - Your Yuki API key (Yuki → Settings → API)
39
+
40
+ ---
41
+
42
+ ## Setup
43
+
44
+ ### 1. Install dependencies
45
+
46
+ ```bash
47
+ npm install
48
+ ```
49
+
50
+ ### 2. Configure environment variables
51
+
52
+ ```bash
53
+ cp .env.example .env
54
+ ```
55
+
56
+ Edit `.env`:
57
+
58
+ ```env
59
+ YUKI_API_KEY=your-api-key-here
60
+ YUKI_DOMAIN_ID=your-administration-guid-here # optional at startup
61
+ ```
62
+
63
+ `YUKI_DOMAIN_ID` can be left empty — the server starts without it. Call `get_administrations` to discover the correct GUID, then pass it via the `administrationId` parameter on individual tools.
64
+
65
+ ### 3. Build
66
+
67
+ ```bash
68
+ npm run build
69
+ ```
70
+
71
+ ### 4. Connect to an MCP host
72
+
73
+ Add to your MCP host configuration (e.g. `claude_desktop_config.json`):
74
+
75
+ ```json
76
+ {
77
+ "mcpServers": {
78
+ "yuki": {
79
+ "command": "node",
80
+ "args": ["/absolute/path/to/yuki-mcp/dist/index.js"],
81
+ "env": {
82
+ "YUKI_API_KEY": "your-api-key-here",
83
+ "YUKI_DOMAIN_ID": "your-administration-guid-here"
84
+ }
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Available tools
93
+
94
+ ### Administrations
95
+
96
+ | Tool | Description |
97
+ |------|-------------|
98
+ | `get_administrations` | List all administrations (companies) for this API key. **Run this first** to find the correct `administrationId`. |
99
+
100
+ ### Relations
101
+
102
+ | Tool | Key parameters | Description |
103
+ |------|----------------|-------------|
104
+ | `search_relations` | `searchValue`, `searchOption?`, `active?`, `pageNumber?` | Search customers and suppliers by name, code, VAT number, email, etc. Returns up to 100 results per page. |
105
+ | `upsert_contact` | `fullName`, `contactCode?`, `contactType?`, … | Create or update a contact. When `contactCode` matches an existing record it is updated; otherwise a new contact is created. |
106
+
107
+ ### Sales invoices
108
+
109
+ | Tool | Key parameters | Description |
110
+ |------|----------------|-------------|
111
+ | `get_sales_invoices` | `dateOutstanding?`, `sortOrder?`, `includeBankTransactions?` | Retrieve outstanding (unpaid) sales invoices. |
112
+ | `process_sales_invoice` | `reference`, `subject`, `date`, `dueDate`, `contact`, `lines` | Create and book a new sales invoice. Optionally email it to the customer. |
113
+
114
+ ### Purchase invoices
115
+
116
+ | Tool | Key parameters | Description |
117
+ |------|----------------|-------------|
118
+ | `get_purchase_invoices` | `dateOutstanding?`, `sortOrder?`, `includeBankTransactions?` | Retrieve outstanding (unpaid) purchase invoices. |
119
+ | `process_purchase_invoice` | `date`, `invoiceAmount`, `invoiceVatAmount`, `contact`, `lines` | Book an incoming purchase invoice. Accepts an optional PDF as base64. |
120
+
121
+ ### Transactions & bank
122
+
123
+ | Tool | Key parameters | Description |
124
+ |------|----------------|-------------|
125
+ | `get_transactions` | `glAccountCode`, `startDate`, `endDate` | Retrieve journal entries for a GL account (e.g. a bank account) in a date range. Use `get_gl_accounts` to find the right code. |
126
+ | `get_transaction_details` | `reference` | Check if an outstanding item still exists and retrieve its current status. |
127
+ | `process_journal` | `subject`, `entries[]` | Post a general journal entry (memoriaal). All entry amounts must sum to exactly 0. Used for bank reconciliation, corrections, and custom bookings. |
128
+
129
+ ### Accounting
130
+
131
+ | Tool | Key parameters | Description |
132
+ |------|----------------|-------------|
133
+ | `get_gl_accounts` | `date?` | Retrieve all GL accounts with their balance at a given date. Use this to find bank account codes before calling `get_transactions`. |
134
+
135
+ ### Documents
136
+
137
+ | Tool | Key parameters | Description |
138
+ |------|----------------|-------------|
139
+ | `upload_document` | `fileName`, `dataBase64`, `folder?`, `amount?` | Upload a PDF to the Yuki archive by passing its content as a base64 string. Use `get_document_folders` first to find the right folder ID. |
140
+ | `upload_document_from_path` | `filePath`, `fileName?`, `folder?`, `amount?` | Upload a PDF from a local file path. Reads and encodes the file internally — preferred over `upload_document` when the file is available on disk. Validates that the file exists and is a valid PDF before uploading. |
141
+ | `get_document_folders` | — | List all archive folders available in the administration. |
142
+
143
+ ---
144
+
145
+ ## Testing
146
+
147
+ ### Option 1 — MCP Inspector (tool-level, no LLM)
148
+
149
+ ```bash
150
+ npm run inspect
151
+ ```
152
+
153
+ Opens a browser UI at `http://localhost:5173` where you can call individual tools and inspect raw responses.
154
+
155
+ ### Option 2 — Agent test harness (with Claude)
156
+
157
+ Runs a full agentic loop: Claude reasons about the task, calls tools, and returns a final answer — exactly as an AI agent would use this MCP.
158
+
159
+ Add your Anthropic API key to `.env`:
160
+
161
+ ```env
162
+ ANTHROPIC_API_KEY=sk-ant-...
163
+ ```
164
+
165
+ Then run a scenario:
166
+
167
+ ```bash
168
+ npm run agent # default: get_administrations
169
+ npm run agent -- --scenario outstanding-invoices
170
+ npm run agent -- --scenario search-relations --arg "Bedrijf BV"
171
+ npm run agent -- --scenario gl-accounts
172
+ npm run agent -- --scenario bank-transactions --arg "1200"
173
+ npm run agent -- --scenario full-workflow
174
+ ```
175
+
176
+ Available scenarios: `get-administrations`, `search-relations`, `outstanding-invoices`, `outstanding-payables`, `gl-accounts`, `bank-transactions`, `full-workflow`.
177
+
178
+ ---
179
+
180
+ ## Architecture
181
+
182
+ ```
183
+ src/
184
+ ├── index.ts # Entry point — loads env, registers tools, starts stdio transport
185
+ ├── yuki-client.ts # SOAP client: envelope builder, axios HTTP, fast-xml-parser, XmlValue
186
+ └── tools/
187
+ ├── administrations.ts # get_administrations
188
+ ├── relations.ts # search_relations, upsert_contact
189
+ ├── invoices.ts # get_sales_invoices, get_purchase_invoices,
190
+ │ # process_sales_invoice, process_purchase_invoice
191
+ ├── transactions.ts # get_transactions, get_transaction_details, process_journal
192
+ ├── accounting.ts # get_gl_accounts
193
+ └── documents.ts # upload_document, upload_document_from_path, get_document_folders
194
+
195
+ scripts/
196
+ └── test-agent.ts # Agent test harness (Claude + MCP client loop)
197
+ ```
198
+
199
+ ### Auth flow
200
+
201
+ Yuki uses a two-step authentication pattern:
202
+
203
+ 1. `Authenticate(accessKey)` → returns a temporary `sessionID`
204
+ 2. All subsequent calls include that `sessionID`
205
+
206
+ `YukiClient.getSessionID()` handles this transparently and caches the session for the lifetime of the process.
207
+
208
+ > **Note:** Parameter casing differs across Yuki's services — `sessionId` (lowercase d) on `Sales.asmx` and `Purchase.asmx`; `sessionID` (uppercase D) on `Accounting.asmx`, `Contact.asmx`, and `Archive.asmx`. This is handled per-tool.
209
+
210
+ ### XML documents
211
+
212
+ Write tools (`process_sales_invoice`, `process_purchase_invoice`, `process_journal`, `upsert_contact`) pass structured data to Yuki as an XML string inside the `xmlDoc` SOAP parameter. The `XmlValue` wrapper ensures this XML is embedded raw (not entity-encoded) in the SOAP envelope. All user-supplied values are XML-escaped via `escapeXml()`.
213
+
214
+ ---
215
+
216
+ ## Rate limits
217
+
218
+ Yuki enforces **1,000 API requests per day**. Each tool call is 1 request. The session ID is cached so `Authenticate` is only called once per server process, not once per tool call.
219
+
220
+ Design agent workflows to fetch broad lists once and reference them from the agent's context window rather than re-fetching on every step.
221
+
222
+ ---
223
+
224
+ ## Troubleshooting
225
+
226
+ | Error | Likely cause |
227
+ |-------|-------------|
228
+ | `SOAP Fault: Authentication failed` | `YUKI_API_KEY` is incorrect or API access is not enabled in Yuki Settings |
229
+ | `SOAP Fault: Administration not found` | Wrong `administrationId` — run `get_administrations` to get the correct GUID |
230
+ | `Journal entries do not balance` | Amounts in `process_journal` don't sum to 0 — check debit/credit signs |
231
+ | `HTTP 500 from api.yukiworks.nl` | Usually a wrong XML namespace or malformed `xmlDoc` — check the WSDL at `https://api.yukiworks.nl/ws/{Service}.asmx?wsdl` |
232
+ | `File does not appear to be a PDF` | The file at `filePath` does not start with the `%PDF` magic bytes — check you're pointing at a valid PDF |
233
+ | `File not found` | `filePath` passed to `upload_document_from_path` does not exist or is inaccessible |
234
+ | `Network error` | No connectivity to `api.yukiworks.nl` — requests time out after 30 seconds |
235
+
236
+ ---
237
+
238
+ ## License
239
+
240
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,2 @@
1
+ import "dotenv/config";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,49 @@
1
+ import "dotenv/config";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { YukiClient } from "./yuki-client.js";
5
+ // Read tools
6
+ import { registerAdministrationTools } from "./tools/administrations.js";
7
+ import { registerRelationTools } from "./tools/relations.js";
8
+ import { registerInvoiceTools } from "./tools/invoices.js";
9
+ import { registerTransactionTools } from "./tools/transactions.js";
10
+ import { registerAccountingTools } from "./tools/accounting.js";
11
+ // Write tools
12
+ import { registerInvoiceWriteTools } from "./tools/invoices.js";
13
+ import { registerJournalWriteTools } from "./tools/transactions.js";
14
+ import { registerContactWriteTools } from "./tools/relations.js";
15
+ import { registerDocumentTools } from "./tools/documents.js";
16
+ // ── Environment validation ────────────────────────────────────────────────────
17
+ const apiKey = process.env["YUKI_API_KEY"];
18
+ const domainId = process.env["YUKI_DOMAIN_ID"] ?? "";
19
+ if (!apiKey) {
20
+ // Write to stderr so the MCP host can surface the message without corrupting
21
+ // the stdio JSON-RPC stream used by the MCP protocol.
22
+ process.stderr.write("[yuki-mcp] Fatal: YUKI_API_KEY environment variable is not set.\n");
23
+ process.exit(1);
24
+ }
25
+ // ── Yuki SOAP client ──────────────────────────────────────────────────────────
26
+ const yukiClient = new YukiClient(apiKey, domainId);
27
+ // ── MCP server ────────────────────────────────────────────────────────────────
28
+ const server = new McpServer({
29
+ name: "yuki-mcp",
30
+ version: "1.0.0",
31
+ });
32
+ // ── Read tools ────────────────────────────────────────────────────────────────
33
+ registerAdministrationTools(server, yukiClient);
34
+ registerRelationTools(server, yukiClient);
35
+ registerInvoiceTools(server, yukiClient);
36
+ registerTransactionTools(server, yukiClient);
37
+ registerAccountingTools(server, yukiClient);
38
+ // ── Write tools ───────────────────────────────────────────────────────────────
39
+ registerInvoiceWriteTools(server, yukiClient);
40
+ registerJournalWriteTools(server, yukiClient);
41
+ registerContactWriteTools(server, yukiClient);
42
+ registerDocumentTools(server, yukiClient);
43
+ // ── Start ─────────────────────────────────────────────────────────────────────
44
+ const transport = new StdioServerTransport();
45
+ await server.connect(transport);
46
+ // Log startup info to stderr only (stdout is reserved for JSON-RPC)
47
+ process.stderr.write(`[yuki-mcp] Server started — 14 tools registered. ` +
48
+ `Domain ID: ${domainId || "(none — run get_administrations to discover)"}\n`);
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,aAAa;AACb,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,cAAc;AACd,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,iFAAiF;AAEjF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;AAErD,IAAI,CAAC,MAAM,EAAE,CAAC;IACZ,6EAA6E;IAC7E,sDAAsD;IACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mEAAmE,CACpE,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,iFAAiF;AAEjF,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAEpD,iFAAiF;AAEjF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,iFAAiF;AACjF,2BAA2B,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAChD,qBAAqB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAC1C,oBAAoB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AACzC,wBAAwB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAC7C,uBAAuB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAE5C,iFAAiF;AACjF,yBAAyB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAC9C,yBAAyB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAC9C,yBAAyB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAC9C,qBAAqB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAE1C,iFAAiF;AAEjF,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAE7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAEhC,oEAAoE;AACpE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mDAAmD;IACjD,cAAc,QAAQ,IAAI,8CAA8C,IAAI,CAC/E,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { YukiClient } from "../yuki-client.js";
3
+ /**
4
+ * Register tools for working with the Yuki chart of accounts.
5
+ *
6
+ * GLAccountBalance returns all GL accounts with their balance at a given date.
7
+ * This is the closest the Yuki SOAP API comes to a "list all GL accounts" call.
8
+ *
9
+ * Yuki service: Accounting.asmx
10
+ * Method: GLAccountBalance(sessionID, administrationID, transactionDate)
11
+ */
12
+ export declare function registerAccountingTools(server: McpServer, client: YukiClient): void;
13
+ //# sourceMappingURL=accounting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounting.d.ts","sourceRoot":"","sources":["../../src/tools/accounting.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,UAAU,GACjB,IAAI,CA2FN"}
@@ -0,0 +1,117 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Register tools for working with the Yuki chart of accounts.
4
+ *
5
+ * GLAccountBalance returns all GL accounts with their balance at a given date.
6
+ * This is the closest the Yuki SOAP API comes to a "list all GL accounts" call.
7
+ *
8
+ * Yuki service: Accounting.asmx
9
+ * Method: GLAccountBalance(sessionID, administrationID, transactionDate)
10
+ */
11
+ export function registerAccountingTools(server, client) {
12
+ /**
13
+ * get_gl_accounts
14
+ *
15
+ * Retrieve all GL accounts (grootboekrekeningen) with their balance at a
16
+ * given date. Returns account code, name, and debit/credit balance.
17
+ *
18
+ * Use this to:
19
+ * - Find the GL account code for a bank account before calling get_transactions
20
+ * - Understand the account structure before booking a journal entry
21
+ * - Get a financial snapshot (balance sheet / P&L) at a specific date
22
+ *
23
+ * Rate cost: 1 request.
24
+ */
25
+ server.registerTool("get_gl_accounts", {
26
+ description: "Retrieve all GL accounts (grootboekrekeningen) with their balance at a given date. " +
27
+ "Use this to find account codes (e.g. bank account codes) or get a financial snapshot. " +
28
+ "Defaults to today's date if no date is provided.",
29
+ inputSchema: {
30
+ date: z
31
+ .string()
32
+ .optional()
33
+ .describe("Date for the balance snapshot in YYYY-MM-DD format. Defaults to today."),
34
+ administrationId: z
35
+ .string()
36
+ .optional()
37
+ .describe("Administration ID (GUID). Defaults to YUKI_DOMAIN_ID env var."),
38
+ },
39
+ }, async ({ date, administrationId }) => {
40
+ try {
41
+ const adminId = administrationId ?? client.defaultDomainId;
42
+ if (!adminId) {
43
+ throw new Error("administrationId is required (or set YUKI_DOMAIN_ID env var)");
44
+ }
45
+ const sessionID = await client.getSessionID();
46
+ // Default to today's date in ISO format when not specified
47
+ const transactionDate = date ?? new Date().toISOString().split("T")[0];
48
+ const result = await client.callSoap({
49
+ service: "Accounting.asmx",
50
+ method: "GLAccountBalance",
51
+ params: {
52
+ sessionID,
53
+ administrationID: adminId,
54
+ transactionDate,
55
+ },
56
+ });
57
+ const accounts = normalizeGLAccounts(result);
58
+ return {
59
+ content: [
60
+ {
61
+ type: "text",
62
+ text: JSON.stringify({
63
+ success: true,
64
+ count: accounts.length,
65
+ balanceDate: transactionDate,
66
+ accounts,
67
+ }, null, 2),
68
+ },
69
+ ],
70
+ };
71
+ }
72
+ catch (err) {
73
+ const message = err instanceof Error ? err.message : String(err);
74
+ return {
75
+ content: [
76
+ {
77
+ type: "text",
78
+ text: JSON.stringify({ success: false, error: message }, null, 2),
79
+ },
80
+ ],
81
+ isError: true,
82
+ };
83
+ }
84
+ });
85
+ }
86
+ /** Unwrap the parsed SOAP GL account balance result into a flat array. */
87
+ function normalizeGLAccounts(result) {
88
+ if (!result)
89
+ return [];
90
+ if (Array.isArray(result))
91
+ return result;
92
+ const rec = result;
93
+ const wrappers = ["GLAccounts", "Accounts", "Rows"];
94
+ const itemTags = ["GLAccount", "Account", "Row"];
95
+ for (const wrapper of wrappers) {
96
+ const c = rec[wrapper];
97
+ if (!c)
98
+ continue;
99
+ if (Array.isArray(c))
100
+ return c;
101
+ const inner = c;
102
+ for (const tag of itemTags) {
103
+ if (Array.isArray(inner[tag]))
104
+ return inner[tag];
105
+ if (inner[tag])
106
+ return [inner[tag]];
107
+ }
108
+ }
109
+ for (const tag of itemTags) {
110
+ if (Array.isArray(rec[tag]))
111
+ return rec[tag];
112
+ if (rec[tag])
113
+ return [rec[tag]];
114
+ }
115
+ return [result];
116
+ }
117
+ //# sourceMappingURL=accounting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounting.js","sourceRoot":"","sources":["../../src/tools/accounting.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { YukiClient } from "../yuki-client.js";
3
+ /**
4
+ * Register tools related to Yuki administrations.
5
+ *
6
+ * A Yuki account can contain multiple administrations (companies/entities).
7
+ * Use get_administrations to discover the correct administrationID / domainID
8
+ * to pass to all other tools.
9
+ *
10
+ * Yuki service: Accounting.asmx
11
+ * Auth flow: Authenticate(accessKey) → sessionID → Administrations(sessionID)
12
+ */
13
+ export declare function registerAdministrationTools(server: McpServer, client: YukiClient): void;
14
+ //# sourceMappingURL=administrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"administrations.d.ts","sourceRoot":"","sources":["../../src/tools/administrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,UAAU,GACjB,IAAI,CAyDN"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Register tools related to Yuki administrations.
3
+ *
4
+ * A Yuki account can contain multiple administrations (companies/entities).
5
+ * Use get_administrations to discover the correct administrationID / domainID
6
+ * to pass to all other tools.
7
+ *
8
+ * Yuki service: Accounting.asmx
9
+ * Auth flow: Authenticate(accessKey) → sessionID → Administrations(sessionID)
10
+ */
11
+ export function registerAdministrationTools(server, client) {
12
+ /**
13
+ * get_administrations
14
+ *
15
+ * List all administrations accessible with the current API key.
16
+ * Always run this first to find the correct administrationID (GUID).
17
+ *
18
+ * Rate cost: 2 requests (1× Authenticate + 1× Administrations).
19
+ * On subsequent calls within the same session, Authenticate is skipped: 1 request.
20
+ */
21
+ server.registerTool("get_administrations", {
22
+ description: "List all Yuki administrations (companies) accessible with the configured API key. " +
23
+ "Run this first to discover the correct administrationID to pass to other tools.",
24
+ }, async () => {
25
+ try {
26
+ // Step 1 — Authenticate and obtain (cached) sessionID
27
+ const sessionID = await client.getSessionID();
28
+ // Step 2 — Fetch administrations for this session
29
+ const result = await client.callSoap({
30
+ service: "Accounting.asmx",
31
+ method: "Administrations",
32
+ params: { sessionID },
33
+ });
34
+ const administrations = normalizeAdministrations(result);
35
+ return {
36
+ content: [
37
+ {
38
+ type: "text",
39
+ text: JSON.stringify({ success: true, count: administrations.length, administrations }, null, 2),
40
+ },
41
+ ],
42
+ };
43
+ }
44
+ catch (err) {
45
+ const message = err instanceof Error ? err.message : String(err);
46
+ return {
47
+ content: [
48
+ {
49
+ type: "text",
50
+ text: JSON.stringify({ success: false, error: message }, null, 2),
51
+ },
52
+ ],
53
+ isError: true,
54
+ };
55
+ }
56
+ });
57
+ }
58
+ /** Normalise the parsed SOAP result into a flat array of administration objects. */
59
+ function normalizeAdministrations(result) {
60
+ if (!result)
61
+ return [];
62
+ if (Array.isArray(result))
63
+ return result;
64
+ const rec = result;
65
+ // The Administrations response wraps items in <Administrations><Administration>
66
+ const containers = [rec["Administrations"], rec["administrations"], result];
67
+ for (const c of containers) {
68
+ if (!c)
69
+ continue;
70
+ if (Array.isArray(c))
71
+ return c;
72
+ const inner = c["Administration"];
73
+ if (Array.isArray(inner))
74
+ return inner;
75
+ if (inner)
76
+ return [inner];
77
+ }
78
+ return [result];
79
+ }
80
+ //# sourceMappingURL=administrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"administrations.js","sourceRoot":"","sources":["../../src/tools/administrations.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,MAAM,UAAU,2BAA2B,CACzC,MAAiB,EACjB,MAAkB;IAElB;;;;;;;;OAQG;IACH,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,WAAW,EACT,oFAAoF;YACpF,iFAAiF;KACpF,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,sDAAsD;YACtD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,YAAY,EAAE,CAAC;YAE9C,kDAAkD;YAClD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC;gBACnC,OAAO,EAAE,iBAAiB;gBAC1B,MAAM,EAAE,iBAAiB;gBACzB,MAAM,EAAE,EAAE,SAAS,EAAE;aACtB,CAAC,CAAC;YAEH,MAAM,eAAe,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;YAEzD,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAClB,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,CAAC,MAAM,EAAE,eAAe,EAAE,EACjE,IAAI,EACJ,CAAC,CACF;qBACF;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;qBAClE;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,SAAS,wBAAwB,CAAC,MAAe;IAC/C,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAEzC,MAAM,GAAG,GAAG,MAAiC,CAAC;IAE9C,gFAAgF;IAChF,MAAM,UAAU,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,GAAG,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5E,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;QAC/B,MAAM,KAAK,GAAI,CAA6B,CAAC,gBAAgB,CAAC,CAAC;QAC/D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACvC,IAAI,KAAK;YAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,CAAC;AAClB,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { YukiClient } from "../yuki-client.js";
3
+ /**
4
+ * Register tools for uploading documents to the Yuki archive.
5
+ *
6
+ * The Yuki Archive service stores source documents (PDFs) alongside
7
+ * their financial data. Attaching a PDF to a purchase invoice is
8
+ * strongly recommended for audit compliance.
9
+ *
10
+ * Yuki service: Archive.asmx
11
+ * Method: UploadDocumentWithData(sessionID, fileName, data, folder,
12
+ * administrationID, currency, amount,
13
+ * costCategory, paymentMethod, project, remarks)
14
+ *
15
+ * Note: Archive.asmx uses sessionID / administrationID (uppercase D).
16
+ */
17
+ export declare function registerDocumentTools(server: McpServer, client: YukiClient): void;
18
+ //# sourceMappingURL=documents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/tools/documents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,UAAU,GACjB,IAAI,CAySN"}