google-calendar

Dependency-free local emulator for Google Calendar API v3 JSON REST calls. It is designed for googleapis Calendar clients, raw fetch, and AI-agent test flows that need calendars, events, ACL rules, free/busy, colors, settings, and watch channels without calling https://www.googleapis.com/calendar/v3.

Default port: 4615

Quick start

import { google } from "googleapis";
import { GoogleCalendarServer } from "./services/google-calendar/src/server.js";

const server = new GoogleCalendarServer(4615);
await server.start();

const calendar = google.calendar({ version: "v3", auth: "not-used-by-parlel" });
calendar.context._options.rootUrl = "http://127.0.0.1:4615/";

const created = await calendar.calendars.insert({
  requestBody: { summary: "Engineering", timeZone: "UTC" },
});

await calendar.events.insert({
  calendarId: created.data.id,
  requestBody: {
    summary: "Planning",
    start: { dateTime: "2026-01-01T10:00:00.000Z" },
    end: { dateTime: "2026-01-01T11:00:00.000Z" },
  },
});

await server.stop();

Reset state with server.reset() or POST /_parlel/reset. Health is available at GET /_parlel/health.

Implemented operations

Calendars

googleapis methodHTTP endpoint
calendar.calendars.insertPOST /calendar/v3/calendars
calendar.calendars.getGET /calendar/v3/calendars/{calendarId}
calendar.calendars.patchPATCH /calendar/v3/calendars/{calendarId}
calendar.calendars.updatePUT /calendar/v3/calendars/{calendarId}
calendar.calendars.clearPOST /calendar/v3/calendars/{calendarId}/clear
calendar.calendars.deleteDELETE /calendar/v3/calendars/{calendarId}

Calendar list

googleapis methodHTTP endpoint
calendar.calendarList.listGET /calendar/v3/users/me/calendarList
calendar.calendarList.insertPOST /calendar/v3/users/me/calendarList
calendar.calendarList.getGET /calendar/v3/users/me/calendarList/{calendarId}
calendar.calendarList.patchPATCH /calendar/v3/users/me/calendarList/{calendarId}
calendar.calendarList.updatePUT /calendar/v3/users/me/calendarList/{calendarId}
calendar.calendarList.deleteDELETE /calendar/v3/users/me/calendarList/{calendarId}
calendar.calendarList.watchPOST /calendar/v3/users/me/calendarList/watch

ACL

googleapis methodHTTP endpoint
calendar.acl.listGET /calendar/v3/calendars/{calendarId}/acl
calendar.acl.insertPOST /calendar/v3/calendars/{calendarId}/acl
calendar.acl.getGET /calendar/v3/calendars/{calendarId}/acl/{ruleId}
calendar.acl.patchPATCH /calendar/v3/calendars/{calendarId}/acl/{ruleId}
calendar.acl.updatePUT /calendar/v3/calendars/{calendarId}/acl/{ruleId}
calendar.acl.deleteDELETE /calendar/v3/calendars/{calendarId}/acl/{ruleId}
calendar.acl.watchPOST /calendar/v3/calendars/{calendarId}/acl/watch

Events

googleapis methodHTTP endpoint
calendar.events.listGET /calendar/v3/calendars/{calendarId}/events
calendar.events.insertPOST /calendar/v3/calendars/{calendarId}/events
calendar.events.importPOST /calendar/v3/calendars/{calendarId}/events/import
calendar.events.quickAddPOST /calendar/v3/calendars/{calendarId}/events/quickAdd?text=...
calendar.events.getGET /calendar/v3/calendars/{calendarId}/events/{eventId}
calendar.events.patchPATCH /calendar/v3/calendars/{calendarId}/events/{eventId}
calendar.events.updatePUT /calendar/v3/calendars/{calendarId}/events/{eventId}
calendar.events.deleteDELETE /calendar/v3/calendars/{calendarId}/events/{eventId}
calendar.events.movePOST /calendar/v3/calendars/{calendarId}/events/{eventId}/move?destination=...
calendar.events.instancesGET /calendar/v3/calendars/{calendarId}/events/{eventId}/instances
calendar.events.watchPOST /calendar/v3/calendars/{calendarId}/events/watch

