Google Calendar
- MCP servers: use Google Calendar tools from ChatGPT, Claude and other MCP clients.
- Tasks: call Google Calendar from Python in an automation.
About Google Calendar
Google Calendar is the calendar in Google Workspace and personal Google accounts. Its API reads and writes the events on any calendar an account can open and reports when people are busy. Sign in with Google to connect an account. The connector can read and change events on every calendar that account can open, including shared team and room calendars, and can check when coworkers are busy if their calendars share free and busy times. Google emails attendees about a change only when the task asks it to. In Python tasks, the connector can post a morning agenda to Slack, book a follow-up call when a CRM deal moves stage, decline invites that land on a blocked day, or find an hour when several people are free and send the invite with a Google Meet link.
Build a Google Calendar MCP server with qbash
Connect Google Calendar to qbash, pick tools such as list calendars, get calendar and list events, and publish them on a hosted MCP server.
MCP clients such as ChatGPT, Claude, Claude Code, Codex, Cursor, Grok and Muse Code all connect to the same remote endpoint.
Build Tasks with Google Calendar
A qbash Task is a Python script that calls Google Calendar alongside model calls and other connectors. qbash deploys each Task to a managed runtime with version history, run logs and credentials shared across your organization.
Run it on a schedule, call it through an API or webhook, or start it from an MCP client.
Google Calendar Task SDK methods (10)
Call these from Python with qbash.integrations.google_calendar.
qbash.integrations.google_calendar.list_calendars(...)
List the calendars in the connected account's calendar list.
list_calendars(min_access_role: str | None = None, show_hidden: bool = False, max_results: int = 250, page_token: str | None = None)
Parameters
min_access_role: str | None = NoneOnly calendars with at least this role: freeBusyReader, reader, writer, or owner.
show_hidden: bool = FalseInclude calendars hidden from the Calendar sidebar.
max_results: int = 250Items per page, from 1 to 2500.
page_token: str | None = Nonenext_page_token from the previous page.
Returns
{"status": "success", "calendars": list[dict], "count": int, "next_page_token": str | None}Notes
Each calendar has id, summary, description, time_zone, access_role, primary, selected, hidden, and background_color. Use the id as calendar_id in the other modes. The primary calendar's id is the account's email address, and "primary" also works.
qbash.integrations.google_calendar.get_calendar(...)
Get one calendar's name, time zone, and the account's access role.
get_calendar(calendar_id: str = "primary")
Parameters
calendar_id: str = "primary"Calendar ID from list_calendars, or primary.
Returns
{"status": "success", "id": str, "summary": str, "description": str | None, "time_zone": str, "access_role": str, "primary": bool, "selected": bool, "hidden": bool, "background_color": str | None}Notes
The calendar must be in the connected account's calendar list.
qbash.integrations.google_calendar.list_events(...)
List events in a calendar. Recurring events are expanded into single instances by default.
list_events(calendar_id: str = "primary", time_min: str | None = None, time_max: str | None = None, query: str | None = None, single_events: bool = True, order_by: str | None = None, show_deleted: bool = False, updated_min: str | None = None, time_zone: str | None = None, max_results: int = 250, page_token: str | None = None)
Parameters
calendar_id: str = "primary"Calendar ID from list_calendars, or primary.
time_min: str | None = NoneOnly events ending after this RFC 3339 timestamp, such as 2026-10-22T00:00:00Z.
time_max: str | None = NoneOnly events starting before this RFC 3339 timestamp.
query: str | None = NoneFree-text search over summary, description, location, and attendee names and emails.
single_events: bool = TrueExpand recurring events into single instances.
order_by: str | None = NonestartTime (needs single_events=True) or updated. Defaults to startTime when single_events is True.
show_deleted: bool = FalseInclude cancelled events.
updated_min: str | None = NoneOnly events changed after this RFC 3339 timestamp.
time_zone: str | None = NoneIANA time zone for the returned times, such as Australia/Perth. Defaults to the calendar's time zone.
max_results: int = 250Items per page, from 1 to 2500.
page_token: str | None = Nonenext_page_token from the previous page.
Returns
{"status": "success", "calendar_id": str, "time_zone": str, "events": list[dict], "count": int, "next_page_token": str | None}Notes
Pass time_min and time_max to bound the window. Without them Google returns every event in the calendar. Each event has id, status, summary, description, location, html_link, start, end, all_day, organizer, creator, attendees, recurrence, recurring_event_id, meet_link, event_type, visibility, transparency, created, updated, and ical_uid. start and end hold date for all-day events and date_time otherwise. With single_events=True, events are sorted by start time and each instance of a recurring event has its own id and a recurring_event_id. Pass next_page_token back as page_token for the next page.
qbash.integrations.google_calendar.get_event(...)
Get one event by ID, including fields the list view leaves out.
get_event(calendar_id: str = "primary", event_id: str)
Parameters
calendar_id: str = "primary"Calendar ID from list_calendars, or primary.
event_id: strrequiredEvent ID from list_events.
Returns
{"status": "success", "calendar_id": str, "event": dict, "raw": dict}Notes
event uses the same fields as list_events. raw is Google's full event resource, with fields such as reminders, attachments, and extendedProperties.
qbash.integrations.google_calendar.create_event(...)
Create an event. Attendees get no email unless send_updates is all or externalOnly.
create_event(calendar_id: str = "primary", summary: str, start: str, end: str, time_zone: str | None = None, description: str | None = None, location: str | None = None, attendees: list[str] | None = None, recurrence: list[str] | None = None, add_google_meet: bool = False, visibility: str | None = None, transparency: str | None = None, send_updates: str = "none")
Parameters
calendar_id: str = "primary"Calendar ID from list_calendars, or primary.
summary: strrequiredEvent title.
start: strrequiredStart date (YYYY-MM-DD) or date-time (ISO 8601).
end: strrequiredEnd date (YYYY-MM-DD, exclusive) or date-time (ISO 8601).
time_zone: str | None = NoneIANA time zone for start and end, such as Australia/Perth.
description: str | None = NoneEvent description. Google accepts basic HTML.
location: str | None = NoneFree-text location.
attendees: list[str] | None = NoneAttendee email addresses.
recurrence: list[str] | None = NoneRRULE, EXRULE, RDATE, or EXDATE lines.
add_google_meet: bool = FalseAttach a new Google Meet link.
visibility: str | None = Nonedefault, public, private, or confidential.
transparency: str | None = Noneopaque (shows as busy) or transparent (shows as free).
send_updates: str = "none"Who Google emails about the change: none, all, or externalOnly.
Returns
{"status": "success", "calendar_id": str, "event": dict}Notes
start and end are both dates (YYYY-MM-DD) for an all-day event, where end is the day after the last day, or both date-times (2026-10-22T09:00:00 or 2026-10-22T09:00:00+08:00). A date-time without an offset uses time_zone, or the calendar's time zone when time_zone is omitted. Recurring events need recurrence rules such as RRULE:FREQ=WEEKLY;BYDAY=MO.
qbash.integrations.google_calendar.quick_add_event(...)
Create an event from a sentence, such as "Lunch with Sam tomorrow at 12pm".
quick_add_event(calendar_id: str = "primary", text: str, send_updates: str = "none")
Parameters
calendar_id: str = "primary"Calendar ID from list_calendars, or primary.
text: strrequiredText describing the event.
send_updates: str = "none"Who Google emails about the change: none, all, or externalOnly.
Returns
{"status": "success", "calendar_id": str, "event": dict}Notes
Google parses the text in the calendar's time zone and language. Check the returned start and end, since Google picks defaults for anything the text leaves out.
qbash.integrations.google_calendar.update_event(...)
Change fields on an event. Only the fields you pass are changed.
update_event(calendar_id: str = "primary", event_id: str, summary: str | None = None, start: str | None = None, end: str | None = None, time_zone: str | None = None, description: str | None = None, location: str | None = None, attendees: list[str] | None = None, recurrence: list[str] | None = None, add_google_meet: bool = False, visibility: str | None = None, transparency: str | None = None, send_updates: str = "none")
Parameters
calendar_id: str = "primary"Calendar ID from list_calendars, or primary.
event_id: strrequiredEvent ID from list_events.
summary: str | None = NoneNew event title.
start: str | None = NoneNew start date or date-time.
end: str | None = NoneNew end date or date-time.
time_zone: str | None = NoneIANA time zone for start and end, such as Australia/Perth.
description: str | None = NoneEvent description. Google accepts basic HTML.
location: str | None = NoneFree-text location.
attendees: list[str] | None = NoneAttendee email addresses.
recurrence: list[str] | None = NoneRRULE, EXRULE, RDATE, or EXDATE lines.
add_google_meet: bool = FalseAttach a new Google Meet link.
visibility: str | None = Nonedefault, public, private, or confidential.
transparency: str | None = Noneopaque (shows as busy) or transparent (shows as free).
send_updates: str = "none"Who Google emails about the change: none, all, or externalOnly.
Returns
{"status": "success", "calendar_id": str, "event": dict}Notes
attendees and recurrence replace the whole list, so read the event first to keep existing entries. start and end follow the same rules as create_event and must be passed together. For one instance of a recurring event, pass that instance's id from list_events with single_events=True.
qbash.integrations.google_calendar.respond_to_event(...)
Accept, decline, or tentatively accept an event the connected account was invited to.
respond_to_event(calendar_id: str = "primary", event_id: str, response: str, comment: str | None = None, send_updates: str = "none")
Parameters
calendar_id: str = "primary"Calendar ID from list_calendars, or primary.
event_id: strrequiredEvent ID from list_events.
response: strrequiredaccepted, declined, or tentative.
comment: str | None = NoneNote shown to the organizer with the response.
send_updates: str = "none"Who Google emails about the change: none, all, or externalOnly.
Returns
{"status": "success", "calendar_id": str, "event": dict}Notes
Fails when the connected account is not on the attendee list. The organizer is not emailed unless send_updates is all.
qbash.integrations.google_calendar.delete_event(...)
Delete an event.
delete_event(calendar_id: str = "primary", event_id: str, send_updates: str = "none")
Parameters
calendar_id: str = "primary"Calendar ID from list_calendars, or primary.
event_id: strrequiredEvent ID from list_events.
send_updates: str = "none"Who Google emails about the change: none, all, or externalOnly.
Returns
{"status": "success", "calendar_id": str, "event_id": str, "deleted": bool}Notes
Deleting a recurring event's parent id deletes every instance. Pass an instance id to delete one occurrence.
qbash.integrations.google_calendar.query_free_busy(...)
Get busy time ranges for one or more calendars.
query_free_busy(time_min: str, time_max: str, calendar_ids: list[str] = ["primary"], time_zone: str | None = None)
Parameters
time_min: strrequiredStart of the window, as an RFC 3339 timestamp.
time_max: strrequiredEnd of the window, as an RFC 3339 timestamp.
calendar_ids: list[str] = ["primary"]Calendar IDs or email addresses to check.
time_zone: str | None = NoneIANA time zone for the returned times. Defaults to UTC.
Returns
{"status": "success", "time_min": str, "time_max": str, "calendars": list[dict]}Notes
Each calendar has calendar_id, busy (a list of start and end times), and errors. A calendar the account cannot see returns an errors entry such as notFound instead of busy times. Up to 50 calendars per call.
Related connectors
Connect Google Calendar to qbash
Sign up, connect your Google Calendar account, and use its tools across qbash.
Sign up free