@casys/mcp-erpnext 3.1.0-beta.3 → 3.1.0-beta.4

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/mcp-erpnext.mjs CHANGED
@@ -37013,6 +37013,8 @@ var bufferToFormData = (arrayBuffer, contentType) => {
37013
37013
  };
37014
37014
 
37015
37015
  // node_modules/hono/dist/utils/body.js
37016
+ var MAX_NESTING_DEPTH = 32;
37017
+ var MAX_NESTED_OBJECTS = 1e4;
37016
37018
  var isRawRequest = (request) => "headers" in request;
37017
37019
  var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
37018
37020
  const { all = false, dot = false } = options;
@@ -37045,6 +37047,7 @@ async function parseFormData(request, options) {
37045
37047
  }
37046
37048
  function convertFormDataToBodyData(formData, options) {
37047
37049
  const form = /* @__PURE__ */ Object.create(null);
37050
+ const nestingState = { count: 0 };
37048
37051
  formData.forEach((value, key) => {
37049
37052
  const shouldParseAllValues = options.all || key.endsWith("[]");
37050
37053
  if (!shouldParseAllValues) {
@@ -37057,7 +37060,7 @@ function convertFormDataToBodyData(formData, options) {
37057
37060
  Object.entries(form).forEach(([key, value]) => {
37058
37061
  const shouldParseDotValues = key.includes(".");
37059
37062
  if (shouldParseDotValues) {
37060
- handleParsingNestedValues(form, key, value);
37063
+ handleParsingNestedValues(form, key, value, nestingState);
37061
37064
  delete form[key];
37062
37065
  }
37063
37066
  });
@@ -37080,23 +37083,32 @@ var handleParsingAllValues = (form, key, value) => {
37080
37083
  }
37081
37084
  }
37082
37085
  };
37083
- var handleParsingNestedValues = (form, key, value) => {
37086
+ var handleParsingNestedValues = (form, key, value, state) => {
37084
37087
  if (/(?:^|\.)__proto__\./.test(key)) {
37085
37088
  return;
37086
37089
  }
37087
37090
  let nestedForm = form;
37088
- const keys = key.split(".");
37091
+ const keys = key.split(".", MAX_NESTING_DEPTH + 2);
37092
+ if (keys.length > MAX_NESTING_DEPTH + 1) {
37093
+ throwNestingLimitExceeded();
37094
+ }
37089
37095
  keys.forEach((key2, index) => {
37090
37096
  if (index === keys.length - 1) {
37091
37097
  nestedForm[key2] = value;
37092
37098
  } else {
37093
37099
  if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
37100
+ if (state.count++ >= MAX_NESTED_OBJECTS) {
37101
+ throwNestingLimitExceeded();
37102
+ }
37094
37103
  nestedForm[key2] = /* @__PURE__ */ Object.create(null);
37095
37104
  }
37096
37105
  nestedForm = nestedForm[key2];
37097
37106
  }
37098
37107
  });
37099
37108
  };
37109
+ var throwNestingLimitExceeded = () => {
37110
+ throw new Error("Nesting limit exceeded");
37111
+ };
37100
37112
 
37101
37113
  // node_modules/hono/dist/utils/url.js
37102
37114
  var splitPath = (path) => {
@@ -37228,6 +37240,10 @@ var _decodeURI = (value) => {
37228
37240
  return tryDecodeURIComponent(value);
37229
37241
  };
37230
37242
  var _getQueryParam = (url2, key, multiple) => {
37243
+ const hashIndex = url2.indexOf("#", 8);
37244
+ if (hashIndex !== -1) {
37245
+ url2 = url2.slice(0, hashIndex);
37246
+ }
37231
37247
  let encoded;
37232
37248
  if (!multiple && key && key.indexOf("%") === -1 && key.indexOf("+") === -1) {
37233
37249
  let keyIndex2 = url2.indexOf("?", 8);
@@ -56452,8 +56468,8 @@ var DOCTYPE_SEND_MESSAGE_HINTS = {
56452
56468
  ],
56453
56469
  "Sales Order": [
56454
56470
  {
56455
- key: "invoice",
56456
- label: "Invoice",
56471
+ key: "invoices",
56472
+ label: "Invoices",
56457
56473
  message: "Show invoices linked to sales order {id}",
56458
56474
  tool: "erpnext_doc_list",
56459
56475
  args: {
@@ -56464,8 +56480,8 @@ var DOCTYPE_SEND_MESSAGE_HINTS = {
56464
56480
  }
56465
56481
  },
56466
56482
  {
56467
- key: "delivery",
56468
- label: "Delivery",
56483
+ key: "deliveries",
56484
+ label: "Delivery notes",
56469
56485
  message: "Show delivery notes for sales order {id}",
56470
56486
  tool: "erpnext_doc_list",
56471
56487
  args: {
@@ -56496,6 +56512,27 @@ var DOCTYPE_SEND_MESSAGE_HINTS = {
56496
56512
  }
56497
56513
  }
56498
56514
  ],
56515
+ "Purchase Invoice": [
56516
+ {
56517
+ key: "payments",
56518
+ label: "Payments",
56519
+ message: "Show payment entries for purchase invoice {id}",
56520
+ tool: "erpnext_doc_list",
56521
+ args: {
56522
+ doctype: "Payment Entry",
56523
+ fields: [
56524
+ "name",
56525
+ "posting_date",
56526
+ "paid_amount",
56527
+ "mode_of_payment",
56528
+ "docstatus"
56529
+ ],
56530
+ filters: [["Payment Entry Reference", "reference_name", "=", "{id}"]],
56531
+ limit: 20
56532
+ },
56533
+ kind: "list"
56534
+ }
56535
+ ],
56499
56536
  "Item": [
56500
56537
  {
56501
56538
  key: "stock",
@@ -56665,68 +56702,69 @@ var INVOICE_ITEM_HINTS = [
56665
56702
  kind: "list"
56666
56703
  }
56667
56704
  ];
56668
- var INVOICE_HINTS = {
56669
- "Sales Invoice": [
56670
- {
56671
- key: "payments",
56672
- label: "Payments",
56673
- message: "Show payment entries for invoice {id}",
56674
- tool: "erpnext_doc_list",
56675
- args: {
56676
- doctype: "Payment Entry",
56677
- fields: [
56678
- "name",
56679
- "posting_date",
56680
- "paid_amount",
56681
- "mode_of_payment",
56682
- "docstatus"
56683
- ],
56684
- filters: [["Payment Entry Reference", "reference_name", "=", "{id}"]],
56685
- limit: 20
56686
- },
56687
- kind: "list"
56688
- },
56689
- {
56705
+ function nonEmptyDocumentString(document, key) {
56706
+ const value = document?.[key];
56707
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
56708
+ }
56709
+ function documentPartyHint(doctype, document) {
56710
+ if (doctype === "Sales Order" || doctype === "Sales Invoice") {
56711
+ const customer = nonEmptyDocumentString(document, "customer");
56712
+ return customer ? {
56690
56713
  key: "customer",
56691
56714
  label: "Customer",
56692
- message: "Show customer {party}",
56715
+ message: `Show customer ${customer}`,
56693
56716
  tool: "erpnext_customer_get",
56694
- args: { name: "{party}" },
56717
+ args: { name: customer },
56695
56718
  kind: "record"
56696
- },
56697
- ...INVOICE_ITEM_HINTS
56698
- ],
56699
- "Purchase Invoice": [
56700
- {
56701
- key: "payments",
56702
- label: "Payments",
56703
- message: "Show payment entries for invoice {id}",
56704
- tool: "erpnext_doc_list",
56705
- args: {
56706
- doctype: "Payment Entry",
56707
- fields: [
56708
- "name",
56709
- "posting_date",
56710
- "paid_amount",
56711
- "mode_of_payment",
56712
- "docstatus"
56713
- ],
56714
- filters: [["Payment Entry Reference", "reference_name", "=", "{id}"]],
56715
- limit: 20
56716
- },
56717
- kind: "list"
56718
- },
56719
- {
56719
+ } : null;
56720
+ }
56721
+ if (doctype === "Purchase Invoice") {
56722
+ const supplier = nonEmptyDocumentString(document, "supplier");
56723
+ return supplier ? {
56720
56724
  key: "supplier",
56721
56725
  label: "Supplier",
56722
- message: "Show supplier {party}",
56726
+ message: `Show supplier ${supplier}`,
56723
56727
  tool: "erpnext_supplier_get",
56724
- args: { name: "{party}" },
56728
+ args: { name: supplier },
56725
56729
  kind: "record"
56726
- },
56727
- ...INVOICE_ITEM_HINTS
56728
- ]
56729
- };
56730
+ } : null;
56731
+ }
56732
+ if (doctype !== "Quotation") return null;
56733
+ const party = nonEmptyDocumentString(document, "party_name");
56734
+ const target = nonEmptyDocumentString(document, "quotation_to");
56735
+ if (!party || target !== "Customer" && target !== "Lead") return null;
56736
+ return target === "Customer" ? {
56737
+ key: "customer",
56738
+ label: "Customer",
56739
+ message: `Show customer ${party}`,
56740
+ tool: "erpnext_customer_get",
56741
+ args: { name: party },
56742
+ kind: "record"
56743
+ } : {
56744
+ key: "lead",
56745
+ label: "Lead",
56746
+ message: `Show lead ${party}`,
56747
+ tool: "erpnext_lead_get",
56748
+ args: { name: party },
56749
+ kind: "record"
56750
+ };
56751
+ }
56752
+ function documentNavigationHints(doctype, document) {
56753
+ const base = DOCTYPE_SEND_MESSAGE_HINTS[doctype] ?? [];
56754
+ const party = documentPartyHint(doctype, document);
56755
+ if (!party) return [...base];
56756
+ return doctype === "Sales Order" ? [party, ...base] : [...base, party];
56757
+ }
56758
+ var INVOICE_LINE_ITEM_DOCTYPES = /* @__PURE__ */ new Set([
56759
+ "Sales Order",
56760
+ "Sales Invoice",
56761
+ "Purchase Invoice",
56762
+ "Quotation"
56763
+ ]);
56764
+ function invoiceNavigationHints(doctype, document) {
56765
+ const documentHints = documentNavigationHints(doctype, document);
56766
+ return INVOICE_LINE_ITEM_DOCTYPES.has(doctype) ? [...documentHints, ...INVOICE_ITEM_HINTS] : documentHints;
56767
+ }
56730
56768
  var STOCK_HINTS = [
56731
56769
  {
56732
56770
  key: "item",
@@ -57174,6 +57212,9 @@ function resultDoctype(result) {
57174
57212
  if (isRecord7(data) && typeof data.doctype === "string") return data.doctype;
57175
57213
  return void 0;
57176
57214
  }
57215
+ function resultDocument(result) {
57216
+ return isRecord7(result.data) ? result.data : void 0;
57217
+ }
57177
57218
  function isInvoiceViewer(result) {
57178
57219
  const uri = result._meta?.ui?.resourceUri;
57179
57220
  return uri === "ui://mcp-erpnext/invoice-viewer";
@@ -57409,8 +57450,8 @@ function withUiRefreshRequest(result, toolName, args, now = /* @__PURE__ */ new
57409
57450
  }
57410
57451
  }
57411
57452
  if (isDoclistResult(enriched) && isDoclistViewer(enriched) && !enriched._sendMessageHints) {
57412
- const hints = DOCTYPE_SEND_MESSAGE_HINTS[enriched.doctype];
57413
- if (hints) {
57453
+ const hints = documentNavigationHints(enriched.doctype);
57454
+ if (hints.length > 0) {
57414
57455
  enriched._sendMessageHints = hints;
57415
57456
  }
57416
57457
  }
@@ -57450,13 +57491,13 @@ function withUiRefreshRequest(result, toolName, args, now = /* @__PURE__ */ new
57450
57491
  if (!enriched._sendMessageHints) {
57451
57492
  const doctype = resultDoctype(enriched);
57452
57493
  if (isInvoiceViewer(enriched) && doctype) {
57453
- const hints = INVOICE_HINTS[doctype];
57454
- if (hints) enriched._sendMessageHints = hints;
57494
+ const hints = invoiceNavigationHints(doctype, resultDocument(enriched));
57495
+ if (hints.length > 0) enriched._sendMessageHints = hints;
57455
57496
  } else if (isStockViewer(enriched)) {
57456
57497
  enriched._sendMessageHints = STOCK_HINTS;
57457
57498
  } else if ((isKanbanViewer(enriched) || isDocViewer(enriched)) && doctype) {
57458
- const hints = DOCTYPE_SEND_MESSAGE_HINTS[doctype];
57459
- if (hints) enriched._sendMessageHints = hints;
57499
+ const hints = documentNavigationHints(doctype, resultDocument(enriched));
57500
+ if (hints.length > 0) enriched._sendMessageHints = hints;
57460
57501
  }
57461
57502
  }
57462
57503
  return filterNavJumpsByAvailableTools(enriched, availableToolNames);
@@ -57640,6 +57681,17 @@ var UI_VIEWERS = [
57640
57681
  "funnel-viewer",
57641
57682
  "kanban-viewer"
57642
57683
  ];
57684
+ var VIEWER_RESOURCE_META = {
57685
+ ui: {
57686
+ csp: {
57687
+ resourceDomains: ["blob:"],
57688
+ frameDomains: ["blob:"]
57689
+ }
57690
+ }
57691
+ };
57692
+ function withViewerResourceMeta(resource) {
57693
+ return { ...resource, _meta: VIEWER_RESOURCE_META };
57694
+ }
57643
57695
 
57644
57696
  // src/ui/viewer-resource-paths.ts
57645
57697
  function fileUrlToPath2(url2) {
@@ -57901,7 +57953,7 @@ async function main() {
57901
57953
  );
57902
57954
  const server = new McpApp({
57903
57955
  name: "mcp-erpnext",
57904
- version: "3.1.0-beta.3",
57956
+ version: "3.1.0-beta.4",
57905
57957
  transport: "stateless",
57906
57958
  cache: {
57907
57959
  ttlMs: 36e5,
@@ -57932,15 +57984,19 @@ async function main() {
57932
57984
  const humanName = viewerName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
57933
57985
  if (distPath) {
57934
57986
  server.registerResource(
57935
- {
57987
+ withViewerResourceMeta({
57936
57988
  uri: resourceUri,
57937
57989
  name: `ERPNext ${humanName}`,
57938
57990
  description: `ERPNext UI: ${viewerName}`,
57939
57991
  mimeType: MCP_APP_MIME_TYPE
57940
- },
57992
+ }),
57941
57993
  async () => {
57942
57994
  const html = await readViewerDist(distPath, readTextFile6);
57943
- return { uri: resourceUri, mimeType: MCP_APP_MIME_TYPE, text: html };
57995
+ return withViewerResourceMeta({
57996
+ uri: resourceUri,
57997
+ mimeType: MCP_APP_MIME_TYPE,
57998
+ text: html
57999
+ });
57944
58000
  }
57945
58001
  );
57946
58002
  console.error(`[mcp-erpnext] Registered UI resource: ${resourceUri}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@casys/mcp-erpnext",
3
- "version": "3.1.0-beta.3",
3
+ "version": "3.1.0-beta.4",
4
4
  "description": "MCP server for ERPNext with interactive UI viewers",
5
5
  "type": "module",
6
6
  "bin": {