@atomicmail/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/README.md ADDED
@@ -0,0 +1,233 @@
1
+ # @atomicmail/mcp
2
+
3
+ AtomicMail MCP server — a local stdio Model Context Protocol server that gives
4
+ an AI agent a programmable email inbox over JMAP, with automatic Proof-of-Work
5
+ auth and capability-token rotation.
6
+
7
+ The server is authored in TypeScript on Deno and built with
8
+ [`dnt`](https://jsr.io/@deno/dnt) into a Node-compatible npm package, so the
9
+ **same source** runs unchanged on Deno, Node, and Bun.
10
+
11
+ It is the MCP companion to [`@atomic-mail/agent-skill`](../../skill/) — a
12
+ CLI-first skill with identical credential semantics. The MCP and the skill share
13
+ the same on-disk credential layout (`credentials.json` + `session.jwt` +
14
+ `capability.jwt`), so you can mix and match the two freely.
15
+
16
+ ## Tools exposed
17
+
18
+ | Tool | Description |
19
+ | -------------- | ----------------------------------------------------------------- |
20
+ | `register` | PoW signup. Persists credentials to disk; returns the API key. |
21
+ | `jmap_session` | `GET /.well-known/jmap` — discover accountId + mailbox URLs. |
22
+ | `jmap_request` | Send any JMAP method-call batch. Inline or via saved preset file. |
23
+ | `get_docs` | Built-in docs. Topics include `installation`, `presets`, `auth`. |
24
+
25
+ ## Install
26
+
27
+ The package is published to npm. Any of these work:
28
+
29
+ ```bash
30
+ npx -y @atomicmail/mcp
31
+ bunx @atomicmail/mcp
32
+ deno run -A npm:@atomicmail/mcp/atomicmail-mcp
33
+ ```
34
+
35
+ You don't typically run it directly — your MCP host (Cursor, Claude Desktop,
36
+ ...) will spawn it for you. See the configuration section below.
37
+
38
+ ## Configure (priority order)
39
+
40
+ The server resolves configuration from two sources, with environment variables
41
+ overriding individual fields from the file:
42
+
43
+ 1. **`credentials.json` in the credential directory.** The same file produced by
44
+ `atomic-mail-signup`. Default location: `~/.atomicmail/credentials.json`.
45
+ Override the directory with `ATOMIC_MAIL_CREDENTIALS_DIR`.
46
+ 2. **Environment variables.** Useful for hermetic MCP host configs:
47
+
48
+ | Variable | Required | Description |
49
+ | ----------------------------- | -------- | --------------------------------- |
50
+ | `ATOMIC_MAIL_AUTH_URL` | Yes\* | Auth service base URL |
51
+ | `ATOMIC_MAIL_API_URL` | Yes\* | API service / JMAP base URL |
52
+ | `ATOMIC_MAIL_SCRYPT_SALT` | No | Optional PoW salt override |
53
+ | `ATOMIC_MAIL_API_KEY` | No | Existing API key (skips signup) |
54
+ | `ATOMIC_MAIL_CREDENTIALS_DIR` | No | Override default credential dir |
55
+
56
+ \* "Yes" means at least one source must provide `authUrl` and `apiUrl`. If
57
+ `credentials.json` is present and complete, no env vars are required. PoW
58
+ salt defaults to the built-in value that matches `auth-service`.
59
+
60
+ If neither source resolves `authUrl` and `apiUrl`, the server fails fast on
61
+ startup with the missing fields listed.
62
+
63
+ ## Credential files
64
+
65
+ All three live in the credential directory and are written with mode `0600`:
66
+
67
+ - `credentials.json` — `{ apiKey, inboxId, authUrl, apiUrl, scryptSalt }`.
68
+ Long-lived. Treat as a secret — copying it to a new machine is enough to log
69
+ in.
70
+ - `session.jwt` — 4-hour TTL, rotated automatically via PoW when near expiry.
71
+ - `capability.jwt` — 2-minute TTL, rotated automatically before each JMAP
72
+ request when near expiry.
73
+
74
+ Because the on-disk layout is identical to the
75
+ [`@atomic-mail/agent-skill`](../../skill/) skill, you can:
76
+
77
+ - Bootstrap once with `atomic-mail-signup`, then point the MCP at the same
78
+ directory.
79
+ - Run `atomic-mail-jmap` from a shell while the MCP is also running — both will
80
+ see fresh JWTs as the other rotates them.
81
+
82
+ ## MCP host configuration examples
83
+
84
+ ### Cursor
85
+
86
+ `~/.cursor/mcp.json` (or per-project `.cursor/mcp.json`):
87
+
88
+ ```json
89
+ {
90
+ "mcpServers": {
91
+ "atomicmail": {
92
+ "command": "npx",
93
+ "args": ["-y", "@atomicmail/mcp"],
94
+ "env": {
95
+ "ATOMIC_MAIL_AUTH_URL": "https://auth.atomicmail.io",
96
+ "ATOMIC_MAIL_API_URL": "https://api.atomicmail.io"
97
+ }
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ ### Claude Desktop
104
+
105
+ `claude_desktop_config.json`:
106
+
107
+ ```json
108
+ {
109
+ "mcpServers": {
110
+ "atomicmail": {
111
+ "command": "npx",
112
+ "args": ["-y", "@atomicmail/mcp"],
113
+ "env": {
114
+ "ATOMIC_MAIL_AUTH_URL": "https://auth.atomicmail.io",
115
+ "ATOMIC_MAIL_API_URL": "https://api.atomicmail.io"
116
+ }
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ### Sharing credentials with the skill CLI
123
+
124
+ If you've already run `atomic-mail-signup`, no auth-service URL needs to be
125
+ repeated — the MCP reads URLs (and optional salt override) from `credentials.json`:
126
+
127
+ ```json
128
+ {
129
+ "mcpServers": {
130
+ "atomicmail": {
131
+ "command": "npx",
132
+ "args": ["-y", "@atomicmail/mcp"],
133
+ "env": {
134
+ "ATOMIC_MAIL_CREDENTIALS_DIR": "/Users/me/.atomicmail"
135
+ }
136
+ }
137
+ }
138
+ }
139
+ ```
140
+
141
+ When you skip the env entirely (`{}`), the MCP defaults to `~/.atomicmail/`.
142
+
143
+ ## Typical agent workflow
144
+
145
+ Once the MCP is wired in, an agent will:
146
+
147
+ 1. Call `register` (only on the first run, with no existing API key).
148
+ 2. Call `jmap_session` to discover the `accountId` and the INBOX id.
149
+ 3. Call `jmap_request` with whatever method calls it needs (inline) or reference
150
+ saved presets via `opsFile`.
151
+ 4. Call `get_docs` with topic `presets` or `jmap_cheatsheet` for ready-to-use
152
+ JMAP recipes.
153
+
154
+ ## JMAP presets (the `opsFile` parameter)
155
+
156
+ `jmap_request` accepts either an inline `methodCalls` array or an `opsFile`
157
+ path. Relative paths resolve against the credential directory, so common batches
158
+ can be saved once and reused. This mirrors the skill's
159
+ `atomic-mail-jmap --ops-file` behavior — both consume the same files.
160
+
161
+ Example: drop `~/.atomicmail/fetch_last_100.json` containing
162
+
163
+ ```json
164
+ [
165
+ [
166
+ "Email/query",
167
+ {
168
+ "accountId": "ACC",
169
+ "filter": { "inMailbox": "INBOX" },
170
+ "sort": [{ "property": "receivedAt", "isAscending": false }],
171
+ "limit": 100
172
+ },
173
+ "q0"
174
+ ]
175
+ ]
176
+ ```
177
+
178
+ …then `jmap_request` with `{ "opsFile": "fetch_last_100.json" }`.
179
+
180
+ See `get_docs presets` for the full preset format.
181
+
182
+ ## Develop
183
+
184
+ The project is a Deno-first codebase. You'll need [Deno](https://deno.com/)
185
+ installed.
186
+
187
+ ```bash
188
+ # Run directly with Deno (live source, no build step)
189
+ deno task start
190
+
191
+ # Type-check
192
+ deno task check
193
+
194
+ # Format
195
+ deno task fmt
196
+
197
+ # Build the npm package via dnt
198
+ deno task build:npm
199
+
200
+ # Run the built artifact under Node
201
+ node npm/esm/main.js
202
+ ```
203
+
204
+ The dnt build emits to `./npm/`. The package's `bin` is `atomicmail-mcp`, mapped
205
+ to `npm/esm/main.js`. After a build you can also `npm install` it locally and
206
+ use the binary directly.
207
+
208
+ ### Project layout
209
+
210
+ ```
211
+ services/mcp-server-local/
212
+ ├── deno.json # Deno config + import map
213
+ ├── build_npm.ts # dnt build script
214
+ ├── README.md
215
+ ├── src/
216
+ │ ├── main.ts # MCP server entry (stdio transport)
217
+ │ ├── auth-session.ts# PoW + JWT rotation, with disk persistence
218
+ │ ├── credentials.ts # File I/O: credentials.json, *.jwt
219
+ │ ├── docs-content.ts# Static text served by the get_docs tool
220
+ │ └── tools/
221
+ │ ├── register.ts
222
+ │ ├── jmap.ts
223
+ │ └── docs.ts
224
+ └── npm/ # dnt output (gitignored)
225
+ ```
226
+
227
+ The auth-session and credentials files are kept in-step with their counterparts
228
+ in [`skill/scripts/lib/`](../../skill/scripts/lib/) so both projects produce and
229
+ consume identical credential files.
230
+
231
+ ## License
232
+
233
+ MIT
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Fixed proof-of-work scrypt salt. The auth-service passes this string (UTF-8
3
+ * bytes of the hex text, not decoded binary) to `scrypt` as the `salt`
4
+ * argument; all PoW clients must use the same value.
5
+ */
6
+ export declare const DEFAULT_POW_SCRYPT_SALT_HEX = "0b980734412c292d6549110276b604ab1dea4883bd460d77d1b984adf8bca083";
7
+ export declare const ONE_SEC_MS = 1000;
8
+ export declare const ONE_MIN_MS: number;
9
+ export declare const ONE_HOUR_MS: number;
10
+ export declare const ONE_DAY_MS: number;
11
+ export declare const ONE_MONTH_MS: number;
12
+ export declare const ONE_YEAR_MS: number;
13
+ //# sourceMappingURL=consts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../../../src/lib/src/consts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,qEAC4B,CAAC;AAErE,eAAO,MAAM,UAAU,OAAO,CAAC;AAC/B,eAAO,MAAM,UAAU,QAAkB,CAAC;AAC1C,eAAO,MAAM,WAAW,QAAkB,CAAC;AAC3C,eAAO,MAAM,UAAU,QAAmB,CAAC;AAC3C,eAAO,MAAM,YAAY,QAAkB,CAAC;AAC5C,eAAO,MAAM,WAAW,QAAmB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Fixed proof-of-work scrypt salt. The auth-service passes this string (UTF-8
3
+ * bytes of the hex text, not decoded binary) to `scrypt` as the `salt`
4
+ * argument; all PoW clients must use the same value.
5
+ */
6
+ export const DEFAULT_POW_SCRYPT_SALT_HEX = "0b980734412c292d6549110276b604ab1dea4883bd460d77d1b984adf8bca083";
7
+ export const ONE_SEC_MS = 1000;
8
+ export const ONE_MIN_MS = ONE_SEC_MS * 60;
9
+ export const ONE_HOUR_MS = ONE_MIN_MS * 60;
10
+ export const ONE_DAY_MS = ONE_HOUR_MS * 24;
11
+ export const ONE_MONTH_MS = ONE_DAY_MS * 30;
12
+ export const ONE_YEAR_MS = ONE_DAY_MS * 365;
@@ -0,0 +1,88 @@
1
+ import { type SkillFiles } from "./credentials.js";
2
+ export declare const SESSION_TTL_MS: number;
3
+ export declare const CAPABILITY_TTL_MS: number;
4
+ export declare const SESSION_SAFETY_MARGIN_MS = 60000;
5
+ export declare const CAPABILITY_SAFETY_MARGIN_MS = 20000;
6
+ export interface JwtPayload {
7
+ exp?: number;
8
+ iat?: number;
9
+ jti?: string;
10
+ inboxId?: string;
11
+ [key: string]: unknown;
12
+ }
13
+ export declare function decodeJwtPayload<T = JwtPayload>(jwt: string): T;
14
+ export declare function isJwtExpired(jwt: string, marginMs: number): boolean;
15
+ export type ConfigSource = "credentials-file" | "env" | "mixed" | "incomplete";
16
+ export interface ResolvedConfig {
17
+ authUrl: string;
18
+ apiUrl: string;
19
+ scryptSalt: string;
20
+ apiKey?: string;
21
+ inboxId?: string;
22
+ credentialDir: string;
23
+ files: SkillFiles;
24
+ source: ConfigSource;
25
+ }
26
+ /**
27
+ * Resolve credential directory from (in priority order):
28
+ * 1. ATOMIC_MAIL_CREDENTIALS_DIR env var
29
+ * 2. ~/.atomicmail/ (POSIX) or %USERPROFILE%/.atomicmail (Windows)
30
+ */
31
+ export declare function resolveCredentialDir(): string;
32
+ /**
33
+ * Resolve server configuration from:
34
+ * 1. credentials.json in the credential directory (if present and valid).
35
+ * 2. ATOMIC_MAIL_* environment variables (overlay on top of (1) so env
36
+ * always wins on a per-field basis).
37
+ *
38
+ * The auth and api base URLs MUST be resolvable from at least one source.
39
+ * PoW scrypt salt defaults to the deployment constant when not set in env or
40
+ * credentials.json.
41
+ * The api key is optional; without it, the agent must call the register tool
42
+ * before any JMAP request.
43
+ */
44
+ export declare function resolveConfig(): Promise<ResolvedConfig>;
45
+ export interface AuthSessionConfig {
46
+ authUrl: string;
47
+ apiUrl: string;
48
+ scryptSalt: string;
49
+ apiKey?: string;
50
+ inboxId?: string;
51
+ credentialDir: string;
52
+ files: SkillFiles;
53
+ }
54
+ export declare class AuthSession {
55
+ private readonly authUrl;
56
+ readonly apiUrl: string;
57
+ private readonly scryptSalt;
58
+ private apiKey;
59
+ private inboxId;
60
+ readonly credentialDir: string;
61
+ readonly files: SkillFiles;
62
+ private sessionJWT;
63
+ private capabilityJWT;
64
+ constructor(cfg: AuthSessionConfig);
65
+ /** Construct a session and load any previously persisted JWTs from disk. */
66
+ static create(cfg: AuthSessionConfig): Promise<AuthSession>;
67
+ get hasApiKey(): boolean;
68
+ get currentInboxId(): string | undefined;
69
+ private loadFromDisk;
70
+ /**
71
+ * Full PoW signup. Persists credentials.json + session.jwt + capability.jwt
72
+ * to disk and updates in-memory state. Throws if an API key is already
73
+ * configured (refuse to clobber an existing account).
74
+ */
75
+ signup(username: string): Promise<{
76
+ apiKey: string;
77
+ inboxId: string;
78
+ }>;
79
+ /**
80
+ * Get a valid capability JWT, rotating session/capability tokens as needed.
81
+ * This is the primary method tool handlers call before every JMAP request.
82
+ */
83
+ getCapabilityToken(): Promise<string>;
84
+ private ensureSession;
85
+ /** Clean shutdown. No-op today; reserved for future cleanup. */
86
+ destroy(): void;
87
+ }
88
+ //# sourceMappingURL=auth-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-session.d.ts","sourceRoot":"","sources":["../../../src/mcp/src/auth-session.ts"],"names":[],"mappings":"AAsBA,OAAO,EAGL,KAAK,UAAU,EAKhB,MAAM,kBAAkB,CAAC;AAO1B,eAAO,MAAM,cAAc,QAAqB,CAAC;AACjD,eAAO,MAAM,iBAAiB,QAAgB,CAAC;AAI/C,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAElD,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAc/D;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQnE;AAgLD,MAAM,MAAM,YAAY,GACpB,kBAAkB,GAClB,KAAK,GACL,OAAO,GACP,YAAY,CAAC;AAEjB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAW7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,cAAc,CAAC,CAqD7D;AAID,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,OAAO,CAAqB;IACpC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAE3B,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,aAAa,CAAqB;gBAE9B,GAAG,EAAE,iBAAiB;IAUlC,4EAA4E;WAC/D,MAAM,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC;IAMjE,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,cAAc,IAAI,MAAM,GAAG,SAAS,CAEvC;YAEa,YAAY;IAK1B;;;;OAIG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QACtC,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IA6CF;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;YA8B7B,aAAa;IA0B3B,gEAAgE;IAChE,OAAO,IAAI,IAAI;CAGhB"}