mcpengine/servers/freshbooks/src/tools/invoices-tools.ts
Jake Shore ff349dc88f FreshBooks: Complete MCP server with 55+ tools and 22 React apps
- 55+ tools across 12 categories (invoices, clients, expenses, estimates, time, projects, payments, items, taxes, reports, recurring, accounts)
- FreshBooks API client with OAuth2, pagination, error handling
- 22 dark-themed React MCP apps (invoice dashboard, builder, client dashboard, expense tracker, time tracker, project dashboard, reports, etc.)
- Full TypeScript types for all FreshBooks entities
- Comprehensive README with examples and architecture docs
2026-02-12 17:09:42 -05:00

269 lines
9.3 KiB
TypeScript

import { z } from 'zod';
import type { FreshBooksClient } from '../clients/freshbooks.js';
import type { Invoice, Payment } from '../types/index.js';
export const invoicesTools = [
{
name: 'freshbooks_list_invoices',
description: 'List all invoices with optional filtering (client, status, date range)',
inputSchema: z.object({
clientid: z.number().optional().describe('Filter by client ID'),
status: z.enum(['draft', 'sent', 'viewed', 'paid', 'partial', 'overdue', 'disputed']).optional(),
date_min: z.string().optional().describe('Minimum date (YYYY-MM-DD)'),
date_max: z.string().optional().describe('Maximum date (YYYY-MM-DD)'),
page: z.number().default(1),
per_page: z.number().default(30),
}),
handler: async (args: any, client: FreshBooksClient) => {
const params: Record<string, any> = {};
if (args.clientid) params.clientid = args.clientid;
if (args.status) params.status = args.status;
if (args.date_min) params.date_min = args.date_min;
if (args.date_max) params.date_max = args.date_max;
const response = await client.getPaginated<{ invoices: Invoice[] }>(
'/invoices/invoices',
args.page,
args.per_page,
params
);
return {
invoices: response.response.result.invoices || [],
page: response.response.page,
pages: response.response.pages,
total: response.response.total,
};
},
},
{
name: 'freshbooks_get_invoice',
description: 'Get a single invoice by ID',
inputSchema: z.object({
invoice_id: z.number().describe('Invoice ID'),
}),
handler: async (args: any, client: FreshBooksClient) => {
const response = await client.get<{ response: { result: { invoice: Invoice } } }>(
`/invoices/invoices/${args.invoice_id}`
);
return response.response.result.invoice;
},
},
{
name: 'freshbooks_create_invoice',
description: 'Create a new invoice',
inputSchema: z.object({
clientid: z.number().describe('Client ID'),
create_date: z.string().optional().describe('Invoice date (YYYY-MM-DD, defaults to today)'),
due_date: z.string().optional().describe('Due date (YYYY-MM-DD)'),
lines: z.array(z.object({
name: z.string().describe('Line item name'),
description: z.string().optional(),
qty: z.number().default(1),
unit_cost: z.string().describe('Unit cost as string (e.g., "100.00")'),
})).describe('Invoice line items'),
currency_code: z.string().default('USD'),
notes: z.string().optional(),
terms: z.string().optional(),
status: z.enum(['draft', 'sent']).default('draft'),
}),
handler: async (args: any, client: FreshBooksClient) => {
const lines = args.lines.map((line: any) => ({
...line,
unit_cost: { amount: line.unit_cost, code: args.currency_code },
}));
const invoiceData = {
invoice: {
clientid: args.clientid,
create_date: args.create_date || new Date().toISOString().split('T')[0],
due_date: args.due_date,
currency_code: args.currency_code,
lines,
notes: args.notes,
terms: args.terms,
status: args.status === 'sent' ? 2 : 1,
},
};
const response = await client.post<{ response: { result: { invoice: Invoice } } }>(
'/invoices/invoices',
invoiceData
);
return response.response.result.invoice;
},
},
{
name: 'freshbooks_update_invoice',
description: 'Update an existing invoice',
inputSchema: z.object({
invoice_id: z.number().describe('Invoice ID'),
clientid: z.number().optional(),
create_date: z.string().optional(),
due_date: z.string().optional(),
lines: z.array(z.object({
name: z.string(),
description: z.string().optional(),
qty: z.number(),
unit_cost: z.string(),
})).optional(),
notes: z.string().optional(),
terms: z.string().optional(),
}),
handler: async (args: any, client: FreshBooksClient) => {
const updateData: any = { invoice: {} };
if (args.clientid) updateData.invoice.clientid = args.clientid;
if (args.create_date) updateData.invoice.create_date = args.create_date;
if (args.due_date) updateData.invoice.due_date = args.due_date;
if (args.notes) updateData.invoice.notes = args.notes;
if (args.terms) updateData.invoice.terms = args.terms;
if (args.lines) {
updateData.invoice.lines = args.lines.map((line: any) => ({
...line,
unit_cost: { amount: line.unit_cost, code: 'USD' },
}));
}
const response = await client.put<{ response: { result: { invoice: Invoice } } }>(
`/invoices/invoices/${args.invoice_id}`,
updateData
);
return response.response.result.invoice;
},
},
{
name: 'freshbooks_delete_invoice',
description: 'Delete an invoice (moves to archived)',
inputSchema: z.object({
invoice_id: z.number().describe('Invoice ID'),
}),
handler: async (args: any, client: FreshBooksClient) => {
await client.put(
`/invoices/invoices/${args.invoice_id}`,
{ invoice: { vis_state: 1 } }
);
return { success: true, message: `Invoice ${args.invoice_id} archived` };
},
},
{
name: 'freshbooks_send_invoice',
description: 'Send an invoice to the client via email',
inputSchema: z.object({
invoice_id: z.number().describe('Invoice ID'),
email_subject: z.string().optional(),
email_body: z.string().optional(),
}),
handler: async (args: any, client: FreshBooksClient) => {
const emailData: any = { invoice: {} };
if (args.email_subject) emailData.invoice.email_subject = args.email_subject;
if (args.email_body) emailData.invoice.email_body = args.email_body;
await client.put(
`/invoices/invoices/${args.invoice_id}`,
{ invoice: { action_email: true, ...emailData.invoice } }
);
return { success: true, message: `Invoice ${args.invoice_id} sent` };
},
},
{
name: 'freshbooks_mark_invoice_paid',
description: 'Mark an invoice as paid',
inputSchema: z.object({
invoice_id: z.number().describe('Invoice ID'),
payment_type: z.string().default('Cash').describe('Payment method'),
payment_date: z.string().optional().describe('Payment date (YYYY-MM-DD, defaults to today)'),
amount: z.string().optional().describe('Payment amount (defaults to outstanding amount)'),
}),
handler: async (args: any, client: FreshBooksClient) => {
// First get the invoice to know the outstanding amount
const invoiceResp = await client.get<{ response: { result: { invoice: Invoice } } }>(
`/invoices/invoices/${args.invoice_id}`
);
const invoice = invoiceResp.response.result.invoice;
const paymentData = {
payment: {
invoiceid: args.invoice_id,
amount: {
amount: args.amount || invoice.outstanding.amount,
code: invoice.currency_code,
},
date: args.payment_date || new Date().toISOString().split('T')[0],
type: args.payment_type,
},
};
const response = await client.post<{ response: { result: { payment: Payment } } }>(
'/payments/payments',
paymentData
);
return response.response.result.payment;
},
},
{
name: 'freshbooks_mark_invoice_unpaid',
description: 'Mark an invoice as unpaid (reopen it)',
inputSchema: z.object({
invoice_id: z.number().describe('Invoice ID'),
}),
handler: async (args: any, client: FreshBooksClient) => {
await client.put(
`/invoices/invoices/${args.invoice_id}`,
{ invoice: { v3_status: 'unpaid' } }
);
return { success: true, message: `Invoice ${args.invoice_id} marked unpaid` };
},
},
{
name: 'freshbooks_get_invoice_payment',
description: 'Get payment details for an invoice',
inputSchema: z.object({
invoice_id: z.number().describe('Invoice ID'),
}),
handler: async (args: any, client: FreshBooksClient) => {
const response = await client.get<{ response: { result: { payments: Payment[] } } }>(
'/payments/payments',
{ invoiceid: args.invoice_id }
);
return response.response.result.payments || [];
},
},
{
name: 'freshbooks_create_payment',
description: 'Create a payment record for an invoice',
inputSchema: z.object({
invoice_id: z.number().describe('Invoice ID'),
amount: z.string().describe('Payment amount'),
date: z.string().optional().describe('Payment date (YYYY-MM-DD)'),
type: z.string().default('Cash').describe('Payment method'),
note: z.string().optional(),
}),
handler: async (args: any, client: FreshBooksClient) => {
const paymentData = {
payment: {
invoiceid: args.invoice_id,
amount: { amount: args.amount, code: 'USD' },
date: args.date || new Date().toISOString().split('T')[0],
type: args.type,
note: args.note,
},
};
const response = await client.post<{ response: { result: { payment: Payment } } }>(
'/payments/payments',
paymentData
);
return response.response.result.payment;
},
},
];