mcp-twake-mail 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.
Files changed (114) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +332 -0
  3. package/build/auth/index.d.ts +3 -0
  4. package/build/auth/index.js +4 -0
  5. package/build/auth/index.js.map +1 -0
  6. package/build/auth/oidc-flow.d.ts +47 -0
  7. package/build/auth/oidc-flow.js +146 -0
  8. package/build/auth/oidc-flow.js.map +1 -0
  9. package/build/auth/token-refresh.d.ts +56 -0
  10. package/build/auth/token-refresh.js +132 -0
  11. package/build/auth/token-refresh.js.map +1 -0
  12. package/build/auth/token-store.d.ts +33 -0
  13. package/build/auth/token-store.js +63 -0
  14. package/build/auth/token-store.js.map +1 -0
  15. package/build/cli/commands/auth.d.ts +5 -0
  16. package/build/cli/commands/auth.js +49 -0
  17. package/build/cli/commands/auth.js.map +1 -0
  18. package/build/cli/commands/check.d.ts +4 -0
  19. package/build/cli/commands/check.js +125 -0
  20. package/build/cli/commands/check.js.map +1 -0
  21. package/build/cli/commands/setup.d.ts +4 -0
  22. package/build/cli/commands/setup.js +172 -0
  23. package/build/cli/commands/setup.js.map +1 -0
  24. package/build/cli/index.d.ts +15 -0
  25. package/build/cli/index.js +56 -0
  26. package/build/cli/index.js.map +1 -0
  27. package/build/cli/prompts/setup-wizard.d.ts +38 -0
  28. package/build/cli/prompts/setup-wizard.js +121 -0
  29. package/build/cli/prompts/setup-wizard.js.map +1 -0
  30. package/build/config/__tests__/logger.test.d.ts +1 -0
  31. package/build/config/__tests__/logger.test.js +28 -0
  32. package/build/config/__tests__/logger.test.js.map +1 -0
  33. package/build/config/__tests__/schema.test.d.ts +1 -0
  34. package/build/config/__tests__/schema.test.js +101 -0
  35. package/build/config/__tests__/schema.test.js.map +1 -0
  36. package/build/config/logger.d.ts +3 -0
  37. package/build/config/logger.js +9 -0
  38. package/build/config/logger.js.map +1 -0
  39. package/build/config/schema.d.ts +28 -0
  40. package/build/config/schema.js +81 -0
  41. package/build/config/schema.js.map +1 -0
  42. package/build/errors.d.ts +34 -0
  43. package/build/errors.js +154 -0
  44. package/build/errors.js.map +1 -0
  45. package/build/errors.test.d.ts +1 -0
  46. package/build/errors.test.js +234 -0
  47. package/build/errors.test.js.map +1 -0
  48. package/build/index.d.ts +2 -0
  49. package/build/index.js +12 -0
  50. package/build/index.js.map +1 -0
  51. package/build/jmap/client.d.ts +96 -0
  52. package/build/jmap/client.js +267 -0
  53. package/build/jmap/client.js.map +1 -0
  54. package/build/mcp/server.d.ts +24 -0
  55. package/build/mcp/server.js +68 -0
  56. package/build/mcp/server.js.map +1 -0
  57. package/build/mcp/tools/attachment.d.ts +30 -0
  58. package/build/mcp/tools/attachment.js +246 -0
  59. package/build/mcp/tools/attachment.js.map +1 -0
  60. package/build/mcp/tools/attachment.test.d.ts +1 -0
  61. package/build/mcp/tools/attachment.test.js +457 -0
  62. package/build/mcp/tools/attachment.test.js.map +1 -0
  63. package/build/mcp/tools/email-operations.d.ts +10 -0
  64. package/build/mcp/tools/email-operations.js +828 -0
  65. package/build/mcp/tools/email-operations.js.map +1 -0
  66. package/build/mcp/tools/email-operations.test.d.ts +1 -0
  67. package/build/mcp/tools/email-operations.test.js +453 -0
  68. package/build/mcp/tools/email-operations.test.js.map +1 -0
  69. package/build/mcp/tools/email-sending.d.ts +10 -0
  70. package/build/mcp/tools/email-sending.js +682 -0
  71. package/build/mcp/tools/email-sending.js.map +1 -0
  72. package/build/mcp/tools/email.d.ts +10 -0
  73. package/build/mcp/tools/email.js +365 -0
  74. package/build/mcp/tools/email.js.map +1 -0
  75. package/build/mcp/tools/email.test.d.ts +1 -0
  76. package/build/mcp/tools/email.test.js +332 -0
  77. package/build/mcp/tools/email.test.js.map +1 -0
  78. package/build/mcp/tools/index.d.ts +14 -0
  79. package/build/mcp/tools/index.js +29 -0
  80. package/build/mcp/tools/index.js.map +1 -0
  81. package/build/mcp/tools/mailbox.d.ts +10 -0
  82. package/build/mcp/tools/mailbox.js +195 -0
  83. package/build/mcp/tools/mailbox.js.map +1 -0
  84. package/build/mcp/tools/mailbox.test.d.ts +1 -0
  85. package/build/mcp/tools/mailbox.test.js +231 -0
  86. package/build/mcp/tools/mailbox.test.js.map +1 -0
  87. package/build/mcp/tools/thread.d.ts +10 -0
  88. package/build/mcp/tools/thread.js +282 -0
  89. package/build/mcp/tools/thread.js.map +1 -0
  90. package/build/mcp/tools/thread.test.d.ts +1 -0
  91. package/build/mcp/tools/thread.test.js +384 -0
  92. package/build/mcp/tools/thread.test.js.map +1 -0
  93. package/build/transformers/__tests__/email.test.d.ts +1 -0
  94. package/build/transformers/__tests__/email.test.js +438 -0
  95. package/build/transformers/__tests__/email.test.js.map +1 -0
  96. package/build/transformers/__tests__/mailbox.test.d.ts +1 -0
  97. package/build/transformers/__tests__/mailbox.test.js +222 -0
  98. package/build/transformers/__tests__/mailbox.test.js.map +1 -0
  99. package/build/transformers/email.d.ts +76 -0
  100. package/build/transformers/email.js +138 -0
  101. package/build/transformers/email.js.map +1 -0
  102. package/build/transformers/index.d.ts +5 -0
  103. package/build/transformers/index.js +6 -0
  104. package/build/transformers/index.js.map +1 -0
  105. package/build/transformers/mailbox.d.ts +43 -0
  106. package/build/transformers/mailbox.js +70 -0
  107. package/build/transformers/mailbox.js.map +1 -0
  108. package/build/types/dto.d.ts +91 -0
  109. package/build/types/dto.js +9 -0
  110. package/build/types/dto.js.map +1 -0
  111. package/build/types/jmap.d.ts +110 -0
  112. package/build/types/jmap.js +5 -0
  113. package/build/types/jmap.js.map +1 -0
  114. package/package.json +71 -0
