@ideadesignmedia/gcal-mcp
Private MCP server (stdio) and CLI for linking and operating multiple Google Calendar accounts. It stores tokens in a local SQLite database and can encrypt refresh tokens at rest with a user password.
The server speaks MCP over stdio and keeps stdout clean (JSON‑RPC only); all human‑readable logs go to stderr.
Requirements
- Node.js >= 18.17
- A Google Cloud OAuth client (Web application) with Calendar API enabled
- You will need
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRET
- You will need
One‑liner (npx)
Use npx to run without installing globally. The package name is @ideadesignmedia/gcal-mcp and the binary is gcal-mcp.
# Help
npx -y @ideadesignmedia/gcal-mcp --help
# Link an account (prints an authorization URL)
npx -y @ideadesignmedia/gcal-mcp add \
--client-id "$GOOGLE_CLIENT_ID" \
--client-secret "$GOOGLE_CLIENT_SECRET"
# Lock the database (encrypts existing refresh tokens)
npx -y @ideadesignmedia/gcal-mcp passwd --pass 'your-strong-pass'
# Start the MCP server over stdio (stdout is JSON only)
GMAIL_MCP_DB_PASS='your-strong-pass' \
npx -y @ideadesignmedia/gcal-mcp start
Database
- Default location:
~/.idm/gcal-mcp/db.sqlite - Override with
--db <path>on any command - When locked, refresh tokens are encrypted with AES‑GCM using a DEK derived from your password via scrypt
Global Flags
--db <path>: SQLite database path (defaults above)--pass <pass>: Database password (where applicable). Forstart, considerGMAIL_MCP_DB_PASSinstead.--read-only: Start server with write tools disabled (create/update/delete)
Environment variables:
GMAIL_MCP_DB_PASS: Password used bystart(and as a fallback elsewhere)GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET: OAuth credentials (used byaddif flags not supplied)
Commands
-
start- Starts the MCP server over stdio
- Respects
--read-only - Stdout: JSON‑RPC messages only; logs to stderr
- Examples:
GMAIL_MCP_DB_PASS='...' npx -y @ideadesignmedia/gcal-mcp start
-
add- Links a Google account and stores its refresh token
- Options:
--client-id <id>/--client-secret <secret>(or use env vars)--devicefor Device Code flow (headless), otherwise uses loopback redirect and prints a URL to visit (default port 43112; change with--listen-port <port>)--scopes <list>to provide custom scopes (comma or space separated)--choose-scopesto interactively select calendar access (read-only vs read/write) and optionally add extra scopes
- Example:
npx -y @ideadesignmedia/gcal-mcp add --client-id $GOOGLE_CLIENT_ID --client-secret $GOOGLE_CLIENT_SECRETnpx -y @ideadesignmedia/gcal-mcp add --choose-scopes --client-id $GOOGLE_CLIENT_ID --client-secret $GOOGLE_CLIENT_SECRET
-
list- Lists linked accounts (id, email, display name, created)
-
remove <key>- Removes an account and its tokens by id or email
-
passwd- Locks the database or rotates an existing password
- Options:
--pass <pass>: Password to set when locking, or new password when rotating--rotate: Rotate the existing password (requires--old-pass)--old-pass <old>: Old password for rotation--hint <text>: Optional password hint stored alongside the KDF parameters
MCP Server Integration
Any MCP‑capable client can launch this server as a stdio process. A generic config entry looks like:
{
"mcpServers": {
"gcal-mcp": {
"command": "npx",
"args": ["-y", "@ideadesignmedia/gcal-mcp", "start"],
"env": {
"GMAIL_MCP_DB_PASS": "${secret:GCAL_DB_PASS}",
"GOOGLE_CLIENT_ID": "${env:GOOGLE_CLIENT_ID}",
"GOOGLE_CLIENT_SECRET": "${env:GOOGLE_CLIENT_SECRET}"
}
}
}
}
Notes:
- The
startcommand writes only JSON‑RPC to stdout as required by MCP stdio. - Use your client’s secret storage for
GMAIL_MCP_DB_PASSwhere available.
Provided Tools
The server exposes the following tools. Names and parameter schemas follow the Chat Completions function tool shape.
Primary calendar only:
- All operations target the account’s primary calendar automatically; there is no
calendarIdparameter.- This removes confusion between account vs calendar. The server passes
primaryinternally.
- This removes confusion between account vs calendar. The server passes
Account resolution rules (applies wherever account is accepted):
-
Accepts exact
emailorid. -
Also accepts a partial, case-insensitive match on email or display name.
-
If
accountis omitted and exactly one account is linked, that account is used. -
If multiple accounts would match or are linked with no
accountprovided, the tool throws a helpful error. Usegcal-list_accountsto choose deterministically. -
gcal-list_accounts- Parameters: none
- Returns:
{ accounts: Array<{ id: string, email: string, displayName: string | null }> }
-
gcal-resolve_account- Parameters:
query(string; optional; email/id or partial). If omitted or blank, returns all accounts.
- Returns:
{ query: string, matches: Array<{ id: string, email: string, displayName: string | null }>, exact: boolean, ambiguous: boolean, count: number }
- Parameters:
-
gcal-search_events- Parameters:
account(string; optional)q(string, optional)timeMin(ISO string, optional)timeMax(ISO string, optional)maxResults(integer 1..250, optional)
- Returns:
{ events: GoogleCalendarEvent[] }(verbatim event objects)
- Parameters:
-
gcal-get_event- Parameters:
account(string; optional)eventId(string)
- Returns:
GoogleCalendarEvent
- Parameters:
-
gcal-create_event- Parameters:
account(string; optional)summary(string)description(string, optional)location(string, optional)start(object:{ dateTime?: string, date?: string, timeZone?: string })end(object:{ dateTime?: string, date?: string, timeZone?: string })attendees(array of{ email: string }, optional)
- Returns:
GoogleCalendarEvent
- Parameters:
-
gcal-update_event- Parameters:
account(string; optional)eventId(string)patch(object; partial event resource)
- Returns:
GoogleCalendarEvent
- Parameters:
-
gcal-delete_event- Parameters:
account(string; optional)eventId(string)
- Returns:
{ ok: true }on success
- Parameters:
Read‑only mode:
- Start with
--read-onlyto disablegcal-create_event,gcal-update_event, andgcal-delete_event.
OAuth Flows
- Default: Loopback flow. The CLI opens your browser to grant access and listens on
127.0.0.1:43112for the redirect (customizable via--listen-port). - Headless: Device Code flow with
--device. The CLI prints a code and verification URL to the terminal. - Scopes used:
https://www.googleapis.com/auth/calendar, plusopenid email profileto capture account identity.
Security Notes
- When locked, refresh tokens are encrypted at rest (AES‑GCM). The key is derived with scrypt using per‑DB salt and stored KDF parameters.
- The MCP
startcommand emits no human‑readable output on stdout. - Prefer setting
GMAIL_MCP_DB_PASSvia your MCP client’s secret system; avoid committing secrets in configs.
Troubleshooting
-
Database is locked
- Set
GMAIL_MCP_DB_PASSor pass--passwhen needed. Forstart, use the env var.
- Set
-
OAuth did not return a refresh_token
- Ensure you used the provided
addcommand (it requests offline access with consent). If you previously granted access without offline permission, remove prior consent in your Google Account or create a new OAuth client.
- Ensure you used the provided
-
Loopback callback failed
- Check firewall or try
--devicefor Device Code flow.
- Check firewall or try
-
MCP client shows parse errors
- Verify you are launching the server with
startand not another command. STDOUT must remain clean JSON‑RPC.
- Verify you are launching the server with
MIT © Idea Design Media