Add mcp-gateway/erpnext-mcp/src/tools/stock.ts

This commit is contained in:
Zac Gaetano 2026-03-31 15:29:34 -04:00
parent 01ede29866
commit 0edde88bc3

View file

@ -0,0 +1,364 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { ERPNextClient, truncateResponse } from "../services/erpnext-client.js";
import { z } from "zod";
export function registerStockTools(server: McpServer, client: ERPNextClient): void {
// ─── 37. Get Stock Balance ─────────────────────────────
server.registerTool(
"erpnext_get_stock_balance",
{
title: "Get Stock Balance",
description: `Get current stock balance for an item in a warehouse.
Args:
- item_code: Item code
- warehouse: Warehouse name
- posting_date: Optional as-of date
- posting_time: Optional as-of time`,
inputSchema: z.object({
item_code: z.string(),
warehouse: z.string(),
posting_date: z.string().optional(),
posting_time: z.string().optional(),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod("erpnext.stock.utils.get_stock_balance", params);
return { content: [{ type: "text", text: `Stock balance for ${params.item_code} in ${params.warehouse}: ${result}` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 38. Get Latest Stock Qty ──────────────────────────
server.registerTool(
"erpnext_get_latest_stock_qty",
{
title: "Get Latest Stock Quantity",
description: `Get the latest available stock quantity for an item across all or a specific warehouse.`,
inputSchema: z.object({
item_code: z.string(),
warehouse: z.string().optional(),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod("erpnext.stock.utils.get_latest_stock_qty", params);
return { content: [{ type: "text", text: `Latest stock qty for ${params.item_code}: ${result}` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 39. Make Stock Entry ──────────────────────────────
server.registerTool(
"erpnext_make_stock_entry",
{
title: "Make Stock Entry",
description: `Create a stock entry for material transfer, receipt, issue, etc.
Args:
- purpose: "Material Transfer", "Material Receipt", "Material Issue", "Manufacture", "Repack", "Send to Subcontractor"
- company: Company name
- items: Array of {item_code, qty, s_warehouse? (source), t_warehouse? (target), basic_rate?}
- posting_date: Optional date`,
inputSchema: z.object({
purpose: z.enum(["Material Transfer", "Material Receipt", "Material Issue", "Manufacture", "Repack", "Send to Subcontractor"]),
company: z.string(),
items: z.array(z.object({
item_code: z.string(),
qty: z.number(),
s_warehouse: z.string().optional(),
t_warehouse: z.string().optional(),
basic_rate: z.number().optional(),
})),
posting_date: z.string().optional(),
from_warehouse: z.string().optional(),
to_warehouse: z.string().optional(),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
},
async (params) => {
try {
const doc = await client.createDocument("Stock Entry", {
stock_entry_type: params.purpose,
company: params.company,
posting_date: params.posting_date,
from_warehouse: params.from_warehouse,
to_warehouse: params.to_warehouse,
items: params.items,
});
return { content: [{ type: "text", text: `Created Stock Entry: ${doc.name} (${params.purpose})` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 40. Scan Barcode ──────────────────────────────────
server.registerTool(
"erpnext_scan_barcode",
{
title: "Scan Barcode",
description: `Look up an item by barcode, serial number, or batch number.`,
inputSchema: z.object({ search_value: z.string().describe("Barcode, serial no, or batch no") }).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod("erpnext.stock.utils.scan_barcode", {
search_value: params.search_value,
});
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 41. Get Projected Qty ─────────────────────────────
server.registerTool(
"erpnext_get_projected_qty",
{
title: "Get Projected Quantity",
description: `Get the projected (planned) quantity for an item in a warehouse, accounting for pending orders.`,
inputSchema: z.object({
item_code: z.string(),
warehouse: z.string(),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod("erpnext.stock.get_item_details.get_projected_qty", params);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 42. Get Bin Details ───────────────────────────────
server.registerTool(
"erpnext_get_bin_details",
{
title: "Get Bin Details",
description: `Get detailed stock bin information (actual qty, planned qty, ordered qty, reserved qty, etc.) for an item in a warehouse.`,
inputSchema: z.object({
item_code: z.string(),
warehouse: z.string(),
company: z.string().optional(),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod("erpnext.stock.get_item_details.get_bin_details", params);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 43. Item Dashboard Data ───────────────────────────
server.registerTool(
"erpnext_get_item_dashboard_data",
{
title: "Get Item Dashboard Data",
description: `Get warehouse-wise stock data for items, showing actual qty, planned qty, and reserved qty.`,
inputSchema: z.object({
item_code: z.string().optional(),
warehouse: z.string().optional(),
item_group: z.string().optional(),
start: z.number().default(0),
sort_by: z.string().default("actual_qty"),
sort_order: z.enum(["asc", "desc"]).default("desc"),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod("erpnext.stock.dashboard.item_dashboard.get_data", params);
return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result, null, 2)) }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 44. Get Batch Qty ─────────────────────────────────
server.registerTool(
"erpnext_get_batch_qty",
{
title: "Get Batch Quantity",
description: `Get available quantity for a specific batch in a warehouse.`,
inputSchema: z.object({
batch_no: z.string(),
warehouse: z.string(),
item_code: z.string().optional(),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod("erpnext.stock.doctype.batch.batch.get_batch_qty", params);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 45. Make Sales Invoice from DN ────────────────────
server.registerTool(
"erpnext_make_sales_invoice_from_dn",
{
title: "Make Sales Invoice from Delivery Note",
description: `Create a Sales Invoice from a Delivery Note.`,
inputSchema: z.object({ source_name: z.string() }).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
{ source_name: params.source_name }
);
return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result, null, 2)) }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 46. Make Purchase Invoice from PR ─────────────────
server.registerTool(
"erpnext_make_purchase_invoice_from_pr",
{
title: "Make Purchase Invoice from Purchase Receipt",
description: `Create a Purchase Invoice from a Purchase Receipt.`,
inputSchema: z.object({ source_name: z.string() }).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice",
{ source_name: params.source_name }
);
return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result, null, 2)) }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 47. Warehouse Capacity Dashboard ──────────────────
server.registerTool(
"erpnext_warehouse_capacity",
{
title: "Get Warehouse Capacity Dashboard",
description: `Get warehouse capacity utilization data.`,
inputSchema: z.object({
warehouse: z.string().optional(),
item_code: z.string().optional(),
parent_warehouse: z.string().optional(),
start: z.number().default(0),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.stock.dashboard.warehouse_capacity_dashboard.get_data",
params
);
return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result, null, 2)) }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 48. Get Stock Reconciliation Items ────────────────
server.registerTool(
"erpnext_get_stock_reconciliation_items",
{
title: "Get Items for Stock Reconciliation",
description: `Get items and their current stock for stock reconciliation.`,
inputSchema: z.object({
warehouse: z.string(),
posting_date: z.string(),
posting_time: z.string().optional(),
company: z.string(),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.get_items",
params
);
return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result, null, 2)) }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 49. Get Valuation Rate ────────────────────────────
server.registerTool(
"erpnext_get_valuation_rate",
{
title: "Get Item Valuation Rate",
description: `Get the valuation rate for an item.`,
inputSchema: z.object({
item_code: z.string(),
company: z.string(),
warehouse: z.string().optional(),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.stock.get_item_details.get_valuation_rate",
params
);
return { content: [{ type: "text", text: `Valuation rate for ${params.item_code}: ${result}` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 50. Get Conversion Factor ─────────────────────────
server.registerTool(
"erpnext_get_conversion_factor",
{
title: "Get UOM Conversion Factor",
description: `Get the conversion factor between a UOM and the stock UOM for an item.`,
inputSchema: z.object({
item_code: z.string(),
uom: z.string(),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod("erpnext.stock.get_item_details.get_conversion_factor", params);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
}