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 +41 -22
- package/dist/client.js +3 -3
- package/dist/index.js +57 -5
- package/dist/utilities.js +37 -12
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,24 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/header.png" alt="HEYLOVE x BIMP" width="480" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">BIMP MCP</h1>
|
|
4
6
|
|
|
5
|
-
|
|
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
|
|
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**
|
|
12
|
-
- **Auto-authentication**
|
|
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
|
-
###
|
|
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
|
|
51
|
+
### 2. Install and run
|
|
33
52
|
|
|
34
53
|
```bash
|
|
35
54
|
npm install
|
|
36
55
|
npm start
|
|
37
56
|
```
|
|
38
57
|
|
|
39
|
-
|
|
58
|
+
For development with auto-reload:
|
|
40
59
|
|
|
41
60
|
```bash
|
|
42
61
|
npm run dev
|
|
43
62
|
```
|
|
44
63
|
|
|
45
|
-
### MCP
|
|
64
|
+
### 3. Connect your MCP client
|
|
46
65
|
|
|
47
|
-
Add to
|
|
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
|
-
##
|
|
85
|
+
## Tools
|
|
67
86
|
|
|
68
|
-
### Auto-
|
|
87
|
+
### Auto-generated (~135 tools)
|
|
69
88
|
|
|
70
|
-
|
|
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
|
|
101
|
+
### Utility tools
|
|
83
102
|
|
|
84
103
|
| Tool | Purpose |
|
|
85
|
-
|
|
86
|
-
| `bimp_fetch_all` | Auto-paginate any readList
|
|
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
|
-
##
|
|
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` |
|
|
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:
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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 (
|
|
97
|
-
allItems = allItems.slice(0,
|
|
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 (
|
|
118
|
-
allItems = allItems.slice(0,
|
|
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 (
|
|
139
|
-
allItems = allItems.slice(0,
|
|
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 (
|
|
155
|
-
allItems = allItems.slice(0,
|
|
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 {
|
|
184
|
+
return {
|
|
185
|
+
items: enrichedItems,
|
|
186
|
+
count: enrichedItems.length,
|
|
187
|
+
skip,
|
|
188
|
+
hasMore: totalFetched > skip + enrichedItems.length,
|
|
189
|
+
};
|
|
170
190
|
}
|
|
171
|
-
return {
|
|
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.
|
|
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",
|