@casys/mcp-erpnext 2.3.0 → 2.3.1
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 +67 -26
- package/mcp-erpnext.mjs +55 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @casys/mcp-erpnext
|
|
2
2
|
|
|
3
|
+
[](https://jsr.io/@casys/mcp-erpnext)
|
|
4
|
+
[](https://www.npmjs.com/package/@casys/mcp-erpnext)
|
|
5
|
+
[](https://github.com/Casys-AI/mcp-erpnext/actions/workflows/test.yml)
|
|
6
|
+
[](https://modelcontextprotocol.io)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
3
9
|
MCP server for [ERPNext](https://erpnext.com) / Frappe ERP — **120 tools**
|
|
4
10
|
across **14 categories**, with **7 interactive UI viewers**.
|
|
5
11
|
|
|
@@ -9,22 +15,59 @@ Copilot, custom) to your ERPNext instance via the
|
|
|
9
15
|
|
|
10
16
|
Works with **self-hosted** and **ERPNext Cloud** (frappe.cloud) instances.
|
|
11
17
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
## Screenshots
|
|
19
|
+
|
|
20
|
+
Interactive viewers rendered inside an MCP host, driven entirely by tool
|
|
21
|
+
results.
|
|
22
|
+
|
|
23
|
+
<table>
|
|
24
|
+
<tr>
|
|
25
|
+
<td width="50%" align="center">
|
|
26
|
+
<img src="docs/assets/doclist-viewer.png" alt="Document list viewer with chip filters and inline detail" width="100%"><br>
|
|
27
|
+
<sub><b>doclist-viewer</b> — any DocType as a sortable table with chip filters and an inline detail panel</sub>
|
|
28
|
+
</td>
|
|
29
|
+
<td width="50%" align="center">
|
|
30
|
+
<img src="docs/assets/invoice-viewer.png" alt="Invoice viewer with line items and actions" width="100%"><br>
|
|
31
|
+
<sub><b>invoice-viewer</b> — invoice with parties, line items, item drill-down and Submit/Cancel/Payments</sub>
|
|
32
|
+
</td>
|
|
33
|
+
</tr>
|
|
34
|
+
<tr>
|
|
35
|
+
<td width="50%" align="center">
|
|
36
|
+
<img src="docs/assets/funnel-viewer.png" alt="Sales funnel viewer" width="100%"><br>
|
|
37
|
+
<sub><b>funnel-viewer</b> — Lead → Opportunity → Quotation → Order with conversion rates</sub>
|
|
38
|
+
</td>
|
|
39
|
+
<td width="50%" align="center">
|
|
40
|
+
<img src="docs/assets/kpi-viewer.png" alt="KPI viewer with sparkline" width="100%"><br>
|
|
41
|
+
<sub><b>kpi-viewer</b> — big-number KPI with delta vs last period and a sparkline</sub>
|
|
42
|
+
</td>
|
|
43
|
+
</tr>
|
|
44
|
+
<tr>
|
|
45
|
+
<td width="50%" align="center">
|
|
46
|
+
<img src="docs/assets/chart-viewer.png" alt="Chart viewer" width="100%"><br>
|
|
47
|
+
<sub><b>chart-viewer</b> — universal Recharts renderer (here: stock levels)</sub>
|
|
48
|
+
</td>
|
|
49
|
+
<td width="50%" align="center">
|
|
50
|
+
<img src="docs/assets/stock-viewer.png" alt="Stock balance viewer" width="100%"><br>
|
|
51
|
+
<sub><b>stock-viewer</b> — stock balance with color-coded quantity badges</sub>
|
|
52
|
+
</td>
|
|
53
|
+
</tr>
|
|
54
|
+
<tr>
|
|
55
|
+
<td width="50%" align="center">
|
|
56
|
+
<img src="docs/assets/kanban-viewer.png" alt="Read-write kanban board" width="100%"><br>
|
|
57
|
+
<sub><b>kanban-viewer</b> — read-write board (Task / Opportunity / Issue) with inline edit</sub>
|
|
58
|
+
</td>
|
|
59
|
+
<td width="50%" align="center">
|
|
60
|
+
<img src="docs/assets/profit-loss.png" alt="Profit and loss composed chart" width="100%"><br>
|
|
61
|
+
<sub><b>chart-viewer</b> — composed dual-axis chart (here: profit & loss)</sub>
|
|
62
|
+
</td>
|
|
63
|
+
</tr>
|
|
64
|
+
</table>
|
|
65
|
+
|
|
66
|
+
## What's New
|
|
67
|
+
|
|
68
|
+
See the [CHANGELOG](CHANGELOG.md) for the full release history, or the
|
|
69
|
+
[latest release](https://github.com/Casys-AI/mcp-erpnext/releases/latest) for
|
|
70
|
+
the current version's highlights.
|
|
28
71
|
|
|
29
72
|
## Quick Start
|
|
30
73
|
|
|
@@ -293,19 +336,17 @@ cd src/ui && npm run dev:kanban
|
|
|
293
336
|
|
|
294
337
|
## Release Flow
|
|
295
338
|
|
|
296
|
-
|
|
339
|
+
Releases are manual and explicit:
|
|
297
340
|
|
|
298
|
-
1.
|
|
299
|
-
2.
|
|
300
|
-
3.
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
5. `.github/workflows/release-please.yml` creates the GitHub release, then calls
|
|
304
|
-
`.github/workflows/publish.yml` to publish the released version to JSR and
|
|
341
|
+
1. Update `deno.json`, `server.ts`, and `CHANGELOG.md`.
|
|
342
|
+
2. Run `deno task release:check` locally.
|
|
343
|
+
3. Commit and push the release commit to `main`.
|
|
344
|
+
4. Create the GitHub release/tag, for example `v2.3.0`.
|
|
345
|
+
5. Run the `Publish` workflow manually to publish the same version to JSR and
|
|
305
346
|
npm.
|
|
306
347
|
|
|
307
|
-
|
|
308
|
-
|
|
348
|
+
The package name stays `@casys/mcp-erpnext`; releases only bump the package
|
|
349
|
+
version.
|
|
309
350
|
|
|
310
351
|
## License
|
|
311
352
|
|
package/mcp-erpnext.mjs
CHANGED
|
@@ -22327,6 +22327,21 @@ var HonoRequest = class {
|
|
|
22327
22327
|
arrayBuffer() {
|
|
22328
22328
|
return this.#cachedBody("arrayBuffer");
|
|
22329
22329
|
}
|
|
22330
|
+
/**
|
|
22331
|
+
* `.bytes()` parses the request body as a `Uint8Array`.
|
|
22332
|
+
*
|
|
22333
|
+
* @see {@link https://hono.dev/docs/api/request#bytes}
|
|
22334
|
+
*
|
|
22335
|
+
* @example
|
|
22336
|
+
* ```ts
|
|
22337
|
+
* app.post('/entry', async (c) => {
|
|
22338
|
+
* const body = await c.req.bytes()
|
|
22339
|
+
* })
|
|
22340
|
+
* ```
|
|
22341
|
+
*/
|
|
22342
|
+
bytes() {
|
|
22343
|
+
return this.#cachedBody("arrayBuffer").then((buffer) => new Uint8Array(buffer));
|
|
22344
|
+
}
|
|
22330
22345
|
/**
|
|
22331
22346
|
* Parses the request body as a `Blob`.
|
|
22332
22347
|
* @example
|
|
@@ -23025,7 +23040,7 @@ var Hono = class _Hono {
|
|
|
23025
23040
|
handler = async (c, next) => (await compose([], app.errorHandler)(c, () => r.handler(c, next))).res;
|
|
23026
23041
|
handler[COMPOSED_HANDLER] = r.handler;
|
|
23027
23042
|
}
|
|
23028
|
-
subApp.#addRoute(r.method, r.path, handler);
|
|
23043
|
+
subApp.#addRoute(r.method, r.path, handler, r.basePath);
|
|
23029
23044
|
});
|
|
23030
23045
|
return this;
|
|
23031
23046
|
}
|
|
@@ -23149,7 +23164,7 @@ var Hono = class _Hono {
|
|
|
23149
23164
|
const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
|
|
23150
23165
|
return (request) => {
|
|
23151
23166
|
const url = new URL(request.url);
|
|
23152
|
-
url.pathname =
|
|
23167
|
+
url.pathname = this.getPath(request).slice(pathPrefixLength) || "/";
|
|
23153
23168
|
return new Request(url, request);
|
|
23154
23169
|
};
|
|
23155
23170
|
})();
|
|
@@ -23163,10 +23178,15 @@ var Hono = class _Hono {
|
|
|
23163
23178
|
this.#addRoute(METHOD_NAME_ALL, mergePath(path, "*"), handler);
|
|
23164
23179
|
return this;
|
|
23165
23180
|
}
|
|
23166
|
-
#addRoute(method, path, handler) {
|
|
23181
|
+
#addRoute(method, path, handler, baseRoutePath) {
|
|
23167
23182
|
method = method.toUpperCase();
|
|
23168
23183
|
path = mergePath(this._basePath, path);
|
|
23169
|
-
const r = {
|
|
23184
|
+
const r = {
|
|
23185
|
+
basePath: baseRoutePath !== void 0 ? mergePath(this._basePath, baseRoutePath) : this._basePath,
|
|
23186
|
+
path,
|
|
23187
|
+
method,
|
|
23188
|
+
handler
|
|
23189
|
+
};
|
|
23170
23190
|
this.router.add(method, path, [handler, r]);
|
|
23171
23191
|
this.routes.push(r);
|
|
23172
23192
|
}
|
|
@@ -26468,12 +26488,16 @@ var LoaderState = class {
|
|
|
26468
26488
|
}
|
|
26469
26489
|
for (const [key, value] of Object.entries(source)) {
|
|
26470
26490
|
if (Object.hasOwn(destination, key)) continue;
|
|
26471
|
-
|
|
26472
|
-
|
|
26473
|
-
|
|
26474
|
-
|
|
26475
|
-
|
|
26476
|
-
|
|
26491
|
+
if (key === "__proto__") {
|
|
26492
|
+
Object.defineProperty(destination, key, {
|
|
26493
|
+
value,
|
|
26494
|
+
writable: true,
|
|
26495
|
+
enumerable: true,
|
|
26496
|
+
configurable: true
|
|
26497
|
+
});
|
|
26498
|
+
} else {
|
|
26499
|
+
destination[key] = value;
|
|
26500
|
+
}
|
|
26477
26501
|
overridableKeys.add(key);
|
|
26478
26502
|
}
|
|
26479
26503
|
}
|
|
@@ -26507,12 +26531,16 @@ var LoaderState = class {
|
|
|
26507
26531
|
this.#scanner.position = startPos || this.#scanner.position;
|
|
26508
26532
|
throw this.#createError("Cannot store mapping pair: duplicated key");
|
|
26509
26533
|
}
|
|
26510
|
-
|
|
26511
|
-
|
|
26512
|
-
|
|
26513
|
-
|
|
26514
|
-
|
|
26515
|
-
|
|
26534
|
+
if (keyNode === "__proto__") {
|
|
26535
|
+
Object.defineProperty(result, keyNode, {
|
|
26536
|
+
value: valueNode,
|
|
26537
|
+
writable: true,
|
|
26538
|
+
enumerable: true,
|
|
26539
|
+
configurable: true
|
|
26540
|
+
});
|
|
26541
|
+
} else {
|
|
26542
|
+
result[keyNode] = valueNode;
|
|
26543
|
+
}
|
|
26516
26544
|
overridableKeys.delete(keyNode);
|
|
26517
26545
|
}
|
|
26518
26546
|
return result;
|
|
@@ -35994,18 +36022,26 @@ var analyticsTools = [
|
|
|
35994
36022
|
if (input.warehouse) {
|
|
35995
36023
|
filters.push(["warehouse", "=", input.warehouse]);
|
|
35996
36024
|
}
|
|
36025
|
+
let allowedItems = null;
|
|
35997
36026
|
if (input.item_group) {
|
|
35998
|
-
|
|
36027
|
+
const groupItems = await ctx.client.list("Item", {
|
|
36028
|
+
fields: ["name"],
|
|
36029
|
+
filters: [["item_group", "=", input.item_group]],
|
|
36030
|
+
limit: 1e3
|
|
36031
|
+
});
|
|
36032
|
+
allowedItems = new Set(groupItems.map((i) => i.name));
|
|
35999
36033
|
}
|
|
36000
36034
|
const bins = await ctx.client.list("Bin", {
|
|
36001
36035
|
fields: ["item_code", "warehouse", "actual_qty", "stock_value"],
|
|
36002
36036
|
filters,
|
|
36003
|
-
|
|
36037
|
+
// widen the fetch when filtering by group in memory, then slice below
|
|
36038
|
+
limit: allowedItems ? 1e3 : limit,
|
|
36004
36039
|
order_by: "actual_qty desc"
|
|
36005
36040
|
});
|
|
36006
36041
|
const byItem = {};
|
|
36007
36042
|
for (const bin of bins) {
|
|
36008
36043
|
const item = bin.item_code;
|
|
36044
|
+
if (allowedItems && !allowedItems.has(item)) continue;
|
|
36009
36045
|
if (!byItem[item]) byItem[item] = { qty: 0, value: 0 };
|
|
36010
36046
|
byItem[item].qty += Number(bin.actual_qty) || 0;
|
|
36011
36047
|
byItem[item].value += Number(bin.stock_value) || 0;
|
|
@@ -38918,8 +38954,7 @@ async function main() {
|
|
|
38918
38954
|
);
|
|
38919
38955
|
const server = new McpApp({
|
|
38920
38956
|
name: "mcp-erpnext",
|
|
38921
|
-
version: "2.3.
|
|
38922
|
-
// x-release-please-version
|
|
38957
|
+
version: "2.3.1",
|
|
38923
38958
|
maxConcurrent: 10,
|
|
38924
38959
|
backpressureStrategy: "queue",
|
|
38925
38960
|
validateSchema: true,
|