Book an appointment
book_appointmentSchedule a patient-provider appointment at a clinic. Provide clinic, provider, patient, start time, and reason. An idempotency key ensures no double bookings.
Instructions
Create an appointment for a patient with a provider. Requires an idempotency_key; repeated calls with the same key return the original appointment instead of double-booking.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| clinic_id | Yes | ||
| provider_id | Yes | ||
| patient_id | Yes | ||
| start_iso | Yes | Appointment start, ISO 8601 | |
| duration_minutes | No | ||
| reason | Yes | ||
| idempotency_key | Yes | Caller-supplied key. Repeated calls with the same key return the original appointment instead of double-booking. |
Implementation Reference
- src/tools/book-appointment.ts:30-79 (handler)Main handler function that validates input, checks idempotency key, checks for time conflicts, and inserts the appointment into the store.
export function bookAppointment( store: ClinicStore, raw: unknown, ): BookAppointmentResult { const args = Args.parse(raw); const existingByKey = store.findAppointmentByIdempotencyKey( args.clinic_id, args.idempotency_key, ); if (existingByKey) { return { appointment: existingByKey, idempotent_replay: true }; } // assertClinic + tenant checks happen inside the store accessors. store.getProvider(args.clinic_id, args.provider_id); store.getPatient(args.clinic_id, args.patient_id); const start = new Date(args.start_iso); if (Number.isNaN(start.getTime())) { throw new ValidationError("start_iso must be a valid ISO 8601 timestamp"); } const end = new Date(start.getTime() + args.duration_minutes * 60_000); const conflict = store .listProviderAppointments(args.clinic_id, args.provider_id) .find( (a) => start.getTime() < new Date(a.end_iso).getTime() && end.getTime() > new Date(a.start_iso).getTime(), ); if (conflict) { throw new ConflictError( `provider ${args.provider_id} already has appointment ${conflict.id} overlapping that slot`, ); } const appointment = store.insertAppointment({ clinic_id: args.clinic_id, provider_id: args.provider_id, patient_id: args.patient_id, start_iso: start.toISOString(), end_iso: end.toISOString(), reason: args.reason, status: "scheduled", idempotency_key: args.idempotency_key, }); return { appointment, idempotent_replay: false }; } - src/tools/book-appointment.ts:6-20 (schema)Input schema definition with clinic_id, provider_id, patient_id, start_iso, duration_minutes (default 30), reason, and idempotency_key.
export const bookAppointmentInput = { clinic_id: z.string(), provider_id: z.string(), patient_id: z.string(), start_iso: z.string().describe("Appointment start, ISO 8601"), duration_minutes: z.number().int().min(15).max(120).default(30), reason: z.string().min(1).max(500), idempotency_key: z .string() .min(8) .max(128) .describe( "Caller-supplied key. Repeated calls with the same key return the original appointment instead of double-booking.", ), }; - src/tools/book-appointment.ts:25-28 (schema)Return type interface with appointment and idempotent_replay flag.
export interface BookAppointmentResult { appointment: Appointment; idempotent_replay: boolean; } - src/server.ts:61-70 (registration)Registration of the 'book_appointment' tool on the MCP server with title, description, input schema, and handler callback.
server.registerTool( "book_appointment", { title: "Book an appointment", description: "Create an appointment for a patient with a provider. Requires an idempotency_key; repeated calls with the same key return the original appointment instead of double-booking.", inputSchema: bookAppointmentInput, }, wrap((args) => bookAppointment(store, args)), ); - src/store/index.ts:96-104 (helper)findAppointmentByIdempotencyKey helper used by the handler to check for idempotent replays.
findAppointmentByIdempotencyKey( clinicId: string, key: string, ): Appointment | undefined { this.assertClinic(clinicId); return this.appointments.find( (a) => a.clinic_id === clinicId && a.idempotency_key === key, ); }