JosefPilter/appointments-chatbot
0
1"""2server.py — FastMCP server wiring layer.3 4Exposes the tools from tools.py over the MCP stdio transport.5All business logic lives in tools.py; this file is purely declaration.6 7Run directly:8 python -m mcp_server.server9 10Or via MCP inspector:11 mcp dev mcp_server/server.py12"""13 14from mcp.server.fastmcp import FastMCP15 16import mcp_server.tools as _tools17 18mcp = FastMCP("appointment-scheduler")19 20 21@mcp.tool()22def get_current_datetime() -> dict:23 """Return the current date and time in the Europe/Zurich timezone.24 25 Call this before checking availability whenever the user provides a relative26 time expression (e.g., 'next Wednesday', 'in three days', 'tomorrow morning').27 Use the returned date as the anchor for all date arithmetic.28 29 Returns a dict with:30 - datetime: ISO 8601 string with TZ offset (e.g. "2026-05-25T14:30:00+02:00")31 - date: ISO 8601 date string (e.g. "2026-05-25")32 - weekday: day name (e.g. "Monday")33 - week_number: ISO week number (int)34 """35 return _tools.get_current_datetime()36 37 38@mcp.tool()39def get_appointment_topics() -> list[dict]:40 """Return all available appointment topics.41 42 Use the returned topic_id values when calling get_appointment_contact_medium43 or check_availability.44 45 Returns a list of objects with topic_id and topic_name.46 """47 return _tools.get_appointment_topics()48 49 50@mcp.tool()51def get_appointment_contact_medium(topic_id: str) -> list[dict] | dict:52 """Return the contact media available for a given topic.53 54 Not all media are available for all topics:55 - Mortgage has no phone option (requires document review).56 - Pension has no branch option (handled remotely).57 58 Args:59 topic_id: A topic_id returned by get_appointment_topics.60 61 Returns a list of objects with contact_medium_id and contact_medium_name,62 or an error dict if topic_id is invalid.63 """64 return _tools.get_appointment_contact_medium(topic_id)65 66 67@mcp.tool()68def check_availability(69 topic_id: str,70 contact_medium_id: str,71 start_datetime: str,72 end_datetime: str,73) -> list[dict] | dict:74 """Return available 60-minute appointment slots within the requested window.75 76 Slots are generated dynamically relative to today. Weekends, fully-booked77 days, and slots within the next 24 hours are excluded automatically.78 79 If the requested range exceeds 3 days, only the first 3 days are checked.80 81 Args:82 topic_id: A topic_id from get_appointment_topics.83 contact_medium_id: A contact_medium_id from get_appointment_contact_medium.84 start_datetime: ISO 8601 string. Timezone defaults to Europe/Zurich if omitted.85 end_datetime: ISO 8601 string. Timezone defaults to Europe/Zurich if omitted.86 87 Returns a list of {datetime_start, datetime_end} dicts (ISO 8601 strings),88 or an error dict on invalid input.89 """90 return _tools.check_availability(91 topic_id, contact_medium_id, start_datetime, end_datetime92 )93 94 95@mcp.tool()96def book_appointment(97 topic_id: str,98 contact_medium_id: str,99 datetime_start: str,100 datetime_end: str,101) -> dict:102 """Book an appointment slot returned by check_availability.103 104 The slot must exist in the generated availability and must not already be105 taken. Use the exact datetime_start / datetime_end values from106 check_availability to guarantee a match.107 108 Args:109 topic_id: A topic_id from get_appointment_topics.110 contact_medium_id: A contact_medium_id from get_appointment_contact_medium.111 datetime_start: ISO 8601 string matching a slot from check_availability.112 datetime_end: ISO 8601 string matching a slot from check_availability.113 114 Returns {"status": "success", "booking_id": "BK-XXXXXX", "details": {...}}115 or {"status": "error", "message": "..."}.116 """117 return _tools.book_appointment(118 topic_id, contact_medium_id, datetime_start, datetime_end119 )120 121 122@mcp.tool()123def reset_bookings() -> dict:124 """Clear all bookings from the in-memory store.125 126 Intended for testing and evaluation resets. Has no effect on slot127 generation (availability patterns are date-seeded and remain stable).128 129 Returns {"status": "reset"}.130 """131 return _tools.reset_bookings()132 133 134@mcp.tool()135def admin_set_availability_override(override_json: str) -> dict:136 """Set a fixed availability profile for the current evaluation scenario run.137 138 Parses override_json (a JSON object mapping ISO date strings to lists of139 "HH:MM-HH:MM" slot strings, all times in Europe/Zurich) and activates the140 override. While active, check_availability returns only the overridden slots;141 dates absent from the dict return empty.142 143 Intended for the evaluation runner only — not for production use.144 145 Args:146 override_json: JSON string, e.g. '{"2026-05-26": ["09:00-10:00"]}'147 148 Returns {"status": "ok", "dates_set": N} or an error dict.149 """150 return _tools.admin_set_availability_override(override_json)151 152 153@mcp.tool()154def admin_clear_availability_override() -> dict:155 """Deactivate the availability override and revert to seeded slot generation.156 157 Call this between evaluation scenario runs to ensure isolation.158 159 Returns {"status": "cleared"}.160 """161 return _tools.admin_clear_availability_override()162 163 164@mcp.tool()165def admin_set_clock_override(reference_date: str) -> dict:166 """Pin get_current_datetime() to the scenario's reference_date.167 168 Call this per evaluation scenario run (alongside the availability override) so169 the agent's get_current_datetime() shares the scenario's pinned clock170 (EVALUATION_FRAMEWORK §3a) — making scenario execution date-independent even171 when the run day differs from the suite anchor.172 173 Args:174 reference_date: ISO date string, e.g. "2026-06-01".175 176 Returns {"status": "ok", "clock": "<iso>"} or an error dict.177 """178 return _tools.admin_set_clock_override(reference_date)179 180 181@mcp.tool()182def admin_clear_clock_override() -> dict:183 """Revert get_current_datetime() to the real wall clock.184 185 Call this between evaluation scenario runs to ensure isolation.186 187 Returns {"status": "cleared"}.188 """189 return _tools.admin_clear_clock_override()190 191 192if __name__ == "__main__":193 mcp.run()194 