@mailkite/mcp 0.4.0 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mailkite/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Model Context Protocol server for MailKite — exposes the MailKite API to LLM agents as tools. A thin layer over the MailKite Node SDK and the shared sdks/spec contract.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,6 +41,6 @@
41
41
  "dependencies": {
42
42
  "@modelcontextprotocol/sdk": "^1.0.0",
43
43
  "ajv": "^8.17.1",
44
- "mailkite": "^0.4.0"
44
+ "mailkite": "^0.6.0"
45
45
  }
46
46
  }
package/server.mjs CHANGED
@@ -202,6 +202,25 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
202
202
  };
203
203
  }
204
204
 
205
+ // uploadAttachment is the one method whose input isn't a plain JSON body: `path` is read
206
+ // off the local disk and streamed as a raw binary upload. The generic resolveCall →
207
+ // mk.request() path only does JSON, so delegate to the SDK method, which picks the
208
+ // transport (path/bytes → binary, url/content → JSON). Validate against the same schema.
209
+ if (tool._method.name === "uploadAttachment") {
210
+ try {
211
+ const input = req.params.arguments || {};
212
+ const validate = validators["upload-attachment-request"];
213
+ if (validate && !validate(input)) {
214
+ return { isError: true, content: [{ type: "text", text: `Invalid input for upload-attachment-request: ${ajv.errorsText(validate.errors)}` }] };
215
+ }
216
+ const result = await mk.uploadAttachment(input);
217
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
218
+ } catch (err) {
219
+ const text = err instanceof MailKiteError ? `MailKite API error ${err.status}: ${err.message}` : `Error: ${err.message}`;
220
+ return { isError: true, content: [{ type: "text", text }] };
221
+ }
222
+ }
223
+
205
224
  try {
206
225
  const { method, urlPath, body } = resolveCall(tool._method, req.params.arguments);
207
226
  const result = await mk.request(method, urlPath, body);
package/spec/api.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mailkite",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Canonical interface contract for every MailKite SDK. One low-level request() plus one function per endpoint. All languages expose the same shape; only naming adapts to each language's convention (e.g. Go exports PascalCase).",
5
5
  "baseUrl": "https://api.mailkite.dev",
6
6
  "auth": {
@@ -25,6 +25,22 @@
25
25
  ],
26
26
  "returns": "send-response"
27
27
  },
28
+ {
29
+ "name": "uploadAttachment",
30
+ "summary": "Upload a file to MailKite storage and get back a secure, time-limited URL. Reference the returned `url` as an attachment in send() (`{ filename, url }`) or link it inline in your HTML — instead of base64-inlining large files on every send. Give the file ONE of four ways: a local `path` (read and streamed as raw bytes by the CLI/SDK/local MCP), a remote `url` (MailKite fetches and re-hosts it), base64 `content`, or — over raw HTTP — the file bytes as the POST body with `?filename=`. `retentionDays` (7/30/90/365, default 7) sets how long the file and URL live.",
31
+ "http": {
32
+ "method": "POST",
33
+ "path": "/v1/attachments"
34
+ },
35
+ "args": [
36
+ {
37
+ "name": "file",
38
+ "in": "body",
39
+ "schema": "upload-attachment-request"
40
+ }
41
+ ],
42
+ "returns": "upload-attachment-response"
43
+ },
28
44
  {
29
45
  "name": "listTemplates",
30
46
  "summary": "List your saved email templates (light metadata only — no body). Use getTemplate for the full template.",
@@ -348,6 +364,30 @@
348
364
  "returns": "string",
349
365
  "sdkOnly": true
350
366
  },
