Content
# Proton Calendar Playwright
Proton Calendar does not expose a public API, making it difficult to integrate with external tools or AI agents. This project bridges that gap by using **Playwright** browser automation to interact with the Proton Calendar web interface programmatically, exposed both as CLI scripts and as an **MCP server** (Model Context Protocol) for AI assistants.
Browser automation for **Proton Calendar** — create events, read your schedule, and manage your calendar from the command line or via an **MCP server** (Model Context Protocol).
Works with any MCP-compatible AI client (opencode, Claude Desktop, etc.) so you can manage your calendar using natural language.
## Quick Start
```bash
# 1. Clone and set up
git clone <repo-url>
cd proton-calendar-playwright
python -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install -r requirements.txt
playwright install chromium
# 2. Set credentials
cp .env.example .env
# Edit .env with your Proton Mail username and password
# 3. Authenticate once
python auth.py --auto
# 4. Create an event
python add_event.py --title "Team Standup" --date 2026-06-05 --time 09:00 --duration 30
# 5. Read your schedule
python read_events.py --days 7
```
## Scripts
| Script | Purpose |
|--------|---------|
| `auth.py` | Login to Proton and save session cookies |
| `add_event.py` | Create calendar events |
| `read_events.py` | Read/extract events from the calendar view |
| `mcp_server.py` | Expose all operations as MCP tools |
### `auth.py` — Authenticate
Saves cookies, localStorage, and IndexedDB to `auth/state.json`. Required before using any other script.
```bash
python auth.py --auto # Auto-fill from .env credentials
python auth.py # Interactive manual login
```
### `add_event.py` — Create Events
```bash
python add_event.py --title "Meeting" --date 2025-12-25 --time 14:00 --duration 60
python add_event.py --title "Lunch" # today, 12:00, 60min
```
| Argument | Required | Default | Description |
|----------|----------|---------|-------------|
| `--title` | Yes | — | Event title |
| `--date` | No | Today | Date (YYYY-MM-DD) |
| `--time` | No | 12:00 | Start time (HH:MM) |
| `--duration` | No | 60 | Duration in minutes |
Batch mode — creates multiple events in a single browser session:
```bash
echo '[{"title": "A", "date": "2026-06-05"}, {"title": "B", "date": "2026-06-06"}]' | python add_event.py --batch
```
### `read_events.py` — Read Events
```bash
python read_events.py --days 7 # Next 7 days
python read_events.py --days 1 # Today only
python read_events.py --date 2026-06-05 # Specific date (fast — direct URL)
```
| Argument | Default | Description |
|----------|---------|-------------|
| `--days` | 7 | Number of days to look ahead |
| `--date` | — | Specific date (YYYY-MM-DD), faster than `--days` |
## MCP Server Setup
The MCP server lets AI assistants (opencode, Claude Desktop, Cursor, etc.) control your calendar directly.
### Start manually
```bash
python mcp_server.py
```
### Configure in opencode
Add to your project's `opencode.json`:
```json
{
"mcp": {
"proton-calendar": {
"type": "local",
"command": ["python", "mcp_server.py"],
"cwd": "/path/to/proton-calendar-playwright",
"enabled": true
}
}
}
```
### Configure in Claude Desktop
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"proton-calendar": {
"command": "python",
"args": ["/path/to/proton-calendar-playwright/mcp_server.py"],
"cwd": "/path/to/proton-calendar-playwright"
}
}
}
```
### Available MCP Tools
| Tool | Description | Parameters |
|------|-------------|------------|
| `add_event` | Create a calendar event | `title` (required), `date?`, `time?`, `duration?` |
| `add_events_batch` | Create multiple events in one browser session | `events` (array of `{title, date?, time?, duration?}`) |
| `read_events` | List upcoming events | `days?` (default 7), `date?` (YYYY-MM-DD) |
| `auth` | Re-authenticate with Proton | `auto?` (default: true) |
## OpenCode Skill Setup
The repository includes three skills under `.opencode/skills/` and `skills/` that give opencode agents deep knowledge of the automation patterns:
| Skill | Location | Purpose |
|-------|----------|---------|
| `playwright-testing` | `.opencode/skills/` | Guides writing new Playwright scripts |
| `proton-automation-developer` | `.opencode/skills/` | Debugging, fixing, and extending scripts |
| `proton-calendar-buddy` | `skills/` | End-user calendar management via MCP |
The skills are automatically loaded when you open this project in opencode. No additional configuration is needed.
### Example Prompts
Once configured, you can ask your AI assistant:
**Calendar management:**
- "What do I have on my calendar today?"
- "Add a dentist appointment tomorrow at 2pm for 1 hour"
- "Am I free on Friday at 3pm?"
- "Schedule a meeting every Monday at 10am"
- "What's my schedule for next week?"
**Development:**
- "Write a script to delete events from Proton Calendar"
- "The add_event script is failing to find the 'Save' button — debug it"
- "Add support for recurring events"
- "Create a script to export my calendar to CSV"
## Technical Details
### Architecture
```
proton-calendar-playwright/
├── auth.py # Login flow — saves session
├── add_event.py # Event creation with Polish/EN UI support
├── read_events.py # Event extraction via DOM parsing
├── mcp_server.py # MCP transport layer (stdio)
├── skills/
│ └── proton-calendar-buddy/
│ └── SKILL.md # User-facing skill
├── .opencode/
│ └── skills/
│ ├── playwright-testing/
│ │ └── SKILL.md # Dev skill for writing new scripts
│ └── proton-automation-developer/
│ └── SKILL.md # Dev skill for debugging/fixing
├── requirements.txt
├── .env.example
└── opencode.json # opencode MCP config
```
### How it works
- **Playwright** (Python sync API) launches Chromium, loads a saved session from `auth/state.json`, and interacts with the Proton Calendar SPA.
- **Session persistence** uses `context.storage_state()` to save cookies + localStorage + IndexedDB — no need to re-login on every run.
- **Event creation** opens the "New event" dialog, fills title/date/time/duration using resilient locator strategies (`get_by_role` → `get_by_text` → CSS fallback), and clicks Save.
- **Event reading** navigates to the week view via direct URL (`/u/0/week/YYYY/M/D`), parses the page title to determine the week range, and extracts event cells from the DOM using their x-positions to assign correct dates.
- **MCP server** uses the official `mcp` Python package (stdio transport), delegating to the CLI scripts via subprocess.
### Resilience patterns
All scripts follow strict rules to avoid hanging in Proton Calendar's slow SPA:
- **No `networkidle`** — uses `"load"` or `"domcontentloaded"` instead
- **Short timeouts** — locators: 3–5s, page loads: 10–15s
- **Graceful degradation** — all locator calls are wrapped in try/except
- **Bilingual** — handles both English and Polish UI labels
- **Auto-dismiss modals** — welcome/onboarding modals are clicked away
- **Session refresh** — session state is re-saved after each operation
- **`finally` cleanup** — browser always closes, even on error
### Dependencies
- Python 3.10+
- `playwright` — browser automation
- `python-dotenv` — credential loading
- `mcp` — MCP server framework
- Chromium browser (installed via `playwright install chromium`)
## Security
- **Never commit `.env`** — it contains your Proton credentials (already gitignored)
- **Never commit `auth/state.json`** — it contains your session tokens (already gitignored)
- The `.env.example` file contains placeholder values only
- All scripts use environment variables for credentials — no hardcoded secrets
## Troubleshooting
| Problem | Fix |
|---------|-----|
| **Session expired** | Run `python auth.py --auto` again |
| **"Could not click..."** | The UI language changed — add the label text to the multilingual lists |
| **Date not filled** | Try Polish month format (e.g., `cze` not `Jun`) |
| **No events found** | Week title parsing may have failed — check the page title format |
| **Script hangs** | Ctrl+C and re-run — all scripts are safe to interrupt |
| **Batch timeout** | Check what was created with `read_events`, add only the missing ones |
| **Modal blocking UI** | Scripts auto-dismiss the welcome modal. If stuck, press `Escape` in the browser |