@@ -0,0 +1,267 @@
1
+ import { JMAPError } from '../errors.js';
2
+ import { createTokenRefresher } from '../auth/token-refresh.js';
3
+ /** Default JMAP capabilities for mail operations */
4
+ const DEFAULT_USING = ['urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail'];
5
+ /** Session fetch timeout (quick check) */
6
+ const SESSION_TIMEOUT = 5000;
7
+ /**
8
+ * JMAP client for interacting with JMAP mail servers.
9
+ * Handles authentication, session management, and request batching.
10
+ */
11
+ export class JMAPClient {
12
+ session = null;
13
+ config;
14
+ logger;
15
+ stateTracker = new Map();
16
+ tokenRefresher = null;
17
+ constructor(config, logger) {
18
+ this.config = config;
19
+ this.logger = logger;
20
+ // Initialize TokenRefresher for OIDC authentication
21
+ if (config.JMAP_AUTH_METHOD === 'oidc' && config.JMAP_OIDC_ISSUER && config.JMAP_OIDC_CLIENT_ID) {
22
+ this.tokenRefresher = createTokenRefresher(config.JMAP_OIDC_ISSUER, config.JMAP_OIDC_CLIENT_ID);
23
+ }
24
+ }
25
+ /**
26
+ * Generate authentication headers based on configured auth method.
27
+ * For OIDC, automatically refreshes tokens if needed via TokenRefresher.
28
+ * @returns Headers object with Authorization and Content-Type
29
+ */
30
+ async getAuthHeaders() {
31
+ const headers = {
32
+ 'Content-Type': 'application/json',
33
+ };
34
+ if (this.config.JMAP_AUTH_METHOD === 'basic') {
35
+ const credentials = `${this.config.JMAP_USERNAME}:${this.config.JMAP_PASSWORD}`;
36
+ const token = Buffer.from(credentials).toString('base64');
37
+ headers['Authorization'] = `Basic ${token}`;
38
+ }
39
+ else if (this.config.JMAP_AUTH_METHOD === 'oidc') {
40
+ // OIDC: use TokenRefresher to get valid token (auto-refreshes if needed)
41
+ if (!this.tokenRefresher) {
42
+ throw new JMAPError('OIDC configuration incomplete', 'oidcConfigError', 'Ensure OIDC_ISSUER and OIDC_CLIENT_ID are configured.');
43
+ }
44
+ const tokens = await this.tokenRefresher.ensureValidToken();
45
+ headers['Authorization'] = `Bearer ${tokens.accessToken}`;
46
+ }
47
+ else {
48
+ // Bearer: use static token from config
49
+ headers['Authorization'] = `Bearer ${this.config.JMAP_TOKEN}`;
50
+ }
51
+ return headers;
52
+ }
53
+ /**
54
+ * Fetch JMAP session from the server.
55
+ * Discovers apiUrl, accountId, and capabilities.
56
+ * @returns JMAPSession with extracted session data
57
+ * @throws JMAPError if session fetch fails or no mail account found
58
+ */
59
+ async fetchSession() {
60
+ this.logger.info({ url: this.config.JMAP_SESSION_URL }, 'Fetching JMAP session...');
61
+ let response;
62
+ try {
63
+ response = await fetch(this.config.JMAP_SESSION_URL, {
64
+ method: 'GET',
65
+ headers: await this.getAuthHeaders(),
66
+ signal: AbortSignal.timeout(SESSION_TIMEOUT),
67
+ });
68
+ }
69
+ catch (error) {
70
+ if (error instanceof Error && (error.name === 'TimeoutError' || error.name === 'AbortError')) {
71
+ throw JMAPError.timeout('session fetch');
72
+ }
73
+ throw error;
74
+ }
75
+ if (!response.ok) {
76
+ throw JMAPError.httpError(response.status, response.statusText);
77
+ }
78
+ const sessionData = (await response.json());
79
+ // Extract primary account for mail capability
80
+ const accountId = sessionData.primaryAccounts['urn:ietf:params:jmap:mail'];
81
+ if (!accountId) {
82
+ throw JMAPError.methodError('noMailAccount', 'No mail account found in JMAP session. The server may not support JMAP Mail.');
83
+ }
84
+ // Validate downloadUrl exists (required for blob downloads)
85
+ if (!sessionData.downloadUrl) {
86
+ this.logger.warn('No downloadUrl in JMAP session - attachment downloads will not work');
87
+ }
88
+ this.session = {
89
+ apiUrl: sessionData.apiUrl,
90
+ downloadUrl: sessionData.downloadUrl || '',
91
+ accountId,
92
+ capabilities: sessionData.capabilities,
93
+ state: sessionData.state,
94
+ };
95
+ this.logger.info({ accountId, apiUrl: sessionData.apiUrl }, 'JMAP session established');
96
+ return this.session;
97
+ }
98
+ /**
99
+ * Get the current session.
100
+ * @returns JMAPSession
101
+ * @throws JMAPError if session not initialized
102
+ */
103
+ getSession() {
104
+ if (!this.session) {
105
+ throw new JMAPError('Session not initialized', 'sessionNotInitialized', 'Call fetchSession() before making requests.');
106
+ }
107
+ return this.session;
108
+ }
109
+ /**
110
+ * Make a batched JMAP request with multiple method calls.
111
+ * @param methodCalls Array of method calls to execute
112
+ * @param using Optional array of capability URIs (defaults to core + mail)
113
+ * @returns JMAPResponse with method responses
114
+ * @throws JMAPError on HTTP errors or timeouts
115
+ */
116
+ async request(methodCalls, using = DEFAULT_USING) {
117
+ const session = this.getSession();
118
+ const requestBody = {
119
+ using,
120
+ methodCalls,
121
+ };
122
+ this.logger.debug({ methodCount: methodCalls.length, methods: methodCalls.map((mc) => mc[0]) }, 'Sending JMAP request');
123
+ let response;
124
+ try {
125
+ response = await fetch(session.apiUrl, {
126
+ method: 'POST',
127
+ headers: await this.getAuthHeaders(),
128
+ body: JSON.stringify(requestBody),
129
+ signal: AbortSignal.timeout(this.config.JMAP_REQUEST_TIMEOUT),
130
+ });
131
+ }
132
+ catch (error) {
133
+ if (error instanceof Error && (error.name === 'TimeoutError' || error.name === 'AbortError')) {
134
+ throw JMAPError.timeout('JMAP request');
135
+ }
136
+ throw error;
137
+ }
138
+ if (!response.ok) {
139
+ throw JMAPError.httpError(response.status, response.statusText);
140
+ }
141
+ const jmapResponse = (await response.json());
142
+ // Check for session state changes
143
+ if (jmapResponse.sessionState && jmapResponse.sessionState !== this.session?.state) {
144
+ this.logger.warn({ oldState: this.session?.state, newState: jmapResponse.sessionState }, 'Session state changed - session refresh may be needed');
145
+ }
146
+ // Update state tracking from method responses
147
+ for (const methodResponse of jmapResponse.methodResponses) {
148
+ this.extractAndUpdateState(methodResponse);
149
+ }
150
+ return jmapResponse;
151
+ }
152
+ /**
153
+ * Parse a method response and extract success/error status.
154
+ * @param response The method response tuple
155
+ * @returns Object with success flag and data or error
156
+ */
157
+ parseMethodResponse(response) {
158
+ const [methodName, responseData] = response;
159
+ // Check if this is an error response
160
+ if (methodName === 'error') {
161
+ const errorResponse = {
162
+ type: responseData.type || 'unknownError',
163
+ description: responseData.description,
164
+ };
165
+ this.logger.debug({ error: errorResponse }, 'JMAP method error');
166
+ return {
167
+ success: false,
168
+ error: errorResponse,
169
+ };
170
+ }
171
+ return {
172
+ success: true,
173
+ data: responseData,
174
+ };
175
+ }
176
+ /**
177
+ * Extract state from method response and update tracker.
178
+ * @param response The method response tuple
179
+ */
180
+ extractAndUpdateState(response) {
181
+ const [methodName, responseData] = response;
182
+ // Skip error responses
183
+ if (methodName === 'error') {
184
+ return;
185
+ }
186
+ // Extract object type from method name (e.g., 'Email/get' -> 'Email')
187
+ const objectType = methodName.split('/')[0];
188
+ // Check for state or newState in response
189
+ const state = responseData.newState || responseData.state;
190
+ if (state && objectType) {
191
+ this.updateState(objectType, state);
192
+ }
193
+ }
194
+ /**
195
+ * Update tracked state for an object type.
196
+ * @param type Object type (e.g., 'Email', 'Mailbox')
197
+ * @param state New state string
198
+ */
199
+ updateState(type, state) {
200
+ this.stateTracker.set(type, state);
201
+ this.logger.debug({ type, state }, 'State updated');
202
+ }
203
+ /**
204
+ * Get tracked state for an object type.
205
+ * @param type Object type (e.g., 'Email', 'Mailbox')
206
+ * @returns State string or undefined if not tracked
207
+ */
208
+ getState(type) {
209
+ return this.stateTracker.get(type);
210
+ }
211
+ /**
212
+ * Clear tracked state.
213
+ * @param type Optional object type to clear. If not provided, clears all state.
214
+ */
215
+ clearState(type) {
216
+ if (type) {
217
+ this.stateTracker.delete(type);
218
+ this.logger.debug({ type }, 'State cleared');
219
+ }
220
+ else {
221
+ this.stateTracker.clear();
222
+ this.logger.debug('All state cleared');
223
+ }
224
+ }
225
+ /**
226
+ * Download a blob (attachment) from the JMAP server.
227
+ * Uses the downloadUrl template from the session.
228
+ * @param blobId The blob ID to download
229
+ * @param name Optional filename for the download
230
+ * @param type Optional MIME type
231
+ * @returns ArrayBuffer containing the blob data
232
+ * @throws JMAPError if download fails
233
+ */
234
+ async downloadBlob(blobId, name, type) {
235
+ const session = this.getSession();
236
+ if (!session.downloadUrl) {
237
+ throw new JMAPError('Download URL not available', 'downloadUrlMissing', 'The JMAP server did not provide a download URL in the session.');
238
+ }
239
+ // Build download URL from template
240
+ // Template format: {downloadUrl}/{accountId}/{blobId}/{name}?type={type}
241
+ let url = session.downloadUrl
242
+ .replace('{accountId}', encodeURIComponent(session.accountId))
243
+ .replace('{blobId}', encodeURIComponent(blobId))
244
+ .replace('{name}', encodeURIComponent(name || 'attachment'))
245
+ .replace('{type}', encodeURIComponent(type || 'application/octet-stream'));
246
+ this.logger.debug({ blobId, name, type, url }, 'Downloading blob');
247
+ let response;
248
+ try {
249
+ response = await fetch(url, {
250
+ method: 'GET',
251
+ headers: await this.getAuthHeaders(),
252
+ signal: AbortSignal.timeout(this.config.JMAP_REQUEST_TIMEOUT),
253
+ });
254
+ }
255
+ catch (error) {
256
+ if (error instanceof Error && (error.name === 'TimeoutError' || error.name === 'AbortError')) {
257
+ throw JMAPError.timeout('blob download');
258
+ }
259
+ throw error;
260
+ }
261
+ if (!response.ok) {
262
+ throw JMAPError.httpError(response.status, response.statusText);
263
+ }
264
+ return response.arrayBuffer();
265
+ }
266
+ }
267
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/jmap/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * MCP server for JMAP mail operations.
3
+ * Uses stdio transport for AI assistant communication.
4
+ */
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { type Config } from '../config/schema.js';
7
+ import { type Logger } from '../config/logger.js';
8
+ import { JMAPClient } from '../jmap/client.js';
9
+ /**
10
+ * Create MCP server instance with JMAP client.
11
+ * @param config Validated configuration
12
+ * @param logger Pino logger (stderr only)
13
+ * @returns Object with server and jmapClient
14
+ */
15
+ export declare function createMCPServer(config: Config, logger: Logger): {
16
+ server: McpServer;
17
+ jmapClient: JMAPClient;
18
+ };
19
+ /**
20
+ * Start the MCP server with JMAP validation.
21
+ * Validates JMAP connection before accepting MCP requests.
22
+ * Exits with code 1 on startup failure.
23
+ */
24
+ export declare function startServer(): Promise<void>;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * MCP server for JMAP mail operations.
3
+ * Uses stdio transport for AI assistant communication.
4
+ */
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { loadConfig } from '../config/schema.js';
8
+ import { createLogger } from '../config/logger.js';
9
+ import { JMAPClient } from '../jmap/client.js';
10
+ import { formatStartupError } from '../errors.js';
11
+ import { registerAllTools } from './tools/index.js';
12
+ /** Server version (matches package.json) */
13
+ const SERVER_VERSION = '0.1.0';
14
+ /** Server name for MCP identification */
15
+ const SERVER_NAME = 'mcp-twake-mail';
16
+ /**
17
+ * Create MCP server instance with JMAP client.
18
+ * @param config Validated configuration
19
+ * @param logger Pino logger (stderr only)
20
+ * @returns Object with server and jmapClient
21
+ */
22
+ export function createMCPServer(config, logger) {
23
+ const server = new McpServer({
24
+ name: SERVER_NAME,
25
+ version: SERVER_VERSION,
26
+ });
27
+ const jmapClient = new JMAPClient(config, logger);
28
+ return { server, jmapClient };
29
+ }
30
+ /**
31
+ * Start the MCP server with JMAP validation.
32
+ * Validates JMAP connection before accepting MCP requests.
33
+ * Exits with code 1 on startup failure.
34
+ */
35
+ export async function startServer() {
36
+ let sessionUrl;
37
+ let logger;
38
+ try {
39
+ // Step 1: Load and validate configuration
40
+ const config = loadConfig();
41
+ sessionUrl = config.JMAP_SESSION_URL;
42
+ // Step 2: Initialize logger (stderr only - stdout reserved for MCP)
43
+ logger = createLogger(config.LOG_LEVEL);
44
+ logger.info({ version: SERVER_VERSION }, 'Starting mcp-twake-mail server');
45
+ // Step 3: Create MCP server and JMAP client
46
+ const { server, jmapClient } = createMCPServer(config, logger);
47
+ // Step 4: Validate JMAP connection BEFORE accepting MCP requests
48
+ const session = await jmapClient.fetchSession();
49
+ logger.info({ accountId: session.accountId }, 'JMAP connection validated');
50
+ // Step 5: Register all MCP tools
51
+ registerAllTools(server, jmapClient, logger);
52
+ // Step 6: Connect MCP server via stdio transport
53
+ const transport = new StdioServerTransport();
54
+ await server.connect(transport);
55
+ logger.info('MCP server running on stdio');
56
+ }
57
+ catch (error) {
58
+ // Format error with AI-friendly message
59
+ const errorMessage = formatStartupError(error instanceof Error ? error : new Error(String(error)), sessionUrl);
60
+ // Log to stderr (NEVER stdout - reserved for MCP JSON-RPC)
61
+ if (logger) {
62
+ logger.fatal({ error }, 'Startup failed');
63
+ }
64
+ process.stderr.write(`\n${errorMessage}\n`);
65
+ process.exit(1);
66
+ }
67
+ }
68
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAe,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAe,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,4CAA4C;AAC5C,MAAM,cAAc,GAAG,OAAO,CAAC;AAE/B,yCAAyC;AACzC,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAErC;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAc,EACd,MAAc;IAEd,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,cAAc;KACxB,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAElD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,UAA8B,CAAC;IACnC,IAAI,MAA0B,CAAC;IAE/B,IAAI,CAAC;QACH,0CAA0C;QAC1C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,UAAU,GAAG,MAAM,CAAC,gBAAgB,CAAC;QAErC,oEAAoE;QACpE,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,gCAAgC,CAAC,CAAC;QAE3E,4CAA4C;QAC5C,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAE/D,iEAAiE;QACjE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,YAAY,EAAE,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,2BAA2B,CAAC,CAAC;QAE3E,iCAAiC;QACjC,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;QAE7C,iDAAiD;QACjD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAEhC,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,wCAAwC;QACxC,MAAM,YAAY,GAAG,kBAAkB,CACrC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EACzD,UAAU,CACX,CAAC;QAEF,2DAA2D;QAC3D,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,YAAY,IAAI,CAAC,CAAC;QAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { JMAPClient } from '../../jmap/client.js';
3
+ import type { Logger } from '../../config/logger.js';
4
+ /**
5
+ * Metadata for a single attachment.
6
+ */
7
+ export interface AttachmentMetadata {
8
+ blobId: string;
9
+ name: string | null;
10
+ type: string;
11
+ size: number;
12
+ isInline: boolean;
13
+ }
14
+ /**
15
+ * Determine if an attachment is inline based on RFC 8621 algorithm.
16
+ * isInline = has cid AND disposition !== 'attachment'
17
+ * @param part The body part to check
18
+ * @returns true if the attachment is inline
19
+ */
20
+ export declare function isInlineAttachment(part: {
21
+ cid?: string | null;
22
+ disposition?: string | null;
23
+ }): boolean;
24
+ /**
25
+ * Register attachment MCP tools with the server.
26
+ * @param server MCP server instance
27
+ * @param jmapClient JMAP client for API calls
28
+ * @param logger Pino logger
29
+ */
30
+ export declare function registerAttachmentTools(server: McpServer, jmapClient: JMAPClient, logger: Logger): void;
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Attachment MCP tools - get_attachments, download_attachment.
3
+ * Enables AI assistants to list and download email attachments.
4
+ */
5
+ import { z } from 'zod';
6
+ import { writeFile, mkdir } from 'fs/promises';
7
+ import { join } from 'path';
8
+ import { homedir } from 'os';
9
+ /** Maximum size for inline base64 response (750KB to stay under 1MB limit with overhead) */
10
+ const MAX_INLINE_SIZE = 750 * 1024;
11
+ /**
12
+ * Common annotations for read-only attachment tools.
13
+ */
14
+ const ATTACHMENT_READ_ANNOTATIONS = {
15
+ readOnlyHint: true,
16
+ destructiveHint: false,
17
+ idempotentHint: true,
18
+ openWorldHint: true,
19
+ };
20
+ /**
21
+ * Determine if an attachment is inline based on RFC 8621 algorithm.
22
+ * isInline = has cid AND disposition !== 'attachment'
23
+ * @param part The body part to check
24
+ * @returns true if the attachment is inline
25
+ */
26
+ export function isInlineAttachment(part) {
27
+ return !!part.cid && part.disposition !== 'attachment';
28
+ }
29
+ /**
30
+ * Transform a JMAP body part to AttachmentMetadata.
31
+ * @param part JMAP body part from attachments array
32
+ * @returns AttachmentMetadata with isInline flag
33
+ */
34
+ function transformAttachment(part) {
35
+ return {
36
+ blobId: part.blobId,
37
+ name: part.name ?? null,
38
+ type: part.type,
39
+ size: part.size,
40
+ isInline: isInlineAttachment(part),
41
+ };
42
+ }
43
+ /**
44
+ * Filter attachments based on optional criteria.
45
+ * @param attachments List of attachment metadata
46
+ * @param excludeInline If true, remove inline attachments
47
+ * @param mimeTypeFilter If provided, only include attachments matching this MIME type prefix
48
+ * @returns Filtered list of attachments
49
+ */
50
+ function filterAttachments(attachments, excludeInline, mimeTypeFilter) {
51
+ return attachments.filter((att) => {
52
+ if (excludeInline && att.isInline)
53
+ return false;
54
+ if (mimeTypeFilter && !att.type.startsWith(mimeTypeFilter))
55
+ return false;
56
+ return true;
57
+ });
58
+ }
59
+ /**
60
+ * Register attachment MCP tools with the server.
61
+ * @param server MCP server instance
62
+ * @param jmapClient JMAP client for API calls
63
+ * @param logger Pino logger
64
+ */
65
+ export function registerAttachmentTools(server, jmapClient, logger) {
66
+ // get_attachments - list attachment metadata for an email (ATTACH-01, ATTACH-02)
67
+ server.registerTool('get_attachments', {
68
+ title: 'Get Attachments',
69
+ description: 'List all attachments for an email with metadata (blobId, name, type, size, isInline). Supports filtering by excludeInline and mimeTypeFilter.',
70
+ inputSchema: {
71
+ emailId: z.string().describe('The unique identifier of the email to get attachments from'),
72
+ excludeInline: z
73
+ .boolean()
74
+ .optional()
75
+ .default(false)
76
+ .describe('Exclude inline attachments (images embedded in HTML body)'),
77
+ mimeTypeFilter: z
78
+ .string()
79
+ .optional()
80
+ .describe('Filter by MIME type prefix (e.g., "image/", "application/pdf")'),
81
+ },
82
+ annotations: ATTACHMENT_READ_ANNOTATIONS,
83
+ }, async ({ emailId, excludeInline, mimeTypeFilter }) => {
84
+ logger.debug({ emailId, excludeInline, mimeTypeFilter }, 'get_attachments called');
85
+ try {
86
+ const session = jmapClient.getSession();
87
+ const response = await jmapClient.request([
88
+ [
89
+ 'Email/get',
90
+ {
91
+ accountId: session.accountId,
92
+ ids: [emailId],
93
+ properties: ['attachments'],
94
+ bodyProperties: ['blobId', 'name', 'type', 'size', 'disposition', 'cid'],
95
+ },
96
+ 'getAttachments',
97
+ ],
98
+ ]);
99
+ const result = jmapClient.parseMethodResponse(response.methodResponses[0]);
100
+ if (!result.success) {
101
+ logger.error({ error: result.error, emailId }, 'JMAP error in get_attachments');
102
+ return {
103
+ isError: true,
104
+ content: [
105
+ {
106
+ type: 'text',
107
+ text: `Failed to retrieve attachments: ${result.error?.description || result.error?.type || 'Unknown error'}`,
108
+ },
109
+ ],
110
+ };
111
+ }
112
+ const emails = result.data?.list;
113
+ if (!emails || emails.length === 0) {
114
+ return {
115
+ isError: true,
116
+ content: [
117
+ {
118
+ type: 'text',
119
+ text: `Email not found: ${emailId}`,
120
+ },
121
+ ],
122
+ };
123
+ }
124
+ // Get attachments array (may be empty or undefined)
125
+ const rawAttachments = emails[0].attachments || [];
126
+ // Transform all attachments to metadata
127
+ const allAttachments = rawAttachments.map(transformAttachment);
128
+ const totalCount = allAttachments.length;
129
+ // Apply filters
130
+ const filteredAttachments = filterAttachments(allAttachments, excludeInline, mimeTypeFilter);
131
+ const filteredCount = filteredAttachments.length;
132
+ logger.debug({ emailId, total: totalCount, filtered: filteredCount }, 'get_attachments success');
133
+ return {
134
+ content: [
135
+ {
136
+ type: 'text',
137
+ text: JSON.stringify({
138
+ emailId,
139
+ attachments: filteredAttachments,
140
+ total: totalCount,
141
+ filtered: filteredCount,
142
+ }, null, 2),
143
+ },
144
+ ],
145
+ };
146
+ }
147
+ catch (error) {
148
+ logger.error({ error, emailId }, 'Exception in get_attachments');
149
+ return {
150
+ isError: true,
151
+ content: [
152
+ {
153
+ type: 'text',
154
+ text: `Error retrieving attachments: ${error instanceof Error ? error.message : String(error)}`,
155
+ },
156
+ ],
157
+ };
158
+ }
159
+ });
160
+ // download_attachment - download attachment content (ATTACH-03)
161
+ server.registerTool('download_attachment', {
162
+ title: 'Download Attachment',
163
+ description: 'Download an attachment by its blobId. Small files (< 750KB) are returned as base64. Large files are automatically saved to ~/Downloads and the path is returned.',
164
+ inputSchema: {
165
+ blobId: z.string().describe('The blob ID of the attachment to download (from get_attachments)'),
166
+ name: z.string().optional().describe('Filename for the attachment'),
167
+ type: z.string().optional().describe('MIME type of the attachment'),
168
+ saveToPath: z.string().optional().describe('Force save to this specific path (overrides automatic behavior)'),
169
+ },
170
+ annotations: ATTACHMENT_READ_ANNOTATIONS,
171
+ }, async ({ blobId, name, type, saveToPath }) => {
172
+ logger.debug({ blobId, name, type, saveToPath }, 'download_attachment called');
173
+ try {
174
+ // Download the blob
175
+ const data = await jmapClient.downloadBlob(blobId, name, type);
176
+ const buffer = Buffer.from(data);
177
+ const filename = name || 'attachment';
178
+ logger.debug({ blobId, size: data.byteLength, maxInline: MAX_INLINE_SIZE }, 'download_attachment downloaded');
179
+ // Decide whether to return inline or save to disk
180
+ const shouldSaveToDisk = saveToPath || data.byteLength > MAX_INLINE_SIZE;
181
+ if (shouldSaveToDisk) {
182
+ // Save to disk
183
+ let outputPath;
184
+ if (saveToPath) {
185
+ outputPath = saveToPath;
186
+ }
187
+ else {
188
+ // Default to ~/Downloads
189
+ const downloadDir = join(homedir(), 'Downloads');
190
+ await mkdir(downloadDir, { recursive: true });
191
+ outputPath = join(downloadDir, filename);
192
+ }
193
+ await writeFile(outputPath, buffer);
194
+ logger.debug({ blobId, size: data.byteLength, path: outputPath }, 'download_attachment saved to disk');
195
+ return {
196
+ content: [
197
+ {
198
+ type: 'text',
199
+ text: JSON.stringify({
200
+ blobId,
201
+ name: filename,
202
+ mimeType: type || 'application/octet-stream',
203
+ size: data.byteLength,
204
+ savedTo: outputPath,
205
+ message: `File saved to ${outputPath} (too large for inline response)`,
206
+ }, null, 2),
207
+ },
208
+ ],
209
+ };
210
+ }
211
+ else {
212
+ // Return inline as base64
213
+ const base64 = buffer.toString('base64');
214
+ logger.debug({ blobId, size: data.byteLength }, 'download_attachment returning inline');
215
+ return {
216
+ content: [
217
+ {
218
+ type: 'text',
219
+ text: JSON.stringify({
220
+ blobId,
221
+ name: filename,
222
+ mimeType: type || 'application/octet-stream',
223
+ size: data.byteLength,
224
+ data: base64,
225
+ }, null, 2),
226
+ },
227
+ ],
228
+ };
229
+ }
230
+ }
231
+ catch (error) {
232
+ logger.error({ error, blobId }, 'Exception in download_attachment');
233
+ return {
234
+ isError: true,
235
+ content: [
236
+ {
237
+ type: 'text',
238
+ text: `Error downloading attachment: ${error instanceof Error ? error.message : String(error)}`,
239
+ },
240
+ ],
241
+ };
242
+ }
243
+ });
244
+ logger.debug('Attachment tools registered: get_attachments, download_attachment');
245
+ }
246
+ //# sourceMappingURL=attachment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment.js","sourceRoot":"","sources":["../../../src/mcp/tools/attachment.ts"],"names":[],"mappings":"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"}