Other resources

googleapis methodHTTP endpoint
calendar.freebusy.queryPOST /calendar/v3/freeBusy
calendar.colors.getGET /calendar/v3/colors
calendar.settings.listGET /calendar/v3/users/me/settings
calendar.settings.getGET /calendar/v3/users/me/settings/{setting}
calendar.settings.watchPOST /calendar/v3/users/me/settings/watch
calendar.channels.stopPOST /calendar/v3/channels/stop

All endpoints also accept the short /v3/... prefix.

Access via MCP / preview URL

When run inside a parlel sandbox, point Google Calendar API clients at the preview URL or local base URL, for example http://127.0.0.1:4615/. Node googleapis users can set calendar.context._options.rootUrl; raw clients can call the documented /calendar/v3/... paths directly. MCP-driven agents can reset state between scenarios with POST /_parlel/reset.

Surface coverage

Legend: ✅ supported · ◐ accepted-not-enforced · ✓ by design · ⟳ roadmap.

FeatureStatusNotes
Google Calendar API v3 JSON REST paths for calendars, calendarList, ACL, events, freeBusy, colors, settings, and channels.stop✅ supportedTested over HTTP with /calendar/v3/... and /v3/... prefixes.
In-memory calendars, calendar list entries, ACL rules, events, settings, and watch channels✅ supportedEphemeral and resettable; primary calendar is seeded on reset.
Required fields for calendars.insert, events.insert, events.import, acl.insert, watch channels, freeBusy.query, and channels.stop✅ supportedMissing fields return Calendar-style 400 JSON errors.
Pagination with maxResults and numeric pageToken✅ supportedCalendar/list/settings/ACL cap at 250; events cap at 2500.
Common event filters✅ supportedq, iCalUID, timeMin, timeMax, updatedMin, showDeleted, singleEvents, and orderBy are exercised.
Recurring event expansion◐ accepted-not-enforcedExpands simple `RRULE:FREQ=DAILY
Event, calendar, ACL, and channel notification side effects✓ by designStores request fields and channel metadata but does not send email, reminders, or outbound webhooks.
OAuth bearer tokens, API keys, IAM scopes, and per-user permissions◐ accepted-not-enforcedReal Google Calendar requires OAuth for most operations; the emulator accepts unauthenticated local requests.
Free/busy calculation✅ supportedReturns calendar#freeBusy and excludes transparent or cancelled events.
Google Meet conference creation✓ by designconferenceData is stored when provided; no real Meet room is created.
Incremental sync semantics for syncToken⟳ roadmapList responses can include nextSyncToken, but sync-token delta replay and 410 fullSyncRequired are not implemented.
Rate limiting and quota errors✓ by designThe local emulator never throttles.
Persistence across process restarts✓ by designState is intentionally in-memory only.

Error codes & shapes

Errors use the Google Calendar API error envelope documented in the Calendar error guide. The body does not include a top-level error.status member.

{
  "error": {
    "errors": [
      {
        "domain": "global",
        "reason": "notFound",
        "message": "Not Found"
      }
    ],
    "code": 404,
    "message": "Not Found"
  }
}

Common codes:

StatusWhen
400invalid JSON, missing required field (reason: required), invalid query combination
404unknown calendar, event, ACL rule, setting, or endpoint (reason: notFound)
405known resource path with an unsupported HTTP method (reason: methodNotAllowed)
409duplicate supplied identifier (reason: duplicate)
500unexpected emulator failure (reason: backendError)

Because auth is accepted-not-enforced, the emulator does not emit real Google 401 authError responses for missing or invalid OAuth credentials.

Manifest

See services/google-calendar/manifest.json:

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

GOOGLE_CALENDAR_EMULATOR_HOST=http://parlel-bridge:4615
GOOGLE_CLOUD_PROJECT=parlel
GCLOUD_PROJECT=parlel
<!-- parlel:testenv:end -->