create_appointment
Schedule a new patient appointment in Cliniko by specifying start time, practitioner, appointment type, and business. Optionally add patient ID and notes.
Instructions
Create a new appointment
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| starts_at | Yes | Appointment start time (ISO 8601) | |
| patient_id | No | Patient ID (optional for walk-ins) | |
| practitioner_id | Yes | Practitioner ID | |
| appointment_type_id | Yes | Appointment type ID | |
| business_id | Yes | Business ID | |
| notes | No | Appointment notes |
Implementation Reference
- src/tools/appointments.ts:94-120 (handler)The MCP tool registration and handler for 'create_appointment'. Registers the tool with server.tool('create_appointment', ...), defines the input schema inline, and calls client.createAppointment(input) on execution.
server.tool('create_appointment', { description: 'Create a new appointment', inputSchema: { type: 'object', properties: { starts_at: { type: 'string', description: 'Appointment start time (ISO 8601)' }, patient_id: { type: 'number', description: 'Patient ID (optional for walk-ins)' }, practitioner_id: { type: 'number', description: 'Practitioner ID' }, appointment_type_id: { type: 'number', description: 'Appointment type ID' }, business_id: { type: 'number', description: 'Business ID' }, notes: { type: 'string', description: 'Appointment notes' } }, required: ['starts_at', 'practitioner_id', 'appointment_type_id', 'business_id'] }, }, async (input: z.infer<typeof AppointmentCreateSchema>) => { try { const appointment = await client.createAppointment(input); return { content: [{ type: 'text', text: JSON.stringify(appointment, null, 2) }] }; } catch (error) { throw new Error(`Failed to create appointment: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); - src/tools/appointments.ts:15-22 (schema)Zod schema for create_appointment input validation: starts_at (required), patient_id (optional), practitioner_id (required), appointment_type_id (required), business_id (required), notes (optional).
const AppointmentCreateSchema = z.object({ starts_at: z.string().describe('Appointment start time (ISO 8601)'), patient_id: z.number().optional().describe('Patient ID (optional for walk-ins)'), practitioner_id: z.number().describe('Practitioner ID'), appointment_type_id: z.number().describe('Appointment type ID'), business_id: z.number().describe('Business ID'), notes: z.string().optional().describe('Appointment notes'), }); - src/cliniko-client.ts:114-126 (helper)ClinikoClient.createAppointment method - sends POST /appointments to the Cliniko API with the appointment data as JSON body. This is the underlying API call that the handler delegates to.
async createAppointment(appointment: { starts_at: string; patient_id?: number; practitioner_id: number; appointment_type_id: number; business_id: number; notes?: string; }): Promise<Appointment> { return this.request<Appointment>('/appointments', { method: 'POST', body: JSON.stringify(appointment), }); } - src/tools/appointments.ts:31-316 (registration)The registerAppointmentTools function exported from appointments.ts. It's called from src/index.ts line 59 to register all appointment-related tools including create_appointment.
export function registerAppointmentTools(server: any, client: ClinikoClient) { // List/Search appointments server.tool('list_appointments', { description: 'List or search for appointments', inputSchema: { type: 'object', properties: { patient_id: { type: 'number', description: 'Filter by patient ID' }, practitioner_id: { type: 'number', description: 'Filter by practitioner ID' }, business_id: { type: 'number', description: 'Filter by business ID' }, starts_at: { type: 'string', description: 'Filter appointments starting from (ISO 8601)' }, ends_at: { type: 'string', description: 'Filter appointments ending before (ISO 8601)' }, status: { type: 'string', description: 'Filter by status (Active, Cancelled, Did not arrive)' }, page: { type: 'number', description: 'Page number' }, per_page: { type: 'number', description: 'Results per page (max 100)' } } }, }, async (params: z.infer<typeof AppointmentSearchSchema>) => { try { const response = await client.listAppointments(params); const appointments = response.appointments || []; return { content: [{ type: 'text', text: JSON.stringify({ appointments, total_entries: response.total_entries, page: params.page || 1, has_more: !!response.links.next }, null, 2) }] }; } catch (error) { throw new Error(`Failed to list appointments: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); // Get appointment by ID server.tool('get_appointment', { description: 'Get a specific appointment by ID', inputSchema: { type: 'object', properties: { appointment_id: { type: 'number', description: 'Appointment ID' } }, required: ['appointment_id'] }, }, async ({ appointment_id }: { appointment_id: number }) => { try { const appointment = await client.getAppointment(appointment_id); return { content: [{ type: 'text', text: JSON.stringify(appointment, null, 2) }] }; } catch (error) { throw new Error(`Failed to get appointment: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); // Create appointment server.tool('create_appointment', { description: 'Create a new appointment', inputSchema: { type: 'object', properties: { starts_at: { type: 'string', description: 'Appointment start time (ISO 8601)' }, patient_id: { type: 'number', description: 'Patient ID (optional for walk-ins)' }, practitioner_id: { type: 'number', description: 'Practitioner ID' }, appointment_type_id: { type: 'number', description: 'Appointment type ID' }, business_id: { type: 'number', description: 'Business ID' }, notes: { type: 'string', description: 'Appointment notes' } }, required: ['starts_at', 'practitioner_id', 'appointment_type_id', 'business_id'] }, }, async (input: z.infer<typeof AppointmentCreateSchema>) => { try { const appointment = await client.createAppointment(input); return { content: [{ type: 'text', text: JSON.stringify(appointment, null, 2) }] }; } catch (error) { throw new Error(`Failed to create appointment: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); // Update appointment server.tool('update_appointment', { description: 'Update an existing appointment', inputSchema: { type: 'object', properties: { appointment_id: { type: 'number', description: 'Appointment ID' }, starts_at: { type: 'string', description: 'New start time (ISO 8601)' }, notes: { type: 'string', description: 'Updated notes' }, patient_id: { type: 'number', description: 'New patient ID' } }, required: ['appointment_id'] }, }, async ({ appointment_id, ...updateData }: any) => { try { const appointment = await client.updateAppointment(appointment_id, updateData); return { content: [{ type: 'text', text: JSON.stringify(appointment, null, 2) }] }; } catch (error) { throw new Error(`Failed to update appointment: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); // Cancel appointment server.tool('cancel_appointment', { description: 'Cancel an appointment', inputSchema: { type: 'object', properties: { appointment_id: { type: 'number', description: 'Appointment ID' }, cancellation_reason: { type: 'string', description: 'Reason for cancellation' } }, required: ['appointment_id'] }, }, async ({ appointment_id, cancellation_reason }: { appointment_id: number; cancellation_reason?: string }) => { try { const appointment = await client.cancelAppointment(appointment_id, cancellation_reason); return { content: [{ type: 'text', text: JSON.stringify(appointment, null, 2) }] }; } catch (error) { throw new Error(`Failed to cancel appointment: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); // Delete appointment server.tool('delete_appointment', { description: 'Delete an appointment completely', inputSchema: { type: 'object', properties: { appointment_id: { type: 'number', description: 'Appointment ID' } }, required: ['appointment_id'] }, }, async ({ appointment_id }: { appointment_id: number }) => { try { await client.deleteAppointment(appointment_id); return { content: [{ type: 'text', text: `Appointment ${appointment_id} has been deleted successfully` }] }; } catch (error) { throw new Error(`Failed to delete appointment: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); // Get available times server.tool('get_available_times', { description: 'Get available appointment times for a practitioner', inputSchema: { type: 'object', properties: { business_id: { type: 'number', description: 'Business ID' }, practitioner_id: { type: 'number', description: 'Practitioner ID' }, from: { type: 'string', description: 'Start date for availability check (YYYY-MM-DD)' }, to: { type: 'string', description: 'End date for availability check (YYYY-MM-DD)' } }, required: ['business_id', 'practitioner_id', 'from', 'to'] }, }, async (params: z.infer<typeof AvailableTimesSchema>) => { try { const availableTimes = await client.getAvailableTimes(params); return { content: [{ type: 'text', text: JSON.stringify({ available_times: availableTimes, total: availableTimes.length, ...params }, null, 2) }] }; } catch (error) { throw new Error(`Failed to get available times: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); // List practitioners (helper for appointments) server.tool('list_practitioners', { description: 'List all practitioners', inputSchema: { type: 'object', properties: { page: { type: 'number', description: 'Page number' }, per_page: { type: 'number', description: 'Results per page' } } }, }, async ({ page, per_page }: { page?: number; per_page?: number }) => { try { const response = await client.listPractitioners({ page, per_page }); const practitioners = response.practitioners || []; return { content: [{ type: 'text', text: JSON.stringify({ practitioners, total_entries: response.total_entries, page: page || 1, has_more: !!response.links.next }, null, 2) }] }; } catch (error) { throw new Error(`Failed to list practitioners: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); // List appointment types (helper for appointments) server.tool('list_appointment_types', { description: 'List all appointment types', inputSchema: { type: 'object', properties: { page: { type: 'number', description: 'Page number' }, per_page: { type: 'number', description: 'Results per page' } } }, }, async ({ page, per_page }: { page?: number; per_page?: number }) => { try { const response = await client.listAppointmentTypes({ page, per_page }); const appointmentTypes = response.appointment_types || []; return { content: [{ type: 'text', text: JSON.stringify({ appointment_types: appointmentTypes, total_entries: response.total_entries, page: page || 1, has_more: !!response.links.next }, null, 2) }] }; } catch (error) { throw new Error(`Failed to list appointment types: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); // List businesses (helper for appointments) server.tool('list_businesses', { description: 'List all businesses', inputSchema: { type: 'object', properties: {} }, }, async () => { try { const response = await client.listBusinesses(); const businesses = response.businesses || []; return { content: [{ type: 'text', text: JSON.stringify({ businesses, total_entries: response.total_entries, }, null, 2) }] }; } catch (error) { throw new Error(`Failed to list businesses: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); } - src/index.ts:57-63 (registration)Registration hook in main entry point - calls registerAppointmentTools(toolRegistry, clinikoClient) which registers create_appointment along with other appointment tools.
// Register all tools registerPatientTools(toolRegistry, clinikoClient); registerAppointmentTools(toolRegistry, clinikoClient); registerSyntheticDataTools(toolRegistry, clinikoClient); registerEnhancedSyntheticDataTools(toolRegistry, clinikoClient); registerInvoiceTools(toolRegistry, clinikoClient); registerDemoInvoiceTools(toolRegistry, clinikoClient);