courier-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/package.json +19 -0
- package/server.mjs +242 -0
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "courier-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Courier — give your AI agent email in under 5 seconds",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"courier-mcp": "./server.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": ["server.mjs"],
|
|
10
|
+
"keywords": ["mcp", "courier", "email", "ai-agents", "otp", "verification", "magic-links"],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/antonioac1/courier"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/server.mjs
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* courier-mcp — MCP server for Courier
|
|
5
|
+
*
|
|
6
|
+
* Give your AI agent an email inbox in under 5 seconds.
|
|
7
|
+
* Zero npm dependencies. Uses native Node.js fetch + readline.
|
|
8
|
+
*
|
|
9
|
+
* MCP Tools:
|
|
10
|
+
* create_inbox — Create a disposable email inbox (no signup)
|
|
11
|
+
* get_inbox — Check an inbox for received emails
|
|
12
|
+
* wait_for_email — Poll until an email arrives (auto-retry)
|
|
13
|
+
* extract_otp — Extract verification codes from inbox
|
|
14
|
+
* extract_magic_link — Extract magic links from inbox
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const API = process.env.COURIER_API || 'https://getcourier.dev';
|
|
18
|
+
|
|
19
|
+
// ─── MCP Protocol Helpers ──────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function send(msg) { process.stdout.write(JSON.stringify(msg) + '\n'); }
|
|
22
|
+
|
|
23
|
+
async function handleRequest(req) {
|
|
24
|
+
const { method, params, id } = req;
|
|
25
|
+
|
|
26
|
+
if (method === 'initialize') {
|
|
27
|
+
return {
|
|
28
|
+
protocolVersion: '2024-11-05',
|
|
29
|
+
capabilities: { tools: {} },
|
|
30
|
+
serverInfo: { name: 'courier-mcp', version: '0.1.0' }
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (method === 'tools/list') {
|
|
35
|
+
return {
|
|
36
|
+
tools: [
|
|
37
|
+
{
|
|
38
|
+
name: 'create_inbox',
|
|
39
|
+
description: 'Create a new disposable email inbox for an AI agent. No signup, no auth.',
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
purpose: { type: 'string', description: 'Why this inbox is needed (e.g. "signup", "verification")' },
|
|
44
|
+
agent: { type: 'string', description: 'Agent identifier' }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'get_inbox',
|
|
50
|
+
description: 'Check an inbox for received emails and their contents.',
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
inbox: { type: 'string', description: 'Inbox address or alias name' }
|
|
55
|
+
},
|
|
56
|
+
required: ['inbox']
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'wait_for_email',
|
|
61
|
+
description: 'Poll an inbox until a new email arrives or timeout. Handles delayed delivery.',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
inbox: { type: 'string', description: 'Inbox address or alias name' },
|
|
66
|
+
timeout: { type: 'number', description: 'Max wait in ms (default: 60000)' },
|
|
67
|
+
check_interval: { type: 'number', description: 'Poll interval in ms (default: 3000)' }
|
|
68
|
+
},
|
|
69
|
+
required: ['inbox']
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'extract_otp',
|
|
74
|
+
description: 'Extract all verification codes (OTP, 2FA, verification codes) from inbox emails.',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
inbox: { type: 'string', description: 'Inbox address or alias name' }
|
|
79
|
+
},
|
|
80
|
+
required: ['inbox']
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'extract_magic_link',
|
|
85
|
+
description: 'Extract all magic links and verification URLs from inbox emails.',
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
inbox: { type: 'string', description: 'Inbox address or alias name' }
|
|
90
|
+
},
|
|
91
|
+
required: ['inbox']
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (method === 'tools/call') {
|
|
99
|
+
const tool = params.name;
|
|
100
|
+
const args = params.arguments || {};
|
|
101
|
+
let result;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
switch (tool) {
|
|
105
|
+
case 'create_inbox': {
|
|
106
|
+
const body = {};
|
|
107
|
+
if (args.purpose) body.purpose = args.purpose;
|
|
108
|
+
if (args.agent) body.agent = args.agent;
|
|
109
|
+
const res = await fetch(`${API}/alias`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: { 'Content-Type': 'application/json' },
|
|
112
|
+
body: JSON.stringify(body)
|
|
113
|
+
});
|
|
114
|
+
const data = await res.json();
|
|
115
|
+
if (data.error) throw new Error(data.message || data.error);
|
|
116
|
+
result = {
|
|
117
|
+
inbox: data.alias?.alias || data.alias,
|
|
118
|
+
email: `${data.alias?.alias || data.alias}@mail.getcourier.dev`,
|
|
119
|
+
status: 'ready'
|
|
120
|
+
};
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
case 'get_inbox': {
|
|
125
|
+
const res = await fetch(`${API}/messages?limit=50`);
|
|
126
|
+
const data = await res.json();
|
|
127
|
+
if (data.error) throw new Error(data.message || data.error);
|
|
128
|
+
result = {
|
|
129
|
+
inbox: args.inbox,
|
|
130
|
+
email_count: (data.messages || []).length,
|
|
131
|
+
emails: (data.messages || []).map(m => ({
|
|
132
|
+
id: m.id,
|
|
133
|
+
from: m.from,
|
|
134
|
+
subject: m.subject,
|
|
135
|
+
classification: m.classification,
|
|
136
|
+
codes: m.codes || [],
|
|
137
|
+
links: m.links || [],
|
|
138
|
+
received_at: m.received_at
|
|
139
|
+
}))
|
|
140
|
+
};
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
case 'wait_for_email': {
|
|
145
|
+
const timeout = args.timeout || 60000;
|
|
146
|
+
const interval = args.check_interval || 3000;
|
|
147
|
+
const start = Date.now();
|
|
148
|
+
let data = null;
|
|
149
|
+
|
|
150
|
+
while (Date.now() - start < timeout) {
|
|
151
|
+
const res = await fetch(`${API}/messages?limit=50`);
|
|
152
|
+
data = await res.json();
|
|
153
|
+
if (data.messages && data.messages.length > 0) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
await new Promise(r => setTimeout(r, interval));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!data || !data.messages || data.messages.length === 0) {
|
|
160
|
+
throw new Error('Timeout waiting for email after ' + (timeout/1000) + 's');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
result = {
|
|
164
|
+
emails: (data.messages || []).map(m => ({
|
|
165
|
+
id: m.id,
|
|
166
|
+
from: m.from,
|
|
167
|
+
subject: m.subject,
|
|
168
|
+
classification: m.classification,
|
|
169
|
+
codes: m.codes || [],
|
|
170
|
+
links: m.links || [],
|
|
171
|
+
received_at: m.received_at
|
|
172
|
+
})),
|
|
173
|
+
waited_ms: Date.now() - start
|
|
174
|
+
};
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
case 'extract_otp': {
|
|
179
|
+
const res = await fetch(`${API}/messages?limit=50`);
|
|
180
|
+
const data = await res.json();
|
|
181
|
+
if (data.error) throw new Error(data.message || data.error);
|
|
182
|
+
const allCodes = [];
|
|
183
|
+
for (const msg of (data.messages || [])) {
|
|
184
|
+
for (const c of (msg.codes || [])) {
|
|
185
|
+
allCodes.push({ code: c.code, type: c.type, from_subject: msg.subject });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
result = { codes: allCodes, email_count: (data.messages || []).length };
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
case 'extract_magic_link': {
|
|
193
|
+
const res = await fetch(`${API}/messages?limit=50`);
|
|
194
|
+
const data = await res.json();
|
|
195
|
+
if (data.error) throw new Error(data.message || data.error);
|
|
196
|
+
const allLinks = [];
|
|
197
|
+
for (const msg of (data.messages || [])) {
|
|
198
|
+
for (const l of (msg.links || [])) {
|
|
199
|
+
allLinks.push({ url: l.url, type: l.type, domain: l.domain, from_subject: msg.subject });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
result = { links: allLinks, email_count: (data.messages || []).length };
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
default:
|
|
207
|
+
return { error: { code: -32601, message: `Tool not found: ${tool}` } };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
212
|
+
};
|
|
213
|
+
} catch (e) {
|
|
214
|
+
return { error: { code: -32000, message: e.message } };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ─── Main Loop ──────────────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
import { createInterface } from 'readline';
|
|
224
|
+
|
|
225
|
+
const rl = createInterface({ input: process.stdin });
|
|
226
|
+
for await (const line of rl) {
|
|
227
|
+
try {
|
|
228
|
+
const req = JSON.parse(line);
|
|
229
|
+
const result = await handleRequest(req);
|
|
230
|
+
if (result) {
|
|
231
|
+
if (result.error) {
|
|
232
|
+
send({ jsonrpc: '2.0', error: result.error, id: req.id });
|
|
233
|
+
} else {
|
|
234
|
+
send({ jsonrpc: '2.0', result, id: req.id });
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
send({ jsonrpc: '2.0', result: null, id: req.id });
|
|
238
|
+
}
|
|
239
|
+
} catch (e) {
|
|
240
|
+
send({ jsonrpc: '2.0', error: { code: -32700, message: e.message }, id: null });
|
|
241
|
+
}
|
|
242
|
+
}
|