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.
Files changed (2) hide show
  1. package/package.json +19 -0
  2. 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
+ }