@centry-digital/bukku-mcp 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +269 -0
  3. package/build/client/bukku-client.d.ts +62 -0
  4. package/build/client/bukku-client.js +195 -0
  5. package/build/config/env.d.ts +19 -0
  6. package/build/config/env.js +36 -0
  7. package/build/errors/transform.d.ts +14 -0
  8. package/build/errors/transform.js +141 -0
  9. package/build/errors/transform.test.d.ts +1 -0
  10. package/build/errors/transform.test.js +101 -0
  11. package/build/index.d.ts +13 -0
  12. package/build/index.js +52 -0
  13. package/build/tools/cache/reference-cache.d.ts +42 -0
  14. package/build/tools/cache/reference-cache.js +63 -0
  15. package/build/tools/configs/account.d.ts +17 -0
  16. package/build/tools/configs/account.js +28 -0
  17. package/build/tools/configs/bank-money-in.d.ts +10 -0
  18. package/build/tools/configs/bank-money-in.js +22 -0
  19. package/build/tools/configs/bank-money-out.d.ts +10 -0
  20. package/build/tools/configs/bank-money-out.js +22 -0
  21. package/build/tools/configs/bank-transfer.d.ts +11 -0
  22. package/build/tools/configs/bank-transfer.js +23 -0
  23. package/build/tools/configs/contact-group.d.ts +11 -0
  24. package/build/tools/configs/contact-group.js +19 -0
  25. package/build/tools/configs/contact.d.ts +14 -0
  26. package/build/tools/configs/contact.js +25 -0
  27. package/build/tools/configs/delivery-order.d.ts +8 -0
  28. package/build/tools/configs/delivery-order.js +20 -0
  29. package/build/tools/configs/file.d.ts +18 -0
  30. package/build/tools/configs/file.js +26 -0
  31. package/build/tools/configs/goods-received-note.d.ts +8 -0
  32. package/build/tools/configs/goods-received-note.js +20 -0
  33. package/build/tools/configs/journal-entry.d.ts +14 -0
  34. package/build/tools/configs/journal-entry.js +26 -0
  35. package/build/tools/configs/location.d.ts +20 -0
  36. package/build/tools/configs/location.js +28 -0
  37. package/build/tools/configs/product-bundle.d.ts +18 -0
  38. package/build/tools/configs/product-bundle.js +29 -0
  39. package/build/tools/configs/product-group.d.ts +14 -0
  40. package/build/tools/configs/product-group.js +22 -0
  41. package/build/tools/configs/product.d.ts +24 -0
  42. package/build/tools/configs/product.js +35 -0
  43. package/build/tools/configs/purchase-bill.d.ts +9 -0
  44. package/build/tools/configs/purchase-bill.js +21 -0
  45. package/build/tools/configs/purchase-credit-note.d.ts +8 -0
  46. package/build/tools/configs/purchase-credit-note.js +20 -0
  47. package/build/tools/configs/purchase-order.d.ts +8 -0
  48. package/build/tools/configs/purchase-order.js +20 -0
  49. package/build/tools/configs/purchase-payment.d.ts +8 -0
  50. package/build/tools/configs/purchase-payment.js +20 -0
  51. package/build/tools/configs/purchase-refund.d.ts +8 -0
  52. package/build/tools/configs/purchase-refund.js +20 -0
  53. package/build/tools/configs/sales-credit-note.d.ts +8 -0
  54. package/build/tools/configs/sales-credit-note.js +20 -0
  55. package/build/tools/configs/sales-invoice.d.ts +8 -0
  56. package/build/tools/configs/sales-invoice.js +20 -0
  57. package/build/tools/configs/sales-order.d.ts +8 -0
  58. package/build/tools/configs/sales-order.js +20 -0
  59. package/build/tools/configs/sales-payment.d.ts +8 -0
  60. package/build/tools/configs/sales-payment.js +20 -0
  61. package/build/tools/configs/sales-quote.d.ts +8 -0
  62. package/build/tools/configs/sales-quote.js +20 -0
  63. package/build/tools/configs/sales-refund.d.ts +8 -0
  64. package/build/tools/configs/sales-refund.js +20 -0
  65. package/build/tools/configs/tag-group.d.ts +11 -0
  66. package/build/tools/configs/tag-group.js +22 -0
  67. package/build/tools/configs/tag.d.ts +11 -0
  68. package/build/tools/configs/tag.js +22 -0
  69. package/build/tools/custom/account-tools.d.ts +21 -0
  70. package/build/tools/custom/account-tools.js +119 -0
  71. package/build/tools/custom/contact-archive.d.ts +17 -0
  72. package/build/tools/custom/contact-archive.js +72 -0
  73. package/build/tools/custom/control-panel-archive.d.ts +17 -0
  74. package/build/tools/custom/control-panel-archive.js +72 -0
  75. package/build/tools/custom/file-upload.d.ts +17 -0
  76. package/build/tools/custom/file-upload.js +43 -0
  77. package/build/tools/custom/journal-entry-tools.d.ts +20 -0
  78. package/build/tools/custom/journal-entry-tools.js +109 -0
  79. package/build/tools/custom/location-tools.d.ts +20 -0
  80. package/build/tools/custom/location-tools.js +96 -0
  81. package/build/tools/custom/product-archive.d.ts +17 -0
  82. package/build/tools/custom/product-archive.js +124 -0
  83. package/build/tools/custom/reference-data.d.ts +21 -0
  84. package/build/tools/custom/reference-data.js +108 -0
  85. package/build/tools/factory.d.ts +26 -0
  86. package/build/tools/factory.js +224 -0
  87. package/build/tools/registry.d.ts +22 -0
  88. package/build/tools/registry.js +140 -0
  89. package/build/tools/validation/double-entry.d.ts +46 -0
  90. package/build/tools/validation/double-entry.js +66 -0
  91. package/build/types/api-responses.d.ts +21 -0
  92. package/build/types/api-responses.js +6 -0
  93. package/build/types/bukku.d.ts +93 -0
  94. package/build/types/bukku.js +11 -0
  95. package/build/utils/logger.d.ts +6 -0
  96. package/build/utils/logger.js +8 -0
  97. package/package.json +51 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Centry Digital Sdn. Bhd.
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,269 @@
1
+ # Bukku MCP Server
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@centry-digital/bukku-mcp)](https://www.npmjs.com/package/@centry-digital/bukku-mcp)
4
+
5
+ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that connects AI assistants to [Bukku](https://bukku.my), a Malaysian accounting platform. This gives your AI the ability to read, create, and manage your accounting data — invoices, bills, payments, contacts, products, and more.
6
+
7
+ ## What can it do?
8
+
9
+ With this MCP server connected, you can ask your AI things like:
10
+
11
+ - "List my unpaid sales invoices"
12
+ - "Create an invoice for RM 5,000 to Acme Corp for consulting services"
13
+ - "Show me all purchase bills from last month"
14
+ - "Record a bank transfer of RM 10,000 from Maybank to CIMB"
15
+ - "Create a new contact for my supplier"
16
+ - "Upload this receipt and attach it to the purchase bill"
17
+
18
+ The server exposes **173 tools** covering the full Bukku API:
19
+
20
+ | Category | Tools | What you can do |
21
+ |----------|-------|-----------------|
22
+ | **Sales** | 42 | Quotes, orders, delivery orders, invoices, credit notes, payments, refunds |
23
+ | **Purchases** | 36 | Purchase orders, bills, credit notes, goods received notes, payments, refunds |
24
+ | **Banking** | 18 | Money in, money out, bank transfers |
25
+ | **Contacts** | 12 | Customers, suppliers, contact groups |
26
+ | **Products** | 18 | Products, product bundles, product groups |
27
+ | **Accounting** | 13 | Journal entries, chart of accounts |
28
+ | **Files** | 3 | Upload and manage file attachments |
29
+ | **Organisation** | 21 | Locations, tags, tag groups |
30
+ | **Reference Data** | 10 | Tax codes, currencies, payment methods, terms, and more |
31
+
32
+ ## Quick Start
33
+
34
+ Get up and running in under 2 minutes.
35
+
36
+ ### Prerequisites
37
+
38
+ - [Node.js](https://nodejs.org) v18 or later
39
+ - A [Bukku](https://bukku.my) account with API access enabled
40
+ - An AI client that supports MCP (e.g. [Claude Desktop](https://claude.ai/download), [Claude Code](https://docs.anthropic.com/en/docs/claude-code))
41
+
42
+ ### Step 1: Get your Bukku API token
43
+
44
+ 1. Log into your Bukku account
45
+ 2. Go to **Control Panel > Integrations > API Access**
46
+ 3. Generate a new API token (or copy your existing one)
47
+ 4. Note your company subdomain — e.g. `mycompany` from `mycompany.bukku.my`
48
+
49
+ ### Step 2: Add to your AI client
50
+
51
+ For Claude Desktop, open your config file:
52
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
53
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
54
+
55
+ Add this configuration:
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "bukku": {
61
+ "command": "npx",
62
+ "args": ["-y", "@centry-digital/bukku-mcp"],
63
+ "env": {
64
+ "BUKKU_API_TOKEN": "your-token-here",
65
+ "BUKKU_COMPANY_SUBDOMAIN": "your-subdomain"
66
+ }
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ ### Step 3: Restart your AI client
73
+
74
+ Quit and reopen Claude Desktop. That's it — you're ready to go!
75
+
76
+ ## Installation
77
+
78
+ ### npx (recommended)
79
+
80
+ The quickest way to use the server is with `npx`. No installation needed — it downloads and runs the latest version automatically:
81
+
82
+ ```bash
83
+ npx @centry-digital/bukku-mcp
84
+ ```
85
+
86
+ This is what the Quick Start configuration uses. `npx` ensures you're always running the latest version without manual updates.
87
+
88
+ ### npm global install
89
+
90
+ If you prefer a persistent installation:
91
+
92
+ ```bash
93
+ npm install -g @centry-digital/bukku-mcp
94
+ ```
95
+
96
+ Then update your AI client configuration to use the installed command instead:
97
+
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "bukku": {
102
+ "command": "bukku-mcp",
103
+ "env": {
104
+ "BUKKU_API_TOKEN": "your-token-here",
105
+ "BUKKU_COMPANY_SUBDOMAIN": "your-subdomain"
106
+ }
107
+ }
108
+ }
109
+ }
110
+ ```
111
+
112
+ ## Configuration
113
+
114
+ ### Environment Variables
115
+
116
+ | Variable | Required | Description |
117
+ |----------|----------|-------------|
118
+ | `BUKKU_API_TOKEN` | Yes | Your Bukku API token from Control Panel > Integrations > API Access |
119
+ | `BUKKU_COMPANY_SUBDOMAIN` | Yes | Your company subdomain (e.g. `mycompany` from `mycompany.bukku.my`) |
120
+
121
+ ### Claude Desktop
122
+
123
+ Open your configuration file:
124
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
125
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
126
+
127
+ **Using npx (recommended):**
128
+
129
+ ```json
130
+ {
131
+ "mcpServers": {
132
+ "bukku": {
133
+ "command": "npx",
134
+ "args": ["-y", "@centry-digital/bukku-mcp"],
135
+ "env": {
136
+ "BUKKU_API_TOKEN": "your-token-here",
137
+ "BUKKU_COMPANY_SUBDOMAIN": "your-subdomain"
138
+ }
139
+ }
140
+ }
141
+ }
142
+ ```
143
+
144
+ **If you installed globally:**
145
+
146
+ ```json
147
+ {
148
+ "mcpServers": {
149
+ "bukku": {
150
+ "command": "bukku-mcp",
151
+ "env": {
152
+ "BUKKU_API_TOKEN": "your-token-here",
153
+ "BUKKU_COMPANY_SUBDOMAIN": "your-subdomain"
154
+ }
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ After updating the config, restart Claude Desktop.
161
+
162
+ ### Claude Code
163
+
164
+ Add to `.claude/settings.json` in your home directory or project:
165
+
166
+ **Using npx (recommended):**
167
+
168
+ ```json
169
+ {
170
+ "mcpServers": {
171
+ "bukku": {
172
+ "command": "npx",
173
+ "args": ["-y", "@centry-digital/bukku-mcp"],
174
+ "env": {
175
+ "BUKKU_API_TOKEN": "your-token-here",
176
+ "BUKKU_COMPANY_SUBDOMAIN": "your-subdomain"
177
+ }
178
+ }
179
+ }
180
+ }
181
+ ```
182
+
183
+ **If you installed globally:**
184
+
185
+ ```json
186
+ {
187
+ "mcpServers": {
188
+ "bukku": {
189
+ "command": "bukku-mcp",
190
+ "env": {
191
+ "BUKKU_API_TOKEN": "your-token-here",
192
+ "BUKKU_COMPANY_SUBDOMAIN": "your-subdomain"
193
+ }
194
+ }
195
+ }
196
+ }
197
+ ```
198
+
199
+ ### Other MCP Clients
200
+
201
+ Any client that supports the [MCP stdio transport](https://modelcontextprotocol.io/docs/concepts/transports) can use this server. Use the `npx @centry-digital/bukku-mcp` command with the two environment variables shown above.
202
+
203
+ ## Troubleshooting
204
+
205
+ **"Configuration Error" on startup**
206
+ - Check that both `BUKKU_API_TOKEN` and `BUKKU_COMPANY_SUBDOMAIN` are set in your client config
207
+ - Verify the environment variables are inside the `"env"` object
208
+
209
+ **"Token validation failed"**
210
+ - Your API token may be invalid or expired
211
+ - Log into Bukku and regenerate your token at Control Panel > Integrations > API Access
212
+
213
+ **Server doesn't appear in your AI client**
214
+ - Verify your configuration JSON syntax is correct (no trailing commas)
215
+ - Make sure you've restarted your AI client after editing the config
216
+ - Check that Node.js v18 or later is installed: `node --version`
217
+
218
+ **"Could not resolve package" with npx**
219
+ - Check that you have Node.js v18 or later installed
220
+ - Verify your network connection and proxy settings if applicable
221
+ - Try running `npm view @centry-digital/bukku-mcp` to confirm the package is accessible
222
+
223
+ **Permission errors with global install**
224
+ - Consider using `npx` instead (no installation needed)
225
+ - Or fix npm permissions: [https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally)
226
+
227
+ ## Development
228
+
229
+ Want to contribute or run from source? Here's how to set up your development environment.
230
+
231
+ ### Clone and build
232
+
233
+ ```bash
234
+ git clone https://github.com/centry-digital/bukku-mcp.git
235
+ cd bukku-mcp
236
+ npm install
237
+ npm run build
238
+ ```
239
+
240
+ ### Commands
241
+
242
+ ```bash
243
+ npm run build # Compile TypeScript
244
+ npm test # Run tests
245
+ npm start # Start server (requires env vars)
246
+ ```
247
+
248
+ ### Project structure
249
+
250
+ ```
251
+ src/
252
+ ├── client/ # Bukku API HTTP client
253
+ ├── config/ # Environment validation
254
+ ├── errors/ # Error handling and transformation
255
+ ├── tools/ # MCP tool definitions (one folder per category)
256
+ │ ├── sales/
257
+ │ ├── purchases/
258
+ │ ├── banking/
259
+ │ ├── contacts/
260
+ │ ├── products/
261
+ │ ├── accounting/
262
+ │ ├── files/
263
+ │ └── ...
264
+ └── index.ts # Server entry point
265
+ ```
266
+
267
+ ## License
268
+
269
+ MIT
@@ -0,0 +1,62 @@
1
+ import type { Env } from "../config/env.js";
2
+ /**
3
+ * HTTP client for Bukku API.
4
+ * Handles authentication via Bearer token and Company-Subdomain header.
5
+ * Base URL: https://api.bukku.my
6
+ */
7
+ export declare class BukkuClient {
8
+ private readonly baseUrl;
9
+ private readonly token;
10
+ private readonly subdomain;
11
+ constructor(env: Env);
12
+ /**
13
+ * Build headers for all requests.
14
+ * CRITICAL: Never log the actual token value - use "Bearer ***" for debugging.
15
+ */
16
+ private getHeaders;
17
+ /**
18
+ * Map file extensions to MIME types for common file types.
19
+ * Returns null for unknown extensions.
20
+ */
21
+ private getMimeType;
22
+ /**
23
+ * Build URL with query parameters.
24
+ */
25
+ private buildUrl;
26
+ /**
27
+ * GET request with optional query parameters.
28
+ */
29
+ get(path: string, params?: Record<string, string | number | undefined>): Promise<unknown>;
30
+ /**
31
+ * POST request with JSON body.
32
+ */
33
+ post(path: string, body: unknown): Promise<unknown>;
34
+ /**
35
+ * PUT request with JSON body.
36
+ */
37
+ put(path: string, body: unknown): Promise<unknown>;
38
+ /**
39
+ * PATCH request with JSON body (for status updates).
40
+ */
41
+ patch(path: string, body: unknown): Promise<unknown>;
42
+ /**
43
+ * DELETE request.
44
+ */
45
+ delete(path: string): Promise<void>;
46
+ /**
47
+ * POST multipart/form-data request for file uploads.
48
+ * Reads file from disk and sends as multipart form data.
49
+ * CRITICAL: Does NOT manually set Content-Type - fetch sets it automatically with boundary.
50
+ *
51
+ * @param path - API endpoint path
52
+ * @param filePath - Absolute path to file on disk
53
+ * @returns API response
54
+ */
55
+ postMultipart(path: string, filePath: string): Promise<unknown>;
56
+ /**
57
+ * Validate token on startup by making a lightweight API call.
58
+ * Uses GET /contacts with page_size=1 to verify authentication.
59
+ * Exits process if token is invalid (401).
60
+ */
61
+ validateToken(): Promise<void>;
62
+ }
@@ -0,0 +1,195 @@
1
+ import { log } from "../utils/logger.js";
2
+ import { readFile } from "node:fs/promises";
3
+ import { basename, extname } from "node:path";
4
+ /**
5
+ * HTTP client for Bukku API.
6
+ * Handles authentication via Bearer token and Company-Subdomain header.
7
+ * Base URL: https://api.bukku.my
8
+ */
9
+ export class BukkuClient {
10
+ baseUrl = "https://api.bukku.my";
11
+ token;
12
+ subdomain;
13
+ constructor(env) {
14
+ this.token = env.BUKKU_API_TOKEN;
15
+ this.subdomain = env.BUKKU_COMPANY_SUBDOMAIN;
16
+ }
17
+ /**
18
+ * Build headers for all requests.
19
+ * CRITICAL: Never log the actual token value - use "Bearer ***" for debugging.
20
+ */
21
+ getHeaders(includeContentType = false) {
22
+ const headers = {
23
+ Authorization: `Bearer ${this.token}`,
24
+ "Company-Subdomain": this.subdomain,
25
+ Accept: "application/json",
26
+ };
27
+ if (includeContentType) {
28
+ headers["Content-Type"] = "application/json";
29
+ }
30
+ return headers;
31
+ }
32
+ /**
33
+ * Map file extensions to MIME types for common file types.
34
+ * Returns null for unknown extensions.
35
+ */
36
+ getMimeType(extension) {
37
+ const mimeMap = {
38
+ ".pdf": "application/pdf",
39
+ ".png": "image/png",
40
+ ".jpg": "image/jpeg",
41
+ ".jpeg": "image/jpeg",
42
+ ".gif": "image/gif",
43
+ ".txt": "text/plain",
44
+ ".csv": "text/csv",
45
+ ".json": "application/json",
46
+ ".xml": "application/xml",
47
+ ".zip": "application/zip",
48
+ };
49
+ return mimeMap[extension.toLowerCase()] || null;
50
+ }
51
+ /**
52
+ * Build URL with query parameters.
53
+ */
54
+ buildUrl(path, params) {
55
+ const url = new URL(path, this.baseUrl);
56
+ if (params) {
57
+ for (const [key, value] of Object.entries(params)) {
58
+ if (value !== undefined) {
59
+ url.searchParams.append(key, String(value));
60
+ }
61
+ }
62
+ }
63
+ return url.toString();
64
+ }
65
+ /**
66
+ * GET request with optional query parameters.
67
+ */
68
+ async get(path, params) {
69
+ const url = this.buildUrl(path, params);
70
+ const response = await fetch(url, {
71
+ method: "GET",
72
+ headers: this.getHeaders(),
73
+ });
74
+ if (!response.ok) {
75
+ throw response;
76
+ }
77
+ return response.json();
78
+ }
79
+ /**
80
+ * POST request with JSON body.
81
+ */
82
+ async post(path, body) {
83
+ const url = this.buildUrl(path);
84
+ const response = await fetch(url, {
85
+ method: "POST",
86
+ headers: this.getHeaders(true),
87
+ body: JSON.stringify(body),
88
+ });
89
+ if (!response.ok) {
90
+ throw response;
91
+ }
92
+ return response.json();
93
+ }
94
+ /**
95
+ * PUT request with JSON body.
96
+ */
97
+ async put(path, body) {
98
+ const url = this.buildUrl(path);
99
+ const response = await fetch(url, {
100
+ method: "PUT",
101
+ headers: this.getHeaders(true),
102
+ body: JSON.stringify(body),
103
+ });
104
+ if (!response.ok) {
105
+ throw response;
106
+ }
107
+ return response.json();
108
+ }
109
+ /**
110
+ * PATCH request with JSON body (for status updates).
111
+ */
112
+ async patch(path, body) {
113
+ const url = this.buildUrl(path);
114
+ const response = await fetch(url, {
115
+ method: "PATCH",
116
+ headers: this.getHeaders(true),
117
+ body: JSON.stringify(body),
118
+ });
119
+ if (!response.ok) {
120
+ throw response;
121
+ }
122
+ return response.json();
123
+ }
124
+ /**
125
+ * DELETE request.
126
+ */
127
+ async delete(path) {
128
+ const url = this.buildUrl(path);
129
+ const response = await fetch(url, {
130
+ method: "DELETE",
131
+ headers: this.getHeaders(),
132
+ });
133
+ if (!response.ok) {
134
+ throw response;
135
+ }
136
+ }
137
+ /**
138
+ * POST multipart/form-data request for file uploads.
139
+ * Reads file from disk and sends as multipart form data.
140
+ * CRITICAL: Does NOT manually set Content-Type - fetch sets it automatically with boundary.
141
+ *
142
+ * @param path - API endpoint path
143
+ * @param filePath - Absolute path to file on disk
144
+ * @returns API response
145
+ */
146
+ async postMultipart(path, filePath) {
147
+ const url = this.buildUrl(path);
148
+ // Read file from disk
149
+ const fileBuffer = await readFile(filePath);
150
+ const fileName = basename(filePath);
151
+ const fileExtension = extname(filePath);
152
+ // Determine MIME type, fallback to generic binary
153
+ const mimeType = this.getMimeType(fileExtension) || "application/octet-stream";
154
+ // Create File object and FormData
155
+ const file = new File([fileBuffer], fileName, { type: mimeType });
156
+ const form = new FormData();
157
+ form.append("file", file);
158
+ // Get auth headers WITHOUT Content-Type (fetch sets it with boundary)
159
+ const headers = this.getHeaders(false);
160
+ const response = await fetch(url, {
161
+ method: "POST",
162
+ headers,
163
+ body: form,
164
+ });
165
+ if (!response.ok) {
166
+ throw response;
167
+ }
168
+ return response.json();
169
+ }
170
+ /**
171
+ * Validate token on startup by making a lightweight API call.
172
+ * Uses GET /contacts with page_size=1 to verify authentication.
173
+ * Exits process if token is invalid (401).
174
+ */
175
+ async validateToken() {
176
+ try {
177
+ await this.get("/contacts", { page_size: 1 });
178
+ log("Token validated successfully");
179
+ }
180
+ catch (error) {
181
+ if (error instanceof Response && error.status === 401) {
182
+ log("Authentication Error\n");
183
+ log("The provided BUKKU_API_TOKEN is invalid or expired.\n");
184
+ log("Please check:");
185
+ log(" 1. Token is copied correctly (no extra spaces)");
186
+ log(" 2. API Access is enabled in Bukku Control Panel -> Integrations");
187
+ log(" 3. Token has not been revoked or regenerated\n");
188
+ process.exit(1);
189
+ }
190
+ // For other errors, log and exit
191
+ log("Failed to validate token:", error);
192
+ process.exit(1);
193
+ }
194
+ }
195
+ }
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Environment variable schema.
4
+ * Required vars:
5
+ * - BUKKU_API_TOKEN: Bearer token from Bukku API settings
6
+ * - BUKKU_COMPANY_SUBDOMAIN: Company subdomain (e.g., 'mycompany' from mycompany.bukku.my)
7
+ */
8
+ declare const envSchema: z.ZodObject<{
9
+ BUKKU_API_TOKEN: z.ZodString;
10
+ BUKKU_COMPANY_SUBDOMAIN: z.ZodString;
11
+ }, z.core.$strip>;
12
+ export type Env = z.infer<typeof envSchema>;
13
+ /**
14
+ * Validates environment variables on startup.
15
+ * Exits process with code 1 if validation fails.
16
+ * Prints setup checklist to stderr on failure.
17
+ */
18
+ export declare function validateEnv(): Env;
19
+ export {};
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+ import { log } from "../utils/logger.js";
3
+ /**
4
+ * Environment variable schema.
5
+ * Required vars:
6
+ * - BUKKU_API_TOKEN: Bearer token from Bukku API settings
7
+ * - BUKKU_COMPANY_SUBDOMAIN: Company subdomain (e.g., 'mycompany' from mycompany.bukku.my)
8
+ */
9
+ const envSchema = z.object({
10
+ BUKKU_API_TOKEN: z.string().min(1, "BUKKU_API_TOKEN is required"),
11
+ BUKKU_COMPANY_SUBDOMAIN: z.string().min(1, "BUKKU_COMPANY_SUBDOMAIN is required"),
12
+ });
13
+ /**
14
+ * Validates environment variables on startup.
15
+ * Exits process with code 1 if validation fails.
16
+ * Prints setup checklist to stderr on failure.
17
+ */
18
+ export function validateEnv() {
19
+ const result = envSchema.safeParse(process.env);
20
+ if (!result.success) {
21
+ log("Configuration Error\n");
22
+ log("Missing required environment variables:");
23
+ for (const issue of result.error.issues) {
24
+ log(` - ${issue.path.join(".")}: ${issue.message}`);
25
+ }
26
+ log("\nSetup checklist:");
27
+ log(" 1. Go to Bukku web app -> Control Panel -> Integrations");
28
+ log(" 2. Turn on API Access and copy the Access Token");
29
+ log(" 3. Set BUKKU_API_TOKEN=<your-token>");
30
+ log(" 4. Set BUKKU_COMPANY_SUBDOMAIN=<your-subdomain>");
31
+ log(" (e.g., 'mycompany' from mycompany.bukku.my)");
32
+ log(" 5. Restart Claude Desktop\n");
33
+ process.exit(1);
34
+ }
35
+ return result.data;
36
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * HTTP to MCP Error Transformation
3
+ * Converts HTTP error responses into conversational MCP error messages
4
+ */
5
+ export interface MCPErrorResponse {
6
+ isError: true;
7
+ content: Array<{
8
+ type: 'text';
9
+ text: string;
10
+ }>;
11
+ [key: string]: unknown;
12
+ }
13
+ export declare function transformHttpError(status: number | null, body: unknown, operation: string): MCPErrorResponse;
14
+ export declare function transformNetworkError(error: unknown, operation: string): MCPErrorResponse;