Add erpnext-mcp/src/tools/stock.ts
This commit is contained in:
parent
fe64194db5
commit
402e4b047a
1 changed files with 364 additions and 0 deletions
364
erpnext-mcp/src/tools/stock.ts
Normal file
364
erpnext-mcp/src/tools/stock.ts
Normal 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}` }] };
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue