fastmail-mcp-server 0.4.0 → 0.4.2
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 +13 -2
- package/package.json +7 -2
- package/src/index.ts +53 -5
package/README.md
CHANGED
|
@@ -66,6 +66,16 @@ Token format: `fmu1-xxxxxxxx-xxxxxxxxxxxx...`
|
|
|
66
66
|
|
|
67
67
|
### 2. Configure Claude Desktop
|
|
68
68
|
|
|
69
|
+
Install the server globally:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Via mise (recommended)
|
|
73
|
+
mise use -g npm:fastmail-mcp-server
|
|
74
|
+
|
|
75
|
+
# Or via npm
|
|
76
|
+
npm install -g fastmail-mcp-server
|
|
77
|
+
```
|
|
78
|
+
|
|
69
79
|
Open the Claude Desktop config file:
|
|
70
80
|
|
|
71
81
|
```bash
|
|
@@ -83,8 +93,7 @@ Add the fastmail server config:
|
|
|
83
93
|
{
|
|
84
94
|
"mcpServers": {
|
|
85
95
|
"fastmail": {
|
|
86
|
-
"command": "
|
|
87
|
-
"args": ["-y", "fastmail-mcp-server"],
|
|
96
|
+
"command": "fastmail-mcp-server",
|
|
88
97
|
"env": {
|
|
89
98
|
"FASTMAIL_API_TOKEN": "fmu1-your-token-here"
|
|
90
99
|
}
|
|
@@ -93,6 +102,8 @@ Add the fastmail server config:
|
|
|
93
102
|
}
|
|
94
103
|
```
|
|
95
104
|
|
|
105
|
+
> **Note:** If Claude Desktop can't find the command, use the full path from `which fastmail-mcp-server`
|
|
106
|
+
|
|
96
107
|
### 3. Restart Claude Desktop
|
|
97
108
|
|
|
98
109
|
Quit Claude Desktop completely (Cmd+Q) and reopen it. The Fastmail tools should now appear.
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastmail-mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "MCP server for Fastmail - read, search, and send emails via Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"fastmail-mcp-server": "src/index.ts"
|
|
9
|
+
},
|
|
7
10
|
"files": [
|
|
8
11
|
"src/**/*"
|
|
9
12
|
],
|
|
@@ -38,10 +41,12 @@
|
|
|
38
41
|
"dependencies": {
|
|
39
42
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
40
43
|
"officeparser": "^6.0.4",
|
|
44
|
+
"sharp": "^0.34.5",
|
|
41
45
|
"zod": "^4.3.4"
|
|
42
46
|
},
|
|
43
47
|
"devDependencies": {
|
|
44
48
|
"@biomejs/biome": "^2.3.10",
|
|
45
|
-
"@types/bun": "latest"
|
|
49
|
+
"@types/bun": "latest",
|
|
50
|
+
"@types/sharp": "^0.32.0"
|
|
46
51
|
}
|
|
47
52
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
7
|
import { parseOffice } from "officeparser";
|
|
8
|
+
import sharp from "sharp";
|
|
8
9
|
import { z } from "zod";
|
|
9
10
|
import {
|
|
10
11
|
buildForward,
|
|
@@ -810,25 +811,72 @@ server.tool(
|
|
|
810
811
|
}
|
|
811
812
|
}
|
|
812
813
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
// Images - return as image content
|
|
814
|
+
// Images - return as image content (resize if >1MB for model limits)
|
|
816
815
|
if (result.type.startsWith("image/")) {
|
|
816
|
+
const MAX_SIZE = 1024 * 1024; // 1MB
|
|
817
|
+
let imageData = result.data;
|
|
818
|
+
let mimeType = result.type;
|
|
819
|
+
let resized = false;
|
|
820
|
+
|
|
821
|
+
if (result.data.byteLength > MAX_SIZE) {
|
|
822
|
+
try {
|
|
823
|
+
// Resize to fit under 1MB, convert to JPEG for better compression
|
|
824
|
+
let quality = 85;
|
|
825
|
+
let resizedBuffer = await sharp(result.data)
|
|
826
|
+
.resize({
|
|
827
|
+
width: 2048,
|
|
828
|
+
height: 2048,
|
|
829
|
+
fit: "inside",
|
|
830
|
+
withoutEnlargement: true,
|
|
831
|
+
})
|
|
832
|
+
.jpeg({ quality })
|
|
833
|
+
.toBuffer();
|
|
834
|
+
|
|
835
|
+
// If still too large, reduce quality progressively
|
|
836
|
+
while (resizedBuffer.byteLength > MAX_SIZE && quality > 30) {
|
|
837
|
+
quality -= 15;
|
|
838
|
+
resizedBuffer = await sharp(result.data)
|
|
839
|
+
.resize({
|
|
840
|
+
width: 1600,
|
|
841
|
+
height: 1600,
|
|
842
|
+
fit: "inside",
|
|
843
|
+
withoutEnlargement: true,
|
|
844
|
+
})
|
|
845
|
+
.jpeg({ quality })
|
|
846
|
+
.toBuffer();
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
imageData = resizedBuffer;
|
|
850
|
+
mimeType = "image/jpeg";
|
|
851
|
+
resized = true;
|
|
852
|
+
} catch (err) {
|
|
853
|
+
console.error(`[get_attachment] Image resize failed:`, err);
|
|
854
|
+
// Fall through with original
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const base64 = Buffer.from(imageData).toString("base64");
|
|
859
|
+
const sizeInfo = resized
|
|
860
|
+
? `${Math.round(result.size / 1024)}KB → ${Math.round(imageData.byteLength / 1024)}KB resized`
|
|
861
|
+
: `${Math.round(result.size / 1024)}KB`;
|
|
862
|
+
|
|
817
863
|
return {
|
|
818
864
|
content: [
|
|
819
865
|
{
|
|
820
866
|
type: "text" as const,
|
|
821
|
-
text: `Attachment: ${result.name || "(unnamed)"} (${
|
|
867
|
+
text: `Attachment: ${result.name || "(unnamed)"} (${sizeInfo})`,
|
|
822
868
|
},
|
|
823
869
|
{
|
|
824
870
|
type: "image" as const,
|
|
825
871
|
data: base64,
|
|
826
|
-
mimeType:
|
|
872
|
+
mimeType: mimeType,
|
|
827
873
|
},
|
|
828
874
|
],
|
|
829
875
|
};
|
|
830
876
|
}
|
|
831
877
|
|
|
878
|
+
const base64 = Buffer.from(result.data).toString("base64");
|
|
879
|
+
|
|
832
880
|
// Other binary - return base64 as last resort
|
|
833
881
|
return {
|
|
834
882
|
content: [
|