Add erpnext-mcp/src/tools/domains.ts

This commit is contained in:
Zac Gaetano 2026-03-31 15:33:32 -04:00
parent 1b069f63c9
commit 56f64d201f

View file

@ -0,0 +1,533 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { ERPNextClient, truncateResponse } from "../services/erpnext-client.js";
import { z } from "zod";
export function registerManufacturingTools(server: McpServer, client: ERPNextClient): void {
// ─── 51. Get Work Order Details ────────────────────────
server.registerTool(
"erpnext_get_work_order_details",
{
title: "Get Work Order Details for Stock Entry",
description: `Get work order details needed to create a stock entry (manufacture).`,
inputSchema: z.object({ work_order: z.string(), 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_entry.stock_entry.get_work_order_details",
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}` }] };
}
}
);
// ─── 52. Make Work Order from SO ───────────────────────
server.registerTool(
"erpnext_make_work_order_from_so",
{
title: "Create Work Orders from Sales Order",
description: `Create Work Orders for items in a Sales Order that have BOMs.`,
inputSchema: z.object({
sales_order: z.string(),
items: z.array(z.object({
item_code: z.string(),
bom: z.string().optional(),
warehouse: z.string().optional(),
qty: z.number().optional(),
})).optional(),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.selling.doctype.sales_order.sales_order.make_work_orders",
{ items: JSON.stringify(params.items ?? []), sales_order: params.sales_order }
);
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}` }] };
}
}
);
// ─── 53. Get Default BOM ───────────────────────────────
server.registerTool(
"erpnext_get_default_bom",
{
title: "Get Default BOM for Item",
description: `Get the default Bill of Materials for an item.`,
inputSchema: z.object({ item_code: 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_default_bom", params);
return { content: [{ type: "text", text: `Default BOM for ${params.item_code}: ${result}` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
}
export function registerHRTools(server: McpServer, client: ERPNextClient): void {
// ─── 54. Get Employee Org Chart ────────────────────────
server.registerTool(
"erpnext_get_org_chart",
{
title: "Get Organization Chart",
description: `Get organizational hierarchy chart data.`,
inputSchema: z.object({
company: z.string(),
method: z.string().default("erpnext.utilities.hierarchy_chart.get_all_nodes"),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(params.method, { company: params.company });
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}` }] };
}
}
);
// ─── 55. Create Employee ───────────────────────────────
server.registerTool(
"erpnext_create_employee",
{
title: "Create Employee",
description: `Create a new Employee record.`,
inputSchema: z.object({
first_name: z.string(),
last_name: z.string().optional(),
company: z.string(),
gender: z.string().optional(),
date_of_birth: z.string().optional(),
date_of_joining: z.string().optional(),
designation: z.string().optional(),
department: z.string().optional(),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
},
async (params) => {
try {
const doc = await client.createDocument("Employee", params);
return { content: [{ type: "text", text: `Created Employee: ${doc.name} (${params.first_name} ${params.last_name || ""})` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 56. Create User from Employee ─────────────────────
server.registerTool(
"erpnext_create_user_from_employee",
{
title: "Create User from Employee",
description: `Create a system user account from an Employee record.`,
inputSchema: z.object({
employee: z.string(),
user: z.string().optional(),
email: z.string().optional(),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod("erpnext.setup.doctype.employee.employee.create_user", params);
return { content: [{ type: "text", text: `User created: ${JSON.stringify(result)}` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 57. Get Department Tree ───────────────────────────
server.registerTool(
"erpnext_get_department_tree",
{
title: "Get Department Tree",
description: `Get department hierarchy tree.`,
inputSchema: z.object({
parent: z.string().optional(),
company: z.string().optional(),
is_root: z.boolean().default(false),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.setup.doctype.department.department.get_children",
{ doctype: "Department", ...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}` }] };
}
}
);
}
export function registerCRMTools(server: McpServer, client: ERPNextClient): void {
// ─── 58. Create Lead ───────────────────────────────────
server.registerTool(
"erpnext_create_lead",
{
title: "Create Lead",
description: `Create a new Lead record.`,
inputSchema: z.object({
lead_name: z.string().optional(),
first_name: z.string().optional(),
last_name: z.string().optional(),
company_name: z.string().optional(),
email_id: z.string().optional(),
mobile_no: z.string().optional(),
source: z.string().optional(),
territory: z.string().optional(),
status: z.string().optional(),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
},
async (params) => {
try {
const doc = await client.createDocument("Lead", params);
return { content: [{ type: "text", text: `Created Lead: ${doc.name}` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 59. Create Opportunity ────────────────────────────
server.registerTool(
"erpnext_create_opportunity",
{
title: "Create Opportunity",
description: `Create a new Opportunity from a Lead or Customer.`,
inputSchema: z.object({
opportunity_from: z.enum(["Lead", "Customer"]),
party_name: z.string(),
company: z.string(),
opportunity_type: z.string().optional(),
status: z.string().optional(),
expected_closing: z.string().optional(),
probability: z.number().optional(),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
},
async (params) => {
try {
const doc = await client.createDocument("Opportunity", params);
return { content: [{ type: "text", text: `Created Opportunity: ${doc.name}` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 60. Get Opportunities by Lead Source ──────────────
server.registerTool(
"erpnext_get_opportunities_by_source",
{
title: "Get Opportunities by Lead Source",
description: `Get opportunities grouped by lead source for analytics.`,
inputSchema: z.object({
from_date: z.string(),
to_date: z.string(),
company: z.string(),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.selling.page.sales_funnel.sales_funnel.get_opp_by_lead_source",
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}` }] };
}
}
);
}
export function registerProjectTools(server: McpServer, client: ERPNextClient): void {
// ─── 61. Create Task ───────────────────────────────────
server.registerTool(
"erpnext_create_task",
{
title: "Create Task",
description: `Create a new Task, optionally linked to a Project.`,
inputSchema: z.object({
subject: z.string(),
project: z.string().optional(),
status: z.string().default("Open"),
priority: z.string().optional(),
description: z.string().optional(),
exp_start_date: z.string().optional(),
exp_end_date: z.string().optional(),
assigned_to: z.string().optional(),
parent_task: z.string().optional(),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
},
async (params) => {
try {
const data: Record<string, unknown> = { ...params };
if (params.assigned_to) {
delete data.assigned_to;
}
const doc = await client.createDocument("Task", data);
if (params.assigned_to) {
await client.callMethod("frappe.desk.form.assign_to.add", {
doctype: "Task",
name: doc.name,
assign_to: [params.assigned_to],
});
}
return { content: [{ type: "text", text: `Created Task: ${doc.name}${params.subject}` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 62. Create Project ────────────────────────────────
server.registerTool(
"erpnext_create_project",
{
title: "Create Project",
description: `Create a new Project.`,
inputSchema: z.object({
project_name: z.string(),
company: z.string(),
status: z.string().default("Open"),
expected_start_date: z.string().optional(),
expected_end_date: z.string().optional(),
priority: z.string().optional(),
project_type: z.string().optional(),
is_active: z.string().default("Yes"),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
},
async (params) => {
try {
const doc = await client.createDocument("Project", params);
return { content: [{ type: "text", text: `Created Project: ${doc.name}` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 63. Get Project Tasks HTML ────────────────────────
server.registerTool(
"erpnext_get_project_tasks",
{
title: "Get Project Tasks",
description: `Get tasks for a project.`,
inputSchema: z.object({
project: z.string(),
start: z.number().default(0),
item_status: z.string().optional(),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.templates.pages.projects.get_task_html",
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}` }] };
}
}
);
}
export function registerSupportTools(server: McpServer, client: ERPNextClient): void {
// ─── 64. Set Issue Status ──────────────────────────────
server.registerTool(
"erpnext_set_issue_status",
{
title: "Set Issue Status",
description: `Set the status of a support Issue.`,
inputSchema: z.object({
name: z.string(),
status: z.string().describe('Status: "Open", "Replied", "Resolved", "Closed"'),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
await client.callMethod("erpnext.support.doctype.issue.issue.set_status", params);
return { content: [{ type: "text", text: `Issue ${params.name} status set to ${params.status}` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 65. Bulk Set Issue Status ─────────────────────────
server.registerTool(
"erpnext_bulk_set_issue_status",
{
title: "Bulk Set Issue Status",
description: `Set the status of multiple Issues at once.`,
inputSchema: z.object({
names: z.array(z.string()),
status: z.string(),
}).strict(),
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
await client.callMethod("erpnext.support.doctype.issue.issue.set_multiple_status", {
names: JSON.stringify(params.names),
status: params.status,
});
return { content: [{ type: "text", text: `Updated ${params.names.length} issues to ${params.status}` }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 66. Make Task from Issue ──────────────────────────
server.registerTool(
"erpnext_make_task_from_issue",
{
title: "Make Task from Issue",
description: `Create a Task from a support Issue.`,
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.support.doctype.issue.issue.make_task",
{ 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}` }] };
}
}
);
}
export function registerSetupTools(server: McpServer, client: ERPNextClient): void {
// ─── 67. Get Company Tree ──────────────────────────────
server.registerTool(
"erpnext_get_company_tree",
{
title: "Get Company Tree",
description: `Get company hierarchy tree.`,
inputSchema: z.object({
parent: z.string().optional(),
is_root: z.boolean().default(false),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.setup.doctype.company.company.get_children",
{ doctype: "Company", ...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}` }] };
}
}
);
// ─── 68. Get Default Company Address ───────────────────
server.registerTool(
"erpnext_get_default_company_address",
{
title: "Get Default Company Address",
description: `Get the default/primary address for a company.`,
inputSchema: z.object({ name: z.string().describe("Company name") }).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.setup.doctype.company.company.get_default_company_address",
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}` }] };
}
}
);
// ─── 69. Get Terms and Conditions ──────────────────────
server.registerTool(
"erpnext_get_terms_and_conditions",
{
title: "Get Terms and Conditions",
description: `Get rendered terms and conditions template for a document.`,
inputSchema: z.object({
template_name: z.string(),
doc: z.record(z.unknown()).describe("Document data for template rendering"),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.setup.doctype.terms_and_conditions.terms_and_conditions.get_terms_and_conditions",
params
);
return { content: [{ type: "text", text: String(result) }] };
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] };
}
}
);
// ─── 70. Get Holiday List Events ───────────────────────
server.registerTool(
"erpnext_get_holidays",
{
title: "Get Holiday Events",
description: `Get holidays from a holiday list for a date range.`,
inputSchema: z.object({
start: z.string().describe("Start date (YYYY-MM-DD)"),
end: z.string().describe("End date (YYYY-MM-DD)"),
}).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
},
async (params) => {
try {
const result = await client.callMethod(
"erpnext.setup.doctype.holiday_list.holiday_list.get_events",
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}` }] };
}
}
);
}