diff --git a/mcp-gateway/erpnext-mcp/src/tools/frappe.ts b/mcp-gateway/erpnext-mcp/src/tools/frappe.ts deleted file mode 100644 index f72a3fd..0000000 --- a/mcp-gateway/erpnext-mcp/src/tools/frappe.ts +++ /dev/null @@ -1,615 +0,0 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { ERPNextClient, truncateResponse } from "../services/erpnext-client.js"; -import { z } from "zod"; - -export function registerFrappeTools(server: McpServer, client: ERPNextClient): void { - - // ─── 71. Get Value ───────────────────────────────────── - server.registerTool( - "erpnext_get_value", - { - title: "Get Field Value", - description: `Get a specific field value from a document without fetching the entire document. - -Args: - - doctype: DocType name - - fieldname: Field to retrieve (or comma-separated list) - - filters: Filters to identify the document (e.g. {"name": "CUST-00001"})`, - inputSchema: z.object({ - doctype: z.string(), - fieldname: z.string().describe('Field name or comma-separated list (e.g. "customer_name" or "customer_name,territory")'), - filters: z.record(z.unknown()), - }).strict(), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.callMethod("frappe.client.get_value", 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}` }] }; - } - } - ); - - // ─── 72. Set Value ───────────────────────────────────── - server.registerTool( - "erpnext_set_value", - { - title: "Set Field Value", - description: `Set a specific field value on a document. - -Args: - - doctype: DocType name - - name: Document name - - fieldname: Field to set - - value: Value to set`, - inputSchema: z.object({ - doctype: z.string(), - name: z.string(), - fieldname: z.string(), - value: z.union([z.string(), z.number(), z.boolean(), z.null()]), - }).strict(), - annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.callMethod("frappe.client.set_value", params); - return { content: [{ type: "text", text: `Set ${params.fieldname}=${params.value} on ${params.doctype} ${params.name}` }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 73. Assign To ───────────────────────────────────── - server.registerTool( - "erpnext_assign_to", - { - title: "Assign Document To User", - description: `Assign a document to one or more users.`, - inputSchema: z.object({ - doctype: z.string(), - name: z.string(), - assign_to: z.array(z.string()).describe("Array of user email addresses"), - description: z.string().optional(), - priority: z.enum(["Low", "Medium", "High"]).optional(), - date: z.string().optional().describe("Due date"), - }).strict(), - annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.callMethod("frappe.desk.form.assign_to.add", params); - return { content: [{ type: "text", text: `Assigned ${params.doctype} ${params.name} to ${params.assign_to.join(", ")}` }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 74. Remove Assignment ───────────────────────────── - server.registerTool( - "erpnext_remove_assignment", - { - title: "Remove Document Assignment", - description: `Remove a user assignment from a document.`, - inputSchema: z.object({ - doctype: z.string(), - name: z.string(), - assign_to: z.string().describe("User email to unassign"), - }).strict(), - annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - await client.callMethod("frappe.desk.form.assign_to.remove", params); - return { content: [{ type: "text", text: `Removed assignment of ${params.assign_to} from ${params.doctype} ${params.name}` }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 75. Get Assignments ─────────────────────────────── - server.registerTool( - "erpnext_get_assignments", - { - title: "Get Document Assignments", - description: `Get all user assignments for a document.`, - inputSchema: z.object({ - doctype: z.string(), - name: z.string(), - }).strict(), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.callMethod("frappe.desk.form.assign_to.get", 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}` }] }; - } - } - ); - - // ─── 76. Add Comment ─────────────────────────────────── - server.registerTool( - "erpnext_add_comment", - { - title: "Add Comment to Document", - description: `Add a comment to any document.`, - inputSchema: z.object({ - doctype: z.string(), - name: z.string(), - content: z.string().describe("Comment text (supports HTML)"), - comment_type: z.enum(["Comment", "Like", "Info", "Label", "Workflow"]).default("Comment"), - }).strict(), - annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.createDocument("Comment", { - comment_type: params.comment_type, - reference_doctype: params.doctype, - reference_name: params.name, - content: params.content, - }); - return { content: [{ type: "text", text: `Comment added to ${params.doctype} ${params.name}` }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 77. Add Tag ─────────────────────────────────────── - server.registerTool( - "erpnext_add_tag", - { - title: "Add Tag to Document", - description: `Add a tag to a document for organization and filtering.`, - inputSchema: z.object({ - tag: z.string(), - dt: z.string().describe("DocType"), - dn: z.string().describe("Document name"), - }).strict(), - annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - await client.callMethod("frappe.desk.doctype.tag.tag.add_tag", params); - return { content: [{ type: "text", text: `Tag "${params.tag}" added to ${params.dt} ${params.dn}` }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 78. Remove Tag ──────────────────────────────────── - server.registerTool( - "erpnext_remove_tag", - { - title: "Remove Tag from Document", - description: `Remove a tag from a document.`, - inputSchema: z.object({ - tag: z.string(), - dt: z.string(), - dn: z.string(), - }).strict(), - annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - await client.callMethod("frappe.desk.doctype.tag.tag.remove_tag", params); - return { content: [{ type: "text", text: `Tag "${params.tag}" removed from ${params.dt} ${params.dn}` }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 79. Get Logged In User ──────────────────────────── - server.registerTool( - "erpnext_get_logged_in_user", - { - title: "Get Current User", - description: `Get the currently authenticated user.`, - inputSchema: {}, - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async () => { - try { - const result = await client.callGetMethod("frappe.auth.get_logged_user"); - return { content: [{ type: "text", text: `Logged in as: ${result}` }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 80. Get System Settings ─────────────────────────── - server.registerTool( - "erpnext_get_system_settings", - { - title: "Get System Settings", - description: `Get system settings values.`, - inputSchema: z.object({ - keys: z.array(z.string()).describe('Setting keys (e.g. ["setup_complete", "default_currency"])'), - }).strict(), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - const results: Record = {}; - for (const key of params.keys) { - const result = await client.callMethod("frappe.client.get_value", { - doctype: "System Settings", - fieldname: key, - filters: { name: "System Settings" }, - }); - results[key] = result; - } - return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 81. Rename Document ─────────────────────────────── - server.registerTool( - "erpnext_rename_document", - { - title: "Rename Document", - description: `Rename a document (change its name/ID).`, - inputSchema: z.object({ - doctype: z.string(), - old: z.string().describe("Current name"), - new_name: z.string().describe("New name"), - merge: z.boolean().default(false).describe("Merge with existing if name exists"), - }).strict(), - annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.callMethod("frappe.client.rename_doc", { - doctype: params.doctype, - old: params.old, - new: params.new_name, - merge: params.merge, - }); - return { content: [{ type: "text", text: `Renamed ${params.doctype} from "${params.old}" to "${params.new_name}"` }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 82. Get Activity Log ────────────────────────────── - server.registerTool( - "erpnext_get_activity_log", - { - title: "Get Activity Log", - description: `Get recent activity log entries.`, - inputSchema: z.object({ - limit: z.number().default(20), - user: z.string().optional(), - }).strict(), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.listDocuments("Activity Log", { - fields: ["name", "user", "subject", "creation", "reference_doctype", "reference_name"], - limit_page_length: params.limit, - order_by: "creation desc", - ...(params.user ? { filters: [["Activity Log", "user", "=", params.user]] } : {}), - }); - return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result.items, null, 2)) }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 83. Get Notifications ───────────────────────────── - server.registerTool( - "erpnext_get_notifications", - { - title: "Get User Notifications", - description: `Get recent notifications for the current user.`, - inputSchema: z.object({ - limit: z.number().default(20), - }).strict(), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.listDocuments("Notification Log", { - fields: ["name", "subject", "type", "read", "creation", "document_type", "document_name"], - limit_page_length: params.limit, - order_by: "creation desc", - }); - return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result.items, null, 2)) }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 84. Get ToDo List ───────────────────────────────── - server.registerTool( - "erpnext_get_todos", - { - title: "Get ToDo List", - description: `Get ToDo items for the current user.`, - inputSchema: z.object({ - status: z.enum(["Open", "Closed", "Cancelled"]).default("Open"), - limit: z.number().default(20), - }).strict(), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.listDocuments("ToDo", { - fields: ["name", "description", "status", "date", "priority", "reference_type", "reference_name", "allocated_to"], - filters: [["ToDo", "status", "=", params.status]], - limit_page_length: params.limit, - order_by: "date asc", - }); - return { content: [{ type: "text", text: truncateResponse(JSON.stringify(result.items, null, 2)) }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 85-90. Leaderboard tools ────────────────────────── - const leaderboardTools = [ - { name: "customers", method: "erpnext.startup.leaderboard.get_all_customers", title: "Top Customers" }, - { name: "suppliers", method: "erpnext.startup.leaderboard.get_all_suppliers", title: "Top Suppliers" }, - { name: "items", method: "erpnext.startup.leaderboard.get_all_items", title: "Top Items" }, - { name: "sales_partners", method: "erpnext.startup.leaderboard.get_all_sales_partner", title: "Top Sales Partners" }, - { name: "sales_persons", method: "erpnext.startup.leaderboard.get_all_sales_person", title: "Top Sales Persons" }, - ]; - - for (const lb of leaderboardTools) { - server.registerTool( - `erpnext_leaderboard_${lb.name}`, - { - title: lb.title, - description: `Get ${lb.title.toLowerCase()} leaderboard data for a date range and company.`, - inputSchema: z.object({ - date_range: z.string().describe('Date range as JSON, e.g. ["2024-01-01","2024-12-31"]'), - company: z.string(), - field: z.string().default("grand_total").describe("Field to aggregate"), - limit: z.number().default(20), - }).strict(), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.callMethod(lb.method, 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}` }] }; - } - } - ); - } - - // ─── 91. Get Linked Documents ────────────────────────── - server.registerTool( - "erpnext_get_linked_documents", - { - title: "Get Linked Documents", - description: `Get documents linked to a specific document (e.g. get all Sales Invoices linked to a Sales Order).`, - inputSchema: z.object({ - doctype: z.string(), - name: z.string(), - link_doctype: z.string().describe("DocType to search for links in"), - link_fieldname: z.string().optional().describe("Fieldname containing the link (auto-detected if not provided)"), - }).strict(), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - const fieldname = params.link_fieldname || params.doctype.toLowerCase().replace(/ /g, "_"); - const result = await client.listDocuments(params.link_doctype, { - fields: ["name", "status", "creation", "modified"], - filters: [[params.link_doctype, fieldname, "=", params.name]], - limit_page_length: 50, - }); - return { content: [{ type: "text", text: `Found ${result.total} linked ${params.link_doctype}:\n${JSON.stringify(result.items, null, 2)}` }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 92. Get Print Formats ───────────────────────────── - server.registerTool( - "erpnext_get_print_formats", - { - title: "Get Print Formats", - description: `List available print formats for a DocType.`, - inputSchema: z.object({ doctype: z.string() }).strict(), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.listDocuments("Print Format", { - fields: ["name", "doc_type", "standard", "disabled"], - filters: [["Print Format", "doc_type", "=", params.doctype]], - limit_page_length: 50, - }); - return { content: [{ type: "text", text: JSON.stringify(result.items, null, 2) }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 93. Attach File to Document ─────────────────────── - server.registerTool( - "erpnext_attach_file", - { - title: "Attach File to Document", - description: `Attach a file (by URL) to a document.`, - inputSchema: z.object({ - doctype: z.string(), - docname: z.string(), - filename: z.string(), - fileurl: z.string().describe("Public URL of the file to attach"), - }).strict(), - annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.callMethod("frappe.client.attach_file", { - doctype: params.doctype, - docname: params.docname, - filename: params.filename, - fileurl: params.fileurl, - }); - return { content: [{ type: "text", text: `File attached: ${JSON.stringify(result)}` }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); - - // ─── 94-100+. Quick access convenience tools ────────── - server.registerTool( - "erpnext_get_email_digest", - { - title: "Get Email Digest", - description: `Get email digest content.`, - inputSchema: z.object({ name: z.string().describe("Email Digest name") }).strict(), - annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - const result = await client.callMethod( - "erpnext.setup.doctype.email_digest.email_digest.get_digest_msg", - 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}` }] }; - } - } - ); - - server.registerTool( - "erpnext_get_warehouse_tree", - { - title: "Get Warehouse Tree", - description: `Get warehouse 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.stock.doctype.warehouse.warehouse.get_children", - { doctype: "Warehouse", ...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}` }] }; - } - } - ); - - server.registerTool( - "erpnext_make_delivery_note_from_dn_return", - { - title: "Make Sales Return from Delivery Note", - description: `Create a sales return (credit note) 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_return", - { 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}` }] }; - } - } - ); - - server.registerTool( - "erpnext_make_purchase_return", - { - title: "Make Purchase Return from Receipt", - description: `Create a purchase return 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_return", - { 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}` }] }; - } - } - ); - - server.registerTool( - "erpnext_make_stock_entry_from_mr", - { - title: "Make Stock Entry from Material Request", - description: `Create a Stock Entry (Material Transfer) from a Material Request.`, - 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.material_request.material_request.make_stock_entry", - { 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}` }] }; - } - } - ); - - server.registerTool( - "erpnext_update_material_request_status", - { - title: "Update Material Request Status", - description: `Update the status of a Material Request.`, - inputSchema: z.object({ - name: z.string(), - status: z.string().describe('"Stopped", "Cancelled", etc.'), - }).strict(), - annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, - }, - async (params) => { - try { - await client.callMethod( - "erpnext.stock.doctype.material_request.material_request.update_status", - params - ); - return { content: [{ type: "text", text: `Material Request ${params.name} status set to ${params.status}` }] }; - } catch (error) { - return { isError: true, content: [{ type: "text", text: `Error: ${(error as Error).message}` }] }; - } - } - ); -}