create_estimate
Create a new estimate for a client. Supports optional line items, custom pricing, taxes, discounts, and purchase order numbers.
Instructions
Create a new estimate for a client with optional line items and terms. Supports custom pricing, taxes, and discounts.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| client_id | Yes | The client ID to create the estimate for (required) | |
| subject | No | Estimate subject line | |
| notes | No | Estimate notes or description | |
| currency | No | 3-letter ISO currency code (e.g., USD, EUR) | |
| issue_date | No | Estimate issue date (YYYY-MM-DD) | |
| tax | No | Tax percentage (0-100) | |
| tax2 | No | Second tax percentage (0-100) | |
| discount | No | Discount percentage (0-100) | |
| purchase_order | No | Client purchase order number |
Implementation Reference
- src/tools/estimates.ts:58-74 (handler)CreateEstimateHandler: The core handler class that executes the create_estimate tool logic. It validates args using CreateEstimateSchema, calls harvestClient.createEstimate(), and returns the result as MCP content.
class CreateEstimateHandler implements ToolHandler { constructor(private readonly config: BaseToolConfig) {} async execute(args: Record<string, any>): Promise<CallToolResult> { try { const validatedArgs = validateInput(CreateEstimateSchema, args, 'create estimate'); logger.info('Creating estimate via Harvest API'); const estimate = await this.config.harvestClient.createEstimate(validatedArgs); return { content: [{ type: 'text', text: JSON.stringify(estimate, null, 2) }], }; } catch (error) { return handleMCPToolError(error, 'create_estimate'); } } } - src/schemas/estimate.ts:79-89 (schema)CreateEstimateSchema: Zod schema defining input validation for creating an estimate (client_id required; subject, notes, currency, issue_date, tax, tax2, discount, purchase_order optional).
export const CreateEstimateSchema = z.object({ client_id: z.number().int().positive(), subject: z.string().optional(), notes: z.string().optional(), currency: z.string().length(3).optional().default('USD'), issue_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be in YYYY-MM-DD format').optional(), tax: z.number().min(0).max(100).optional(), tax2: z.number().min(0).max(100).optional(), discount: z.number().min(0).max(100).optional(), purchase_order: z.string().optional(), }); - src/tools/estimates.ts:151-173 (registration)Tool registration entry for 'create_estimate' within registerEstimateTools(). Defines tool name, description, inputSchema metadata, and wires it to CreateEstimateHandler.
{ tool: { name: 'create_estimate', description: 'Create a new estimate for a client with optional line items and terms. Supports custom pricing, taxes, and discounts.', inputSchema: { type: 'object', properties: { client_id: { type: 'number', description: 'The client ID to create the estimate for (required)' }, subject: { type: 'string', description: 'Estimate subject line' }, notes: { type: 'string', description: 'Estimate notes or description' }, currency: { type: 'string', minLength: 3, maxLength: 3, description: '3-letter ISO currency code (e.g., USD, EUR)' }, issue_date: { type: 'string', format: 'date', description: 'Estimate issue date (YYYY-MM-DD)' }, tax: { type: 'number', minimum: 0, maximum: 100, description: 'Tax percentage (0-100)' }, tax2: { type: 'number', minimum: 0, maximum: 100, description: 'Second tax percentage (0-100)' }, discount: { type: 'number', minimum: 0, maximum: 100, description: 'Discount percentage (0-100)' }, purchase_order: { type: 'string', description: 'Client purchase order number' }, }, required: ['client_id'], additionalProperties: false, }, }, handler: new CreateEstimateHandler(config), }, - src/server.ts:84-95 (registration)Server-level registration: registerEstimateTools(config) is called in registerAllTools() and its results are stored in the tools and toolHandlers maps.
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/client/estimates-client.ts:61-85 (helper)EstimatesClient.createEstimate(): The API client layer that validates input via CreateEstimateSchema and posts to the Harvest API /estimates endpoint.
async createEstimate(input: CreateEstimateInput): Promise<any> { try { const validatedInput = CreateEstimateSchema.parse(input); this.logger.debug('Creating estimate', { clientId: validatedInput.client_id, subject: validatedInput.subject }); const response: AxiosResponse = await this.client.post('/estimates', validatedInput); this.logger.info('Successfully created estimate', { estimateId: response.data.id, estimateNumber: response.data.number }); return response.data; } catch (error) { if (error instanceof z.ZodError) { this.logger.error('Create estimate validation failed:', error.errors); throw new Error('Invalid estimate input data'); } throw error; } }