Skip to main content
Glama
dazzle-blip

ProtonScope

by dazzle-blip
README.md
# ProtonScope

A lightweight **MCP server** that gives an AI agent a **scoped, read-and-draft-only** view
into your Proton Mail through **Proton Bridge**.

The scope is enforced in code, not by prompting: the agent physically cannot read or touch
mail outside the workspace you configure, and **it can never send** — replies are saved as
drafts for you to review and send yourself in Proton.

## What it does (and doesn't)

- ✅ Read mail within a configured scope (specific folders/labels, optionally only starred,
  optionally only certain correspondents — including mail where you were **BCC'd**).
- ✅ Work **thread-centric**: list and read whole conversations.
- ✅ **Draft** replies and new messages (saved to your Drafts).
- ✅ Optional housekeeping: mark read/unread, star, move/label within scope, optional delete.
- ❌ **No sending. No SMTP.** The server never opens an SMTP connection and has no send tool.
- ❌ No calendar, no contacts. Mail only.

> Proton Bridge exposes mail over local IMAP/SMTP only; this server uses **IMAP exclusively**.

## How scope works

Each *workspace* is a single committed YAML file, `workspaces/<name>.yaml` (name/description/
account plus a `mail:` section for permission, scope, and write targets). Scope is
**deny-by-default** and combines (AND) up to three filters — a message is in scope only if
**all** apply:

1. it is in an allowed **source** mailbox (`scope.sources`);
2. if `require_starred: true`, it is **starred**;
3. if `scope.addresses` is set, one of those addresses appears in From/To/Cc **or** the
   delivery headers (`Delivered-To` / `X-Original-To` / `Envelope-To` — this is how BCC'd
   mail to your aliases is matched).

See [`workspaces/example-clients.yaml`](workspaces/example-clients.yaml) for a fully
commented example. One server instance serves exactly one workspace.

## Prerequisites

1. **Proton Bridge** installed, running, and signed in. Add your account and note the
   per-account **IMAP username** and **Bridge password** (Bridge → account → *Mailbox
   configuration*). This is the Bridge password, **not** your Proton login password.
2. **Python 3.11+** and [**uv**](https://docs.astral.sh/uv/).

## Install

```bash
git clone <this-repo> code-protonscope
cd code-protonscope
uv sync
```

Run the tests (no Proton Bridge needed — scope logic is pure):

```bash
uv run pytest
```

<details>
<summary>No <code>uv</code>? Use a plain venv + pip instead</summary>

```bash
python -m venv .venv
# Linux/macOS:
source .venv/bin/activate
# Windows PowerShell:
.\.venv\Scripts\Activate.ps1

pip install -e . pytest
pytest
protonscope --workspace workspaces/my-clients.yaml   # after configuring (see below)
```

Requires a real Python 3.11+ on `PATH` (the Microsoft Store stub won't work).
</details>

## Configure a workspace

Copy the example and edit it:

```bash
# Linux/macOS
cp workspaces/example-clients.yaml workspaces/my-clients.yaml
```

```powershell
# Windows (PowerShell)
Copy-Item workspaces/example-clients.yaml workspaces/my-clients.yaml
```

Edit `workspaces/my-clients.yaml`: your `username`, and under `mail:` your scope,
permission tier, and the real names of your Drafts/Trash mailboxes as Bridge reports them.

## Try it locally with the MCP Inspector

```bash
PROTONSCOPE_BRIDGE_PASSWORD="<bridge-password>" \
  uv run protonscope --workspace workspaces/my-clients.yaml
```

Or with the interactive inspector:

```bash
PROTONSCOPE_WORKSPACE=workspaces/my-clients.yaml \
PROTONSCOPE_BRIDGE_PASSWORD="<bridge-password>" \
  uv run mcp dev src/protonscope/__main__.py
```

On **Windows PowerShell**, set the env vars first:

```powershell
$env:PROTONSCOPE_BRIDGE_PASSWORD = "<bridge-password>"
uv run protonscope --workspace workspaces/my-clients.yaml
```

## Register with an MCP client

Add **one entry per workspace** to your client config (e.g. Claude Desktop's
`claude_desktop_config.json`, or a project `.mcp.json`):

```json
{
  "mcpServers": {
    "protonscope-clients": {
      "command": "uv",
      "args": ["run", "protonscope", "--workspace", "workspaces/my-clients.yaml"],
      "env": { "PROTONSCOPE_BRIDGE_PASSWORD": "<bridge-password>" }
    }
  }
}
```

> On Windows, use the absolute path to the workspace and ensure `uv` is on `PATH` (or give
> the full path to `uv.exe`). Bridge's default ports (IMAP 1143) are identical on both OSes.

### Picking a permission tier

| `permission` | Reads | Drafts & housekeeping | Sends |
|--------------|:-----:|:---------------------:|:-----:|
| `readonly`   | ✅    | ❌                    | ❌ (never) |
| `read-write` | ✅    | ✅                    | ❌ (never) |

`delete_message` is only offered when `allow_delete: true` (and needs `write_targets.trash`).

## Security notes

- Workspace YAML is committed; **secrets are not**. The Bridge password is only ever read
  from `PROTONSCOPE_BRIDGE_PASSWORD`. `workspaces/.gitignore` also ignores `*.secret` /
  `*.local.yaml`.
- Thread reconstruction stays within your allowed sources, so a thread may come back
  *partial* if some messages are outside scope — this is intentional (no peeking via All Mail).
- The scope core ([`src/protonscope/scope.py`](src/protonscope/scope.py)) is pure and fully
  unit-tested ([`tests/test_scope.py`](tests/test_scope.py)).