@forwardemail/mcp-server 1.0.5 → 1.0.7
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/lib/index.js +3 -0
- package/lib/tools.js +743 -89
- package/package.json +10 -10
- package/server.json +42 -0
package/lib/index.js
CHANGED
|
@@ -18,9 +18,12 @@ class McpServer {
|
|
|
18
18
|
tools: Object.values(this.tools).map((tool) => ({
|
|
19
19
|
name: tool.toolSpec.name,
|
|
20
20
|
description: tool.toolSpec.description,
|
|
21
|
+
annotations: tool.toolSpec.annotations,
|
|
22
|
+
_meta: tool.toolSpec._meta,
|
|
21
23
|
inputSchema: {
|
|
22
24
|
type: 'object',
|
|
23
25
|
properties: tool.toolSpec.input?.properties ?? {},
|
|
26
|
+
required: tool.toolSpec.input?.required ?? [],
|
|
24
27
|
},
|
|
25
28
|
})),
|
|
26
29
|
}));
|
package/lib/tools.js
CHANGED
|
@@ -37,108 +37,347 @@ const getTools = (options = {}) => {
|
|
|
37
37
|
// otherwise falls back to API key
|
|
38
38
|
// 'none' – no authentication required
|
|
39
39
|
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
40
|
+
// Descriptions for well-known path parameters
|
|
41
|
+
const pathParameterDescriptions = {
|
|
42
|
+
domain_id: 'Domain ID or fully qualified domain name (e.g. "example.com")',
|
|
43
|
+
alias_id: 'Alias ID',
|
|
44
|
+
id: 'Resource ID',
|
|
45
|
+
token_id: 'Token ID for the catch-all password',
|
|
46
|
+
member_id: 'Member ID',
|
|
47
|
+
script_id: 'Sieve script ID',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Descriptions for well-known query parameters
|
|
51
|
+
const queryParameterDescriptions = {
|
|
52
|
+
sort: 'Sort field and direction (e.g. "created_at" or "-created_at" for descending)',
|
|
53
|
+
page: 'Page number for pagination (1-based)',
|
|
54
|
+
limit: 'Number of results per page',
|
|
55
|
+
q: 'Search query string',
|
|
56
|
+
domain: 'Domain name to filter by',
|
|
57
|
+
bounce_category: 'Filter by bounce category',
|
|
58
|
+
response_code: 'Filter by SMTP response code',
|
|
59
|
+
always_send_email:
|
|
60
|
+
'Whether to always send the log download via email (boolean)',
|
|
61
|
+
is_scheduled: 'Filter by scheduled status (boolean)',
|
|
62
|
+
folder: 'IMAP folder name (e.g. "INBOX", "Sent", "Drafts")',
|
|
63
|
+
is_unread: 'Filter by unread status (boolean)',
|
|
64
|
+
is_flagged: 'Filter by flagged status (boolean)',
|
|
65
|
+
is_deleted: 'Filter by deleted status (boolean)',
|
|
66
|
+
is_draft: 'Filter by draft status (boolean)',
|
|
67
|
+
is_junk: 'Filter by junk/spam status (boolean)',
|
|
68
|
+
is_copied: 'Filter by copied status (boolean)',
|
|
69
|
+
is_encrypted: 'Filter by encrypted status (boolean)',
|
|
70
|
+
is_searchable: 'Filter by searchable status (boolean)',
|
|
71
|
+
is_expired: 'Filter by expired status (boolean)',
|
|
72
|
+
has_attachments: 'Filter messages with attachments (boolean)',
|
|
73
|
+
has_attachment: 'Filter messages with attachments (boolean)',
|
|
74
|
+
subject: 'Filter by message subject',
|
|
75
|
+
body: 'Search within message body',
|
|
76
|
+
text: 'Full text search query',
|
|
77
|
+
headers: 'Search within message headers',
|
|
78
|
+
message_id: 'Filter by Message-ID header',
|
|
79
|
+
search: 'IMAP SEARCH query string',
|
|
80
|
+
since: 'Filter messages after this date (ISO 8601)',
|
|
81
|
+
before: 'Filter messages before this date (ISO 8601)',
|
|
82
|
+
min_size: 'Minimum message size in bytes',
|
|
83
|
+
max_size: 'Maximum message size in bytes',
|
|
84
|
+
from: 'Filter by sender address',
|
|
85
|
+
to: 'Filter by recipient address',
|
|
86
|
+
cc: 'Filter by CC address',
|
|
87
|
+
bcc: 'Filter by BCC address',
|
|
88
|
+
date: 'Filter by message date',
|
|
89
|
+
'reply-to': 'Filter by Reply-To address',
|
|
90
|
+
eml: 'Return raw EML format (boolean)',
|
|
91
|
+
nodemailer: 'Return in Nodemailer-compatible format (boolean)',
|
|
92
|
+
attachments: 'Include attachments in response (boolean)',
|
|
93
|
+
raw: 'Return raw message source (boolean)',
|
|
94
|
+
subscribed: 'Filter by subscription status (boolean)',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const LIST_MESSAGES_MAX_RESULT_SIZE_CHARS = 500_000;
|
|
98
|
+
const LIST_MESSAGES_METADATA_FIELDS = [
|
|
99
|
+
'id',
|
|
100
|
+
'uid',
|
|
101
|
+
'subject',
|
|
102
|
+
'from',
|
|
103
|
+
'to',
|
|
104
|
+
'date',
|
|
105
|
+
'size',
|
|
106
|
+
'has_attachment',
|
|
107
|
+
'has_attachments',
|
|
108
|
+
'flags',
|
|
109
|
+
'folder',
|
|
110
|
+
];
|
|
111
|
+
const LIST_MESSAGES_COLLECTION_KEYS = [
|
|
112
|
+
'results',
|
|
113
|
+
'messages',
|
|
114
|
+
'items',
|
|
115
|
+
'data',
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const isTruthyBoolean = (value) => {
|
|
119
|
+
if (typeof value === 'boolean') return value;
|
|
120
|
+
if (typeof value === 'number') return value === 1;
|
|
121
|
+
if (typeof value !== 'string') return false;
|
|
122
|
+
|
|
123
|
+
return ['1', 'true', 'yes', 'on'].includes(value.toLowerCase());
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const getListMessagesCollection = (result) => {
|
|
127
|
+
if (Array.isArray(result)) {
|
|
128
|
+
return {key: null, items: result};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!result || typeof result !== 'object') return null;
|
|
132
|
+
|
|
133
|
+
for (const key of LIST_MESSAGES_COLLECTION_KEYS) {
|
|
134
|
+
if (Array.isArray(result[key])) {
|
|
135
|
+
return {key, items: result[key]};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return null;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const toListMessageMetadata = (message) => {
|
|
143
|
+
if (!message || typeof message !== 'object' || Array.isArray(message)) {
|
|
144
|
+
return message;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const metadata = {};
|
|
148
|
+
for (const field of LIST_MESSAGES_METADATA_FIELDS) {
|
|
149
|
+
if (message[field] !== undefined) metadata[field] = message[field];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return Object.keys(metadata).length > 0 ? metadata : message;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const replaceListMessagesCollection = (
|
|
156
|
+
originalResult,
|
|
157
|
+
collection,
|
|
158
|
+
items,
|
|
159
|
+
extraProperties = {},
|
|
160
|
+
) => {
|
|
161
|
+
if (!collection) return originalResult;
|
|
162
|
+
if (collection.key === null) return items;
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
...originalResult,
|
|
166
|
+
...extraProperties,
|
|
167
|
+
[collection.key]: items,
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const toMetadataOnlyListMessagesResult = (result) => {
|
|
172
|
+
const collection = getListMessagesCollection(result);
|
|
173
|
+
if (!collection) return result;
|
|
174
|
+
|
|
175
|
+
return replaceListMessagesCollection(
|
|
176
|
+
result,
|
|
177
|
+
collection,
|
|
178
|
+
collection.items.map((item) => toListMessageMetadata(item)),
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const fitListMessagesResultToMaxSize = (
|
|
183
|
+
result,
|
|
184
|
+
maxChars = LIST_MESSAGES_MAX_RESULT_SIZE_CHARS,
|
|
185
|
+
) => {
|
|
186
|
+
const metadataResult = toMetadataOnlyListMessagesResult(result);
|
|
187
|
+
if (JSON.stringify(metadataResult).length <= maxChars) {
|
|
188
|
+
return metadataResult;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const collection = getListMessagesCollection(metadataResult);
|
|
192
|
+
if (!collection) return metadataResult;
|
|
193
|
+
|
|
194
|
+
const extraProperties =
|
|
195
|
+
collection.key === null
|
|
196
|
+
? {}
|
|
197
|
+
: {
|
|
198
|
+
notice:
|
|
199
|
+
'Result truncated to stay within the MCP result-size limit. ' +
|
|
200
|
+
'Use narrower filters or call getMessage for full message content.',
|
|
201
|
+
truncated: true,
|
|
202
|
+
total_count: collection.items.length,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const keptItems = [];
|
|
206
|
+
for (const item of collection.items) {
|
|
207
|
+
const candidateItems = [...keptItems, item];
|
|
208
|
+
const candidateResult = replaceListMessagesCollection(
|
|
209
|
+
metadataResult,
|
|
210
|
+
collection,
|
|
211
|
+
candidateItems,
|
|
212
|
+
collection.key === null
|
|
213
|
+
? {}
|
|
214
|
+
: {
|
|
215
|
+
...extraProperties,
|
|
216
|
+
returned_count: candidateItems.length,
|
|
65
217
|
},
|
|
66
|
-
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
if (JSON.stringify(candidateResult).length > maxChars) break;
|
|
221
|
+
keptItems.push(item);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return replaceListMessagesCollection(
|
|
225
|
+
metadataResult,
|
|
226
|
+
collection,
|
|
227
|
+
keptItems,
|
|
228
|
+
collection.key === null
|
|
229
|
+
? {}
|
|
230
|
+
: {
|
|
231
|
+
...extraProperties,
|
|
232
|
+
returned_count: keptItems.length,
|
|
233
|
+
},
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const createTool = (spec) => {
|
|
238
|
+
// Auto-extract path parameter names from the URL template
|
|
239
|
+
const pathParameters = (spec.path.match(/{(\w+)}/g) || []).map((match) =>
|
|
240
|
+
match.slice(1, -1),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Auto-generate property definitions for path parameters
|
|
244
|
+
const pathProperties = {};
|
|
245
|
+
for (const parameter of pathParameters) {
|
|
246
|
+
pathProperties[parameter] = {
|
|
247
|
+
type: 'string',
|
|
248
|
+
description:
|
|
249
|
+
pathParameterDescriptions[parameter] ||
|
|
250
|
+
`${parameter.replaceAll('_', ' ')}`,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Auto-generate property definitions for query parameters
|
|
255
|
+
const queryProperties = {};
|
|
256
|
+
if (spec.query) {
|
|
257
|
+
for (const parameter of spec.query) {
|
|
258
|
+
queryProperties[parameter] = {
|
|
259
|
+
type: 'string',
|
|
260
|
+
description:
|
|
261
|
+
queryParameterDescriptions[parameter] ||
|
|
262
|
+
`${parameter.replaceAll('_', ' ')}`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
toolSpec: {
|
|
269
|
+
name: spec.name,
|
|
270
|
+
description: spec.description,
|
|
271
|
+
annotations: spec.annotations,
|
|
272
|
+
_meta: spec._meta,
|
|
273
|
+
input: {
|
|
274
|
+
type: 'object',
|
|
275
|
+
properties: {
|
|
276
|
+
// Path parameters first
|
|
277
|
+
...pathProperties,
|
|
278
|
+
// Query parameters next
|
|
279
|
+
...queryProperties,
|
|
280
|
+
// Explicit inputs override auto-generated ones
|
|
281
|
+
...spec.inputs,
|
|
282
|
+
// Inject alias credential inputs for alias-auth and both-auth tools
|
|
283
|
+
|
|
284
|
+
...((spec.auth === 'aliasAuth' || spec.auth === 'both') && {
|
|
285
|
+
alias_username: {
|
|
286
|
+
type: 'string',
|
|
287
|
+
description:
|
|
288
|
+
'Alias email address for authentication (e.g. user@example.com). ' +
|
|
289
|
+
'Required for alias-authenticated endpoints. ' +
|
|
290
|
+
'Falls back to FORWARD_EMAIL_ALIAS_USER env var.',
|
|
291
|
+
},
|
|
292
|
+
alias_password: {
|
|
293
|
+
type: 'string',
|
|
294
|
+
description:
|
|
295
|
+
'Generated alias password for authentication. ' +
|
|
296
|
+
'Required for alias-authenticated endpoints. ' +
|
|
297
|
+
'Falls back to FORWARD_EMAIL_ALIAS_PASSWORD env var. ' +
|
|
298
|
+
'Generate one with the generateAliasPassword tool.',
|
|
299
|
+
},
|
|
300
|
+
}),
|
|
301
|
+
},
|
|
302
|
+
// Path parameters are always required; merge with explicit requiredInputs
|
|
303
|
+
required: [...pathParameters, ...(spec.requiredInputs || [])],
|
|
67
304
|
},
|
|
68
305
|
},
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const bodyArguments = {};
|
|
76
|
-
|
|
77
|
-
// Extract alias credentials from arguments (don't send them to the API)
|
|
78
|
-
const aliasUser = arguments_.alias_username || defaultAliasUser;
|
|
79
|
-
const aliasPass = arguments_.alias_password || defaultAliasPassword;
|
|
80
|
-
|
|
81
|
-
for (const key in arguments_) {
|
|
82
|
-
if (!Object.hasOwn(arguments_, key)) continue;
|
|
83
|
-
// Skip credential fields
|
|
84
|
-
if (key === 'alias_username' || key === 'alias_password') continue;
|
|
85
|
-
|
|
86
|
-
if (pathParameters.includes(`{${key}}`)) {
|
|
87
|
-
path = path.replace(`{${key}}`, arguments_[key]);
|
|
88
|
-
} else if (spec.query && spec.query.includes(key)) {
|
|
89
|
-
queryArguments[key] = arguments_[key];
|
|
90
|
-
} else {
|
|
91
|
-
bodyArguments[key] = arguments_[key];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
306
|
+
auth: spec.auth || 'apiKey',
|
|
307
|
+
async invoke(arguments_) {
|
|
308
|
+
let {path} = spec;
|
|
309
|
+
const pathParameters = path.match(/{(\w+)}/g) || [];
|
|
310
|
+
const queryArguments = {};
|
|
311
|
+
const bodyArguments = {};
|
|
94
312
|
|
|
95
|
-
|
|
96
|
-
|
|
313
|
+
// Extract alias credentials from arguments (don't send them to the API)
|
|
314
|
+
const aliasUser = arguments_.alias_username || defaultAliasUser;
|
|
315
|
+
const aliasPass = arguments_.alias_password || defaultAliasPassword;
|
|
97
316
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
client = createAliasClient(aliasUser, aliasPass);
|
|
317
|
+
for (const key in arguments_) {
|
|
318
|
+
if (!Object.hasOwn(arguments_, key)) continue;
|
|
319
|
+
// Skip credential fields
|
|
320
|
+
if (key === 'alias_username' || key === 'alias_password') continue;
|
|
103
321
|
|
|
104
|
-
|
|
322
|
+
if (pathParameters.includes(`{${key}}`)) {
|
|
323
|
+
path = path.replace(`{${key}}`, arguments_[key]);
|
|
324
|
+
} else if (spec.query && spec.query.includes(key)) {
|
|
325
|
+
queryArguments[key] = arguments_[key];
|
|
326
|
+
} else {
|
|
327
|
+
bodyArguments[key] = arguments_[key];
|
|
328
|
+
}
|
|
105
329
|
}
|
|
106
330
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
client =
|
|
110
|
-
aliasUser && aliasPass
|
|
111
|
-
? createAliasClient(aliasUser, aliasPass)
|
|
112
|
-
: apiKeyClient;
|
|
331
|
+
const config = {params: queryArguments};
|
|
332
|
+
const hasBody = Object.keys(bodyArguments).length > 0;
|
|
113
333
|
|
|
114
|
-
|
|
115
|
-
|
|
334
|
+
// Choose the right client based on auth type
|
|
335
|
+
let client;
|
|
336
|
+
switch (spec.auth) {
|
|
337
|
+
case 'aliasAuth': {
|
|
338
|
+
client = createAliasClient(aliasUser, aliasPass);
|
|
116
339
|
|
|
117
|
-
|
|
118
|
-
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
119
342
|
|
|
120
|
-
|
|
121
|
-
|
|
343
|
+
case 'both': {
|
|
344
|
+
// Use alias credentials if provided, otherwise fall back to API key
|
|
345
|
+
client =
|
|
346
|
+
aliasUser && aliasPass
|
|
347
|
+
? createAliasClient(aliasUser, aliasPass)
|
|
348
|
+
: apiKeyClient;
|
|
349
|
+
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
case 'none': {
|
|
354
|
+
client = axios.create({baseURL});
|
|
122
355
|
|
|
123
|
-
|
|
124
|
-
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
default: {
|
|
360
|
+
client = apiKeyClient;
|
|
361
|
+
}
|
|
125
362
|
}
|
|
126
|
-
}
|
|
127
363
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
364
|
+
let response;
|
|
365
|
+
if (spec.method === 'get' || spec.method === 'delete') {
|
|
366
|
+
response = await client[spec.method](path, config);
|
|
367
|
+
} else {
|
|
368
|
+
response = await client[spec.method](
|
|
369
|
+
path,
|
|
370
|
+
hasBody ? bodyArguments : undefined,
|
|
371
|
+
config,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
138
374
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
375
|
+
return typeof spec.transformResponse === 'function'
|
|
376
|
+
? spec.transformResponse(response.data, arguments_)
|
|
377
|
+
: response.data;
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
};
|
|
142
381
|
|
|
143
382
|
const tools = {
|
|
144
383
|
//
|
|
@@ -161,6 +400,24 @@ const getTools = (options = {}) => {
|
|
|
161
400
|
method: 'put',
|
|
162
401
|
path: '/v1/account',
|
|
163
402
|
auth: 'both',
|
|
403
|
+
inputs: {
|
|
404
|
+
email: {
|
|
405
|
+
type: 'string',
|
|
406
|
+
description: 'Email address to update on the account',
|
|
407
|
+
},
|
|
408
|
+
given_name: {
|
|
409
|
+
type: 'string',
|
|
410
|
+
description: 'First name',
|
|
411
|
+
},
|
|
412
|
+
family_name: {
|
|
413
|
+
type: 'string',
|
|
414
|
+
description: 'Last name',
|
|
415
|
+
},
|
|
416
|
+
avatar_url: {
|
|
417
|
+
type: 'string',
|
|
418
|
+
description: 'Link to avatar image (URL)',
|
|
419
|
+
},
|
|
420
|
+
},
|
|
164
421
|
}),
|
|
165
422
|
|
|
166
423
|
//
|
|
@@ -322,6 +579,61 @@ const getTools = (options = {}) => {
|
|
|
322
579
|
method: 'post',
|
|
323
580
|
path: '/v1/domains',
|
|
324
581
|
auth: 'apiKey',
|
|
582
|
+
inputs: {
|
|
583
|
+
domain: {
|
|
584
|
+
type: 'string',
|
|
585
|
+
description:
|
|
586
|
+
'Fully qualified domain name or IP address (e.g. "example.com")',
|
|
587
|
+
},
|
|
588
|
+
plan: {
|
|
589
|
+
type: 'string',
|
|
590
|
+
description: 'Plan type: "free", "enhanced_protection", or "team"',
|
|
591
|
+
},
|
|
592
|
+
catchall: {
|
|
593
|
+
type: 'string',
|
|
594
|
+
description:
|
|
595
|
+
'Create a default catch-all alias (email address or "true" for default)',
|
|
596
|
+
},
|
|
597
|
+
has_adult_content_protection: {
|
|
598
|
+
type: 'boolean',
|
|
599
|
+
description: 'Enable Spam Scanner adult content protection',
|
|
600
|
+
},
|
|
601
|
+
has_phishing_protection: {
|
|
602
|
+
type: 'boolean',
|
|
603
|
+
description: 'Enable Spam Scanner phishing protection',
|
|
604
|
+
},
|
|
605
|
+
has_executable_protection: {
|
|
606
|
+
type: 'boolean',
|
|
607
|
+
description: 'Enable Spam Scanner executable protection',
|
|
608
|
+
},
|
|
609
|
+
has_virus_protection: {
|
|
610
|
+
type: 'boolean',
|
|
611
|
+
description: 'Enable Spam Scanner virus protection',
|
|
612
|
+
},
|
|
613
|
+
has_recipient_verification: {
|
|
614
|
+
type: 'boolean',
|
|
615
|
+
description:
|
|
616
|
+
'Require alias recipients to click email verification link',
|
|
617
|
+
},
|
|
618
|
+
ignore_mx_check: {
|
|
619
|
+
type: 'boolean',
|
|
620
|
+
description: 'Ignore MX record check on the domain',
|
|
621
|
+
},
|
|
622
|
+
retention_days: {
|
|
623
|
+
type: 'number',
|
|
624
|
+
description:
|
|
625
|
+
'Number of days to retain emails (integer between 0 and 30)',
|
|
626
|
+
},
|
|
627
|
+
bounce_webhook: {
|
|
628
|
+
type: 'string',
|
|
629
|
+
description: 'Webhook URL for bounce notifications',
|
|
630
|
+
},
|
|
631
|
+
max_quota_per_alias: {
|
|
632
|
+
type: 'string',
|
|
633
|
+
description: 'Maximum storage quota per alias (e.g. "1GB")',
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
requiredInputs: ['domain'],
|
|
325
637
|
}),
|
|
326
638
|
getDomain: createTool({
|
|
327
639
|
name: 'getDomain',
|
|
@@ -336,6 +648,50 @@ const getTools = (options = {}) => {
|
|
|
336
648
|
method: 'put',
|
|
337
649
|
path: '/v1/domains/{domain_id}',
|
|
338
650
|
auth: 'apiKey',
|
|
651
|
+
inputs: {
|
|
652
|
+
smtp_port: {
|
|
653
|
+
type: 'string',
|
|
654
|
+
description: 'Custom SMTP forwarding port number',
|
|
655
|
+
},
|
|
656
|
+
has_adult_content_protection: {
|
|
657
|
+
type: 'boolean',
|
|
658
|
+
description: 'Enable Spam Scanner adult content protection',
|
|
659
|
+
},
|
|
660
|
+
has_phishing_protection: {
|
|
661
|
+
type: 'boolean',
|
|
662
|
+
description: 'Enable Spam Scanner phishing protection',
|
|
663
|
+
},
|
|
664
|
+
has_executable_protection: {
|
|
665
|
+
type: 'boolean',
|
|
666
|
+
description: 'Enable Spam Scanner executable protection',
|
|
667
|
+
},
|
|
668
|
+
has_virus_protection: {
|
|
669
|
+
type: 'boolean',
|
|
670
|
+
description: 'Enable Spam Scanner virus protection',
|
|
671
|
+
},
|
|
672
|
+
has_recipient_verification: {
|
|
673
|
+
type: 'boolean',
|
|
674
|
+
description:
|
|
675
|
+
'Require alias recipients to click email verification link',
|
|
676
|
+
},
|
|
677
|
+
ignore_mx_check: {
|
|
678
|
+
type: 'boolean',
|
|
679
|
+
description: 'Ignore MX record check on the domain',
|
|
680
|
+
},
|
|
681
|
+
retention_days: {
|
|
682
|
+
type: 'number',
|
|
683
|
+
description:
|
|
684
|
+
'Number of days to retain emails (integer between 0 and 30)',
|
|
685
|
+
},
|
|
686
|
+
bounce_webhook: {
|
|
687
|
+
type: 'string',
|
|
688
|
+
description: 'Webhook URL for bounce notifications',
|
|
689
|
+
},
|
|
690
|
+
max_quota_per_alias: {
|
|
691
|
+
type: 'string',
|
|
692
|
+
description: 'Maximum storage quota per alias (e.g. "1GB")',
|
|
693
|
+
},
|
|
694
|
+
},
|
|
339
695
|
}),
|
|
340
696
|
deleteDomain: createTool({
|
|
341
697
|
name: 'deleteDomain',
|
|
@@ -364,6 +720,7 @@ const getTools = (options = {}) => {
|
|
|
364
720
|
method: 'post',
|
|
365
721
|
path: '/v1/domains/{domain_id}/test-s3-connection',
|
|
366
722
|
auth: 'apiKey',
|
|
723
|
+
inputs: {},
|
|
367
724
|
}),
|
|
368
725
|
|
|
369
726
|
//
|
|
@@ -382,6 +739,17 @@ const getTools = (options = {}) => {
|
|
|
382
739
|
method: 'post',
|
|
383
740
|
path: '/v1/domains/{domain_id}/catch-all-passwords',
|
|
384
741
|
auth: 'apiKey',
|
|
742
|
+
inputs: {
|
|
743
|
+
new_password: {
|
|
744
|
+
type: 'string',
|
|
745
|
+
description:
|
|
746
|
+
'Custom password to set (leave empty for auto-generated password)',
|
|
747
|
+
},
|
|
748
|
+
description: {
|
|
749
|
+
type: 'string',
|
|
750
|
+
description: 'Description for organizing this password',
|
|
751
|
+
},
|
|
752
|
+
},
|
|
385
753
|
}),
|
|
386
754
|
deleteCatchAllPassword: createTool({
|
|
387
755
|
name: 'deleteCatchAllPassword',
|
|
@@ -407,6 +775,18 @@ const getTools = (options = {}) => {
|
|
|
407
775
|
method: 'post',
|
|
408
776
|
path: '/v1/domains/{domain_id}/invites',
|
|
409
777
|
auth: 'apiKey',
|
|
778
|
+
inputs: {
|
|
779
|
+
email: {
|
|
780
|
+
type: 'string',
|
|
781
|
+
description: 'Email address of the user to invite',
|
|
782
|
+
},
|
|
783
|
+
group: {
|
|
784
|
+
type: 'string',
|
|
785
|
+
description:
|
|
786
|
+
'Group assignment for the invited user: "admin" or "user"',
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
requiredInputs: ['email', 'group'],
|
|
410
790
|
}),
|
|
411
791
|
removeDomainInvite: createTool({
|
|
412
792
|
name: 'removeDomainInvite',
|
|
@@ -425,6 +805,13 @@ const getTools = (options = {}) => {
|
|
|
425
805
|
method: 'put',
|
|
426
806
|
path: '/v1/domains/{domain_id}/members/{member_id}',
|
|
427
807
|
auth: 'apiKey',
|
|
808
|
+
inputs: {
|
|
809
|
+
group: {
|
|
810
|
+
type: 'string',
|
|
811
|
+
description: 'Group assignment: "admin" or "user"',
|
|
812
|
+
},
|
|
813
|
+
},
|
|
814
|
+
requiredInputs: ['group'],
|
|
428
815
|
}),
|
|
429
816
|
removeDomainMember: createTool({
|
|
430
817
|
name: 'removeDomainMember',
|
|
@@ -451,6 +838,76 @@ const getTools = (options = {}) => {
|
|
|
451
838
|
method: 'post',
|
|
452
839
|
path: '/v1/domains/{domain_id}/aliases',
|
|
453
840
|
auth: 'apiKey',
|
|
841
|
+
inputs: {
|
|
842
|
+
name: {
|
|
843
|
+
type: 'string',
|
|
844
|
+
description:
|
|
845
|
+
'Alias name (the part before @). Random if not provided.',
|
|
846
|
+
},
|
|
847
|
+
recipients: {
|
|
848
|
+
type: 'string',
|
|
849
|
+
description:
|
|
850
|
+
'Comma or newline separated email addresses to forward to',
|
|
851
|
+
},
|
|
852
|
+
description: {
|
|
853
|
+
type: 'string',
|
|
854
|
+
description: 'Alias description',
|
|
855
|
+
},
|
|
856
|
+
labels: {
|
|
857
|
+
type: 'string',
|
|
858
|
+
description: 'Comma separated list of labels',
|
|
859
|
+
},
|
|
860
|
+
has_recipient_verification: {
|
|
861
|
+
type: 'boolean',
|
|
862
|
+
description: 'Require recipients to click an email verification link',
|
|
863
|
+
},
|
|
864
|
+
is_enabled: {
|
|
865
|
+
type: 'boolean',
|
|
866
|
+
description: 'Whether the alias is enabled for email routing',
|
|
867
|
+
},
|
|
868
|
+
error_code_if_disabled: {
|
|
869
|
+
type: 'number',
|
|
870
|
+
description:
|
|
871
|
+
'SMTP error code when alias is disabled: 250, 421, or 550',
|
|
872
|
+
},
|
|
873
|
+
has_imap: {
|
|
874
|
+
type: 'boolean',
|
|
875
|
+
description: 'Enable or disable IMAP storage for the alias',
|
|
876
|
+
},
|
|
877
|
+
has_pgp: {
|
|
878
|
+
type: 'boolean',
|
|
879
|
+
description: 'Enable OpenPGP encryption for IMAP/POP3 storage',
|
|
880
|
+
},
|
|
881
|
+
public_key: {
|
|
882
|
+
type: 'string',
|
|
883
|
+
description: 'OpenPGP public key in ASCII Armor format',
|
|
884
|
+
},
|
|
885
|
+
max_quota: {
|
|
886
|
+
type: 'string',
|
|
887
|
+
description: 'Maximum storage quota for this alias (e.g. "1GB")',
|
|
888
|
+
},
|
|
889
|
+
vacation_responder_is_enabled: {
|
|
890
|
+
type: 'boolean',
|
|
891
|
+
description: 'Enable automatic vacation responder',
|
|
892
|
+
},
|
|
893
|
+
vacation_responder_start_date: {
|
|
894
|
+
type: 'string',
|
|
895
|
+
description:
|
|
896
|
+
'Vacation responder start date (MM/DD/YYYY or YYYY-MM-DD)',
|
|
897
|
+
},
|
|
898
|
+
vacation_responder_end_date: {
|
|
899
|
+
type: 'string',
|
|
900
|
+
description: 'Vacation responder end date (MM/DD/YYYY or YYYY-MM-DD)',
|
|
901
|
+
},
|
|
902
|
+
vacation_responder_subject: {
|
|
903
|
+
type: 'string',
|
|
904
|
+
description: 'Subject line for the vacation responder (plaintext)',
|
|
905
|
+
},
|
|
906
|
+
vacation_responder_message: {
|
|
907
|
+
type: 'string',
|
|
908
|
+
description: 'Message body for the vacation responder (plaintext)',
|
|
909
|
+
},
|
|
910
|
+
},
|
|
454
911
|
}),
|
|
455
912
|
getAlias: createTool({
|
|
456
913
|
name: 'getAlias',
|
|
@@ -465,6 +922,75 @@ const getTools = (options = {}) => {
|
|
|
465
922
|
method: 'put',
|
|
466
923
|
path: '/v1/domains/{domain_id}/aliases/{alias_id}',
|
|
467
924
|
auth: 'apiKey',
|
|
925
|
+
inputs: {
|
|
926
|
+
name: {
|
|
927
|
+
type: 'string',
|
|
928
|
+
description: 'Alias name (the part before @)',
|
|
929
|
+
},
|
|
930
|
+
recipients: {
|
|
931
|
+
type: 'string',
|
|
932
|
+
description:
|
|
933
|
+
'Comma or newline separated email addresses to forward to',
|
|
934
|
+
},
|
|
935
|
+
description: {
|
|
936
|
+
type: 'string',
|
|
937
|
+
description: 'Alias description',
|
|
938
|
+
},
|
|
939
|
+
labels: {
|
|
940
|
+
type: 'string',
|
|
941
|
+
description: 'Comma separated list of labels',
|
|
942
|
+
},
|
|
943
|
+
has_recipient_verification: {
|
|
944
|
+
type: 'boolean',
|
|
945
|
+
description: 'Require recipients to click an email verification link',
|
|
946
|
+
},
|
|
947
|
+
is_enabled: {
|
|
948
|
+
type: 'boolean',
|
|
949
|
+
description: 'Whether the alias is enabled for email routing',
|
|
950
|
+
},
|
|
951
|
+
error_code_if_disabled: {
|
|
952
|
+
type: 'number',
|
|
953
|
+
description:
|
|
954
|
+
'SMTP error code when alias is disabled: 250, 421, or 550',
|
|
955
|
+
},
|
|
956
|
+
has_imap: {
|
|
957
|
+
type: 'boolean',
|
|
958
|
+
description: 'Enable or disable IMAP storage for the alias',
|
|
959
|
+
},
|
|
960
|
+
has_pgp: {
|
|
961
|
+
type: 'boolean',
|
|
962
|
+
description: 'Enable OpenPGP encryption for IMAP/POP3 storage',
|
|
963
|
+
},
|
|
964
|
+
public_key: {
|
|
965
|
+
type: 'string',
|
|
966
|
+
description: 'OpenPGP public key in ASCII Armor format',
|
|
967
|
+
},
|
|
968
|
+
max_quota: {
|
|
969
|
+
type: 'string',
|
|
970
|
+
description: 'Maximum storage quota for this alias (e.g. "1GB")',
|
|
971
|
+
},
|
|
972
|
+
vacation_responder_is_enabled: {
|
|
973
|
+
type: 'boolean',
|
|
974
|
+
description: 'Enable automatic vacation responder',
|
|
975
|
+
},
|
|
976
|
+
vacation_responder_start_date: {
|
|
977
|
+
type: 'string',
|
|
978
|
+
description:
|
|
979
|
+
'Vacation responder start date (MM/DD/YYYY or YYYY-MM-DD)',
|
|
980
|
+
},
|
|
981
|
+
vacation_responder_end_date: {
|
|
982
|
+
type: 'string',
|
|
983
|
+
description: 'Vacation responder end date (MM/DD/YYYY or YYYY-MM-DD)',
|
|
984
|
+
},
|
|
985
|
+
vacation_responder_subject: {
|
|
986
|
+
type: 'string',
|
|
987
|
+
description: 'Subject line for the vacation responder (plaintext)',
|
|
988
|
+
},
|
|
989
|
+
vacation_responder_message: {
|
|
990
|
+
type: 'string',
|
|
991
|
+
description: 'Message body for the vacation responder (plaintext)',
|
|
992
|
+
},
|
|
993
|
+
},
|
|
468
994
|
}),
|
|
469
995
|
deleteAlias: createTool({
|
|
470
996
|
name: 'deleteAlias',
|
|
@@ -482,6 +1008,28 @@ const getTools = (options = {}) => {
|
|
|
482
1008
|
method: 'post',
|
|
483
1009
|
path: '/v1/domains/{domain_id}/aliases/{alias_id}/generate-password',
|
|
484
1010
|
auth: 'apiKey',
|
|
1011
|
+
inputs: {
|
|
1012
|
+
new_password: {
|
|
1013
|
+
type: 'string',
|
|
1014
|
+
description:
|
|
1015
|
+
'Custom password to set (leave empty for auto-generated password)',
|
|
1016
|
+
},
|
|
1017
|
+
password: {
|
|
1018
|
+
type: 'string',
|
|
1019
|
+
description:
|
|
1020
|
+
'Existing password to change without deleting IMAP storage',
|
|
1021
|
+
},
|
|
1022
|
+
is_override: {
|
|
1023
|
+
type: 'boolean',
|
|
1024
|
+
description:
|
|
1025
|
+
'Override existing password and delete associated IMAP storage',
|
|
1026
|
+
},
|
|
1027
|
+
emailed_instructions: {
|
|
1028
|
+
type: 'string',
|
|
1029
|
+
description:
|
|
1030
|
+
'Email address to send the password and setup instructions to',
|
|
1031
|
+
},
|
|
1032
|
+
},
|
|
485
1033
|
}),
|
|
486
1034
|
|
|
487
1035
|
//
|
|
@@ -603,6 +1151,74 @@ const getTools = (options = {}) => {
|
|
|
603
1151
|
method: 'post',
|
|
604
1152
|
path: '/v1/emails',
|
|
605
1153
|
auth: 'both',
|
|
1154
|
+
inputs: {
|
|
1155
|
+
from: {
|
|
1156
|
+
type: 'string',
|
|
1157
|
+
description: 'Sender email address',
|
|
1158
|
+
},
|
|
1159
|
+
to: {
|
|
1160
|
+
type: 'string',
|
|
1161
|
+
description: 'Comma separated list of recipient email addresses',
|
|
1162
|
+
},
|
|
1163
|
+
cc: {
|
|
1164
|
+
type: 'string',
|
|
1165
|
+
description: 'Comma separated list of CC recipient email addresses',
|
|
1166
|
+
},
|
|
1167
|
+
bcc: {
|
|
1168
|
+
type: 'string',
|
|
1169
|
+
description: 'Comma separated list of BCC recipient email addresses',
|
|
1170
|
+
},
|
|
1171
|
+
subject: {
|
|
1172
|
+
type: 'string',
|
|
1173
|
+
description: 'Email subject line',
|
|
1174
|
+
},
|
|
1175
|
+
text: {
|
|
1176
|
+
type: 'string',
|
|
1177
|
+
description: 'Plaintext version of the email body',
|
|
1178
|
+
},
|
|
1179
|
+
html: {
|
|
1180
|
+
type: 'string',
|
|
1181
|
+
description: 'HTML version of the email body',
|
|
1182
|
+
},
|
|
1183
|
+
attachments: {
|
|
1184
|
+
type: 'string',
|
|
1185
|
+
description:
|
|
1186
|
+
'JSON array of attachment objects with filename, content, and encoding',
|
|
1187
|
+
},
|
|
1188
|
+
sender: {
|
|
1189
|
+
type: 'string',
|
|
1190
|
+
description: 'Email address for the Sender header',
|
|
1191
|
+
},
|
|
1192
|
+
replyTo: {
|
|
1193
|
+
type: 'string',
|
|
1194
|
+
description: 'Email address for the Reply-To header',
|
|
1195
|
+
},
|
|
1196
|
+
inReplyTo: {
|
|
1197
|
+
type: 'string',
|
|
1198
|
+
description: 'Message-ID that this email is replying to',
|
|
1199
|
+
},
|
|
1200
|
+
references: {
|
|
1201
|
+
type: 'string',
|
|
1202
|
+
description:
|
|
1203
|
+
'Space separated list of Message-IDs in the reference chain',
|
|
1204
|
+
},
|
|
1205
|
+
priority: {
|
|
1206
|
+
type: 'string',
|
|
1207
|
+
description: 'Email priority: "high", "normal" (default), or "low"',
|
|
1208
|
+
},
|
|
1209
|
+
messageId: {
|
|
1210
|
+
type: 'string',
|
|
1211
|
+
description: 'Custom Message-ID value for the email header',
|
|
1212
|
+
},
|
|
1213
|
+
date: {
|
|
1214
|
+
type: 'string',
|
|
1215
|
+
description: 'Date value for the email Date header (ISO 8601)',
|
|
1216
|
+
},
|
|
1217
|
+
raw: {
|
|
1218
|
+
type: 'string',
|
|
1219
|
+
description: 'Custom RFC822 formatted message to send as raw email',
|
|
1220
|
+
},
|
|
1221
|
+
},
|
|
606
1222
|
}),
|
|
607
1223
|
getEmailLimit: createTool({
|
|
608
1224
|
name: 'getEmailLimit',
|
|
@@ -632,11 +1248,41 @@ const getTools = (options = {}) => {
|
|
|
632
1248
|
listMessages: createTool({
|
|
633
1249
|
name: 'listMessages',
|
|
634
1250
|
description:
|
|
635
|
-
'List and search messages in a folder. ' +
|
|
636
|
-
'
|
|
1251
|
+
'List and search messages in a folder. Supports metadata_only to ' +
|
|
1252
|
+
'return envelope fields only and automatically trims oversized ' +
|
|
1253
|
+
'results so the tool remains usable in Anthropic clients. Requires ' +
|
|
1254
|
+
'alias credentials (alias_username and alias_password).',
|
|
637
1255
|
method: 'get',
|
|
638
1256
|
path: '/v1/messages',
|
|
639
1257
|
auth: 'aliasAuth',
|
|
1258
|
+
_meta: {
|
|
1259
|
+
'anthropic/maxResultSizeChars': LIST_MESSAGES_MAX_RESULT_SIZE_CHARS,
|
|
1260
|
+
},
|
|
1261
|
+
inputs: {
|
|
1262
|
+
metadata_only: {
|
|
1263
|
+
type: 'boolean',
|
|
1264
|
+
description:
|
|
1265
|
+
'If true, only return lightweight message metadata (id, uid, ' +
|
|
1266
|
+
'subject, from, to, date, size, attachment flags, flags, and ' +
|
|
1267
|
+
'folder). Use getMessage to fetch full content for a specific ' +
|
|
1268
|
+
'message.',
|
|
1269
|
+
},
|
|
1270
|
+
},
|
|
1271
|
+
transformResponse(result, arguments_) {
|
|
1272
|
+
if (isTruthyBoolean(arguments_.metadata_only)) {
|
|
1273
|
+
return fitListMessagesResultToMaxSize(
|
|
1274
|
+
toMetadataOnlyListMessagesResult(result),
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
if (
|
|
1279
|
+
JSON.stringify(result).length <= LIST_MESSAGES_MAX_RESULT_SIZE_CHARS
|
|
1280
|
+
) {
|
|
1281
|
+
return result;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return fitListMessagesResultToMaxSize(result);
|
|
1285
|
+
},
|
|
640
1286
|
query: [
|
|
641
1287
|
'folder',
|
|
642
1288
|
'is_unread',
|
|
@@ -757,6 +1403,14 @@ const getTools = (options = {}) => {
|
|
|
757
1403
|
method: 'post',
|
|
758
1404
|
path: '/v1/encrypt',
|
|
759
1405
|
auth: 'none',
|
|
1406
|
+
inputs: {
|
|
1407
|
+
input: {
|
|
1408
|
+
type: 'string',
|
|
1409
|
+
description:
|
|
1410
|
+
'Any valid Forward Email plaintext DNS TXT record value to encrypt',
|
|
1411
|
+
},
|
|
1412
|
+
},
|
|
1413
|
+
requiredInputs: ['input'],
|
|
760
1414
|
}),
|
|
761
1415
|
};
|
|
762
1416
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardemail/mcp-server",
|
|
3
3
|
"description": "Model Context Protocol (MCP) server for Forward Email. Connect AI to your inbox.",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.7",
|
|
5
5
|
"author": "Forward Email LLC <support@forwardemail.net> (https://forwardemail.net)",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mcp-server": "bin/mcp-server.js"
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"files": [
|
|
39
|
+
"server.json",
|
|
39
40
|
"lib/",
|
|
40
41
|
"bin/",
|
|
41
42
|
"README.md",
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
],
|
|
56
57
|
"license": "BUSL-1.1",
|
|
57
58
|
"main": "lib/index.js",
|
|
58
|
-
"
|
|
59
|
+
"mcpName": "net.forwardemail/mcp-server",
|
|
59
60
|
"prettier": {
|
|
60
61
|
"useTabs": false,
|
|
61
62
|
"tabWidth": 2,
|
|
@@ -68,13 +69,6 @@
|
|
|
68
69
|
"type": "git",
|
|
69
70
|
"url": "git+https://github.com/forwardemail/mcp-server.git"
|
|
70
71
|
},
|
|
71
|
-
"scripts": {
|
|
72
|
-
"lint": "fixpack && remark . -qfo && xo --fix",
|
|
73
|
-
"pre-commit": "lint-staged",
|
|
74
|
-
"prepare": "husky",
|
|
75
|
-
"pretest": "pnpm run lint",
|
|
76
|
-
"test": "c8 --reporter=text --reporter=html node --test"
|
|
77
|
-
},
|
|
78
72
|
"types": "lib/index.d.ts",
|
|
79
73
|
"xo": {
|
|
80
74
|
"space": 2,
|
|
@@ -90,5 +84,11 @@
|
|
|
90
84
|
}
|
|
91
85
|
]
|
|
92
86
|
}
|
|
87
|
+
},
|
|
88
|
+
"scripts": {
|
|
89
|
+
"lint": "fixpack && remark . -qfo && xo --fix",
|
|
90
|
+
"pre-commit": "lint-staged",
|
|
91
|
+
"pretest": "pnpm run lint",
|
|
92
|
+
"test": "c8 --reporter=text --reporter=html node --test"
|
|
93
93
|
}
|
|
94
|
-
}
|
|
94
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "net.forwardemail/mcp-server",
|
|
4
|
+
"title": "Forward Email",
|
|
5
|
+
"description": "Privacy-focused email: domains, DNS verification, aliases, SMTP sending, mailboxes and calendars.",
|
|
6
|
+
"websiteUrl": "https://forwardemail.net/en/blog/docs/mcp",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/forwardemail/mcp-server",
|
|
9
|
+
"source": "github"
|
|
10
|
+
},
|
|
11
|
+
"version": "1.0.7",
|
|
12
|
+
"packages": [
|
|
13
|
+
{
|
|
14
|
+
"registryType": "npm",
|
|
15
|
+
"identifier": "@forwardemail/mcp-server",
|
|
16
|
+
"version": "1.0.7",
|
|
17
|
+
"transport": { "type": "stdio" },
|
|
18
|
+
"environmentVariables": [
|
|
19
|
+
{
|
|
20
|
+
"name": "FORWARD_EMAIL_API_KEY",
|
|
21
|
+
"description": "API key from https://forwardemail.net/my-account/security",
|
|
22
|
+
"isRequired": true,
|
|
23
|
+
"isSecret": true,
|
|
24
|
+
"format": "string"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "FORWARD_EMAIL_ALIAS_USER",
|
|
28
|
+
"description": "Alias address for the mailbox, calendar and contact tools",
|
|
29
|
+
"isRequired": false,
|
|
30
|
+
"format": "string"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "FORWARD_EMAIL_ALIAS_PASSWORD",
|
|
34
|
+
"description": "Generated password for that alias",
|
|
35
|
+
"isRequired": false,
|
|
36
|
+
"isSecret": true,
|
|
37
|
+
"format": "string"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|