mercury_delete_customer
Permanently deletes an Accounts Receivable customer. Requires confirmation as action is irreversible; cancel any outstanding invoices first.
Instructions
Permanently delete an Accounts Receivable customer. DESTRUCTIVE.
USE WHEN: removing a customer that was created by mistake, or that the user explicitly wants to purge. ALWAYS confirm with the user before calling — there is no undo.
DO NOT USE: when the customer has invoices in paid / outstanding status — Mercury rejects deletion in those cases and returns a 409. Cancel outstanding invoices first via mercury_cancel_invoice.
SIDE EFFECTS: permanent deletion on Mercury's side. The customer disappears from the AR list. Past invoices' customerId may dangle (Mercury does not cascade-delete invoices). NOT recoverable from API. ALWAYS confirm with the user.
RETURNS: confirmation payload from Mercury ({ deleted: true, ... } or similar).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| customerId | Yes | Customer ID |
Implementation Reference
- src/tools/customers.ts:125-147 (handler)The core handler for mercury_delete_customer tool. Defined via defineTool(), it accepts a customerId (UUID), calls client.delete('/ar/customers/${customerId}'), and returns the Mercury API response via textResult(). Marked with destructiveHint: true.
defineTool( server, "mercury_delete_customer", [ "Permanently delete an Accounts Receivable customer. **DESTRUCTIVE.**", "", "USE WHEN: removing a customer that was created by mistake, or that the user explicitly wants to purge. ALWAYS confirm with the user before calling — there is no undo.", "", "DO NOT USE: when the customer has invoices in `paid` / `outstanding` status — Mercury rejects deletion in those cases and returns a 409. Cancel outstanding invoices first via `mercury_cancel_invoice`.", "", "SIDE EFFECTS: **permanent deletion** on Mercury's side. The customer disappears from the AR list. Past invoices' `customerId` may dangle (Mercury does not cascade-delete invoices). NOT recoverable from API. ALWAYS confirm with the user.", "", "RETURNS: confirmation payload from Mercury (`{ deleted: true, ... }` or similar).", ].join("\n"), { customerId: z.uuid().describe("Customer ID"), }, async ({ customerId }) => { const data = await client.delete(`/ar/customers/${customerId}`); return textResult(data); }, { title: "Delete Customer", destructiveHint: true, openWorldHint: true }, ); - src/tools/customers.ts:139-141 (schema)Input schema for mercury_delete_customer: requires a single customerId field of type z.uuid() (UUID).
{ customerId: z.uuid().describe("Customer ID"), }, - src/tools/customers.ts:18-148 (registration)The tool is registered via registerCustomerTools() in customers.ts (line 18). This function is called from src/tools/index.ts (line 34) inside registerAllTools(). The registration uses the defineTool helper which internally calls server.registerTool() with the name, schema, description, annotations, and wrapped handler.
export function registerCustomerTools(server: McpServer, client: MercuryClient): void { defineTool( server, "mercury_list_customers", [ "List Accounts Receivable customers, with cursor-based pagination.", "", "USE WHEN: enumerating AR customers before creating an invoice (need a `customerId` for `mercury_create_invoice`), or for a customer-level audit. Use `startAfter` / `endBefore` for paging beyond the limit.", "", "DO NOT USE: for payment recipients (`mercury_list_recipients` is the bank-payment counterparty list, distinct from AR customers). For one customer whose ID is known, prefer `mercury_get_customer`.", "", "RETURNS: `{ customers: [{ id, name, email, address, ... }] }`.", ].join("\n"), { limit: z.number().int().min(1).max(1000).optional().describe("Max results (1-1000)"), order: z.enum(["asc", "desc"]).optional(), startAfter: z.uuid().optional().describe("Pagination cursor (forward)"), endBefore: z.uuid().optional().describe("Pagination cursor (reverse)"), }, async (args) => { const query: Record<string, string | number | undefined> = { limit: args.limit, order: args.order, start_after: args.startAfter, end_before: args.endBefore, }; const data = await client.get("/ar/customers", query); return textResult(data); }, { title: "List Customers", readOnlyHint: true, openWorldHint: true }, ); defineTool( server, "mercury_get_customer", [ "Retrieve a specific Accounts Receivable customer by ID.", "", "USE WHEN: fetching the full detail of one customer whose ID is already known. Faster than relisting + filtering when you have the ID.", "", "DO NOT USE: to enumerate customers (use `mercury_list_customers`). For payment recipients use `mercury_list_recipients` (different surface).", "", "RETURNS: `{ id, name, email, address, ... }`.", ].join("\n"), { customerId: z.uuid().describe("Customer ID"), }, async ({ customerId }) => { const data = await client.get(`/ar/customers/${customerId}`); return textResult(data); }, { title: "Get Customer", readOnlyHint: true, openWorldHint: true }, ); defineTool( server, "mercury_create_customer", [ "Create a new Accounts Receivable customer (a billable entity you will later invoice).", "", "USE WHEN: onboarding a new customer before issuing them an invoice. The returned `id` is what `mercury_create_invoice` expects as `customerId`.", "", "DO NOT USE: for payment recipients (use `mercury_add_recipient` — different surface, used for outbound bank transfers, not invoicing).", "", "SIDE EFFECTS: writes a new customer to your Mercury workspace. Persistent. NOT idempotent at the API level — calling twice with the same payload creates two customers; check `mercury_list_customers` for existing entries before creating to avoid duplicates.", "", "RETURNS: `{ id, name, email, address, ... }` — keep `id` for the invoicing tools.", ].join("\n"), { name: z.string().describe("Customer name"), email: z.email().describe("Customer email"), address: addressSchema.optional(), }, async (args) => { const data = await client.post("/ar/customers", args); return textResult(data); }, { title: "Create Customer", destructiveHint: false, openWorldHint: true }, ); defineTool( server, "mercury_update_customer", [ "Update an existing Accounts Receivable customer. Pass only the fields you want to change.", "", "USE WHEN: amending a customer's contact details (name, email, billing address) after creation. Existing invoices are not retroactively modified.", "", "DO NOT USE: to delete a customer (use `mercury_delete_customer`). To change the customer of an existing invoice, cancel + recreate the invoice.", "", "SIDE EFFECTS: writes the new customer record to Mercury. Persistent. Only the fields you pass are changed — omitted fields keep their current value.", "", "RETURNS: `{ id, name, email, address, ... }` — the updated customer.", ].join("\n"), { customerId: z.uuid().describe("Customer ID"), name: z.string().optional(), email: z.email().optional(), address: addressSchema.optional(), }, async ({ customerId, ...body }) => { const data = await client.patch(`/ar/customers/${customerId}`, body); return textResult(data); }, { title: "Update Customer", destructiveHint: false, openWorldHint: true }, ); defineTool( server, "mercury_delete_customer", [ "Permanently delete an Accounts Receivable customer. **DESTRUCTIVE.**", "", "USE WHEN: removing a customer that was created by mistake, or that the user explicitly wants to purge. ALWAYS confirm with the user before calling — there is no undo.", "", "DO NOT USE: when the customer has invoices in `paid` / `outstanding` status — Mercury rejects deletion in those cases and returns a 409. Cancel outstanding invoices first via `mercury_cancel_invoice`.", "", "SIDE EFFECTS: **permanent deletion** on Mercury's side. The customer disappears from the AR list. Past invoices' `customerId` may dangle (Mercury does not cascade-delete invoices). NOT recoverable from API. ALWAYS confirm with the user.", "", "RETURNS: confirmation payload from Mercury (`{ deleted: true, ... }` or similar).", ].join("\n"), { customerId: z.uuid().describe("Customer ID"), }, async ({ customerId }) => { const data = await client.delete(`/ar/customers/${customerId}`); return textResult(data); }, { title: "Delete Customer", destructiveHint: true, openWorldHint: true }, ); } - src/tools/_shared.ts:28-54 (helper)The defineTool() helper function that wraps the handler with rate-limit/dry-run/audit middleware via wrapToolHandler() and registers it on the MCP server via server.registerTool().
export function defineTool<S extends ZodRawShape>( server: McpServer, name: string, description: string, inputSchema: S, handler: (args: z.infer<z.ZodObject<S>>) => Promise<ToolResult>, annotations: ToolAnnotations, ): void { const wrapped = wrapToolHandler(name, handler); const strictSchema = z.object(inputSchema).strict(); // MCP behavioral annotations (readOnlyHint / destructiveHint / // idempotentHint / openWorldHint) — declared machine-readable so // hosts and rubrics (TDQS / Glama Behavior dimension) can detect // tool semantics without scraping the prose description. Required // (not optional) so every new tool ships with explicit semantics — // forgetting the annotation now fails typecheck instead of // silently shipping a tool with no hint set. // The MCP SDK overloads `registerTool` with shape narrowing the runtime // strict-schema and the wrapped callback can't satisfy through generics. // Both casts are runtime-safe — the signatures only diverge at the type // level. Asserted by the existing tool-registration tests. (server.registerTool as unknown as (...a: unknown[]) => unknown)( name, { description, inputSchema: strictSchema, annotations }, wrapped, ); } - src/middleware.ts:58-60 (helper)Rate-limit bucket mapping: mercury_delete_customer is mapped to the 'customers_write' bucket, sharing the same daily limit (3/day) and monthly limit (60/month) with mercury_create_customer and mercury_update_customer.
mercury_create_customer: "customers_write", mercury_update_customer: "customers_write", mercury_delete_customer: "customers_write",