bimp-mcp 0.3.0 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,24 +1,43 @@
1
- # bimp-mcp
1
+ <p align="center">
2
+ <img src="assets/header.png" alt="HEYLOVE x BIMP" width="480" />
3
+ </p>
2
4
 
3
- MCP server for [BIMP ERP](https://bimpsoft.com) -- a Ukrainian cloud-based ERP system for small and medium businesses covering sales, inventory, finance, manufacturing, and procurement.
5
+ <h1 align="center">BIMP MCP</h1>
4
6
 
5
- This server enables LLMs to interact with BIMP data through the [Model Context Protocol](https://modelcontextprotocol.io): read, create, update entities, perform bulk operations, and analyze business data.
7
+ <p align="center">
8
+ MCP server for <a href="https://bimpsoft.com">BIMP ERP</a> — a Ukrainian cloud ERP for small and medium businesses
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://github.com/dutchakdev/bimp-mcp/actions/workflows/ci.yml"><img src="https://github.com/dutchakdev/bimp-mcp/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
13
+ <a href="https://github.com/dutchakdev/bimp-mcp/actions/workflows/release.yml"><img src="https://github.com/dutchakdev/bimp-mcp/actions/workflows/release.yml/badge.svg" alt="Release" /></a>
14
+ <a href="https://www.npmjs.com/package/bimp-mcp"><img src="https://img.shields.io/npm/v/bimp-mcp" alt="npm" /></a>
15
+ <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/bimp-mcp" alt="Node.js" /></a>
16
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ Enable LLMs to interact with BIMP data through the <a href="https://modelcontextprotocol.io">Model Context Protocol</a>:<br/>
21
+ read, create, update entities, perform bulk operations, and analyze business data.
22
+ </p>
23
+
24
+ ---
6
25
 
7
26
  ## Features
8
27
 
9
- - **~135 auto-generated tools** from OpenAPI 3.1 spec -- adding a new endpoint requires only editing `bimp-api.json` and restarting
28
+ - **~135 auto-generated tools** from OpenAPI 3.1 spec edit `bimp-api.json`, restart, done
10
29
  - **3 utility tools** for bulk operations: `bimp_fetch_all`, `bimp_batch_read`, `bimp_bulk_update`
11
- - **6 MCP prompts** providing ERP domain context, workflow guides, and data analysis instructions
12
- - **Auto-authentication** with token refresh -- login triggered on first API call, tokens refreshed transparently on 401
30
+ - **6 MCP prompts** with ERP domain context, workflow guides, and data analysis patterns
31
+ - **Auto-authentication** login on first call, transparent token refresh on 401
13
32
 
14
33
  ## Quick Start
15
34
 
16
35
  ### Prerequisites
17
36
 
18
37
  - Node.js 20+
19
- - A BIMP ERP account with API access
38
+ - A [BIMP ERP](https://bimpsoft.com) account with API access
20
39
 
21
- ### Environment Variables
40
+ ### 1. Configure environment
22
41
 
23
42
  Create a `.env` file (see `.env.example`):
24
43
 
@@ -29,22 +48,22 @@ BIMP_PASSWORD=your-password
29
48
  BIMP_COMPANY_CODE=000001398
30
49
  ```
31
50
 
32
- ### Install and Run
51
+ ### 2. Install and run
33
52
 
34
53
  ```bash
35
54
  npm install
36
55
  npm start
37
56
  ```
38
57
 
39
- The server starts on stdio transport. For development with auto-reload:
58
+ For development with auto-reload:
40
59
 
41
60
  ```bash
42
61
  npm run dev
43
62
  ```
44
63
 
45
- ### MCP Client Configuration
64
+ ### 3. Connect your MCP client
46
65
 
47
- Add to your `claude_desktop_config.json` (Claude Desktop) or equivalent MCP client config:
66
+ Add to `claude_desktop_config.json` (Claude Desktop) or equivalent:
48
67
 
49
68
  ```json
50
69
  {
@@ -63,14 +82,14 @@ Add to your `claude_desktop_config.json` (Claude Desktop) or equivalent MCP clie
63
82
  }
64
83
  ```
65
84
 
66
- ## Available Tools
85
+ ## Tools
67
86
 
68
- ### Auto-Generated (~135 tools)
87
+ ### Auto-generated (~135 tools)
69
88
 
70
- Tools are generated from `bimp-api.json` at startup. Naming convention: `bimp_{entity}_{action}`.
89
+ Generated from `bimp-api.json` at startup. Naming: `bimp_{entity}_{action}`.
71
90
 
72
91
  | Domain | Examples |
73
- |--------|----------|
92
+ |---|---|
74
93
  | **Sales** | `bimp_salesInvoice_readList`, `bimp_salesInvoice_create`, `bimp_invoiceForCustomerPayment_readList` |
75
94
  | **Inventory** | `bimp_nomenclature_readList`, `bimp_inventory_readList_cursor`, `bimp_movementOfInventories_create` |
76
95
  | **Finance** | `bimp_customerPayment_readList`, `bimp_supplierPayment_create`, `bimp_cashBox_readList` |
@@ -79,20 +98,20 @@ Tools are generated from `bimp-api.json` at startup. Naming convention: `bimp_{e
79
98
  | **Reference Data** | `bimp_counterparty_readList`, `bimp_employee_readList`, `bimp_warehouse_readList` |
80
99
  | **Auth** | `bimp_auth_listCompanies`, `bimp_auth_switchCompany` |
81
100
 
82
- ### Utility Tools
101
+ ### Utility tools
83
102
 
84
103
  | Tool | Purpose |
85
- |------|---------|
86
- | `bimp_fetch_all` | Auto-paginate any readList endpoint. Supports offset/count, cursor, and page/pageSize pagination. Optional `enrich` mode fetches full details for each record. |
104
+ |---|---|
105
+ | `bimp_fetch_all` | Auto-paginate any readList. Supports offset/count, cursor, page/pageSize. Optional `enrich` mode fetches full details per record. |
87
106
  | `bimp_batch_read` | Parallel read of full details for an array of UUIDs with configurable concurrency. |
88
107
  | `bimp_bulk_update` | Mass update records with batched concurrency and per-item error reporting. |
89
108
 
90
- ## Available Prompts
109
+ ## Prompts
91
110
 
92
111
  | Prompt | Description |
93
- |--------|-------------|
112
+ |---|---|
94
113
  | `bimp_erp_context` | Entity structure, relationships, Ukrainian terminology mapping |
95
- | `bimp_data_analysis` | How to analyze BIMP data effectively, pagination quirks, enrichment |
114
+ | `bimp_data_analysis` | Data analysis patterns, pagination quirks, enrichment strategies |
96
115
  | `bimp_bulk_operations` | Mass operation patterns: price updates, bulk edits, imports |
97
116
  | `bimp_sales_workflow` | Sales process: order, realization, payment, returns |
98
117
  | `bimp_production_workflow` | Production: specification, order, assembly, material write-offs |
package/dist/client.js CHANGED
@@ -17,7 +17,7 @@ export class BimpClient {
17
17
  async request(method, path, params = {}, options) {
18
18
  await this.ensureAuthenticated();
19
19
  const result = await this.executeRequest(method, path, params, options?.timeout);
20
- if (result.status === 401) {
20
+ if (result.status === 401 || result.status === 498) {
21
21
  await this.refreshAuth();
22
22
  const retry = await this.executeRequest(method, path, params, options?.timeout);
23
23
  if (!retry.ok) {
@@ -110,8 +110,8 @@ export class BimpClient {
110
110
  }
111
111
  async executeRequest(method, pathTemplate, params, timeout) {
112
112
  const resp = await this.rawFetch(method, pathTemplate, params, { "access-token": this.tokens.companyAccessToken }, timeout);
113
- if (resp.status === 401) {
114
- return { ok: false, status: 401, data: null };
113
+ if (resp.status === 401 || resp.status === 498) {
114
+ return { ok: false, status: resp.status, data: null };
115
115
  }
116
116
  const json = (await resp.json());
117
117
  return { ok: resp.ok, status: resp.status, data: json };
package/dist/index.js CHANGED
@@ -12,6 +12,58 @@ import { createUtilityTools } from "./utilities.js";
12
12
  import { createNomenclaturesTools } from "./nomenclatures-extended.js";
13
13
  import { PROMPT_TEXTS } from "./prompts.js";
14
14
  const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ /**
16
+ * Maximum response size in characters.
17
+ * MCP clients (Claude Code, Claude Desktop) have tool result size limits.
18
+ * We truncate large responses to stay within safe bounds.
19
+ */
20
+ const MAX_RESPONSE_SIZE = 80_000;
21
+ /**
22
+ * Truncate a tool result if its JSON representation exceeds MAX_RESPONSE_SIZE.
23
+ * For array-based results (items/data), uses binary search to fit max items.
24
+ */
25
+ function truncateResponse(result) {
26
+ const json = JSON.stringify(result, null, 2);
27
+ if (json.length <= MAX_RESPONSE_SIZE)
28
+ return json;
29
+ if (typeof result === "object" && result !== null) {
30
+ const obj = result;
31
+ const arrayKey = ["items", "data"].find((k) => Array.isArray(obj[k]));
32
+ if (arrayKey) {
33
+ const arr = obj[arrayKey];
34
+ const totalCount = arr.length;
35
+ // Binary search for max number of items that fit
36
+ let lo = 0;
37
+ let hi = arr.length;
38
+ while (lo < hi) {
39
+ const mid = Math.ceil((lo + hi) / 2);
40
+ const test = JSON.stringify({ ...obj, [arrayKey]: arr.slice(0, mid), count: mid, _truncated: { total: totalCount, returned: mid } }, null, 2);
41
+ if (test.length <= MAX_RESPONSE_SIZE) {
42
+ lo = mid;
43
+ }
44
+ else {
45
+ hi = mid - 1;
46
+ }
47
+ }
48
+ return JSON.stringify({
49
+ ...obj,
50
+ [arrayKey]: arr.slice(0, lo),
51
+ count: lo,
52
+ _truncated: {
53
+ total: totalCount,
54
+ returned: lo,
55
+ message: `Response truncated: showing ${lo} of ${totalCount} items. ` +
56
+ `To get all data, re-call with limit=${lo} and iterate using skip parameter: ` +
57
+ `skip=0 limit=${lo}, then skip=${lo} limit=${lo}, etc. ` +
58
+ `For bimp_fetch_all: recommended limit is 50 with enrich=true, 200 without.`,
59
+ },
60
+ }, null, 2);
61
+ }
62
+ }
63
+ // Fallback: hard truncate for non-array responses
64
+ return (json.slice(0, MAX_RESPONSE_SIZE) +
65
+ "\n\n... [TRUNCATED: response exceeded size limit. Use limit parameter or filters to reduce result size.]");
66
+ }
15
67
  const config = {
16
68
  email: process.env.BIMP_EMAIL ?? "",
17
69
  password: process.env.BIMP_PASSWORD ?? "",
@@ -38,7 +90,7 @@ for (const tool of generatedTools) {
38
90
  }
39
91
  const utilityTools = createUtilityTools(client, toolMap);
40
92
  const nomenclaturesTools = createNomenclaturesTools(client);
41
- const server = new McpServer({ name: "bimp-mcp", version: "0.3.0" }, { capabilities: { logging: {} } });
93
+ const server = new McpServer({ name: "bimp-mcp", version: "0.3.3" }, { capabilities: { logging: {} } });
42
94
  // Register prompts via McpServer (uses Zod, type-safe)
43
95
  for (const [name, prompt] of Object.entries(PROMPT_TEXTS)) {
44
96
  server.registerPrompt(name, { description: prompt.description }, () => ({
@@ -175,7 +227,7 @@ lowLevelServer.setRequestHandler(CallToolRequestSchema, async (request) => {
175
227
  const result = await utilityTool.handler(params);
176
228
  return {
177
229
  content: [
178
- { type: "text", text: JSON.stringify(result, null, 2) },
230
+ { type: "text", text: truncateResponse(result) },
179
231
  ],
180
232
  };
181
233
  }
@@ -185,7 +237,7 @@ lowLevelServer.setRequestHandler(CallToolRequestSchema, async (request) => {
185
237
  const result = await nomenclaturesTool.handler(params);
186
238
  return {
187
239
  content: [
188
- { type: "text", text: JSON.stringify(result, null, 2) },
240
+ { type: "text", text: truncateResponse(result) },
189
241
  ],
190
242
  };
191
243
  }
@@ -206,7 +258,7 @@ lowLevelServer.setRequestHandler(CallToolRequestSchema, async (request) => {
206
258
  const result = await client.request(toolDef.metadata.method, toolDef.metadata.path, callParams);
207
259
  return {
208
260
  content: [
209
- { type: "text", text: JSON.stringify(result, null, 2) },
261
+ { type: "text", text: truncateResponse(result) },
210
262
  ],
211
263
  };
212
264
  }
@@ -221,7 +273,7 @@ lowLevelServer.setRequestHandler(CallToolRequestSchema, async (request) => {
221
273
  const result = await client.request(toolDef.metadata.method, toolDef.metadata.path, params);
222
274
  return {
223
275
  content: [
224
- { type: "text", text: JSON.stringify(result, null, 2) },
276
+ { type: "text", text: truncateResponse(result) },
225
277
  ],
226
278
  };
227
279
  }
package/dist/utilities.js CHANGED
@@ -49,7 +49,10 @@ function createFetchAllTool(client, toolMap) {
49
49
  name: "bimp_fetch_all",
50
50
  description: "Auto-paginate any readList endpoint to fetch all items. " +
51
51
  "Supports offset, cursor, page, and none pagination types. " +
52
- "Use enrich=true to call the corresponding read endpoint for full details on each item.",
52
+ "Use enrich=true to call the corresponding read endpoint for full details on each item.\n\n" +
53
+ "IMPORTANT: Always set limit (recommended: 50 with enrich, 200 without) to avoid response truncation. " +
54
+ "Use skip to paginate through results: first call skip=0 limit=50, then skip=50 limit=50, etc. " +
55
+ "If the response contains _truncated, use skip and limit to get remaining items.",
53
56
  inputSchema: {
54
57
  type: "object",
55
58
  properties: {
@@ -57,9 +60,13 @@ function createFetchAllTool(client, toolMap) {
57
60
  type: "string",
58
61
  description: "Name of a readList tool to paginate (e.g. bimp_nomenclature_readList)",
59
62
  },
63
+ skip: {
64
+ type: "number",
65
+ description: "Number of items to skip from the beginning (default: 0). Use with limit to paginate: skip=0 limit=50, then skip=50 limit=50, etc.",
66
+ },
60
67
  limit: {
61
68
  type: "number",
62
- description: "Maximum number of items to return (default: unlimited)",
69
+ description: "Maximum number of items to return. RECOMMENDED: 50 with enrich=true, 200 without enrich. Avoid omitting to prevent response truncation.",
63
70
  },
64
71
  enrich: {
65
72
  type: "boolean",
@@ -74,9 +81,12 @@ function createFetchAllTool(client, toolMap) {
74
81
  },
75
82
  handler: async (params) => {
76
83
  const toolName = params.tool;
84
+ const skip = params.skip ?? 0;
77
85
  const limit = params.limit;
78
86
  const enrich = params.enrich;
79
87
  const filters = (params.filters ?? {});
88
+ // Effective limit accounts for skip: fetch skip+limit items, then slice
89
+ const fetchLimit = limit ? skip + limit : undefined;
80
90
  const toolDef = toolMap.get(toolName);
81
91
  if (!toolDef) {
82
92
  throw new Error(`Tool not found: ${toolName}`);
@@ -93,8 +103,8 @@ function createFetchAllTool(client, toolMap) {
93
103
  const response = (await client.request(toolDef.metadata.method, toolDef.metadata.path, requestParams));
94
104
  const page = response.data ?? [];
95
105
  allItems.push(...page);
96
- if (limit && allItems.length >= limit) {
97
- allItems = allItems.slice(0, limit);
106
+ if (fetchLimit && allItems.length >= fetchLimit) {
107
+ allItems = allItems.slice(0, fetchLimit);
98
108
  break;
99
109
  }
100
110
  if (page.length < PAGE_SIZE) {
@@ -114,8 +124,8 @@ function createFetchAllTool(client, toolMap) {
114
124
  const response = (await client.request(toolDef.metadata.method, toolDef.metadata.path, requestParams));
115
125
  const page = response.data ?? [];
116
126
  allItems.push(...page);
117
- if (limit && allItems.length >= limit) {
118
- allItems = allItems.slice(0, limit);
127
+ if (fetchLimit && allItems.length >= fetchLimit) {
128
+ allItems = allItems.slice(0, fetchLimit);
119
129
  break;
120
130
  }
121
131
  cursor = response.cursor;
@@ -135,8 +145,8 @@ function createFetchAllTool(client, toolMap) {
135
145
  const response = (await client.request(toolDef.metadata.method, toolDef.metadata.path, requestParams));
136
146
  const items = response.data ?? [];
137
147
  allItems.push(...items);
138
- if (limit && allItems.length >= limit) {
139
- allItems = allItems.slice(0, limit);
148
+ if (fetchLimit && allItems.length >= fetchLimit) {
149
+ allItems = allItems.slice(0, fetchLimit);
140
150
  break;
141
151
  }
142
152
  if (items.length < PAGE_SIZE) {
@@ -151,10 +161,15 @@ function createFetchAllTool(client, toolMap) {
151
161
  const response = (await client.request(toolDef.metadata.method, toolDef.metadata.path, requestParams));
152
162
  const items = response.data ?? [];
153
163
  allItems.push(...items);
154
- if (limit && allItems.length > limit) {
155
- allItems = allItems.slice(0, limit);
164
+ if (fetchLimit && allItems.length > fetchLimit) {
165
+ allItems = allItems.slice(0, fetchLimit);
156
166
  }
157
167
  }
168
+ // Apply skip — slice off the first `skip` items
169
+ const totalFetched = allItems.length;
170
+ if (skip > 0) {
171
+ allItems = allItems.slice(skip);
172
+ }
158
173
  // Enrich: call the read endpoint for each item
159
174
  if (enrich && allItems.length > 0) {
160
175
  const readToolName = deriveReadToolName(toolName);
@@ -166,9 +181,19 @@ function createFetchAllTool(client, toolMap) {
166
181
  .map((item) => item.uuid)
167
182
  .filter(Boolean);
168
183
  const { items: enrichedItems } = await batchReadUuids(client, readToolDef, uuids, 10);
169
- return { items: enrichedItems, count: enrichedItems.length };
184
+ return {
185
+ items: enrichedItems,
186
+ count: enrichedItems.length,
187
+ skip,
188
+ hasMore: totalFetched > skip + enrichedItems.length,
189
+ };
170
190
  }
171
- return { items: allItems, count: allItems.length };
191
+ return {
192
+ items: allItems,
193
+ count: allItems.length,
194
+ skip,
195
+ hasMore: totalFetched > skip + allItems.length,
196
+ };
172
197
  },
173
198
  };
174
199
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimp-mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
4
4
  "description": "MCP server for BIMP ERP API — ~140 tools dynamically generated from OpenAPI spec, plus planning/accounting fields and bulk operations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,7 +21,11 @@
21
21
  "test:functional": "vitest run --project functional",
22
22
  "test:all": "vitest run",
23
23
  "test:watch": "vitest --project unit",
24
- "demo:nomenclatures": "tsx scripts/demo-nomenclatures.ts"
24
+ "demo:nomenclatures": "tsx scripts/demo-nomenclatures.ts",
25
+ "release": "./scripts/release.sh",
26
+ "release:patch": "./scripts/release.sh patch",
27
+ "release:minor": "./scripts/release.sh minor",
28
+ "release:major": "./scripts/release.sh major"
25
29
  },
26
30
  "keywords": [
27
31
  "mcp",