@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/README.md +39 -19
- package/mcp-erpnext.mjs +506 -36
- package/package.json +1 -1
- package/ui-dist/chart-viewer/index.html +56 -55
- package/ui-dist/doc-viewer/index.html +116 -0
- package/ui-dist/doclist-viewer/index.html +43 -42
- package/ui-dist/funnel-viewer/index.html +39 -38
- package/ui-dist/invoice-viewer/index.html +42 -41
- package/ui-dist/kanban-viewer/index.html +40 -39
- package/ui-dist/kpi-viewer/index.html +40 -39
- package/ui-dist/stock-viewer/index.html +42 -41
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
|
-
|
|
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.
|
|
57881
|
+
version: "3.1.0-beta.2",
|
|
57412
57882
|
transport: "stateless",
|
|
57413
57883
|
cache: {
|
|
57414
57884
|
ttlMs: 36e5,
|