list_clients
Retrieve clients filtered by active status and updated date, with paginated results that include billing details.
Instructions
Retrieve a list of clients with optional filtering by active status and updated date. Returns paginated results with client details including billing information.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| is_active | No | Filter by active status | |
| updated_since | No | Filter by clients updated since this timestamp | |
| page | No | Page number for pagination | |
| per_page | No | Number of clients per page (max 2000) |
Implementation Reference
- src/tools/clients.ts:20-36 (handler)ListClientsHandler class that executes the list_clients tool logic. Validates arguments via ClientQuerySchema, calls harvestClient.getClients(), and returns paginated client results as JSON.
class ListClientsHandler implements ToolHandler { constructor(private readonly config: BaseToolConfig) {} async execute(args: Record<string, any>): Promise<CallToolResult> { try { const validatedArgs = validateInput(ClientQuerySchema, args, 'client query'); logger.info('Listing clients from Harvest API'); const clients = await this.config.harvestClient.getClients(validatedArgs); return { content: [{ type: 'text', text: JSON.stringify(clients, null, 2) }], }; } catch (error) { return handleMCPToolError(error, 'list_clients'); } } } - src/schemas/client.ts:49-55 (schema)ClientQuerySchema - Zod schema that validates input for list_clients tool: optional is_active, updated_since, page, per_page (max 2000).
// Query parameters for listing clients export const ClientQuerySchema = z.object({ is_active: z.boolean().optional(), updated_since: z.string().datetime({ offset: true }).optional(), page: z.number().int().positive().optional(), per_page: z.number().int().min(1).max(2000).optional().default(2000), }); - src/tools/clients.ts:114-132 (registration)registerClientTools function that registers list_clients tool with name, description, inputSchema (object with is_active, updated_since, page, per_page), and pairs it with ListClientsHandler.
export function registerClientTools(config: BaseToolConfig): ToolRegistration[] { return [ { tool: { name: 'list_clients', description: 'Retrieve a list of clients with optional filtering by active status and updated date. Returns paginated results with client details including billing information.', inputSchema: { type: 'object', properties: { is_active: { type: 'boolean', description: 'Filter by active status' }, updated_since: { type: 'string', format: 'date-time', description: 'Filter by clients updated since this timestamp' }, page: { type: 'number', minimum: 1, description: 'Page number for pagination' }, per_page: { type: 'number', minimum: 1, maximum: 2000, description: 'Number of clients per page (max 2000)' }, }, additionalProperties: false, }, }, handler: new ListClientsHandler(config), }, - src/server.ts:75-94 (registration)Tool registration wiring in HarvestMCPServer - registerClientTools is called alongside other tool modules, iterating registrations and storing them in this.tools and this.toolHandlers Maps.
const toolModules = [ registerCompanyTools(config), registerTimeEntryTools(config), registerProjectTools(config), registerTaskTools(config), registerClientTools(config), registerUserTools(config), registerInvoiceTools(config), registerExpenseTools(config), registerEstimateTools(config), registerReportTools(config), ]; // Flatten and register all tools toolModules.forEach(toolRegistrations => { toolRegistrations.forEach(({ tool, handler }) => { this.tools.set(tool.name, tool); this.toolHandlers.set(tool.name, handler); }); }); - src/schemas/client.ts:9-55 (helper)ClientSchema and ClientsListSchema - type definitions for the client response data structure including pagination fields.
export const ClientSchema = z.object({ id: z.number().int().positive(), name: z.string().min(1), is_active: z.boolean(), address: z.string().nullable(), statement_key: z.string().nullable(), currency: z.string().length(3).optional(), // ISO currency code created_at: z.string().datetime({ offset: true }), updated_at: z.string().datetime({ offset: true }), }); // Clients list response (paginated) export const ClientsListSchema = z.object({ clients: z.array(ClientSchema), per_page: z.number().int().positive(), total_pages: z.number().int().min(0), total_entries: z.number().int().min(0), next_page: z.number().int().positive().nullable(), previous_page: z.number().int().positive().nullable(), page: z.number().int().positive(), links: z.object({ first: z.string().url(), next: z.string().url().nullable(), previous: z.string().url().nullable(), last: z.string().url(), }), }); // Input schemas for creating/updating clients export const CreateClientSchema = z.object({ name: z.string().min(1, 'Client name is required'), is_active: z.boolean().optional().default(true), address: z.string().optional(), currency: z.string().length(3, 'Currency must be a 3-letter ISO code').optional().default('USD'), }); export const UpdateClientSchema = CreateClientSchema.partial().extend({ id: z.number().int().positive(), }); // Query parameters for listing clients export const ClientQuerySchema = z.object({ is_active: z.boolean().optional(), updated_since: z.string().datetime({ offset: true }).optional(), page: z.number().int().positive().optional(), per_page: z.number().int().min(1).max(2000).optional().default(2000), });