Add mcp-gateway/erpnext-mcp/src/tools/stock.ts
This commit is contained in:
parent
01ede29866
commit
0edde88bc3
1 changed files with 364 additions and 0 deletions
364
mcp-gateway/erpnext-mcp/src/tools/stock.ts
Normal file
364
mcp-gateway/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