@prbartosh/poczta-mcp 0.1.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/.env.example ADDED
@@ -0,0 +1,19 @@
1
+ # Sekrety. Skopiuj do .env i uzupelnij. NIE commitowac (.env jest w .gitignore).
2
+ # Nazwy zmiennych musza zgadzac sie z *Env w config.json.
3
+
4
+ # --- konto password (np. AGH, firmowe IMAP) ---
5
+ AGH_APP_PASS=app-password-z-panelu-webmail
6
+
7
+ # --- Gmail app-password (najprostsze, wymaga 2FA; provider=generic/password) ---
8
+ # GMAIL_PASSWORD=
9
+
10
+ # --- Gmail OAuth2 (alternatywa) ---
11
+ GMAIL_CLIENT_SECRET=
12
+ GMAIL_REFRESH_TOKEN=
13
+
14
+ # --- Microsoft 365 OAuth2 ---
15
+ FIRMA_CLIENT_SECRET=
16
+ FIRMA_REFRESH_TOKEN=
17
+
18
+ # --- kanal digestu: webhook (opcjonalnie) ---
19
+ # DIGEST_WEBHOOK_URL=
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 prbartosh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # poczta-mcp
2
+
3
+ > Pakiet npm: **`@prbartosh/poczta-mcp`**. Komendy: `npx -y @prbartosh/poczta-mcp <setup|install|server|doctor|digest|oauth>`. Binarka po globalnej instalacji nazywa sie `poczta-mcp`.
4
+
5
+ Serwer MCP do obslugi wielu skrzynek pocztowych (IMAP/SMTP) z jednego miejsca, instalowany przez `npx`. Plus codzienny przeglad: agent zbiera nieprzeczytane, mowi co wazne i co wymaga akcji, dostarcza podsumowanie i oznacza jako przeczytane tylko maile bez akcji.
6
+
7
+ Obsluga auth: zwykle haslo / app-password (AGH, firmowe IMAP), Gmail OAuth2, Microsoft 365 OAuth2 (basic auth IMAP w M365 jest wycofany, dlatego OAuth2).
8
+
9
+ ## Jak to dziala
10
+
11
+ - `poczta-mcp` to lokalny serwer MCP (transport stdio). Wystawia tooly do poczty, sam z siebie nic nie robi cyklicznie.
12
+ - Codzienny automat = **osobny scheduled agent** (`claude -p` z lokalnego crona) ktory uzywa tych tooli i skilla `daily-briefing`.
13
+ - Wszystko lokalnie: config i hasla nie wychodza na zewnatrz. "Podglad zewszad" robi sie przez kanal digestu (mail do siebie / Slack), a nie przez hostowanie serwera.
14
+
15
+ ## Tooly
16
+
17
+ | Tool | Opis |
18
+ |---|---|
19
+ | `list_accounts` | lista skonfigurowanych kont |
20
+ | `get_unread` | nieprzeczytane; bez `account` ze WSZYSTKICH kont |
21
+ | `get_email_body` | pelna tresc maila (account + uid) |
22
+ | `search_emails` | szukanie po nadawcy/temacie/dacie |
23
+ | `mark_as_read` | oznacz przeczytane (batch uid per konto) |
24
+ | `send_email` | wyslij (tylko po potwierdzeniu; poza przegladem) |
25
+ | `deliver_digest` | dostarcz podsumowanie kanalami z config |
26
+ | `get_folders` | lista folderow IMAP konta |
27
+
28
+ ## Wymagania
29
+
30
+ - Node >= 20.12
31
+ - Claude Code CLI (do codziennego automatu) albo Claude Desktop (do recznego uzycia)
32
+
33
+ ## Instalacja i konfiguracja
34
+
35
+ ```bash
36
+ npx poczta-mcp setup
37
+ ```
38
+
39
+ Wizard pyta o konta i kanaly, zapisuje:
40
+ - `config.json` — struktura (konta, hosty, porty, kanaly digestu). Bez sekretow, tylko NAZWY zmiennych env.
41
+ - `.env` — sekrety (hasla, client secret, refresh token). W `.gitignore`.
42
+
43
+ Ponowne `setup` wykrywa istniejacy `config.json` i wchodzi w **menu** (nie zaczyna od zera): dodaj konto / usun konto / zmien kanaly podsumowania / test polaczen / zapisz. Edycja jednego konta nie rusza sekretow pozostalych.
44
+
45
+ Test polaczen:
46
+
47
+ ```bash
48
+ npx poczta-mcp doctor # sprawdza auth kazdego konta
49
+ npx poczta-mcp digest # surowy (bez AI) digest -> kanaly, do testu pipeline
50
+ ```
51
+
52
+ Reczna edycja zamiast wizarda: skopiuj `config.example.json` -> `config.json` i `.env.example` -> `.env`.
53
+
54
+ ### Globalna instalacja + rejestracja w agencie
55
+
56
+ ```bash
57
+ npx poczta-mcp install # lub: node dist/index.js install
58
+ ```
59
+
60
+ - Opcjonalnie `npm i -g` (komenda `poczta-mcp` globalnie). Jak odmowisz, wpis uzyje `node <abs sciezka>/dist/index.js`.
61
+ - Rejestruje serwer MCP (multiselect) w:
62
+ - **Claude Code** — przez `claude mcp add poczta -s user` (fallback: `~/.claude.json`),
63
+ - **Claude Desktop** — `claude_desktop_config.json` (restart aplikacji),
64
+ - **Codex CLI** — `~/.codex/config.toml` (`[mcp_servers.poczta]`).
65
+ - Wpis zawiera `POCZTA_MCP_CONFIG` i `POCZTA_MCP_ENV` (absolutne sciezki do Twojego config.json / .env), wiec dziala niezaleznie od katalogu.
66
+
67
+ Po instalacji zrestartuj klienta i zapytaj agenta np. "co mam nieprzeczytane".
68
+
69
+ ### Sciezki config
70
+
71
+ Domyslnie `./config.json` i `./.env` w katalogu uruchomienia. Nadpisanie:
72
+ `POCZTA_MCP_CONFIG=/abs/config.json`, `POCZTA_MCP_ENV=/abs/.env`.
73
+
74
+ ## Auth per dostawca
75
+
76
+ ### Password / app-password (AGH, firmowe IMAP)
77
+ W panelu webmail wygeneruj **app password** (AGH: Ustawienia -> Hasla do aplikacji). Wpisz do `.env` pod nazwa z `passwordEnv`.
78
+
79
+ ### Gmail — dwie drogi
80
+
81
+ **A) App-password (najprostsze, jak email+haslo w Thunderbird).** Wymaga 2FA na koncie Google.
82
+ 1. myaccount.google.com -> Bezpieczenstwo -> wlacz weryfikacje 2-etapowa.
83
+ 2. Haslo do aplikacji (App passwords) -> Mail -> skopiuj 16 znakow.
84
+ 3. W Gmailu wlacz IMAP (Ustawienia -> Przekazywanie i POP/IMAP).
85
+ 4. W kreatorze wybierz Gmail -> "App-password". To NIE jest zwykle haslo konta.
86
+ (Uwaga: Thunderbird "tylko email+haslo" dziala bo ma wbudowany wlasny clientId i robi OAuth w tle. Tu odpowiednik bez clientId to app-password.)
87
+
88
+ **B) OAuth2.** Kreator otwiera przegladarke i sam zlapie refresh token (loopback).
89
+ 1. Google Cloud Console -> projekt -> OAuth consent screen (Test user + scope `https://mail.google.com/`).
90
+ 2. Credentials -> OAuth client typu **Desktop app** -> masz `clientId` i `client secret`.
91
+ 3. Kreator (Gmail -> OAuth2) otworzy przegladarke, klikasz zgode, token laduje do `.env`.
92
+ Samodzielnie: `npx poczta-mcp oauth google`.
93
+
94
+ ### Microsoft 365 (OAuth2)
95
+ Azure Portal -> Microsoft Entra ID -> App registrations -> New registration.
96
+ - `tenantId`, `clientId` -> config.json; client secret -> `.env`.
97
+ - Dwa tryby (`auth.flow` w config):
98
+ - `refresh` (delegowany): API permissions (delegated) `IMAP.AccessAsUser.All`, `SMTP.Send`, `offline_access`. W Authentication dodaj platforme "Mobile and desktop applications" z redirect `http://localhost:3000`. Kreator (M365 -> refresh) otworzy przegladarke i zlapie token. Samodzielnie: `npx poczta-mcp oauth microsoft`.
99
+ - `client_credentials` (app-only, bez uzytkownika, pod automat): permission **application** `IMAP.AccessAsUser.All` + zgoda admina, oraz per-skrzynka `New-ApplicationAccessPolicy` w Exchange Online PowerShell.
100
+ - SMTP M365: host `smtp.office365.com`, port `587`, `secure:false` (STARTTLS).
101
+
102
+ ## Codzienny automat
103
+
104
+ 1. Skopiuj skilla do projektu:
105
+ ```bash
106
+ mkdir -p .claude/skills && cp -r skills/daily-briefing .claude/skills/daily-briefing
107
+ ```
108
+ 2. Skopiuj `.mcp.json.example` -> `.mcp.json`, uzupelnij sciezki do `config.json` i `.env`.
109
+ 3. Test recznie:
110
+ ```bash
111
+ claude -p "Wykonaj skill daily-briefing." --allowedTools "mcp__poczta__get_unread mcp__poczta__get_email_body mcp__poczta__mark_as_read mcp__poczta__deliver_digest mcp__poczta__list_accounts"
112
+ ```
113
+ 4. Cron: patrz `cron.example`.
114
+
115
+ Regula oznaczania (w skillu): 🔴 wazne i 🟠 wymaga akcji zostaja **unread**; 🟡 warto wiedziec i ⚪ opcjonalne -> oznaczone przeczytane. `send_email` nie jest na liscie allowedTools przegladu.
116
+
117
+ ## Podglad w Claude Desktop (reczne uzycie)
118
+
119
+ `~/Library/Application Support/Claude/claude_desktop_config.json`:
120
+
121
+ ```json
122
+ {
123
+ "mcpServers": {
124
+ "poczta": {
125
+ "command": "npx",
126
+ "args": ["-y", "poczta-mcp", "server"],
127
+ "env": { "POCZTA_MCP_CONFIG": "/abs/config.json", "POCZTA_MCP_ENV": "/abs/.env" }
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ Pytaj np. "co mam nieprzeczytane na wszystkich kontach", "podsumuj poczte".
134
+
135
+ ## Bezpieczenstwo
136
+
137
+ - `.env` i `config.json` w `.gitignore`. Sekrety tylko w `.env`.
138
+ - Serwer lokalny (stdio). Brak wystawiania po sieci = brak zewnetrznej powierzchni ataku.
139
+ - `send_email` poza przegladem i tylko po potwierdzeniu tresci.
140
+
141
+ Stary jednoplikowy serwer Python jest w `legacy/`.
@@ -0,0 +1,50 @@
1
+ {
2
+ "accounts": [
3
+ {
4
+ "id": "agh",
5
+ "label": "AGH",
6
+ "email": "123456@student.agh.edu.pl",
7
+ "imap": { "host": "poczta.agh.edu.pl", "port": 993, "secure": true },
8
+ "smtp": { "host": "poczta.agh.edu.pl", "port": 465, "secure": true },
9
+ "auth": { "type": "password", "passwordEnv": "AGH_APP_PASS" }
10
+ },
11
+ {
12
+ "id": "gmail",
13
+ "label": "Gmail prywatny",
14
+ "email": "ktos@gmail.com",
15
+ "imap": { "host": "imap.gmail.com", "port": 993, "secure": true },
16
+ "smtp": { "host": "smtp.gmail.com", "port": 465, "secure": true },
17
+ "auth": {
18
+ "type": "oauth2",
19
+ "provider": "google",
20
+ "clientId": "xxxxx.apps.googleusercontent.com",
21
+ "clientSecretEnv": "GMAIL_CLIENT_SECRET",
22
+ "refreshTokenEnv": "GMAIL_REFRESH_TOKEN"
23
+ }
24
+ },
25
+ {
26
+ "id": "firma",
27
+ "label": "ERES M365",
28
+ "email": "ktos@eresholding.com",
29
+ "imap": { "host": "outlook.office365.com", "port": 993, "secure": true },
30
+ "smtp": { "host": "smtp.office365.com", "port": 587, "secure": false },
31
+ "auth": {
32
+ "type": "oauth2",
33
+ "provider": "microsoft",
34
+ "tenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
35
+ "clientId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
36
+ "clientSecretEnv": "FIRMA_CLIENT_SECRET",
37
+ "flow": "refresh",
38
+ "refreshTokenEnv": "FIRMA_REFRESH_TOKEN"
39
+ }
40
+ }
41
+ ],
42
+ "digest": {
43
+ "timezone": "Europe/Warsaw",
44
+ "channels": [
45
+ { "type": "chat" },
46
+ { "type": "file", "path": "~/poczta-digest/{date}.md" },
47
+ { "type": "email", "to": "ja@eresholding.com", "fromAccount": "firma", "subject": "Przeglad poczty {date}" }
48
+ ]
49
+ }
50
+ }
package/cron.example ADDED
@@ -0,0 +1,19 @@
1
+ # Codzienny przeglad poczty — lokalny cron (run zostaje na tej maszynie, creds nie wychodza).
2
+ # Edycja: crontab -e
3
+ #
4
+ # Wymaga:
5
+ # - zainstalowany Claude Code CLI (claude) i zalogowany
6
+ # - w katalogu projektu .mcp.json (skopiowany z .mcp.json.example, sciezki uzupelnione)
7
+ # - skill daily-briefing dostepny: skopiuj skills/daily-briefing do .claude/skills/daily-briefing
8
+ # cp -r skills/daily-briefing .claude/skills/daily-briefing
9
+ #
10
+ # Codziennie o 7:30:
11
+
12
+ 30 7 * * * cd /ABS/PATH/poczta-agh-mcp && /usr/local/bin/claude -p "Wykonaj skill daily-briefing: codzienny przeglad nieprzeczytanej poczty ze wszystkich kont." --allowedTools "mcp__poczta__get_unread mcp__poczta__get_email_body mcp__poczta__mark_as_read mcp__poczta__deliver_digest mcp__poczta__list_accounts" >> $HOME/poczta-digest/run.log 2>&1
13
+
14
+ # Uwagi:
15
+ # - send_email celowo NIE jest na allowedTools (przeglad nigdy nie wysyla maili do ludzi).
16
+ # - 'which claude' poda wlasciwa sciezke do binarki, podmien /usr/local/bin/claude.
17
+ # - Podglad zewszad: ustaw w config.json kanal digestu 'email' lub 'webhook' (Slack),
18
+ # wtedy podsumowanie laduje tam gdzie czytasz, a MCP/.env zostaja lokalnie.
19
+ # - Maszyna musi byc wlaczona o tej godzinie. Dla automatu 24/7 odpal cron na serwerze IT.
@@ -0,0 +1,29 @@
1
+ import { requireEnv } from "../config.js";
2
+ const TOKEN_URL = "https://oauth2.googleapis.com/token";
3
+ /**
4
+ * Exchange a long-lived refresh token for a short-lived access token.
5
+ *
6
+ * Prereqs (one-time, done in setup):
7
+ * - Google Cloud project, OAuth client of type "Desktop app".
8
+ * - Consent with scope https://mail.google.com/ to obtain a refresh token.
9
+ * - The Gmail account must allow IMAP (Settings -> Forwarding and POP/IMAP).
10
+ */
11
+ export async function getGoogleAccessToken(acc) {
12
+ const body = new URLSearchParams({
13
+ grant_type: "refresh_token",
14
+ client_id: acc.clientId,
15
+ client_secret: requireEnv(acc.clientSecretEnv),
16
+ refresh_token: requireEnv(acc.refreshTokenEnv),
17
+ });
18
+ const res = await fetch(TOKEN_URL, {
19
+ method: "POST",
20
+ headers: { "content-type": "application/x-www-form-urlencoded" },
21
+ body,
22
+ });
23
+ const json = (await res.json());
24
+ if (!res.ok || !json.access_token) {
25
+ throw new Error(`Google token refresh failed (${res.status}): ${json.error ?? ""} ${json.error_description ?? ""}`.trim());
26
+ }
27
+ return { accessToken: json.access_token, expiresInSec: json.expires_in ?? 3600 };
28
+ }
29
+ //# sourceMappingURL=google.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/auth/google.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,MAAM,SAAS,GAAG,qCAAqC,CAAC;AAExD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,GAAqD;IAErD,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;QAC/B,UAAU,EAAE,eAAe;QAC3B,SAAS,EAAE,GAAG,CAAC,QAAQ;QACvB,aAAa,EAAE,UAAU,CAAC,GAAG,CAAC,eAAe,CAAC;QAC9C,aAAa,EAAE,UAAU,CAAC,GAAG,CAAC,eAAe,CAAC;KAC/C,CAAC,CAAC;IAEH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;QACjC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI;KACL,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAK7B,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CACb,gCAAgC,GAAG,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,IAAI,EAAE,IAAI,IAAI,CAAC,iBAAiB,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAC1G,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;AACnF,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { requireEnv } from "../config.js";
2
+ import { getGoogleAccessToken } from "./google.js";
3
+ import { getMicrosoftAccessToken } from "./microsoft.js";
4
+ const tokenCache = new Map();
5
+ // Refresh a bit before actual expiry to avoid races on long-running ops.
6
+ const SKEW_MS = 60_000;
7
+ function nowMs() {
8
+ // Date.now is allowed in the running server/CLI (only workflow scripts forbid it).
9
+ return Date.now();
10
+ }
11
+ async function accessTokenFor(acc) {
12
+ const cached = tokenCache.get(acc.id);
13
+ if (cached && cached.expiresAtMs - SKEW_MS > nowMs())
14
+ return cached.accessToken;
15
+ if (acc.auth.type !== "oauth2")
16
+ throw new Error("accessTokenFor called on non-oauth account");
17
+ const { accessToken, expiresInSec } = acc.auth.provider === "google"
18
+ ? await getGoogleAccessToken(acc.auth)
19
+ : await getMicrosoftAccessToken(acc.auth);
20
+ tokenCache.set(acc.id, { accessToken, expiresAtMs: nowMs() + expiresInSec * 1000 });
21
+ return accessToken;
22
+ }
23
+ /** Resolve credentials for an account, refreshing/caching OAuth tokens as needed. */
24
+ export async function resolveCreds(acc) {
25
+ if (acc.auth.type === "password") {
26
+ return { kind: "password", user: acc.email, pass: requireEnv(acc.auth.passwordEnv) };
27
+ }
28
+ const accessToken = await accessTokenFor(acc);
29
+ return { kind: "oauth2", user: acc.email, accessToken };
30
+ }
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAQzD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAsB,CAAC;AAEjD,yEAAyE;AACzE,MAAM,OAAO,GAAG,MAAM,CAAC;AAEvB,SAAS,KAAK;IACZ,mFAAmF;IACnF,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;AACpB,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,GAAY;IACxC,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtC,IAAI,MAAM,IAAI,MAAM,CAAC,WAAW,GAAG,OAAO,GAAG,KAAK,EAAE;QAAE,OAAO,MAAM,CAAC,WAAW,CAAC;IAEhF,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAE9F,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,GACjC,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,QAAQ;QAC5B,CAAC,CAAC,MAAM,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC;QACtC,CAAC,CAAC,MAAM,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE9C,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,YAAY,GAAG,IAAI,EAAE,CAAC,CAAC;IACpF,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAY;IAC7C,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QACjC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;IACvF,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;IAC9C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { requireEnv } from "../config.js";
2
+ const OUTLOOK = "https://outlook.office365.com";
3
+ /**
4
+ * Acquire an access token for Outlook/Microsoft 365 IMAP+SMTP.
5
+ *
6
+ * Two flows (set in config: auth.flow):
7
+ *
8
+ * refresh (delegated): interactive consent once -> refresh token stored in .env.
9
+ * scopes: IMAP.AccessAsUser.All, SMTP.Send, offline_access.
10
+ *
11
+ * client_credentials (app-only, unattended): no user, no refresh token. Requires:
12
+ * - Azure app with APPLICATION permission IMAP.AccessAsUser.All (admin consent), and
13
+ * - per-mailbox grant: New-ApplicationAccessPolicy in Exchange Online PowerShell.
14
+ * scope: https://outlook.office365.com/.default
15
+ *
16
+ * Note: Microsoft has retired IMAP/SMTP basic auth for most M365 tenants, so OAuth2 is the path.
17
+ */
18
+ export async function getMicrosoftAccessToken(acc) {
19
+ const tokenUrl = `https://login.microsoftonline.com/${acc.tenantId}/oauth2/v2.0/token`;
20
+ const body = new URLSearchParams({
21
+ client_id: acc.clientId,
22
+ client_secret: requireEnv(acc.clientSecretEnv),
23
+ });
24
+ if (acc.flow === "client_credentials") {
25
+ body.set("grant_type", "client_credentials");
26
+ body.set("scope", `${OUTLOOK}/.default`);
27
+ }
28
+ else {
29
+ body.set("grant_type", "refresh_token");
30
+ body.set("refresh_token", requireEnv(acc.refreshTokenEnv));
31
+ body.set("scope", `${OUTLOOK}/IMAP.AccessAsUser.All ${OUTLOOK}/SMTP.Send offline_access`);
32
+ }
33
+ const res = await fetch(tokenUrl, {
34
+ method: "POST",
35
+ headers: { "content-type": "application/x-www-form-urlencoded" },
36
+ body,
37
+ });
38
+ const json = (await res.json());
39
+ if (!res.ok || !json.access_token) {
40
+ throw new Error(`Microsoft token (${acc.flow}) failed (${res.status}): ${json.error ?? ""} ${json.error_description ?? ""}`.trim());
41
+ }
42
+ return { accessToken: json.access_token, expiresInSec: json.expires_in ?? 3600 };
43
+ }
44
+ //# sourceMappingURL=microsoft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"microsoft.js","sourceRoot":"","sources":["../../src/auth/microsoft.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,MAAM,OAAO,GAAG,+BAA+B,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,GAAwD;IAExD,MAAM,QAAQ,GAAG,qCAAqC,GAAG,CAAC,QAAQ,oBAAoB,CAAC;IAEvF,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;QAC/B,SAAS,EAAE,GAAG,CAAC,QAAQ;QACvB,aAAa,EAAE,UAAU,CAAC,GAAG,CAAC,eAAe,CAAC;KAC/C,CAAC,CAAC;IAEH,IAAI,GAAG,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;QACtC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,oBAAoB,CAAC,CAAC;QAC7C,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,OAAO,WAAW,CAAC,CAAC;IAC3C,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,CAAC,GAAG,CAAC,eAAgB,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC,GAAG,CACN,OAAO,EACP,GAAG,OAAO,0BAA0B,OAAO,2BAA2B,CACvE,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI;KACL,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAK7B,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CACb,oBAAoB,GAAG,CAAC,IAAI,aAAa,GAAG,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,IAAI,EAAE,IAAI,IAAI,CAAC,iBAAiB,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CACnH,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;AACnF,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,201 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, isAbsolute, resolve } from "node:path";
4
+ import { z } from "zod";
5
+ /**
6
+ * Config model.
7
+ *
8
+ * Split of responsibilities:
9
+ * - config.json -> structure (accounts, hosts, ports, digest channels). Safe to read.
10
+ * - .env -> secrets only. config.json references env-var NAMES (fields ending *Env),
11
+ * never the secret value itself. Keeps secrets out of any committed/synced file.
12
+ */
13
+ const endpoint = z.object({
14
+ host: z.string().min(1),
15
+ port: z.number().int().positive(),
16
+ secure: z.boolean().default(true), // true = implicit TLS (993/465); false = STARTTLS (143/587)
17
+ });
18
+ const passwordAuth = z.object({
19
+ type: z.literal("password"),
20
+ /** env var holding the password / app-password */
21
+ passwordEnv: z.string().min(1),
22
+ });
23
+ const googleAuth = z.object({
24
+ type: z.literal("oauth2"),
25
+ provider: z.literal("google"),
26
+ clientId: z.string().min(1),
27
+ clientSecretEnv: z.string().min(1),
28
+ refreshTokenEnv: z.string().min(1),
29
+ });
30
+ const microsoftAuth = z.object({
31
+ type: z.literal("oauth2"),
32
+ provider: z.literal("microsoft"),
33
+ tenantId: z.string().min(1),
34
+ clientId: z.string().min(1),
35
+ clientSecretEnv: z.string().min(1),
36
+ /**
37
+ * "refresh" -> delegated, uses a stored refresh token (interactive once).
38
+ * "client_credentials" -> app-only, unattended. Requires Azure admin to grant the
39
+ * IMAP.AccessAsUser.All application permission AND a per-mailbox
40
+ * ApplicationAccessPolicy (New-ApplicationAccessPolicy).
41
+ */
42
+ flow: z.enum(["refresh", "client_credentials"]).default("refresh"),
43
+ refreshTokenEnv: z.string().optional(),
44
+ });
45
+ // Not a discriminatedUnion on "type": google and microsoft share type:"oauth2".
46
+ // z.union resolves by trying each (provider literal disambiguates the oauth2 pair).
47
+ const authSchema = z.union([passwordAuth, googleAuth, microsoftAuth]);
48
+ const accountSchema = z
49
+ .object({
50
+ /** stable key used in every tool call (e.g. "agh", "gmail", "firma") */
51
+ id: z.string().min(1).regex(/^[a-z0-9_-]+$/i, "id: only letters, digits, _ and -"),
52
+ label: z.string().optional(),
53
+ email: z.string().email(),
54
+ imap: endpoint,
55
+ smtp: endpoint,
56
+ auth: authSchema,
57
+ })
58
+ .superRefine((acc, ctx) => {
59
+ if (acc.auth.type === "oauth2" && acc.auth.provider === "microsoft") {
60
+ if (acc.auth.flow === "refresh" && !acc.auth.refreshTokenEnv) {
61
+ ctx.addIssue({
62
+ code: z.ZodIssueCode.custom,
63
+ message: "microsoft flow 'refresh' requires refreshTokenEnv",
64
+ path: ["auth", "refreshTokenEnv"],
65
+ });
66
+ }
67
+ }
68
+ });
69
+ const digestChannel = z.discriminatedUnion("type", [
70
+ z.object({ type: z.literal("chat") }),
71
+ z.object({
72
+ type: z.literal("file"),
73
+ /** {date} and {datetime} are substituted. ~ expands to home. */
74
+ path: z.string().min(1),
75
+ }),
76
+ z.object({
77
+ type: z.literal("email"),
78
+ to: z.string().email(),
79
+ /** id of the account used to send */
80
+ fromAccount: z.string().min(1),
81
+ subject: z.string().default("Codzienny przeglad poczty {date}"),
82
+ }),
83
+ z.object({
84
+ type: z.literal("webhook"),
85
+ /** env var holding the webhook URL (e.g. Slack/Teams incoming webhook) */
86
+ urlEnv: z.string().min(1),
87
+ /** "slack" -> {text}, "raw" -> {markdown,date} */
88
+ format: z.enum(["slack", "raw"]).default("raw"),
89
+ }),
90
+ ]);
91
+ export const configSchema = z.object({
92
+ accounts: z.array(accountSchema).min(1, "at least one account"),
93
+ digest: z
94
+ .object({
95
+ channels: z.array(digestChannel).default([{ type: "chat" }]),
96
+ timezone: z.string().optional(),
97
+ })
98
+ .default({ channels: [{ type: "chat" }] }),
99
+ });
100
+ /** Resolve the config.json path. Override with POCZTA_MCP_CONFIG. */
101
+ export function configPath() {
102
+ const fromEnv = process.env.POCZTA_MCP_CONFIG;
103
+ if (fromEnv)
104
+ return isAbsolute(fromEnv) ? fromEnv : resolve(process.cwd(), fromEnv);
105
+ // Prefer a project-local config.json, fall back to XDG-ish home location.
106
+ const local = resolve(process.cwd(), "config.json");
107
+ if (existsSync(local))
108
+ return local;
109
+ return join(homedir(), ".config", "poczta-mcp", "config.json");
110
+ }
111
+ /** Default .env path, sits next to config.json unless POCZTA_MCP_ENV overrides. */
112
+ export function envPath() {
113
+ const fromEnv = process.env.POCZTA_MCP_ENV;
114
+ if (fromEnv)
115
+ return isAbsolute(fromEnv) ? fromEnv : resolve(process.cwd(), fromEnv);
116
+ const local = resolve(process.cwd(), ".env");
117
+ if (existsSync(local))
118
+ return local;
119
+ return join(homedir(), ".config", "poczta-mcp", ".env");
120
+ }
121
+ /** Load secrets from .env into process.env (does not overwrite already-set vars). */
122
+ export function loadEnv() {
123
+ const p = envPath();
124
+ if (!existsSync(p))
125
+ return;
126
+ try {
127
+ // Node >=20.12 ships process.loadEnvFile; fall back to a tiny parser otherwise.
128
+ const loader = process.loadEnvFile;
129
+ if (typeof loader === "function") {
130
+ loader.call(process, p);
131
+ return;
132
+ }
133
+ }
134
+ catch {
135
+ /* fall through to manual parse */
136
+ }
137
+ for (const line of readFileSync(p, "utf8").split("\n")) {
138
+ const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
139
+ if (!m)
140
+ continue;
141
+ const key = m[1];
142
+ let val = m[2];
143
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
144
+ val = val.slice(1, -1);
145
+ }
146
+ if (process.env[key] === undefined)
147
+ process.env[key] = val;
148
+ }
149
+ }
150
+ /** Read an env var by name, throwing a clear error if missing. */
151
+ export function requireEnv(name) {
152
+ const v = process.env[name];
153
+ if (v === undefined || v === "") {
154
+ throw new Error(`Missing secret: env var ${name} is not set (referenced from config.json)`);
155
+ }
156
+ return v;
157
+ }
158
+ export function loadConfig() {
159
+ loadEnv();
160
+ const p = configPath();
161
+ if (!existsSync(p)) {
162
+ throw new Error(`Config not found at ${p}. Run 'npx poczta-mcp setup' to create it, or set POCZTA_MCP_CONFIG.`);
163
+ }
164
+ let raw;
165
+ try {
166
+ raw = JSON.parse(readFileSync(p, "utf8"));
167
+ }
168
+ catch (e) {
169
+ throw new Error(`Config at ${p} is not valid JSON: ${e.message}`);
170
+ }
171
+ const parsed = configSchema.safeParse(raw);
172
+ if (!parsed.success) {
173
+ const issues = parsed.error.issues
174
+ .map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`)
175
+ .join("\n");
176
+ throw new Error(`Config at ${p} is invalid:\n${issues}`);
177
+ }
178
+ const cfg = parsed.data;
179
+ const ids = new Set();
180
+ for (const a of cfg.accounts) {
181
+ if (ids.has(a.id))
182
+ throw new Error(`Duplicate account id: ${a.id}`);
183
+ ids.add(a.id);
184
+ }
185
+ // digest email channels must point at a real account
186
+ for (const ch of cfg.digest.channels) {
187
+ if (ch.type === "email" && !ids.has(ch.fromAccount)) {
188
+ throw new Error(`digest email channel fromAccount '${ch.fromAccount}' is not a known account id`);
189
+ }
190
+ }
191
+ return cfg;
192
+ }
193
+ export function getAccount(cfg, id) {
194
+ const a = cfg.accounts.find((x) => x.id === id);
195
+ if (!a) {
196
+ const known = cfg.accounts.map((x) => x.id).join(", ");
197
+ throw new Error(`Unknown account '${id}'. Known: ${known}`);
198
+ }
199
+ return a;
200
+ }
201
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}