From c6b6641dfae16c70940acef4faad56414760c76e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Jun 2026 05:30:16 +0000 Subject: [PATCH] =?UTF-8?q?Expand=20MCP=20from=2024=20to=2049=20tools=20?= =?UTF-8?q?=E2=80=94=20full=20coverage=20of=20all=20major=20features?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New tools added: Expenses (5): expenses_list, expenses_get, expenses_create, expenses_update, expenses_delete Recurring invoices (7): recurring_list, recurring_create, recurring_update, recurring_pause, recurring_resume, recurring_generate_now, recurring_delete Dashboard (1): dashboard_get_stats — revenue, pending, overdue, client count, MoM change, 6-month chart, recent invoices Invoice extras (8): invoices_get_current_open, invoices_send (email with PDF attachment), invoices_send_reminder, invoices_generate_public_token, invoices_revoke_public_token, invoices_bulk_update_status, invoices_bulk_delete New Zod schemas and JSON schemas for all added tools. https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG --- src/app/api/mcp/route.ts | 287 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) diff --git a/src/app/api/mcp/route.ts b/src/app/api/mcp/route.ts index 4f1b8b9..0cf8067 100644 --- a/src/app/api/mcp/route.ts +++ b/src/app/api/mcp/route.ts @@ -81,6 +81,45 @@ const invoiceUpdateSchema = invoiceCreateSchema.partial().extend({ id: z.string(), }); +const expenseCreateSchema = z.object({ + date: dateString, + description: z.string().min(1), + amount: z.number().min(0), + currency: z.string().length(3).default("USD"), + category: z.string().optional().or(z.literal("")), + billable: z.boolean().default(false), + reimbursable: z.boolean().default(false), + taxDeductible: z.boolean().default(false), + notes: z.string().optional().or(z.literal("")), + clientId: z.string().optional().or(z.literal("")), + businessId: z.string().optional().or(z.literal("")), + invoiceId: z.string().optional().or(z.literal("")), +}); + +const expenseUpdateSchema = expenseCreateSchema.partial().extend({ id: z.string() }); + +const recurringItemSchema = z.object({ + description: z.string().min(1), + hours: z.number().min(0), + rate: z.number().min(0), + position: z.number().int().default(0), +}); + +const recurringCreateSchema = z.object({ + name: z.string().min(1).max(255), + clientId: z.string().min(1), + businessId: z.string().optional().or(z.literal("")), + schedule: z.enum(["weekly", "biweekly", "monthly", "quarterly", "yearly"]), + invoicePrefix: z.string().optional().default("#"), + taxRate: z.number().min(0).max(100).default(0), + currency: z.string().length(3).default("USD"), + notes: z.string().optional().or(z.literal("")), + emailMessage: z.string().optional().or(z.literal("")), + items: z.array(recurringItemSchema).min(1), +}); + +const recurringUpdateSchema = recurringCreateSchema.extend({ id: z.string() }); + const jsonSchemas = { empty: { type: "object", properties: {}, additionalProperties: false }, id: { @@ -191,6 +230,94 @@ const jsonSchemas = { required: ["name"], additionalProperties: false, }, + expenseCreate: { + type: "object", + properties: { + date: { type: "string", format: "date-time" }, + description: { type: "string", minLength: 1 }, + amount: { type: "number", minimum: 0 }, + currency: { type: "string", minLength: 3, maxLength: 3 }, + category: { type: "string", enum: ["Travel", "Meals & Entertainment", "Software & Subscriptions", "Hardware & Equipment", "Office Supplies", "Marketing", "Professional Services", "Utilities", "Other"] }, + billable: { type: "boolean" }, + reimbursable: { type: "boolean" }, + taxDeductible: { type: "boolean" }, + notes: { type: "string", maxLength: 500 }, + clientId: { type: "string" }, + businessId: { type: "string" }, + invoiceId: { type: "string" }, + }, + required: ["date", "description", "amount"], + additionalProperties: false, + }, + recurringItem: { + type: "object", + properties: { + description: { type: "string", minLength: 1 }, + hours: { type: "number", minimum: 0 }, + rate: { type: "number", minimum: 0 }, + position: { type: "integer" }, + }, + required: ["description", "hours", "rate"], + additionalProperties: false, + }, + recurringCreate: { + type: "object", + properties: { + name: { type: "string", minLength: 1, maxLength: 255 }, + clientId: { type: "string", minLength: 1 }, + businessId: { type: "string" }, + schedule: { type: "string", enum: ["weekly", "biweekly", "monthly", "quarterly", "yearly"] }, + invoicePrefix: { type: "string" }, + taxRate: { type: "number", minimum: 0, maximum: 100 }, + currency: { type: "string", minLength: 3, maxLength: 3 }, + notes: { type: "string" }, + emailMessage: { type: "string" }, + items: { + type: "array", + minItems: 1, + items: { + type: "object", + properties: { + description: { type: "string", minLength: 1 }, + hours: { type: "number", minimum: 0 }, + rate: { type: "number", minimum: 0 }, + position: { type: "integer" }, + }, + required: ["description", "hours", "rate"], + additionalProperties: false, + }, + }, + }, + required: ["name", "clientId", "schedule", "items"], + additionalProperties: false, + }, + invoiceSend: { + type: "object", + properties: { + invoiceId: { type: "string" }, + customSubject: { type: "string" }, + customMessage: { type: "string" }, + ccEmails: { type: "string", description: "Comma-separated CC email addresses" }, + bccEmails: { type: "string", description: "Comma-separated BCC email addresses" }, + }, + required: ["invoiceId"], + additionalProperties: false, + }, + bulkIds: { + type: "object", + properties: { ids: { type: "array", items: { type: "string" }, minItems: 1 } }, + required: ["ids"], + additionalProperties: false, + }, + bulkStatus: { + type: "object", + properties: { + ids: { type: "array", items: { type: "string" }, minItems: 1 }, + status: { type: "string", enum: ["draft", "sent", "paid"] }, + }, + required: ["ids", "status"], + additionalProperties: false, + }, } as const; type ToolDefinition = { @@ -572,6 +699,166 @@ const tools = { to: input.to ? parseDate(input.to, "to") : undefined, }), }), + // ── Expenses ──────────────────────────────────────────────────────────────── + expenses_list: defineTool({ + description: "List all expenses for the authenticated user, ordered by date descending.", + inputSchema: jsonSchemas.empty, + schema: z.object({}).optional().default({}), + handler: async (_input, caller) => caller.expenses.getAll(), + }), + expenses_get: defineTool({ + description: "Get a single expense by ID.", + inputSchema: jsonSchemas.id, + schema: z.object({ id: z.string() }), + handler: async (input, caller) => caller.expenses.getById(input), + }), + expenses_create: defineTool({ + description: "Create an expense. Category must be one of the allowed values. Set billable=true if this will be charged to a client, taxDeductible=true for tax purposes.", + inputSchema: jsonSchemas.expenseCreate, + schema: expenseCreateSchema, + handler: async (input, caller) => + caller.expenses.create({ + ...input, + date: parseDate(input.date, "date"), + }), + }), + expenses_update: defineTool({ + description: "Update an existing expense by ID. All fields are optional except id.", + inputSchema: { + ...jsonSchemas.expenseCreate, + required: ["id"], + properties: { id: { type: "string" }, ...jsonSchemas.expenseCreate.properties }, + }, + schema: expenseUpdateSchema, + handler: async (input, caller) => + caller.expenses.update({ + ...input, + date: input.date ? parseDate(input.date, "date") : undefined, + }), + }), + expenses_delete: defineTool({ + description: "Delete an expense by ID.", + inputSchema: jsonSchemas.id, + schema: z.object({ id: z.string() }), + handler: async (input, caller) => caller.expenses.delete(input), + }), + + // ── Recurring Invoices ─────────────────────────────────────────────────────── + recurring_list: defineTool({ + description: "List all recurring invoice templates for the authenticated user, ordered by next due date.", + inputSchema: jsonSchemas.empty, + schema: z.object({}).optional().default({}), + handler: async (_input, caller) => caller.recurringInvoices.getAll(), + }), + recurring_create: defineTool({ + description: "Create a recurring invoice template. Invoices will be auto-generated on the given schedule. Items are line item templates (description, hours, rate).", + inputSchema: jsonSchemas.recurringCreate, + schema: recurringCreateSchema, + handler: async (input, caller) => caller.recurringInvoices.create(input), + }), + recurring_update: defineTool({ + description: "Update a recurring invoice template. Replaces all items.", + inputSchema: { + ...jsonSchemas.recurringCreate, + required: ["id", "name", "clientId", "schedule", "items"], + properties: { id: { type: "string" }, ...jsonSchemas.recurringCreate.properties }, + }, + schema: recurringUpdateSchema, + handler: async (input, caller) => caller.recurringInvoices.update(input), + }), + recurring_pause: defineTool({ + description: "Pause a recurring invoice template. No invoices will be generated until resumed.", + inputSchema: jsonSchemas.id, + schema: z.object({ id: z.string() }), + handler: async (input, caller) => caller.recurringInvoices.pause(input), + }), + recurring_resume: defineTool({ + description: "Resume a paused recurring invoice template.", + inputSchema: jsonSchemas.id, + schema: z.object({ id: z.string() }), + handler: async (input, caller) => caller.recurringInvoices.resume(input), + }), + recurring_generate_now: defineTool({ + description: "Immediately generate a draft invoice from a recurring template, regardless of schedule. Returns the new invoice ID.", + inputSchema: jsonSchemas.id, + schema: z.object({ id: z.string() }), + handler: async (input, caller) => caller.recurringInvoices.generateNow(input), + }), + recurring_delete: defineTool({ + description: "Delete a recurring invoice template by ID.", + inputSchema: jsonSchemas.id, + schema: z.object({ id: z.string() }), + handler: async (input, caller) => caller.recurringInvoices.delete(input), + }), + + // ── Dashboard ──────────────────────────────────────────────────────────────── + dashboard_get_stats: defineTool({ + description: "Get a business overview: total revenue (paid invoices), pending amount (sent/overdue invoices), overdue invoice count, total clients, month-over-month revenue change percentage, 6-month revenue chart data, and 5 most recent invoices.", + inputSchema: jsonSchemas.empty, + schema: z.object({}).optional().default({}), + handler: async (_input, caller) => caller.dashboard.getStats(), + }), + + // ── Invoice extras ─────────────────────────────────────────────────────────── + invoices_get_current_open: defineTool({ + description: "Get the most recent draft invoice for the authenticated user. Useful for quickly finding the active working invoice.", + inputSchema: jsonSchemas.empty, + schema: z.object({}).optional().default({}), + handler: async (_input, caller) => caller.invoices.getCurrentOpen(), + }), + invoices_send: defineTool({ + description: "Send an invoice to the client via email with a PDF attachment. Updates the invoice status to 'sent'. Requires email to be configured (Resend API key on the business or platform).", + inputSchema: jsonSchemas.invoiceSend, + schema: z.object({ + invoiceId: z.string(), + customSubject: z.string().optional(), + customMessage: z.string().optional(), + ccEmails: z.string().optional(), + bccEmails: z.string().optional(), + }), + handler: async (input, caller) => caller.email.sendInvoice({ ...input, useHtml: false }), + }), + invoices_send_reminder: defineTool({ + description: "Send a payment reminder email to the client for a sent or overdue invoice.", + inputSchema: { + type: "object", + properties: { + id: { type: "string" }, + customMessage: { type: "string", description: "Optional custom message to include in the reminder" }, + }, + required: ["id"], + additionalProperties: false, + }, + schema: z.object({ id: z.string(), customMessage: z.string().optional() }), + handler: async (input, caller) => caller.invoices.sendReminder(input), + }), + invoices_generate_public_token: defineTool({ + description: "Generate a shareable public link token for an invoice. Clients can view the invoice without logging in.", + inputSchema: jsonSchemas.id, + schema: z.object({ id: z.string() }), + handler: async (input, caller) => caller.invoices.generatePublicToken(input), + }), + invoices_revoke_public_token: defineTool({ + description: "Revoke the public shareable link for an invoice, making it inaccessible without authentication.", + inputSchema: jsonSchemas.id, + schema: z.object({ id: z.string() }), + handler: async (input, caller) => caller.invoices.revokePublicToken(input), + }), + invoices_bulk_update_status: defineTool({ + description: "Update the status of multiple invoices at once.", + inputSchema: jsonSchemas.bulkStatus, + schema: z.object({ + ids: z.array(z.string()).min(1), + status: z.enum(["draft", "sent", "paid"]), + }), + handler: async (input, caller) => caller.invoices.bulkUpdateStatus(input), + }), + invoices_bulk_delete: defineTool({ + description: "Delete multiple invoices at once.", + inputSchema: jsonSchemas.bulkIds, + schema: z.object({ ids: z.array(z.string()).min(1) }), + handler: async (input, caller) => caller.invoices.bulkDelete(input), + }), } satisfies Record; function rpcResult(id: JsonRpcId, result: unknown, init?: ResponseInit) {