367
+ {
368
+ "name": "replySpam",
369
+ "summary": "Control-mode reply a webhook consumer returns to tell MailKite to mark the message as spam — the string `{\"status\":\"spam\"}`. Local, no network call.",
370
+ "local": true,
371
+ "args": [],
372
+ "returns": "string",
373
+ "sdkOnly": true
374
+ },
375
+ {
376
+ "name": "replyDrop",
377
+ "summary": "Control-mode reply a webhook consumer returns to tell MailKite to drop (discard) the message — the string `{\"status\":\"drop\"}`. Local, no network call.",
378
+ "local": true,
379
+ "args": [],
380
+ "returns": "string",
381
+ "sdkOnly": true
382
+ },
383
+ {
384
+ "name": "replyBlockSender",
385
+ "summary": "Control-mode reply a webhook consumer returns to tell MailKite to block the sender — the string `{\"status\":\"ok\",\"actions\":[{\"type\":\"block-sender\"}]}`. Local, no network call.",
386
+ "local": true,
387
+ "args": [],
388
+ "returns": "string",
389
+ "sdkOnly": true
390
+ },
351
391
  {
352
392
  "name": "encrypt",
353
393
  "summary": "Encrypt a UTF-8 string to a domain's RSA public key (SPKI/PEM), returning the at-rest envelope JSON (`{v,keyAlg,fp,enc,iv,wrappedKey,ciphertext}`). Hybrid scheme: a fresh AES-256-GCM content key wrapped with RSA-OAEP(SHA-256) — byte-compatible with MailKite's own at-rest encryption. Local, no network call.",
package/spec/cases.json CHANGED
@@ -34,6 +34,82 @@
34
34
  "status": "queued"
35
35
  }
36
36
  },
37
+ {
38
+ "name": "upload_attachment",
39
+ "method": "uploadAttachment",
40
+ "args": {
41
+ "filename": "po.pdf",
42
+ "content": "JVBERi0xLjQK",
43
+ "contentType": "application/pdf"
44
+ },
45
+ "request": {
46
+ "method": "POST",
47
+ "path": "/v1/attachments",
48
+ "bodySchema": "upload-attachment-request",
49
+ "body": {
50
+ "filename": "po.pdf",
51
+ "content": "JVBERi0xLjQK",
52
+ "contentType": "application/pdf"
53
+ }
54
+ },
55
+ "response": {
56
+ "status": 201,
57
+ "body": {
58
+ "id": "7d/usr_demo/0a1b2c3d/po.pdf",
59
+ "url": "https://api.mailkite.dev/up/7d/usr_demo/0a1b2c3d/po.pdf?exp=1799999999&sig=deadbeef",
60
+ "filename": "po.pdf",
61
+ "contentType": "application/pdf",
62
+ "size": 9,
63
+ "expiresAt": "2026-07-02T00:00:00.000Z"
64
+ }
65
+ },
66
+ "result": {
67
+ "id": "7d/usr_demo/0a1b2c3d/po.pdf",
68
+ "url": "https://api.mailkite.dev/up/7d/usr_demo/0a1b2c3d/po.pdf?exp=1799999999&sig=deadbeef",
69
+ "filename": "po.pdf",
70
+ "contentType": "application/pdf",
71
+ "size": 9,
72
+ "expiresAt": "2026-07-02T00:00:00.000Z"
73
+ }
74
+ },
75
+ {
76
+ "name": "upload_attachment_url",
77
+ "method": "uploadAttachment",
78
+ "args": {
79
+ "url": "https://files.example.com/po.pdf",
80
+ "filename": "po.pdf",
81
+ "contentType": "application/pdf"
82
+ },
83
+ "request": {
84
+ "method": "POST",
85
+ "path": "/v1/attachments",
86
+ "bodySchema": "upload-attachment-request",
87
+ "body": {
88
+ "url": "https://files.example.com/po.pdf",
89
+ "filename": "po.pdf",
90
+ "contentType": "application/pdf"
91
+ }
92
+ },
93
+ "response": {
94
+ "status": 201,
95
+ "body": {
96
+ "id": "7d/usr_demo/0a1b2c3d/po.pdf",
97
+ "url": "https://api.mailkite.dev/up/7d/usr_demo/0a1b2c3d/po.pdf?exp=1799999999&sig=deadbeef",
98
+ "filename": "po.pdf",
99
+ "contentType": "application/pdf",
100
+ "size": 9,
101
+ "expiresAt": "2026-07-02T00:00:00.000Z"
102
+ }
103
+ },
104
+ "result": {
105
+ "id": "7d/usr_demo/0a1b2c3d/po.pdf",
106
+ "url": "https://api.mailkite.dev/up/7d/usr_demo/0a1b2c3d/po.pdf?exp=1799999999&sig=deadbeef",
107
+ "filename": "po.pdf",
108
+ "contentType": "application/pdf",
109
+ "size": 9,
110
+ "expiresAt": "2026-07-02T00:00:00.000Z"
111
+ }
112
+ },
37
113
  {
38
114
  "name": "send_full",
39
115
  "method": "send",
@@ -876,6 +952,27 @@
876
952
  "args": {},
877
953
  "result": "{\"status\":\"ok\"}"
878
954
  },
