@casys/mcp-erpnext 3.1.0-beta.1 → 3.1.0-beta.2

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
@@ -47185,6 +47185,7 @@ var viewer = (name) => ({
47185
47185
  ui: { resourceUri: `ui://mcp-erpnext/${name}` }
47186
47186
  });
47187
47187
  var DOCLIST_META = viewer("doclist-viewer");
47188
+ var DOC_META = viewer("doc-viewer");
47188
47189
  var INVOICE_META = viewer("invoice-viewer");
47189
47190
  var STOCK_META = viewer("stock-viewer");
47190
47191
  var CHART_META = viewer("chart-viewer");
@@ -47284,6 +47285,115 @@ function stableStringify(value) {
47284
47285
  var DEFAULT_RETRY_STATUSES = [408, 429, 502, 503, 504];
47285
47286
  var DEFAULT_RETRY_METHODS = ["GET"];
47286
47287
  var DEFAULT_MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
47288
+ var DEFAULT_MAX_DOWNLOAD_BYTES = 10 * 1024 * 1024;
47289
+ var MAX_ERROR_BODY_BYTES = 64 * 1024;
47290
+ var ResponseBodyLimitError = class extends Error {
47291
+ constructor(label, size, limit) {
47292
+ super(
47293
+ `[FrappeClient] ${label} size ${size} bytes exceeds the ${limit}-byte limit`
47294
+ );
47295
+ this.name = "ResponseBodyLimitError";
47296
+ }
47297
+ };
47298
+ async function readResponseBytes(response, maxBytes, label) {
47299
+ const declaredLength = response.headers.get("content-length")?.trim();
47300
+ if (declaredLength && /^\d+$/.test(declaredLength)) {
47301
+ const size = Number(declaredLength);
47302
+ if (size > maxBytes) {
47303
+ try {
47304
+ await response.body?.cancel();
47305
+ } catch {
47306
+ }
47307
+ throw new ResponseBodyLimitError(label, size, maxBytes);
47308
+ }
47309
+ }
47310
+ if (!response.body) return new Uint8Array();
47311
+ const reader = response.body.getReader();
47312
+ const chunks = [];
47313
+ let total = 0;
47314
+ try {
47315
+ while (true) {
47316
+ const { done, value } = await reader.read();
47317
+ if (done) break;
47318
+ total += value.byteLength;
47319
+ if (total > maxBytes) {
47320
+ try {
47321
+ await reader.cancel();
47322
+ } catch {
47323
+ }
47324
+ throw new ResponseBodyLimitError(label, total, maxBytes);
47325
+ }
47326
+ chunks.push(value);
47327
+ }
47328
+ } finally {
47329
+ reader.releaseLock();
47330
+ }
47331
+ const bytes = new Uint8Array(total);
47332
+ let offset = 0;
47333
+ for (const chunk of chunks) {
47334
+ bytes.set(chunk, offset);
47335
+ offset += chunk.byteLength;
47336
+ }
47337
+ return bytes;
47338
+ }
47339
+ function isUnsafeControl(character) {
47340
+ const codePoint = character.codePointAt(0) ?? 0;
47341
+ return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297;
47342
+ }
47343
+ function validateLocalFileUrl(fileUrl, isPrivate) {
47344
+ if (typeof fileUrl !== "string" || fileUrl.length === 0) {
47345
+ throw new Error("[FrappeClient] File.file_url must be a non-empty string");
47346
+ }
47347
+ if (fileUrl !== fileUrl.trim()) {
47348
+ throw new Error(
47349
+ "[FrappeClient] File.file_url must not contain whitespace padding"
47350
+ );
47351
+ }
47352
+ const expectedPrefix = isPrivate ? "/private/files/" : "/files/";
47353
+ let decoded = fileUrl;
47354
+ for (let depth = 0; depth < 5; depth++) {
47355
+ if (Array.from(decoded).some(isUnsafeControl) || decoded.includes("\\") || decoded.includes("?") || decoded.includes("#")) {
47356
+ throw new Error(
47357
+ "[FrappeClient] File.file_url must be a local Frappe file path without query, fragment, NUL, or backslash"
47358
+ );
47359
+ }
47360
+ if (!decoded.startsWith(expectedPrefix)) {
47361
+ throw new Error(
47362
+ `[FrappeClient] File.file_url does not match is_private=${isPrivate ? 1 : 0}`
47363
+ );
47364
+ }
47365
+ const fileSegment = decoded.slice(expectedPrefix.length);
47366
+ if (!fileSegment || fileSegment === "." || fileSegment === ".." || fileSegment.includes("/")) {
47367
+ throw new Error(
47368
+ "[FrappeClient] File.file_url must contain exactly one safe file segment"
47369
+ );
47370
+ }
47371
+ let next;
47372
+ try {
47373
+ next = decodeURIComponent(decoded);
47374
+ } catch {
47375
+ throw new Error(
47376
+ "[FrappeClient] File.file_url contains malformed percent encoding"
47377
+ );
47378
+ }
47379
+ if (next === decoded) return fileUrl;
47380
+ decoded = next;
47381
+ }
47382
+ throw new Error(
47383
+ "[FrappeClient] File.file_url contains excessive nested percent encoding"
47384
+ );
47385
+ }
47386
+ function sanitizeDownloadFileName(raw2, fileId) {
47387
+ const fallbackId = fileId.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80) || "file";
47388
+ if (typeof raw2 !== "string") return `erpnext-${fallbackId}`;
47389
+ const normalized = Array.from(raw2.normalize("NFC")).filter((character) => !isUnsafeControl(character)).join("").replace(/[\\/:*?"<>|]/g, "_").trim().replace(/[. ]+$/g, "");
47390
+ const bounded = Array.from(normalized).slice(0, 255).join("");
47391
+ return !bounded || bounded === "." || bounded === ".." ? `erpnext-${fallbackId}` : bounded;
47392
+ }
47393
+ function normalizeMimeType(raw2) {
47394
+ const candidate = (raw2 ?? "").split(";", 1)[0].trim().toLowerCase();
47395
+ return /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/i.test(candidate) && candidate !== "application/unknown" ? candidate : "application/octet-stream";
47396
+ }
47287
47397
  function decodeBase64File(contentBase64, maxBytes) {
47288
47398
  if (contentBase64.length === 0) {
47289
47399
  throw new Error("[FrappeClient] File content must not be empty");
@@ -47369,6 +47479,7 @@ var FrappeClient = class {
47369
47479
  authHeader;
47370
47480
  timeoutMs;
47371
47481
  maxUploadBytes;
47482
+ maxDownloadBytes;
47372
47483
  retries;
47373
47484
  retryStatuses;
47374
47485
  retryBackoffMs;
@@ -47384,6 +47495,12 @@ var FrappeClient = class {
47384
47495
  "[FrappeClient] maxUploadBytes must be a positive integer"
47385
47496
  );
47386
47497
  }
47498
+ this.maxDownloadBytes = config2.maxDownloadBytes ?? DEFAULT_MAX_DOWNLOAD_BYTES;
47499
+ if (!Number.isInteger(this.maxDownloadBytes) || this.maxDownloadBytes <= 0) {
47500
+ throw new Error(
47501
+ "[FrappeClient] maxDownloadBytes must be a positive integer"
47502
+ );
47503
+ }
47387
47504
  this.retries = config2.retries ?? 3;
47388
47505
  this.retryStatuses = config2.retryStatuses ?? DEFAULT_RETRY_STATUSES;
47389
47506
  this.retryBackoffMs = config2.retryBackoffMs ?? 200;
@@ -47498,6 +47615,152 @@ var FrappeClient = class {
47498
47615
  }
47499
47616
  return responseBody;
47500
47617
  }
47618
+ buildSameOriginUrl(path) {
47619
+ if (!path.startsWith("/")) {
47620
+ throw new Error("[FrappeClient] Download path must be root-relative");
47621
+ }
47622
+ let base;
47623
+ let target;
47624
+ try {
47625
+ base = new URL(this.baseUrl);
47626
+ target = new URL(`${this.baseUrl}${path}`);
47627
+ } catch {
47628
+ throw new Error("[FrappeClient] baseUrl must be a valid absolute URL");
47629
+ }
47630
+ if (target.origin !== base.origin) {
47631
+ throw new Error("[FrappeClient] Refusing a cross-origin download");
47632
+ }
47633
+ return target.href;
47634
+ }
47635
+ async requestBinary(path) {
47636
+ let lastError;
47637
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
47638
+ try {
47639
+ return await this.requestBinaryOnce(path);
47640
+ } catch (err) {
47641
+ lastError = err;
47642
+ if (attempt === this.retries || !this.isRetryable(err, "GET")) {
47643
+ throw err;
47644
+ }
47645
+ const delay = this.computeBackoff(attempt, err);
47646
+ if (delay > 0) {
47647
+ await new Promise((resolve) => setTimeout(resolve, delay));
47648
+ }
47649
+ }
47650
+ }
47651
+ throw lastError;
47652
+ }
47653
+ async requestBinaryOnce(path) {
47654
+ const url2 = this.buildSameOriginUrl(path);
47655
+ const controller = new AbortController();
47656
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
47657
+ let response;
47658
+ try {
47659
+ response = await fetch(url2, {
47660
+ method: "GET",
47661
+ headers: {
47662
+ "Authorization": this.authHeader,
47663
+ "Accept": "*/*",
47664
+ "Cache-Control": "no-store"
47665
+ },
47666
+ cache: "no-store",
47667
+ redirect: "manual",
47668
+ signal: controller.signal
47669
+ });
47670
+ } catch (err) {
47671
+ clearTimeout(timer);
47672
+ if (err instanceof Error && err.name === "AbortError") {
47673
+ throw new FrappeAPIError(
47674
+ `Request timed out after ${this.timeoutMs}ms: GET ${path}`,
47675
+ 408,
47676
+ null
47677
+ );
47678
+ }
47679
+ throw new FrappeAPIError(
47680
+ `Network error on GET ${path}: ${err.message}`,
47681
+ 0,
47682
+ null
47683
+ );
47684
+ }
47685
+ try {
47686
+ if (response.redirected || response.type === "opaqueredirect" || response.status >= 300 && response.status < 400) {
47687
+ try {
47688
+ await response.body?.cancel();
47689
+ } catch {
47690
+ }
47691
+ throw new FrappeAPIError(
47692
+ `GET ${path} refused an HTTP redirect`,
47693
+ response.status,
47694
+ null
47695
+ );
47696
+ }
47697
+ if (!response.ok) {
47698
+ let rawText;
47699
+ try {
47700
+ const errorBytes = await readResponseBytes(
47701
+ response,
47702
+ MAX_ERROR_BODY_BYTES,
47703
+ "Error response body"
47704
+ );
47705
+ rawText = new TextDecoder().decode(errorBytes);
47706
+ } catch (err) {
47707
+ if (!(err instanceof ResponseBodyLimitError)) throw err;
47708
+ rawText = err.message;
47709
+ }
47710
+ const contentType = response.headers.get("content-type") ?? "";
47711
+ let responseBody = rawText;
47712
+ if (contentType.includes("application/json") && rawText.length > 0) {
47713
+ try {
47714
+ responseBody = JSON.parse(rawText);
47715
+ } catch {
47716
+ responseBody = rawText;
47717
+ }
47718
+ }
47719
+ let message2 = response.statusText;
47720
+ if (typeof responseBody === "object" && responseBody !== null) {
47721
+ const body = responseBody;
47722
+ const baseMessage = body.message ?? body.exc_type ?? response.statusText;
47723
+ const serverDetails = extractServerMessages(body._server_messages);
47724
+ message2 = serverDetails ? `${baseMessage}: ${serverDetails}` : baseMessage;
47725
+ } else if (typeof responseBody === "string" && responseBody.length > 0) {
47726
+ message2 = responseBody.slice(0, 200);
47727
+ }
47728
+ throw new FrappeAPIError(
47729
+ `GET ${path} failed: ${message2}`,
47730
+ response.status,
47731
+ responseBody,
47732
+ parseRetryAfter(response.headers.get("retry-after"))
47733
+ );
47734
+ }
47735
+ const bytes = await readResponseBytes(
47736
+ response,
47737
+ this.maxDownloadBytes,
47738
+ "Downloaded file"
47739
+ );
47740
+ return {
47741
+ bytes,
47742
+ mimeType: normalizeMimeType(response.headers.get("content-type"))
47743
+ };
47744
+ } catch (err) {
47745
+ if (err instanceof FrappeAPIError || err instanceof ResponseBodyLimitError) {
47746
+ throw err;
47747
+ }
47748
+ if (err instanceof Error && err.name === "AbortError") {
47749
+ throw new FrappeAPIError(
47750
+ `Request timed out after ${this.timeoutMs}ms: GET ${path}`,
47751
+ 408,
47752
+ null
47753
+ );
47754
+ }
47755
+ throw new FrappeAPIError(
47756
+ `Network error while reading GET ${path}: ${err.message}`,
47757
+ 0,
47758
+ null
47759
+ );
47760
+ } finally {
47761
+ clearTimeout(timer);
47762
+ }
47763
+ }
47501
47764
  // ── Resource CRUD ───────────────────────────────────────────────────────────
47502
47765
  /**
47503
47766
  * List documents of a DocType.
@@ -47619,6 +47882,73 @@ var FrappeClient = class {
47619
47882
  );
47620
47883
  this.invalidate(doctype, name);
47621
47884
  }
47885
+ /**
47886
+ * Download one File row through Frappe's authenticated download handler.
47887
+ *
47888
+ * The caller supplies the expected attachment owner as a confused-deputy
47889
+ * guard. The File row is fetched fresh, its local path is validated, and the
47890
+ * response stream is stopped as soon as it exceeds `maxDownloadBytes`.
47891
+ */
47892
+ async downloadFile(input) {
47893
+ const fileId = input.fileId.trim();
47894
+ const attachedToDoctype = input.attachedToDoctype.trim();
47895
+ const attachedToName = input.attachedToName.trim();
47896
+ if (!fileId) {
47897
+ throw new Error("[FrappeClient] fileId must not be empty");
47898
+ }
47899
+ if (!attachedToDoctype) {
47900
+ throw new Error("[FrappeClient] attachedToDoctype must not be empty");
47901
+ }
47902
+ if (!attachedToName) {
47903
+ throw new Error("[FrappeClient] attachedToName must not be empty");
47904
+ }
47905
+ const metadata = await this.request(
47906
+ "GET",
47907
+ `/api/resource/File/${encodeURIComponent(fileId)}`
47908
+ );
47909
+ const file = metadata.data;
47910
+ if (file.name !== fileId) {
47911
+ throw new Error("[FrappeClient] File response identity mismatch");
47912
+ }
47913
+ if (file.attached_to_doctype !== attachedToDoctype || file.attached_to_name !== attachedToName) {
47914
+ throw new Error(
47915
+ "[FrappeClient] File is not attached to the requested document"
47916
+ );
47917
+ }
47918
+ if (file.is_private !== 0 && file.is_private !== 1) {
47919
+ throw new Error("[FrappeClient] File.is_private must be 0 or 1");
47920
+ }
47921
+ const fileUrl = validateLocalFileUrl(
47922
+ file.file_url,
47923
+ file.is_private === 1
47924
+ );
47925
+ const fileSize = file.file_size;
47926
+ if (fileSize !== void 0 && fileSize !== null) {
47927
+ if (typeof fileSize !== "number" || !Number.isInteger(fileSize) || fileSize < 0) {
47928
+ throw new Error(
47929
+ "[FrappeClient] File.file_size must be a non-negative integer"
47930
+ );
47931
+ }
47932
+ if (fileSize > this.maxDownloadBytes) {
47933
+ throw new ResponseBodyLimitError(
47934
+ "File metadata",
47935
+ fileSize,
47936
+ this.maxDownloadBytes
47937
+ );
47938
+ }
47939
+ }
47940
+ const params = new URLSearchParams({ file_url: fileUrl });
47941
+ const download = await this.requestBinary(
47942
+ `/api/method/frappe.handler.download_file?${params.toString()}`
47943
+ );
47944
+ return {
47945
+ fileId,
47946
+ fileName: sanitizeDownloadFileName(file.file_name, fileId),
47947
+ mimeType: download.mimeType,
47948
+ bytes: download.bytes,
47949
+ isPrivate: file.is_private === 1
47950
+ };
47951
+ }
47622
47952
  /**
47623
47953
  * Upload file bytes and attach the native File document to another document.
47624
47954
  * POST /api/method/upload_file
@@ -47689,6 +48019,7 @@ function getFrappeClient() {
47689
48019
  const apiKey = env6("ERPNEXT_API_KEY");
47690
48020
  const apiSecret = env6("ERPNEXT_API_SECRET");
47691
48021
  const maxUploadBytesRaw = env6("ERPNEXT_MAX_UPLOAD_BYTES");
48022
+ const maxDownloadBytesRaw = env6("ERPNEXT_MAX_DOWNLOAD_BYTES");
47692
48023
  if (!url2) {
47693
48024
  throw new Error(
47694
48025
  "[lib/erpnext] ERPNEXT_URL is required. Set it to your ERPNext instance URL, e.g. http://localhost:8000"
@@ -47704,7 +48035,8 @@ function getFrappeClient() {
47704
48035
  apiKey,
47705
48036
  apiSecret,
47706
48037
  cache: getCache(),
47707
- maxUploadBytes: maxUploadBytesRaw?.trim() ? Number(maxUploadBytesRaw) : void 0
48038
+ maxUploadBytes: maxUploadBytesRaw?.trim() ? Number(maxUploadBytesRaw) : void 0,
48039
+ maxDownloadBytes: maxDownloadBytesRaw?.trim() ? Number(maxDownloadBytesRaw) : void 0
47708
48040
  });
47709
48041
  return _client;
47710
48042
  }
@@ -47929,6 +48261,7 @@ var salesTools = [
47929
48261
  {
47930
48262
  name: "erpnext_customer_get",
47931
48263
  annotations: { readOnlyHint: true },
48264
+ _meta: DOC_META,
47932
48265
  description: "Get a single ERPNext customer by name (ID). Returns all fields including contact details.",
47933
48266
  category: "sales",
47934
48267
  inputSchema: {
@@ -47943,7 +48276,7 @@ var salesTools = [
47943
48276
  throw new Error("[erpnext_customer_get] 'name' is required");
47944
48277
  }
47945
48278
  const doc = await ctx.client.get("Customer", input.name);
47946
- return { data: doc };
48279
+ return { data: { ...doc, doctype: "Customer" } };
47947
48280
  }
47948
48281
  },
47949
48282
  {
@@ -48851,6 +49184,7 @@ var inventoryTools = [
48851
49184
  {
48852
49185
  name: "erpnext_item_get",
48853
49186
  annotations: { readOnlyHint: true },
49187
+ _meta: DOC_META,
48854
49188
  description: "Get a single ERPNext Item by name/item_code. Returns all fields including pricing and stock details.",
48855
49189
  category: "inventory",
48856
49190
  inputSchema: {
@@ -48865,7 +49199,7 @@ var inventoryTools = [
48865
49199
  throw new Error("[erpnext_item_get] 'name' is required");
48866
49200
  }
48867
49201
  const doc = await ctx.client.get("Item", input.name);
48868
- return { data: doc };
49202
+ return { data: { ...doc, doctype: "Item" } };
48869
49203
  }
48870
49204
  },
48871
49205
  {
@@ -49135,6 +49469,7 @@ var inventoryTools = [
49135
49469
  {
49136
49470
  name: "erpnext_stock_entry_get",
49137
49471
  annotations: { readOnlyHint: true },
49472
+ _meta: DOC_META,
49138
49473
  description: "Get a single Stock Entry by name. Returns full document with item details.",
49139
49474
  category: "inventory",
49140
49475
  inputSchema: {
@@ -49152,7 +49487,7 @@ var inventoryTools = [
49152
49487
  throw new Error("[erpnext_stock_entry_get] 'name' is required");
49153
49488
  }
49154
49489
  const doc = await ctx.client.get("Stock Entry", input.name);
49155
- return { data: doc };
49490
+ return { data: { ...doc, doctype: "Stock Entry" } };
49156
49491
  }
49157
49492
  },
49158
49493
  {
@@ -49347,6 +49682,7 @@ var accountingTools = [
49347
49682
  {
49348
49683
  name: "erpnext_journal_entry_get",
49349
49684
  annotations: { readOnlyHint: true },
49685
+ _meta: DOC_META,
49350
49686
  description: "Get a single Journal Entry by name (e.g. JV-00001). Returns full document with accounts.",
49351
49687
  category: "accounting",
49352
49688
  inputSchema: {
@@ -49364,7 +49700,7 @@ var accountingTools = [
49364
49700
  throw new Error("[erpnext_journal_entry_get] 'name' is required");
49365
49701
  }
49366
49702
  const doc = await ctx.client.get("Journal Entry", input.name);
49367
- return { data: doc };
49703
+ return { data: { ...doc, doctype: "Journal Entry" } };
49368
49704
  }
49369
49705
  },
49370
49706
  // ── Payment Entries ───────────────────────────────────────────────────────
@@ -49452,6 +49788,7 @@ var accountingTools = [
49452
49788
  {
49453
49789
  name: "erpnext_payment_entry_get",
49454
49790
  annotations: { readOnlyHint: true },
49791
+ _meta: DOC_META,
49455
49792
  description: "Get a single Payment Entry by name (e.g. PE-00001). Returns full document including references.",
49456
49793
  category: "accounting",
49457
49794
  inputSchema: {
@@ -49469,7 +49806,7 @@ var accountingTools = [
49469
49806
  throw new Error("[erpnext_payment_entry_get] 'name' is required");
49470
49807
  }
49471
49808
  const doc = await ctx.client.get("Payment Entry", input.name);
49472
- return { data: doc };
49809
+ return { data: { ...doc, doctype: "Payment Entry" } };
49473
49810
  }
49474
49811
  },
49475
49812
  {
@@ -49595,6 +49932,7 @@ var hrTools = [
49595
49932
  {
49596
49933
  name: "erpnext_employee_get",
49597
49934
  annotations: { readOnlyHint: true },
49935
+ _meta: DOC_META,
49598
49936
  description: "Get a single Employee by name/ID (e.g. HR-EMP-00001). Returns all fields.",
49599
49937
  category: "hr",
49600
49938
  inputSchema: {
@@ -49619,7 +49957,7 @@ var hrTools = [
49619
49957
  { allowPartialMatch: false, inputPath: "name" }
49620
49958
  );
49621
49959
  const doc = await ctx.client.get("Employee", employeeId);
49622
- return { data: doc };
49960
+ return { data: { ...doc, doctype: "Employee" } };
49623
49961
  }
49624
49962
  },
49625
49963
  // ── Attendance ────────────────────────────────────────────────────────────
@@ -49770,6 +50108,7 @@ var hrTools = [
49770
50108
  {
49771
50109
  name: "erpnext_leave_application_get",
49772
50110
  annotations: { readOnlyHint: true },
50111
+ _meta: DOC_META,
49773
50112
  description: "Get a single Leave Application by name. Returns full document.",
49774
50113
  category: "hr",
49775
50114
  inputSchema: {
@@ -49787,7 +50126,7 @@ var hrTools = [
49787
50126
  "Leave Application",
49788
50127
  input.name
49789
50128
  );
49790
- return { data: doc };
50129
+ return { data: { ...doc, doctype: "Leave Application" } };
49791
50130
  }
49792
50131
  },
49793
50132
  {
@@ -49933,6 +50272,7 @@ var hrTools = [
49933
50272
  {
49934
50273
  name: "erpnext_salary_slip_get",
49935
50274
  annotations: { readOnlyHint: true },
50275
+ _meta: DOC_META,
49936
50276
  description: "Get a single Salary Slip by name/ID. Returns all fields including earnings and deductions.",
49937
50277
  category: "hr",
49938
50278
  inputSchema: {
@@ -49950,7 +50290,7 @@ var hrTools = [
49950
50290
  throw new Error("[erpnext_salary_slip_get] 'name' is required");
49951
50291
  }
49952
50292
  const doc = await ctx.client.get("Salary Slip", input.name);
49953
- return { data: doc };
50293
+ return { data: { ...doc, doctype: "Salary Slip" } };
49954
50294
  }
49955
50295
  },
49956
50296
  // ── Payroll Entries ───────────────────────────────────────────────────────
@@ -50441,6 +50781,7 @@ var projectTools = [
50441
50781
  {
50442
50782
  name: "erpnext_project_get",
50443
50783
  annotations: { readOnlyHint: true },
50784
+ _meta: DOC_META,
50444
50785
  description: "Get a single Project by name. Returns full document including tasks summary.",
50445
50786
  category: "project",
50446
50787
  inputSchema: {
@@ -50455,7 +50796,7 @@ var projectTools = [
50455
50796
  throw new Error("[erpnext_project_get] 'name' is required");
50456
50797
  }
50457
50798
  const doc = await ctx.client.get("Project", input.name);
50458
- return { data: doc };
50799
+ return { data: { ...doc, doctype: "Project" } };
50459
50800
  }
50460
50801
  },
50461
50802
  // ── Tasks ─────────────────────────────────────────────────────────────────
@@ -50620,6 +50961,7 @@ var projectTools = [
50620
50961
  {
50621
50962
  name: "erpnext_task_get",
50622
50963
  annotations: { readOnlyHint: true },
50964
+ _meta: DOC_META,
50623
50965
  description: "Get a single Task by name. Returns full document including description and dependencies.",
50624
50966
  category: "project",
50625
50967
  inputSchema: {
@@ -50634,7 +50976,7 @@ var projectTools = [
50634
50976
  throw new Error("[erpnext_task_get] 'name' is required");
50635
50977
  }
50636
50978
  const doc = await ctx.client.get("Task", input.name);
50637
- return { data: doc };
50979
+ return { data: { ...doc, doctype: "Task" } };
50638
50980
  }
50639
50981
  },
50640
50982
  {
@@ -50810,6 +51152,7 @@ var projectTools = [
50810
51152
  {
50811
51153
  name: "erpnext_timesheet_get",
50812
51154
  annotations: { readOnlyHint: true },
51155
+ _meta: DOC_META,
50813
51156
  description: "Get a single Timesheet by name. Returns full document with time log details.",
50814
51157
  category: "project",
50815
51158
  inputSchema: {
@@ -50824,7 +51167,7 @@ var projectTools = [
50824
51167
  throw new Error("[erpnext_timesheet_get] 'name' is required");
50825
51168
  }
50826
51169
  const doc = await ctx.client.get("Timesheet", input.name);
50827
- return { data: doc };
51170
+ return { data: { ...doc, doctype: "Timesheet" } };
50828
51171
  }
50829
51172
  },
50830
51173
  {
@@ -50946,6 +51289,7 @@ var purchasingTools = [
50946
51289
  {
50947
51290
  name: "erpnext_supplier_get",
50948
51291
  annotations: { readOnlyHint: true },
51292
+ _meta: DOC_META,
50949
51293
  description: "Get a single ERPNext supplier by name (ID). Returns all fields including contact details.",
50950
51294
  category: "purchasing",
50951
51295
  inputSchema: {
@@ -50960,7 +51304,7 @@ var purchasingTools = [
50960
51304
  throw new Error("[erpnext_supplier_get] 'name' is required");
50961
51305
  }
50962
51306
  const doc = await ctx.client.get("Supplier", input.name);
50963
- return { data: doc };
51307
+ return { data: { ...doc, doctype: "Supplier" } };
50964
51308
  }
50965
51309
  },
50966
51310
  {
@@ -51089,6 +51433,7 @@ var purchasingTools = [
51089
51433
  {
51090
51434
  name: "erpnext_purchase_order_get",
51091
51435
  annotations: { readOnlyHint: true },
51436
+ _meta: DOC_META,
51092
51437
  description: "Get a single Purchase Order by name (e.g. PO-00001). Returns full document with line items.",
51093
51438
  category: "purchasing",
51094
51439
  inputSchema: {
@@ -51106,7 +51451,7 @@ var purchasingTools = [
51106
51451
  throw new Error("[erpnext_purchase_order_get] 'name' is required");
51107
51452
  }
51108
51453
  const doc = await ctx.client.get("Purchase Order", input.name);
51109
- return { data: doc };
51454
+ return { data: { ...doc, doctype: "Purchase Order" } };
51110
51455
  }
51111
51456
  },
51112
51457
  {
@@ -51257,6 +51602,7 @@ var purchasingTools = [
51257
51602
  {
51258
51603
  name: "erpnext_purchase_invoice_get",
51259
51604
  annotations: { readOnlyHint: true },
51605
+ _meta: DOC_META,
51260
51606
  description: "Get a single Purchase Invoice by name (e.g. PINV-00001). Returns full document with line items.",
51261
51607
  category: "purchasing",
51262
51608
  inputSchema: {
@@ -51277,7 +51623,7 @@ var purchasingTools = [
51277
51623
  "Purchase Invoice",
51278
51624
  input.name
51279
51625
  );
51280
- return { data: doc };
51626
+ return { data: { ...doc, doctype: "Purchase Invoice" } };
51281
51627
  }
51282
51628
  },
51283
51629
  // ── Purchase Receipts ─────────────────────────────────────────────────────
@@ -51351,6 +51697,7 @@ var purchasingTools = [
51351
51697
  {
51352
51698
  name: "erpnext_purchase_receipt_get",
51353
51699
  annotations: { readOnlyHint: true },
51700
+ _meta: DOC_META,
51354
51701
  description: "Get a single Purchase Receipt by name (e.g. MAT-PRE-00001). Returns full document with received items.",
51355
51702
  category: "purchasing",
51356
51703
  inputSchema: {
@@ -51371,7 +51718,7 @@ var purchasingTools = [
51371
51718
  "Purchase Receipt",
51372
51719
  input.name
51373
51720
  );
51374
- return { data: doc };
51721
+ return { data: { ...doc, doctype: "Purchase Receipt" } };
51375
51722
  }
51376
51723
  },
51377
51724
  // ── Supplier Quotations ───────────────────────────────────────────────────
@@ -51516,6 +51863,7 @@ var deliveryTools = [
51516
51863
  {
51517
51864
  name: "erpnext_delivery_note_get",
51518
51865
  annotations: { readOnlyHint: true },
51866
+ _meta: DOC_META,
51519
51867
  description: "Get a single Delivery Note by name (e.g. MAT-DN-00001). Returns full document with delivered items.",
51520
51868
  category: "delivery",
51521
51869
  inputSchema: {
@@ -51533,7 +51881,7 @@ var deliveryTools = [
51533
51881
  throw new Error("[erpnext_delivery_note_get] 'name' is required");
51534
51882
  }
51535
51883
  const doc = await ctx.client.get("Delivery Note", input.name);
51536
- return { data: doc };
51884
+ return { data: { ...doc, doctype: "Delivery Note" } };
51537
51885
  }
51538
51886
  },
51539
51887
  {
@@ -51666,6 +52014,7 @@ var deliveryTools = [
51666
52014
  {
51667
52015
  name: "erpnext_shipment_get",
51668
52016
  annotations: { readOnlyHint: true },
52017
+ _meta: DOC_META,
51669
52018
  description: "Get a single Shipment by name. Returns full shipment details including parcels.",
51670
52019
  category: "delivery",
51671
52020
  inputSchema: {
@@ -51680,7 +52029,7 @@ var deliveryTools = [
51680
52029
  throw new Error("[erpnext_shipment_get] 'name' is required");
51681
52030
  }
51682
52031
  const doc = await ctx.client.get("Shipment", input.name);
51683
- return { data: doc };
52032
+ return { data: { ...doc, doctype: "Shipment" } };
51684
52033
  }
51685
52034
  }
51686
52035
  ];
@@ -51760,6 +52109,7 @@ var manufacturingTools = [
51760
52109
  {
51761
52110
  name: "erpnext_bom_get",
51762
52111
  annotations: { readOnlyHint: true },
52112
+ _meta: DOC_META,
51763
52113
  description: "Get a single BOM by name (e.g. BOM-ITEM-00001). Returns full document with raw materials and operations.",
51764
52114
  category: "manufacturing",
51765
52115
  inputSchema: {
@@ -51774,7 +52124,7 @@ var manufacturingTools = [
51774
52124
  throw new Error("[erpnext_bom_get] 'name' is required");
51775
52125
  }
51776
52126
  const doc = await ctx.client.get("BOM", input.name);
51777
- return { data: doc };
52127
+ return { data: { ...doc, doctype: "BOM" } };
51778
52128
  }
51779
52129
  },
51780
52130
  // ── Work Orders ───────────────────────────────────────────────────────────
@@ -51852,6 +52202,7 @@ var manufacturingTools = [
51852
52202
  {
51853
52203
  name: "erpnext_work_order_get",
51854
52204
  annotations: { readOnlyHint: true },
52205
+ _meta: DOC_META,
51855
52206
  description: "Get a single Work Order by name (e.g. MFG-WO-00001). Returns full document with operations and materials.",
51856
52207
  category: "manufacturing",
51857
52208
  inputSchema: {
@@ -51869,7 +52220,7 @@ var manufacturingTools = [
51869
52220
  throw new Error("[erpnext_work_order_get] 'name' is required");
51870
52221
  }
51871
52222
  const doc = await ctx.client.get("Work Order", input.name);
51872
- return { data: doc };
52223
+ return { data: { ...doc, doctype: "Work Order" } };
51873
52224
  }
51874
52225
  },
51875
52226
  {
@@ -51985,6 +52336,7 @@ var manufacturingTools = [
51985
52336
  {
51986
52337
  name: "erpnext_job_card_get",
51987
52338
  annotations: { readOnlyHint: true },
52339
+ _meta: DOC_META,
51988
52340
  description: "Get a single Job Card by name. Returns full document with time logs and material transfers.",
51989
52341
  category: "manufacturing",
51990
52342
  inputSchema: {
@@ -51999,7 +52351,7 @@ var manufacturingTools = [
51999
52351
  throw new Error("[erpnext_job_card_get] 'name' is required");
52000
52352
  }
52001
52353
  const doc = await ctx.client.get("Job Card", input.name);
52002
- return { data: doc };
52354
+ return { data: { ...doc, doctype: "Job Card" } };
52003
52355
  }
52004
52356
  }
52005
52357
  ];
@@ -52061,6 +52413,7 @@ var crmTools = [
52061
52413
  {
52062
52414
  name: "erpnext_lead_get",
52063
52415
  annotations: { readOnlyHint: true },
52416
+ _meta: DOC_META,
52064
52417
  description: "Get a single CRM Lead by name. Returns all lead details including contact info.",
52065
52418
  category: "crm",
52066
52419
  inputSchema: {
@@ -52075,7 +52428,7 @@ var crmTools = [
52075
52428
  throw new Error("[erpnext_lead_get] 'name' is required");
52076
52429
  }
52077
52430
  const doc = await ctx.client.get("Lead", input.name);
52078
- return { data: doc };
52431
+ return { data: { ...doc, doctype: "Lead" } };
52079
52432
  }
52080
52433
  },
52081
52434
  {
@@ -52207,6 +52560,7 @@ var crmTools = [
52207
52560
  {
52208
52561
  name: "erpnext_opportunity_get",
52209
52562
  annotations: { readOnlyHint: true },
52563
+ _meta: DOC_META,
52210
52564
  description: "Get a single CRM Opportunity by name. Returns full details including items and competitors.",
52211
52565
  category: "crm",
52212
52566
  inputSchema: {
@@ -52221,7 +52575,7 @@ var crmTools = [
52221
52575
  throw new Error("[erpnext_opportunity_get] 'name' is required");
52222
52576
  }
52223
52577
  const doc = await ctx.client.get("Opportunity", input.name);
52224
- return { data: doc };
52578
+ return { data: { ...doc, doctype: "Opportunity" } };
52225
52579
  }
52226
52580
  },
52227
52581
  // ── Contacts ──────────────────────────────────────────────────────────────
@@ -52276,6 +52630,7 @@ var crmTools = [
52276
52630
  {
52277
52631
  name: "erpnext_contact_get",
52278
52632
  annotations: { readOnlyHint: true },
52633
+ _meta: DOC_META,
52279
52634
  description: "Get a single Contact by name. Returns all contact details.",
52280
52635
  category: "crm",
52281
52636
  inputSchema: {
@@ -52290,7 +52645,7 @@ var crmTools = [
52290
52645
  throw new Error("[erpnext_contact_get] 'name' is required");
52291
52646
  }
52292
52647
  const doc = await ctx.client.get("Contact", input.name);
52293
- return { data: doc };
52648
+ return { data: { ...doc, doctype: "Contact" } };
52294
52649
  }
52295
52650
  },
52296
52651
  // ── Campaigns ─────────────────────────────────────────────────────────────
@@ -52440,6 +52795,7 @@ var assetsTools = [
52440
52795
  {
52441
52796
  name: "erpnext_asset_get",
52442
52797
  annotations: { readOnlyHint: true },
52798
+ _meta: DOC_META,
52443
52799
  description: "Get a single Asset by name. Returns full details including depreciation schedule and maintenance logs.",
52444
52800
  category: "assets",
52445
52801
  inputSchema: {
@@ -52454,7 +52810,7 @@ var assetsTools = [
52454
52810
  throw new Error("[erpnext_asset_get] 'name' is required");
52455
52811
  }
52456
52812
  const doc = await ctx.client.get("Asset", input.name);
52457
- return { data: doc };
52813
+ return { data: { ...doc, doctype: "Asset" } };
52458
52814
  }
52459
52815
  },
52460
52816
  {
@@ -52590,6 +52946,7 @@ var assetsTools = [
52590
52946
  {
52591
52947
  name: "erpnext_asset_movement_get",
52592
52948
  annotations: { readOnlyHint: true },
52949
+ _meta: DOC_META,
52593
52950
  description: "Get a single Asset Movement by name. Returns full details including assets moved.",
52594
52951
  category: "assets",
52595
52952
  inputSchema: {
@@ -52604,7 +52961,7 @@ var assetsTools = [
52604
52961
  throw new Error("[erpnext_asset_movement_get] 'name' is required");
52605
52962
  }
52606
52963
  const doc = await ctx.client.get("Asset Movement", input.name);
52607
- return { data: doc };
52964
+ return { data: { ...doc, doctype: "Asset Movement" } };
52608
52965
  }
52609
52966
  },
52610
52967
  // ── Asset Maintenance ─────────────────────────────────────────────────────
@@ -52661,6 +53018,7 @@ var assetsTools = [
52661
53018
  {
52662
53019
  name: "erpnext_asset_maintenance_get",
52663
53020
  annotations: { readOnlyHint: true },
53021
+ _meta: DOC_META,
52664
53022
  description: "Get a single Asset Maintenance record by name. Returns full details including maintenance tasks.",
52665
53023
  category: "assets",
52666
53024
  inputSchema: {
@@ -52678,7 +53036,7 @@ var assetsTools = [
52678
53036
  "Asset Maintenance",
52679
53037
  input.name
52680
53038
  );
52681
- return { data: doc };
53039
+ return { data: { ...doc, doctype: "Asset Maintenance" } };
52682
53040
  }
52683
53041
  },
52684
53042
  // ── Asset Categories ──────────────────────────────────────────────────────
@@ -52713,6 +53071,16 @@ var assetsTools = [
52713
53071
  ];
52714
53072
 
52715
53073
  // src/tools/operations.ts
53074
+ function bytesToBase64(bytes) {
53075
+ const chunkSize = 32 * 1024;
53076
+ let binary = "";
53077
+ for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
53078
+ binary += String.fromCharCode(
53079
+ ...bytes.subarray(offset, Math.min(offset + chunkSize, bytes.byteLength))
53080
+ );
53081
+ }
53082
+ return btoa(binary);
53083
+ }
52716
53084
  var operationsTools = [
52717
53085
  // ── File Attachments ───────────────────────────────────────────────────────
52718
53086
  {
@@ -52790,6 +53158,74 @@ var operationsTools = [
52790
53158
  };
52791
53159
  }
52792
53160
  },
53161
+ {
53162
+ name: "erpnext_file_download",
53163
+ annotations: { readOnlyHint: true },
53164
+ _meta: {
53165
+ ui: {
53166
+ resourceUri: DOC_META.ui.resourceUri,
53167
+ visibility: ["app"]
53168
+ }
53169
+ },
53170
+ description: "Download one ERPNext attachment for the document viewer. The tool accepts a File ID, verifies its document attachment, and returns one embedded binary resource.",
53171
+ category: "operations",
53172
+ inputSchema: {
53173
+ type: "object",
53174
+ properties: {
53175
+ file_id: {
53176
+ type: "string",
53177
+ description: "Native ERPNext File.name identifier, never a URL.",
53178
+ minLength: 1
53179
+ },
53180
+ attached_to_doctype: {
53181
+ type: "string",
53182
+ description: "Expected parent document DocType.",
53183
+ minLength: 1
53184
+ },
53185
+ attached_to_name: {
53186
+ type: "string",
53187
+ description: "Expected parent document name/ID.",
53188
+ minLength: 1
53189
+ }
53190
+ },
53191
+ required: ["file_id", "attached_to_doctype", "attached_to_name"],
53192
+ additionalProperties: false
53193
+ },
53194
+ handler: async (input, ctx) => {
53195
+ for (const field of [
53196
+ "file_id",
53197
+ "attached_to_doctype",
53198
+ "attached_to_name"
53199
+ ]) {
53200
+ if (typeof input[field] !== "string" || !input[field].trim()) {
53201
+ throw new Error(
53202
+ `[erpnext_file_download] '${field}' must be a non-empty string`
53203
+ );
53204
+ }
53205
+ }
53206
+ const file = await ctx.client.downloadFile({
53207
+ fileId: input.file_id.trim(),
53208
+ attachedToDoctype: input.attached_to_doctype.trim(),
53209
+ attachedToName: input.attached_to_name.trim()
53210
+ });
53211
+ return {
53212
+ content: [
53213
+ {
53214
+ type: "text",
53215
+ text: `Prepared ${file.fileName} for download (${file.bytes.byteLength} bytes).`
53216
+ },
53217
+ {
53218
+ type: "resource",
53219
+ resource: {
53220
+ uri: `file:///${encodeURIComponent(file.fileName)}`,
53221
+ mimeType: file.mimeType,
53222
+ blob: bytesToBase64(file.bytes)
53223
+ }
53224
+ }
53225
+ ]
53226
+ };
53227
+ }
53228
+ },
52793
53229
  {
52794
53230
  name: "erpnext_file_upload",
52795
53231
  annotations: { destructiveHint: true },
@@ -53091,6 +53527,7 @@ var operationsTools = [
53091
53527
  {
53092
53528
  name: "erpnext_doc_get",
53093
53529
  annotations: { readOnlyHint: true },
53530
+ _meta: DOC_META,
53094
53531
  description: "Get any ERPNext document by DocType and name. Useful for DocTypes not covered by dedicated tools. Returns the full document with all fields.",
53095
53532
  category: "operations",
53096
53533
  inputSchema: {
@@ -53108,17 +53545,18 @@ var operationsTools = [
53108
53545
  required: ["doctype", "name"]
53109
53546
  },
53110
53547
  handler: async (input, ctx) => {
53111
- if (!input.doctype) {
53548
+ if (typeof input.doctype !== "string" || !input.doctype.trim()) {
53112
53549
  throw new Error("[erpnext_doc_get] 'doctype' is required");
53113
53550
  }
53114
- if (!input.name) {
53551
+ if (typeof input.name !== "string" || !input.name.trim()) {
53115
53552
  throw new Error("[erpnext_doc_get] 'name' is required");
53116
53553
  }
53554
+ const doctype = input.doctype.trim();
53117
53555
  const doc = await ctx.client.get(
53118
- input.doctype,
53119
- input.name
53556
+ doctype,
53557
+ input.name.trim()
53120
53558
  );
53121
- return { data: doc };
53559
+ return { data: { ...doc, doctype } };
53122
53560
  }
53123
53561
  },
53124
53562
  // ── Generic List ──────────────────────────────────────────────────────────
@@ -56755,12 +57193,24 @@ function isDoclistViewer(result) {
56755
57193
  const uri = result._meta?.ui?.resourceUri;
56756
57194
  return uri === "ui://mcp-erpnext/doclist-viewer";
56757
57195
  }
57196
+ function isDocViewer(result) {
57197
+ const uri = result._meta?.ui?.resourceUri;
57198
+ return uri === "ui://mcp-erpnext/doc-viewer";
57199
+ }
56758
57200
  var VIEWER_TOOL_CANDIDATES = {
56759
57201
  "ui://mcp-erpnext/invoice-viewer": [
56760
57202
  "erpnext_item_get",
56761
- "erpnext_stock_balance"
57203
+ "erpnext_stock_balance",
57204
+ "erpnext_file_list",
57205
+ "erpnext_file_upload",
57206
+ "erpnext_file_download"
56762
57207
  ],
56763
57208
  "ui://mcp-erpnext/doclist-viewer": [],
57209
+ "ui://mcp-erpnext/doc-viewer": [
57210
+ "erpnext_file_list",
57211
+ "erpnext_file_upload",
57212
+ "erpnext_file_download"
57213
+ ],
56764
57214
  "ui://mcp-erpnext/kanban-viewer": [
56765
57215
  "erpnext_doc_get",
56766
57216
  "erpnext_doc_update",
@@ -56803,7 +57253,7 @@ var INVOICE_DEDICATED_MUTATION_TOOLS = {
56803
57253
  };
56804
57254
  function addDoctypeMutationCandidates(target, uri, doctype) {
56805
57255
  if (!doctype || !SUBMITTABLE_VIEWER_DOCTYPES.has(doctype)) return;
56806
- if (uri === "ui://mcp-erpnext/invoice-viewer" || uri === "ui://mcp-erpnext/doclist-viewer") {
57256
+ if (uri === "ui://mcp-erpnext/invoice-viewer" || uri === "ui://mcp-erpnext/doclist-viewer" || uri === "ui://mcp-erpnext/doc-viewer") {
56807
57257
  target.add("erpnext_doc_submit");
56808
57258
  target.add("erpnext_doc_cancel");
56809
57259
  }
@@ -57004,7 +57454,7 @@ function withUiRefreshRequest(result, toolName, args, now = /* @__PURE__ */ new
57004
57454
  if (hints) enriched._sendMessageHints = hints;
57005
57455
  } else if (isStockViewer(enriched)) {
57006
57456
  enriched._sendMessageHints = STOCK_HINTS;
57007
- } else if (isKanbanViewer(enriched) && doctype) {
57457
+ } else if ((isKanbanViewer(enriched) || isDocViewer(enriched)) && doctype) {
57008
57458
  const hints = DOCTYPE_SEND_MESSAGE_HINTS[doctype];
57009
57459
  if (hints) enriched._sendMessageHints = hints;
57010
57460
  }
@@ -57016,6 +57466,19 @@ function withUiRefreshRequest(result, toolName, args, now = /* @__PURE__ */ new
57016
57466
  function isRecord8(value) {
57017
57467
  return typeof value === "object" && value !== null && !Array.isArray(value);
57018
57468
  }
57469
+ function isSingleEmbeddedBlobResult(value) {
57470
+ if (!isRecord8(value) || !Array.isArray(value.content)) return false;
57471
+ if (Object.keys(value).some((key) => key !== "content")) return false;
57472
+ if (value.content.length !== 2) return false;
57473
+ const [summary, embedded] = value.content;
57474
+ if (!isRecord8(summary) || summary.type !== "text" || typeof summary.text !== "string") {
57475
+ return false;
57476
+ }
57477
+ if (!isRecord8(embedded) || embedded.type !== "resource") return false;
57478
+ if (!isRecord8(embedded.resource)) return false;
57479
+ const resource = embedded.resource;
57480
+ return typeof resource.uri === "string" && resource.uri.startsWith("file:///") && typeof resource.mimeType === "string" && typeof resource.blob === "string";
57481
+ }
57019
57482
  function withSafeUiRefresh(result, tool, args, availableToolNames) {
57020
57483
  const readOnly = tool.annotations?.readOnlyHint === true;
57021
57484
  if (!isRecord8(result)) {
@@ -57115,6 +57578,9 @@ var ErpNextToolsClient = class {
57115
57578
  if (execution.result !== null && typeof execution.result === "object" && !Array.isArray(execution.result) && execution.result.resultType === "input_required") {
57116
57579
  return execution.result;
57117
57580
  }
57581
+ if (tool.name === "erpnext_file_download" && isSingleEmbeddedBlobResult(execution.result)) {
57582
+ return execution.result;
57583
+ }
57118
57584
  const result = withSafeUiRefresh(
57119
57585
  execution.result,
57120
57586
  tool,
@@ -57146,6 +57612,9 @@ var ErpNextToolsClient = class {
57146
57612
  }
57147
57613
  const client = getFrappeClient();
57148
57614
  const result = await tool.handler(args, { client });
57615
+ if (tool.name === "erpnext_file_download" && isSingleEmbeddedBlobResult(result)) {
57616
+ return result;
57617
+ }
57149
57618
  return withSafeUiRefresh(
57150
57619
  result,
57151
57620
  tool,
@@ -57165,6 +57634,7 @@ var UI_VIEWERS = [
57165
57634
  "invoice-viewer",
57166
57635
  "stock-viewer",
57167
57636
  "doclist-viewer",
57637
+ "doc-viewer",
57168
57638
  "chart-viewer",
57169
57639
  "kpi-viewer",
57170
57640
  "funnel-viewer",
@@ -57408,7 +57878,7 @@ async function main() {
57408
57878
  );
57409
57879
  const server = new McpApp({
57410
57880
  name: "mcp-erpnext",
57411
- version: "3.1.0-beta.1",
57881
+ version: "3.1.0-beta.2",
57412
57882
  transport: "stateless",
57413
57883
  cache: {
57414
57884
  ttlMs: 36e5,