stalwart-mail-mcp 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.cs.md +226 -0
- package/README.md +224 -0
- package/THIRD-PARTY-NOTICES.md +17 -0
- package/dist/index.cjs +100572 -0
- package/docs/mala-infrastruktura.md +149 -0
- package/docs/publishing.md +86 -0
- package/docs/small-infrastructure.md +150 -0
- package/package.json +58 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Zapojení do malé vlastní infrastruktury
|
|
2
|
+
|
|
3
|
+
*[English](small-infrastructure.md)*
|
|
4
|
+
|
|
5
|
+
Popisuje sestavu, pro kterou server vznikl: jeden domácí nebo kancelářský stroj, na kterém běží
|
|
6
|
+
Stalwart pro rodinu nebo pár lidí, před ním reverzní proxy a na notebooku každého Claude
|
|
7
|
+
Desktop. Názvy hostitelů jsou příklady.
|
|
8
|
+
|
|
9
|
+
## Jak to vypadá
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
notebook tvůj server
|
|
13
|
+
┌───────────────────────────┐ ┌──────────────────────────────────────────────┐
|
|
14
|
+
│ Claude Desktop │ │ reverzní proxy (Traefik / Caddy / nginx), TLS│
|
|
15
|
+
│ └─ Stalwart Mail MCP ────┼── HTTPS ──▶│ /.well-known/jmap, /jmap/* ──▶ Stalwart │
|
|
16
|
+
│ (místní proces) │ JMAP │ (správcovské API zůstává uvnitř) │
|
|
17
|
+
│ └─ další tvoje MCP │ │ Stalwart: SMTP · IMAP · JMAP · CardDAV │
|
|
18
|
+
└───────────┬───────────────┘ │ webmail (volitelně) na stejném názvu │
|
|
19
|
+
│ volitelně, jen skeny └──────────────────────────────────────────────┘
|
|
20
|
+
▼
|
|
21
|
+
OCR: lokální model (Ollama, LM Studio) nebo API (Mistral, Anthropic, kompatibilní s OpenAI)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
MCP server je místní proces na notebooku. Drží heslo schránky (z klíčenky), mluví s tvým
|
|
25
|
+
serverem přes JMAP a dává Claudovi deset nástrojů. Na straně serveru není potřeba žádný
|
|
26
|
+
doplněk, účet navíc ani API klíč — jen JMAP dostupný přes HTTPS.
|
|
27
|
+
|
|
28
|
+
## Co musí server nabízet
|
|
29
|
+
|
|
30
|
+
1. **JMAP přes HTTPS** na veřejném názvu (nebo dostupném přes VPN) s platným certifikátem.
|
|
31
|
+
2. **Dvě cesty přes reverzní proxy**: `/.well-known/jmap` a všechno pod `/jmap` (session,
|
|
32
|
+
API, nahrávání i stahování žijí tam). Nic dalšího potřeba není — správcovské API a správu
|
|
33
|
+
Stalwartu na veřejný router nedávej.
|
|
34
|
+
3. **Adresy v session musí ukazovat na veřejný název.** Klient si z objektu session přečte
|
|
35
|
+
`apiUrl`, `uploadUrl` a `downloadUrl` a volá je tak, jak jsou. Ověření:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
curl -s -u jana@example.com https://mail.example.com/.well-known/jmap -L | head -c 600
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Když adresy ukazují na vnitřní název nebo `http://`, oprav nejdřív veřejnou adresu
|
|
42
|
+
Stalwartu / hlavičky proxy — jinak by hledání fungovalo, ale přílohy a odesílání ne.
|
|
43
|
+
4. **Ke Stalwartu musí dojít skutečná IP klienta.** Stalwart banuje adresy, které hádají
|
|
44
|
+
hesla. Když vidí jen adresu proxy, jedno špatně opsané heslo v nastavení něčího rozšíření
|
|
45
|
+
zabanuje proxy, tedy všechny. Předej IP klienta dál (PROXY protocol nebo důvěryhodné
|
|
46
|
+
hlavičky) a bany zůstanou na jednom člověku.
|
|
47
|
+
|
|
48
|
+
Router Traefiku pro JMAP vypadá takto (file provider):
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
http:
|
|
52
|
+
routers:
|
|
53
|
+
stalwart-jmap:
|
|
54
|
+
rule: "Host(`mail.example.com`) && (PathPrefix(`/jmap`) || Path(`/.well-known/jmap`))"
|
|
55
|
+
entryPoints: [websecure]
|
|
56
|
+
service: stalwart-web
|
|
57
|
+
tls: {}
|
|
58
|
+
services:
|
|
59
|
+
stalwart-web:
|
|
60
|
+
loadBalancer:
|
|
61
|
+
servers:
|
|
62
|
+
- url: "http://stalwart:8080"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Pokud ti proti Stalwartu už běží webmail přes JMAP, cesty jsou hotové a není co měnit.
|
|
66
|
+
|
|
67
|
+
## Účty a přihlášení
|
|
68
|
+
|
|
69
|
+
- Každý se přihlašuje **svou schránkou** — stejnou adresou a heslem jako v telefonu. Pokud
|
|
70
|
+
tvůj Stalwart nabízí hesla aplikací, použij pro rozšíření jedno z nich, ať jde zrušit
|
|
71
|
+
samostatně.
|
|
72
|
+
- `STALWART_TOKEN` přijme místo hesla přístupový token OAuth (posílá se jako Bearer), pokud
|
|
73
|
+
si tokeny vydáváš sám; rozšíření žádné interaktivní přihlášení OAuth nemá.
|
|
74
|
+
- **Sdílené schránky a skupiny** se na klientu nenastavují: každý účet, ke kterému má
|
|
75
|
+
přihlášený uživatel přístup, je v JMAP session a nástroje ho berou parametrem `account`
|
|
76
|
+
(`account: "rodina"`). Vypíše je `mail_list_mailboxes`. Sdílený účet má vlastní složky,
|
|
77
|
+
odesílací identitu i adresář.
|
|
78
|
+
- **Kontakty** jsou adresáře serveru (JMAP for Contacts). Tytéž adresáře se do telefonů
|
|
79
|
+
synchronizují přes CardDAV, takže kontakt, který Claude přidá do sdíleného adresáře, se
|
|
80
|
+
objeví všem v telefonu.
|
|
81
|
+
|
|
82
|
+
## Co Claude může a co ne
|
|
83
|
+
|
|
84
|
+
- Čte, hledá, zakládá koncepty a odesílá **jako ten člověk** — nic víc, než dovolí heslo
|
|
85
|
+
schránky, a nic v cizích soukromých schránkách.
|
|
86
|
+
- Odeslání je okamžité. Popisy nástrojů modelu ukládají posílat jen na výslovný pokyn
|
|
87
|
+
a jinak uložit koncept; koncepty jsou vidět ve webmailu i v telefonu ke kontrole. Kdo chce
|
|
88
|
+
tvrdou pojistku místo pokynu, vypne v nastavení rozšíření **Povolit odesílání** — nástroje
|
|
89
|
+
pro odeslání pak pro model neexistují — nebo si je v klientu nechá schvalovat ručně.
|
|
90
|
+
- Přijatou ani odeslanou poštu smazat neumí; `mail_delete_draft` odmítne cokoli, co není
|
|
91
|
+
koncept.
|
|
92
|
+
- Přílohy se ukládají na notebook (výchozí `~/Downloads/Mail-Attachments`).
|
|
93
|
+
|
|
94
|
+
## Co odchází z počítače
|
|
95
|
+
|
|
96
|
+
| Data | Kam jdou |
|
|
97
|
+
|---|---|
|
|
98
|
+
| heslo schránky, veškerý poštovní provoz | jen na tvůj Stalwart |
|
|
99
|
+
| obsah zpráv a příloh, které model čte | k AI klientu, který používáš (jako výstup každého nástroje) |
|
|
100
|
+
| naskenované strany PDF a fotky, když je zapnuté OCR | ke zvolenému poskytovateli OCR — nebo s lokálním modelem nikam |
|
|
101
|
+
|
|
102
|
+
OCR je vypnuté, dokud není nastavený poskytovatel, a `ocr: false` ho přeskočí u jedné přílohy.
|
|
103
|
+
Když dokumenty nesmí opustit dům, pusť model s viděním v Ollamě na notebooku nebo na serveru
|
|
104
|
+
a rozšíření na něj nasměruj (`ollama`, nebo `custom` s adresou serveru).
|
|
105
|
+
|
|
106
|
+
## Jedno rozšíření pro celou domácnost
|
|
107
|
+
|
|
108
|
+
Psát adresu serveru je pro většinu lidí o jedno pole víc, než je zdrávo. Vyrob **předvolbu**:
|
|
109
|
+
kopii manifestu s předvyplněným serverem, vlastním názvem a ikonou a — pokud chceš, aby se
|
|
110
|
+
nástroje v rozhovoru četly přirozeně — s vlastní předponou a výchozím jazykem.
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
moje-predvolba/
|
|
114
|
+
manifest.json name, display_name, výchozí server_url, env: MAIL_TOOL_PREFIX, MAIL_BRAND, MAIL_LANG
|
|
115
|
+
icon.png
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
./pack.sh --preset ~/infra/moje-predvolba # → ~/infra/moje-predvolba/<name>.mcpb
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Soubor `.mcpb` rozdej lidem (není tajný, žádné přihlašovací údaje v něm nejsou); otevřou ho,
|
|
123
|
+
zadají e-mail a heslo a je hotovo. Předvolbu drž vedle ostatní konfigurace infrastruktury
|
|
124
|
+
a přestav ji, kdykoli se tento repozitář posune. Její `name` neměň, aby klient nabídl
|
|
125
|
+
aktualizaci, a ne druhé rozšíření.
|
|
126
|
+
|
|
127
|
+
## Kombinace s dalšími nástroji
|
|
128
|
+
|
|
129
|
+
Smysl pošty za MCP je v tom, že se v jednom rozhovoru potká s ostatními daty:
|
|
130
|
+
|
|
131
|
+
- oborový nástroj (CRM, databáze nemovitostí, fakturace jako vlastní MCP server) a pošta:
|
|
132
|
+
„podívej se, jestli už makléř odpověděl, přečti přiložený průkaz energetické náročnosti
|
|
133
|
+
a doplň zjištění k záznamu“;
|
|
134
|
+
- ranní naplánovaná úloha: nepřečtená pošta od včerejška podle odesílatelů, s přílohami
|
|
135
|
+
shrnutými;
|
|
136
|
+
- odpovědi z poznámek — Claude napíše koncept, ty ho odešleš z telefonu.
|
|
137
|
+
|
|
138
|
+
Každou oblast drž ve vlastním MCP serveru; tenhle zná jen poštu a kontakty.
|
|
139
|
+
|
|
140
|
+
## Provozní poznámky
|
|
141
|
+
|
|
142
|
+
- **Bany**: po změně hesla uprav nastavení rozšíření dřív, než zavoláš další nástroj. Ban se
|
|
143
|
+
projeví jako odmítnuté spojení nebo 401 po nějakou dobu; zmizí sám, nebo ho zrušíš ve
|
|
144
|
+
správě Stalwartu.
|
|
145
|
+
- **Aktualizace**: `git pull && ./pack.sh` (nebo `--preset …`) a otevřít nový `.mcpb`.
|
|
146
|
+
- **Záloh** se netýká — server nedrží žádný stav, všechno je ve Stalwartu.
|
|
147
|
+
- **Monitoring**: sonda na `https://mail.example.com/.well-known/jmap` řekne, že cesta, na
|
|
148
|
+
které rozšíření stojí, žije — bez přihlášení Stalwart odpoví 307 na `/jmap/session`, která
|
|
149
|
+
vrátí anonymní session (200, schopnosti serveru, ale žádné účty).
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Publishing checklist
|
|
2
|
+
|
|
3
|
+
The repository starts private. Nothing below happens automatically — each step is a decision.
|
|
4
|
+
|
|
5
|
+
## 0. Before anything goes public
|
|
6
|
+
|
|
7
|
+
- Read `git log` and the tree once more for anything private (hostnames, names, credentials).
|
|
8
|
+
The test fixtures are synthetic; presets with real server names belong in your own
|
|
9
|
+
infrastructure repository, not here.
|
|
10
|
+
- `npm run test:offline` and, against a real mailbox, `node test/smoke.mjs`.
|
|
11
|
+
- Bump `version` in `package.json` (and `server.json`); `pack.sh` copies it into the manifest.
|
|
12
|
+
|
|
13
|
+
## 1. GitHub: make the repository public and cut a release
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gh repo edit cybersmurf/stalwart-mail-mcp --visibility public --accept-visibility-change-consequences
|
|
17
|
+
./pack.sh
|
|
18
|
+
git tag v2.0.0 && git push origin v2.0.0
|
|
19
|
+
gh release create v2.0.0 stalwart-mail.mcpb --title "Stalwart Mail MCP 2.0.0" --notes "First public release."
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The `.mcpb` attached to the release is what Claude Desktop users download and open. This step
|
|
23
|
+
alone is enough for people to use it.
|
|
24
|
+
|
|
25
|
+
## 2. npm: `npx stalwart-mail-mcp` for every other MCP client
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm adduser # once
|
|
29
|
+
npm publish --access public # prepublishOnly builds and runs the offline test
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
After that any client can run it without cloning:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{ "command": "npx", "args": ["-y", "stalwart-mail-mcp"], "env": { "STALWART_URL": "…", "STALWART_USER": "…", "STALWART_PASSWORD": "…" } }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 3. Official MCP Registry (feeds the catalogs of MCP clients)
|
|
39
|
+
|
|
40
|
+
The registry stores metadata only and verifies it against the npm package, so step 2 comes
|
|
41
|
+
first. `package.json` already carries `mcpName` and `server.json` is in the repository root;
|
|
42
|
+
both must name `io.github.cybersmurf/stalwart-mail-mcp` and the same version as on npm.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
brew install mcp-publisher
|
|
46
|
+
mcp-publisher login github # device code in the browser
|
|
47
|
+
mcp-publisher publish
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Guide: <https://modelcontextprotocol.io/registry/quickstart>
|
|
51
|
+
|
|
52
|
+
## 4. Claude directory (optional, reviewed by Anthropic)
|
|
53
|
+
|
|
54
|
+
The directory no longer lists local servers packaged as desktop extensions (`.mcpb`). A local
|
|
55
|
+
MCP server gets there inside a **plugin bundle**, and that has a consequence worth knowing
|
|
56
|
+
before spending time on it: per Anthropic's platform table, a local server in a plugin runs in
|
|
57
|
+
**Claude Code** and in **Cowork sessions on your own computer**, and is **ignored in chat**
|
|
58
|
+
(claude.ai, desktop and mobile). Cowork also does not ask for settings, so a server that needs
|
|
59
|
+
a password only really works in Claude Code. People who want mail in Claude Desktop chat keep
|
|
60
|
+
using the `.mcpb` from the GitHub release.
|
|
61
|
+
|
|
62
|
+
The bundle is in [`plugin/`](../plugin): `.claude-plugin/plugin.json` (settings as
|
|
63
|
+
`userConfig`, secrets marked `sensitive`), `.mcp.json` (starts `npx -y stalwart-mail-mcp@<exact
|
|
64
|
+
version>`), a README that says what it runs and where data goes, and the license.
|
|
65
|
+
|
|
66
|
+
1. npm publish first (step 2) — the bundle starts the package from npm.
|
|
67
|
+
2. Keep the version in `plugin/.mcp.json` and `plugin/.claude-plugin/plugin.json` equal to
|
|
68
|
+
the published one (`claude plugin validate ./plugin` checks the files are well-formed).
|
|
69
|
+
3. From a paid Claude account open <https://claude.ai/directory/manage> → **Submit new** →
|
|
70
|
+
**Plugin bundle** → repository `cybersmurf/stalwart-mail-mcp`, folder `plugin` → **Validate**.
|
|
71
|
+
4. Expect holds for a reviewer rather than an instant listing: a pinned `npx` package is
|
|
72
|
+
always reviewed by a person, and the name contains "Stalwart", a brand that is not ours —
|
|
73
|
+
say in the submission that this is an independent client for Stalwart servers.
|
|
74
|
+
|
|
75
|
+
Checklist: <https://claude.com/docs/plugins/pre-submission-checklist>
|
|
76
|
+
|
|
77
|
+
## 5. Tell people who run Stalwart
|
|
78
|
+
|
|
79
|
+
A short post in Stalwart's GitHub Discussions (or their community chat) with the link to the
|
|
80
|
+
release and to `docs/small-infrastructure.md` reaches exactly the audience this is for.
|
|
81
|
+
|
|
82
|
+
## Releasing an update
|
|
83
|
+
|
|
84
|
+
Bump the version in `package.json`, `server.json`, `plugin/.claude-plugin/plugin.json` and `plugin/.mcp.json` → `npm run test:offline` → commit and tag →
|
|
85
|
+
`./pack.sh` and `gh release create` → `npm publish` → `mcp-publisher publish`. Branded presets
|
|
86
|
+
are rebuilt with `./pack.sh --preset <dir>` from their own repositories.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Plugging it into a small self-hosted setup
|
|
2
|
+
|
|
3
|
+
*[Česky](mala-infrastruktura.md)*
|
|
4
|
+
|
|
5
|
+
This describes the kind of setup the server was built for: one home or small-office machine
|
|
6
|
+
that runs Stalwart for a family or a handful of people, behind a reverse proxy, with Claude
|
|
7
|
+
Desktop on everyone's laptop. Hostnames below are examples.
|
|
8
|
+
|
|
9
|
+
## The picture
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
laptop your server
|
|
13
|
+
┌───────────────────────────┐ ┌──────────────────────────────────────────────┐
|
|
14
|
+
│ Claude Desktop │ │ reverse proxy (Traefik / Caddy / nginx), TLS │
|
|
15
|
+
│ └─ Stalwart Mail MCP ────┼── HTTPS ──▶│ /.well-known/jmap, /jmap/* ──▶ Stalwart │
|
|
16
|
+
│ (local process) │ JMAP │ (admin API stays internal) │
|
|
17
|
+
│ └─ your other MCP servers│ │ Stalwart: SMTP · IMAP · JMAP · CardDAV │
|
|
18
|
+
└───────────┬───────────────┘ │ webmail (optional) on the same host name │
|
|
19
|
+
│ optional, scans only └──────────────────────────────────────────────┘
|
|
20
|
+
▼
|
|
21
|
+
OCR: a local model (Ollama, LM Studio) or an API (Mistral, Anthropic, OpenAI-compatible)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The MCP server is a local process on the laptop. It holds the mailbox password (from the
|
|
25
|
+
keychain), speaks JMAP to your server, and gives Claude ten tools. The server side needs no
|
|
26
|
+
plugin, no extra account and no API key — only JMAP reachable over HTTPS.
|
|
27
|
+
|
|
28
|
+
## What the server has to provide
|
|
29
|
+
|
|
30
|
+
1. **JMAP over HTTPS** on a public (or VPN-reachable) host name with a valid certificate.
|
|
31
|
+
2. **Two paths through the reverse proxy**: `/.well-known/jmap` and everything under `/jmap`
|
|
32
|
+
(session, API, upload and download all live there). Nothing else is required — keep
|
|
33
|
+
Stalwart's management API and admin UI off the public router.
|
|
34
|
+
3. **Session URLs that point at the public name.** The client reads `apiUrl`, `uploadUrl` and
|
|
35
|
+
`downloadUrl` from the session object and calls them as they are. Check it:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
curl -s -u jane@example.com https://mail.example.com/.well-known/jmap -L | head -c 600
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If the URLs show an internal host or `http://`, fix Stalwart's public URL / proxy headers
|
|
42
|
+
first — attachments and sending would otherwise fail while searching still works.
|
|
43
|
+
4. **The real client IP reaching Stalwart.** Stalwart bans addresses that guess passwords. If
|
|
44
|
+
it only ever sees the proxy's address, one mistyped password in someone's extension settings
|
|
45
|
+
bans the proxy, i.e. everybody. Pass the client IP through (PROXY protocol or trusted
|
|
46
|
+
forwarded headers) and bans stay per person.
|
|
47
|
+
|
|
48
|
+
A Traefik router for the JMAP part looks like this (file provider):
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
http:
|
|
52
|
+
routers:
|
|
53
|
+
stalwart-jmap:
|
|
54
|
+
rule: "Host(`mail.example.com`) && (PathPrefix(`/jmap`) || Path(`/.well-known/jmap`))"
|
|
55
|
+
entryPoints: [websecure]
|
|
56
|
+
service: stalwart-web
|
|
57
|
+
tls: {}
|
|
58
|
+
services:
|
|
59
|
+
stalwart-web:
|
|
60
|
+
loadBalancer:
|
|
61
|
+
servers:
|
|
62
|
+
- url: "http://stalwart:8080"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If a webmail client already works against your Stalwart over JMAP, the routes are already
|
|
66
|
+
there and there is nothing to change.
|
|
67
|
+
|
|
68
|
+
## Accounts and sign-in
|
|
69
|
+
|
|
70
|
+
- Each person signs in with **their own mailbox** — the same address and password as on the
|
|
71
|
+
phone. If your Stalwart offers app passwords, use one for the extension so it can be revoked
|
|
72
|
+
on its own.
|
|
73
|
+
- `STALWART_TOKEN` takes an OAuth access token instead (sent as Bearer) if you issue tokens
|
|
74
|
+
yourself; the extension has no interactive OAuth flow.
|
|
75
|
+
- **Shared mailboxes and groups** need no setup on the client: every account the signed-in
|
|
76
|
+
user can access appears in the JMAP session, and tools take it through the `account`
|
|
77
|
+
parameter (`account: "family"`). `mail_list_mailboxes` lists them. A shared account has its
|
|
78
|
+
own folders, sending identity and address book.
|
|
79
|
+
- **Contacts** are the server's address books (JMAP for Contacts). The same books sync to
|
|
80
|
+
phones over CardDAV, so a contact Claude adds to the shared book appears on everyone's phone.
|
|
81
|
+
|
|
82
|
+
## What Claude can and cannot do
|
|
83
|
+
|
|
84
|
+
- It reads, searches, drafts and sends **as that person** — nothing more than the mailbox
|
|
85
|
+
password allows, and nothing on other people's private mailboxes.
|
|
86
|
+
- Sending is immediate. The tool descriptions instruct the model to send only when the user
|
|
87
|
+
explicitly asks and to save a draft otherwise; drafts show up in webmail and on the phone
|
|
88
|
+
for review. If you want a hard guarantee rather than an instruction, switch **Allow sending** off
|
|
89
|
+
in the extension settings — the send tools then do not exist for the model — or keep them
|
|
90
|
+
on manual approval in the client.
|
|
91
|
+
- It cannot delete received or sent mail; `mail_delete_draft` refuses anything that is not a
|
|
92
|
+
draft.
|
|
93
|
+
- Attachments are written to the laptop (`~/Downloads/Mail-Attachments` by default).
|
|
94
|
+
|
|
95
|
+
## What leaves the machine
|
|
96
|
+
|
|
97
|
+
| Data | Goes to |
|
|
98
|
+
|---|---|
|
|
99
|
+
| mailbox password, all mail traffic | your Stalwart server only |
|
|
100
|
+
| message and attachment content the model reads | the AI client you use (as with any tool output) |
|
|
101
|
+
| scanned PDF pages and photos, when OCR is on | the OCR provider you chose — or nowhere, with a local model |
|
|
102
|
+
|
|
103
|
+
OCR is off until a provider is set up, and `ocr: false` skips it for a single attachment. If
|
|
104
|
+
the documents must not leave the house, run a vision model in Ollama on the laptop or on the
|
|
105
|
+
server and point the extension at it (`ollama`, or `custom` with the server's address).
|
|
106
|
+
|
|
107
|
+
## One extension for the whole household
|
|
108
|
+
|
|
109
|
+
Typing a server address is one field too many for most people. Build a **preset**: a copy of
|
|
110
|
+
the manifest with your server pre-filled, your own name and icon, and — if you want the tools
|
|
111
|
+
to read naturally in conversations — your own prefix and default language.
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
my-preset/
|
|
115
|
+
manifest.json name, display_name, default server_url, env: MAIL_TOOL_PREFIX, MAIL_BRAND, MAIL_LANG
|
|
116
|
+
icon.png
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
./pack.sh --preset ~/infra/my-preset # → ~/infra/my-preset/<name>.mcpb
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Hand the `.mcpb` to people (it is not secret — it contains no credentials); they open it,
|
|
124
|
+
type e-mail and password, done. Keep the preset next to the rest of your infrastructure
|
|
125
|
+
config and rebuild it whenever this repository moves forward. Keep its `name` unchanged so
|
|
126
|
+
the client offers an update instead of a second extension.
|
|
127
|
+
|
|
128
|
+
## Combining it with other tools
|
|
129
|
+
|
|
130
|
+
The point of putting mail behind MCP is that it meets your other data in one conversation:
|
|
131
|
+
|
|
132
|
+
- a domain tool (a CRM, a property database, an invoicing system as its own MCP server) plus
|
|
133
|
+
mail: "check whether the agent already answered, read the attached energy certificate and
|
|
134
|
+
add the findings to the record";
|
|
135
|
+
- a scheduled morning task: unread mail since yesterday, grouped by sender, with attachments
|
|
136
|
+
summarized;
|
|
137
|
+
- drafting replies from notes — Claude writes the draft, you send it from the phone.
|
|
138
|
+
|
|
139
|
+
Keep each concern in its own MCP server; this one only knows mail and contacts.
|
|
140
|
+
|
|
141
|
+
## Operating notes
|
|
142
|
+
|
|
143
|
+
- **Bans**: after a password change, update the extension settings before the next tool call.
|
|
144
|
+
A ban shows up as a refused connection or 401 for a while; it lifts by itself or from the
|
|
145
|
+
Stalwart admin UI.
|
|
146
|
+
- **Updates**: `git pull && ./pack.sh` (or `--preset …`) and open the new `.mcpb`.
|
|
147
|
+
- **Backups** are unaffected — the server keeps no state; everything lives in Stalwart.
|
|
148
|
+
- **Monitoring**: a blackbox probe on `https://mail.example.com/.well-known/jmap` tells you
|
|
149
|
+
the route the extension depends on is alive — without credentials Stalwart answers 307 to
|
|
150
|
+
`/jmap/session`, which returns an anonymous session (200, capabilities but no accounts).
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "stalwart-mail-mcp",
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "MCP server for Stalwart Mail over JMAP: search, read (attachments, OCR of scans), reply, send, drafts, contacts",
|
|
5
|
+
"mcpName": "io.github.cybersmurf/stalwart-mail-mcp",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Petr Šrámek",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/cybersmurf/stalwart-mail-mcp.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/cybersmurf/stalwart-mail-mcp#readme",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"stalwart",
|
|
17
|
+
"jmap",
|
|
18
|
+
"email",
|
|
19
|
+
"contacts",
|
|
20
|
+
"ocr",
|
|
21
|
+
"claude"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"stalwart-mail-mcp": "dist/index.cjs"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist/index.cjs",
|
|
28
|
+
"README.md",
|
|
29
|
+
"README.cs.md",
|
|
30
|
+
"docs",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"THIRD-PARTY-NOTICES.md"
|
|
33
|
+
],
|
|
34
|
+
"type": "module",
|
|
35
|
+
"main": "dist/index.cjs",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/index.cjs --log-level=warning",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "npm run build && node test/offline.mjs && node test/smoke.mjs",
|
|
40
|
+
"test:offline": "npm run build && node test/offline.mjs",
|
|
41
|
+
"pack": "bash pack.sh",
|
|
42
|
+
"prepublishOnly": "npm run build && node test/offline.mjs"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@anthropic-ai/sdk": "^0.131.0",
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
50
|
+
"unpdf": "^1.8.1",
|
|
51
|
+
"zod": "^4.6.5"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^26.6.3",
|
|
55
|
+
"esbuild": "^0.27.0",
|
|
56
|
+
"typescript": "^5.9.3"
|
|
57
|
+
}
|
|
58
|
+
}
|