955
+ {
956
+ "name": "reply_spam",
957
+ "method": "replySpam",
958
+ "local": true,
959
+ "args": {},
960
+ "result": "{\"status\":\"spam\"}"
961
+ },
962
+ {
963
+ "name": "reply_drop",
964
+ "method": "replyDrop",
965
+ "local": true,
966
+ "args": {},
967
+ "result": "{\"status\":\"drop\"}"
968
+ },
969
+ {
970
+ "name": "reply_block_sender",
971
+ "method": "replyBlockSender",
972
+ "local": true,
973
+ "args": {},
974
+ "result": "{\"status\":\"ok\",\"actions\":[{\"type\":\"block-sender\"}]}"
975
+ },
879
976
  {
880
977
  "name": "decrypt_envelope",
881
978
  "method": "decrypt",
@@ -37,9 +37,9 @@
37
37
  "required": ["filename"],
38
38
  "properties": {
39
39
  "filename": { "type": "string" },
40
- "url": { "type": "string" },
41
- "content": { "type": "string" },
42
- "contentType": { "type": "string" }
40
+ "url": { "type": "string", "description": "Fetch the file from this URL at send time and attach it. Any URL works; an uploadAttachment() URL is the secure, recommended choice. Prefer this over `content` for large files." },
41
+ "content": { "type": "string", "description": "Inline file bytes as base64. Simple for tiny files, but re-uploaded on every send — use `url` (see uploadAttachment) for anything large." },
42
+ "contentType": { "type": "string", "description": "MIME type; defaults to application/octet-stream." }
43
43
  }
44
44
  }
45
45
  }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "upload-attachment-request",
4
+ "title": "Upload attachment request body",
5
+ "description": "Provide the file ONE of four ways: `path` (a local file the SDK/CLI/MCP reads and streams as raw bytes), `url` (a remote file MailKite fetches and re-hosts), `content` (base64-encoded bytes), or — at the HTTP layer — the raw file bytes as the POST body with `?filename=`. `path`/raw-bytes go up as a real binary upload (like an S3/R2 PUT); `url`/`content` go up as JSON.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "filename": { "type": "string", "description": "The file's name, e.g. \"invoice.pdf\". Shown to recipients on download. Optional when it can be derived from `path` or `url`." },
10
+ "path": { "type": "string", "description": "Local filesystem path to the file. Read client-side by the CLI, SDKs, and the local MCP server, then uploaded as raw bytes. Not available on the hosted MCP (no filesystem)." },
11
+ "url": { "type": "string", "description": "A remote http(s) URL. MailKite fetches it and re-hosts the bytes under your account. Max 25 MB." },
12
+ "content": { "type": "string", "description": "The file bytes, base64-encoded. The lowest-common-denominator fallback when you can't send a path, URL, or raw bytes." },
13
+ "contentType": { "type": "string", "description": "MIME type, e.g. \"application/pdf\". Defaults to application/octet-stream (or is inferred from the file extension / fetched response)." },
14
+ "retentionDays": {
15
+ "type": "integer",
16
+ "enum": [7, 30, 90, 365],
17
+ "description": "How long the file (and its signed URL) stays valid. One of 7, 30, 90, 365. Defaults to 7."
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "upload-attachment-response",
4
+ "title": "Upload attachment response",
5
+ "type": "object",
6
+ "properties": {
7
+ "id": { "type": "string", "description": "Storage key for the uploaded file." },
8
+ "url": { "type": "string", "description": "Secure, time-limited URL. Pass it as an attachment's `url` in send(), or link it inline in your HTML." },
9
+ "filename": { "type": "string" },
10
+ "contentType": { "type": "string" },
11
+ "size": { "type": "integer", "description": "Stored size in bytes." },
12
+ "expiresAt": { "type": "string", "description": "ISO-8601 timestamp when the file and URL expire." }
13
+ }
14
+ }