@casys/mcp-erpnext 2.4.0 → 2.4.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 +18 -3
- package/mcp-erpnext.mjs +82 -3
- package/package.json +1 -1
- package/ui-dist/kanban-viewer/index.html +46 -46
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://modelcontextprotocol.io)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
MCP server for [ERPNext](https://erpnext.com) / Frappe ERP — **
|
|
9
|
+
MCP server for [ERPNext](https://erpnext.com) / Frappe ERP — **123 tools**
|
|
10
10
|
across **14 categories**, with **7 interactive UI viewers**.
|
|
11
11
|
|
|
12
12
|
Connect any MCP-compatible AI agent (Claude Desktop, Claude Code, VS Code
|
|
@@ -151,6 +151,21 @@ ERPNEXT_API_SECRET=xxx \
|
|
|
151
151
|
npx -y @casys/mcp-erpnext --http --port=3012
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
+
### Deno (HTTP mode)
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
ERPNEXT_URL=http://localhost:8000 \
|
|
158
|
+
ERPNEXT_API_KEY=xxx \
|
|
159
|
+
ERPNEXT_API_SECRET=xxx \
|
|
160
|
+
deno run -A npm:@casys/mcp-erpnext --http --port=3012
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> **Note:** Versions ≤ 2.3.1 of the npm bundle crashed with
|
|
164
|
+
> `ReferenceError: Deno is not defined` in HTTP mode — fixed in 2.4.0
|
|
165
|
+
> (`@casys/mcp-server` ≥ 0.21.1). If you hit this error, upgrade with
|
|
166
|
+
> `npx -y @casys/mcp-erpnext@latest`, or use the Deno runner above. See
|
|
167
|
+
> [`docs/known-issues.md`](docs/known-issues.md).
|
|
168
|
+
|
|
154
169
|
### Category filtering
|
|
155
170
|
|
|
156
171
|
Load only the categories you need:
|
|
@@ -220,7 +235,7 @@ npm install
|
|
|
220
235
|
node build-all.mjs
|
|
221
236
|
```
|
|
222
237
|
|
|
223
|
-
## Tools (
|
|
238
|
+
## Tools (123)
|
|
224
239
|
|
|
225
240
|
**14 categories** covering the full ERPNext surface. Each `_list` tool returns
|
|
226
241
|
interactive results in the doclist-viewer with row click, inline detail, and
|
|
@@ -238,7 +253,7 @@ cross-viewer navigation.
|
|
|
238
253
|
| **Manufacturing** | 7 | doclist | BOMs, Work Orders, Job Cards |
|
|
239
254
|
| **CRM** | 8 | doclist | Leads, Opportunities, Contacts, Campaigns |
|
|
240
255
|
| **Assets** | 8 | doclist | Assets, Movements, Maintenance, Categories |
|
|
241
|
-
| **Operations** |
|
|
256
|
+
| **Operations** | 9 | doclist | Generic CRUD + native assignment for **any** DocType (`erpnext_doc_*`) |
|
|
242
257
|
| **Kanban** | 2 | kanban | Task/Opportunity/Issue boards with drag-and-drop |
|
|
243
258
|
| **Analytics** | 17 | chart / kpi / funnel | 12 chart types, 5 KPIs, sales funnel |
|
|
244
259
|
| **Setup** | 3 | — | Company creation, assignable user listing |
|
package/mcp-erpnext.mjs
CHANGED
|
@@ -39517,6 +39517,7 @@ var hrTools = [
|
|
|
39517
39517
|
|
|
39518
39518
|
// src/tools/assignment.ts
|
|
39519
39519
|
var ASSIGNMENT_METHOD = "frappe.desk.form.assign_to.add";
|
|
39520
|
+
var UNASSIGNMENT_METHOD = "frappe.desk.form.assign_to.remove";
|
|
39520
39521
|
var MAX_ASSIGNEES_PER_CALL = 50;
|
|
39521
39522
|
var ASSIGNMENT_INPUT_PROPERTIES = {
|
|
39522
39523
|
assign_to: {
|
|
@@ -39640,13 +39641,31 @@ async function applyAssignment(doctype, name, assignment, ctx, failureContext) {
|
|
|
39640
39641
|
}) : [];
|
|
39641
39642
|
return { notify_user: true, assignees: assignment.assignees, todos };
|
|
39642
39643
|
}
|
|
39643
|
-
async function
|
|
39644
|
+
async function removeAssignment(doctype, name, assignee, ctx, failureContext) {
|
|
39645
|
+
let nativeResult;
|
|
39646
|
+
try {
|
|
39647
|
+
nativeResult = await ctx.client.callMethod(UNASSIGNMENT_METHOD, {
|
|
39648
|
+
doctype,
|
|
39649
|
+
name,
|
|
39650
|
+
assign_to: assignee
|
|
39651
|
+
});
|
|
39652
|
+
} catch (error2) {
|
|
39653
|
+
const reason = error2 instanceof Error ? error2.message : String(error2);
|
|
39654
|
+
throw new Error(`${failureContext}: ${reason}`, { cause: error2 });
|
|
39655
|
+
}
|
|
39656
|
+
const todos = Array.isArray(nativeResult) ? nativeResult.map((todo) => {
|
|
39657
|
+
const record2 = todo;
|
|
39658
|
+
return { owner: record2.owner, name: record2.name };
|
|
39659
|
+
}) : [];
|
|
39660
|
+
return { removed: assignee, remaining: todos };
|
|
39661
|
+
}
|
|
39662
|
+
async function fetchDocAfterAssignment(doctype, name, ctx, toolName, action = "assignment") {
|
|
39644
39663
|
try {
|
|
39645
39664
|
return await ctx.client.get(doctype, name);
|
|
39646
39665
|
} catch (error2) {
|
|
39647
39666
|
const reason = error2 instanceof Error ? error2.message : String(error2);
|
|
39648
39667
|
throw new Error(
|
|
39649
|
-
`[${toolName}] ${doctype} ${name}
|
|
39668
|
+
`[${toolName}] ${doctype} ${name} ${action} succeeded, but re-fetching the document failed: ${reason}`,
|
|
39650
39669
|
{ cause: error2 }
|
|
39651
39670
|
);
|
|
39652
39671
|
}
|
|
@@ -42168,6 +42187,66 @@ var operationsTools = [
|
|
|
42168
42187
|
assignment: assignmentInfo
|
|
42169
42188
|
};
|
|
42170
42189
|
}
|
|
42190
|
+
},
|
|
42191
|
+
// ── Generic Unassign ──────────────────────────────────────────────────────
|
|
42192
|
+
{
|
|
42193
|
+
name: "erpnext_doc_unassign",
|
|
42194
|
+
description: "Remove one user's assignment from any ERPNext document through Frappe's native workflow (closes the user's ToDo and resyncs _assign). Works on any DocType. Pass one user per call. Idempotent: removing a user who is not assigned is a no-op on the Frappe side.",
|
|
42195
|
+
category: "operations",
|
|
42196
|
+
inputSchema: {
|
|
42197
|
+
type: "object",
|
|
42198
|
+
properties: {
|
|
42199
|
+
doctype: {
|
|
42200
|
+
type: "string",
|
|
42201
|
+
description: "ERPNext DocType name (e.g. 'Task', 'Issue', 'Opportunity')"
|
|
42202
|
+
},
|
|
42203
|
+
name: {
|
|
42204
|
+
type: "string",
|
|
42205
|
+
description: "Document name/ID (e.g. 'TASK-2026-00001')"
|
|
42206
|
+
},
|
|
42207
|
+
assign_to: {
|
|
42208
|
+
type: "string",
|
|
42209
|
+
description: "User email whose assignment should be removed",
|
|
42210
|
+
minLength: 1
|
|
42211
|
+
}
|
|
42212
|
+
},
|
|
42213
|
+
required: ["doctype", "name", "assign_to"]
|
|
42214
|
+
},
|
|
42215
|
+
handler: async (input, ctx) => {
|
|
42216
|
+
if (!input.doctype) {
|
|
42217
|
+
throw new Error("[erpnext_doc_unassign] 'doctype' is required");
|
|
42218
|
+
}
|
|
42219
|
+
if (!input.name) {
|
|
42220
|
+
throw new Error("[erpnext_doc_unassign] 'name' is required");
|
|
42221
|
+
}
|
|
42222
|
+
if (typeof input.assign_to !== "string" || !input.assign_to.trim()) {
|
|
42223
|
+
throw new Error(
|
|
42224
|
+
"[erpnext_doc_unassign] 'assign_to' must be a non-empty user email"
|
|
42225
|
+
);
|
|
42226
|
+
}
|
|
42227
|
+
const doctype = input.doctype;
|
|
42228
|
+
const name = input.name;
|
|
42229
|
+
const assignee = input.assign_to.trim();
|
|
42230
|
+
const unassignment = await removeAssignment(
|
|
42231
|
+
doctype,
|
|
42232
|
+
name,
|
|
42233
|
+
assignee,
|
|
42234
|
+
ctx,
|
|
42235
|
+
`[erpnext_doc_unassign] ${doctype} ${name} unassignment failed`
|
|
42236
|
+
);
|
|
42237
|
+
const doc = await fetchDocAfterAssignment(
|
|
42238
|
+
doctype,
|
|
42239
|
+
name,
|
|
42240
|
+
ctx,
|
|
42241
|
+
"erpnext_doc_unassign",
|
|
42242
|
+
"unassignment"
|
|
42243
|
+
);
|
|
42244
|
+
return {
|
|
42245
|
+
data: doc,
|
|
42246
|
+
message: `${assignee} unassigned from ${doctype} ${name}`,
|
|
42247
|
+
assignment: unassignment
|
|
42248
|
+
};
|
|
42249
|
+
}
|
|
42171
42250
|
}
|
|
42172
42251
|
];
|
|
42173
42252
|
|
|
@@ -45265,7 +45344,7 @@ async function main() {
|
|
|
45265
45344
|
);
|
|
45266
45345
|
const server = new McpApp({
|
|
45267
45346
|
name: "mcp-erpnext",
|
|
45268
|
-
version: "2.4.
|
|
45347
|
+
version: "2.4.1",
|
|
45269
45348
|
maxConcurrent: 10,
|
|
45270
45349
|
backpressureStrategy: "queue",
|
|
45271
45350
|
validateSchema: true,
|