decoy-mcp-server 1.0.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.
@@ -0,0 +1,9 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const alertTools: Tool[];
3
+ export declare function handleAlertTool(name: string, args: Record<string, unknown> | undefined, apiUrl: string): Promise<{
4
+ content: Array<{
5
+ type: string;
6
+ text: string;
7
+ }>;
8
+ isError?: boolean;
9
+ }>;
@@ -0,0 +1,111 @@
1
+ export const alertTools = [
2
+ {
3
+ name: "alert_list",
4
+ description: "List all alerts for the authenticated user",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ access_token: {
9
+ type: "string",
10
+ description: "Access token from auth_login",
11
+ },
12
+ include_dismissed: {
13
+ type: "boolean",
14
+ description: "Include dismissed alerts (default: false)",
15
+ },
16
+ },
17
+ required: ["access_token"],
18
+ },
19
+ },
20
+ {
21
+ name: "alert_dismiss",
22
+ description: "Dismiss an alert",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ access_token: {
27
+ type: "string",
28
+ description: "Access token from auth_login",
29
+ },
30
+ alert_id: {
31
+ type: "string",
32
+ description: "ID of the alert to dismiss",
33
+ },
34
+ },
35
+ required: ["access_token", "alert_id"],
36
+ },
37
+ },
38
+ ];
39
+ export async function handleAlertTool(name, args, apiUrl) {
40
+ const accessToken = args?.access_token;
41
+ if (!accessToken) {
42
+ return {
43
+ content: [{ type: "text", text: "access_token is required" }],
44
+ isError: true,
45
+ };
46
+ }
47
+ const headers = {
48
+ "Content-Type": "application/json",
49
+ Authorization: `Bearer ${accessToken}`,
50
+ };
51
+ switch (name) {
52
+ case "alert_list": {
53
+ const response = await fetch(`${apiUrl}/alerts`, {
54
+ method: "GET",
55
+ headers,
56
+ });
57
+ const data = await response.json();
58
+ if (!response.ok) {
59
+ return {
60
+ content: [{ type: "text", text: `Failed to list alerts: ${JSON.stringify(data)}` }],
61
+ isError: true,
62
+ };
63
+ }
64
+ const alerts = data.alerts || [];
65
+ const includeDismissed = args?.include_dismissed;
66
+ const filteredAlerts = includeDismissed
67
+ ? alerts
68
+ : alerts.filter((a) => !a.dismissed);
69
+ if (filteredAlerts.length === 0) {
70
+ return {
71
+ content: [{ type: "text", text: "No alerts found." }],
72
+ };
73
+ }
74
+ const summary = filteredAlerts
75
+ .map((a) => `- [${a.type}] ID: ${a.id} | Dismissed: ${a.dismissed} | Created: ${a.created_at}`)
76
+ .join("\n");
77
+ return {
78
+ content: [{ type: "text", text: `Found ${filteredAlerts.length} alerts:\n${summary}` }],
79
+ };
80
+ }
81
+ case "alert_dismiss": {
82
+ const alertId = args?.alert_id;
83
+ if (!alertId) {
84
+ return {
85
+ content: [{ type: "text", text: "alert_id is required" }],
86
+ isError: true,
87
+ };
88
+ }
89
+ const response = await fetch(`${apiUrl}/alerts`, {
90
+ method: "PUT",
91
+ headers,
92
+ body: JSON.stringify({ id: alertId, dismissed: true }),
93
+ });
94
+ const data = await response.json();
95
+ if (!response.ok) {
96
+ return {
97
+ content: [{ type: "text", text: `Failed to dismiss alert: ${JSON.stringify(data)}` }],
98
+ isError: true,
99
+ };
100
+ }
101
+ return {
102
+ content: [{ type: "text", text: `Alert ${alertId} dismissed.` }],
103
+ };
104
+ }
105
+ default:
106
+ return {
107
+ content: [{ type: "text", text: `Unknown alert tool: ${name}` }],
108
+ isError: true,
109
+ };
110
+ }
111
+ }
@@ -0,0 +1,9 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const authTools: Tool[];
3
+ export declare function handleAuthTool(name: string, args: Record<string, unknown> | undefined, apiUrl: string): Promise<{
4
+ content: Array<{
5
+ type: string;
6
+ text: string;
7
+ }>;
8
+ isError?: boolean;
9
+ }>;
@@ -0,0 +1,90 @@
1
+ export const authTools = [
2
+ {
3
+ name: "auth_login",
4
+ description: "Login to Decoy API using email hash or Apple identity token",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ email_hash: {
9
+ type: "string",
10
+ description: "SHA-256 hash of user email (for email-based login)",
11
+ },
12
+ apple_id_token: {
13
+ type: "string",
14
+ description: "Apple identity token (for Sign in with Apple)",
15
+ },
16
+ },
17
+ oneOf: [
18
+ { required: ["email_hash"] },
19
+ { required: ["apple_id_token"] },
20
+ ],
21
+ },
22
+ },
23
+ {
24
+ name: "auth_refresh",
25
+ description: "Refresh an expired access token using a refresh token",
26
+ inputSchema: {
27
+ type: "object",
28
+ properties: {
29
+ refresh_token: {
30
+ type: "string",
31
+ description: "The refresh token",
32
+ },
33
+ },
34
+ required: ["refresh_token"],
35
+ },
36
+ },
37
+ ];
38
+ export async function handleAuthTool(name, args, apiUrl) {
39
+ switch (name) {
40
+ case "auth_login": {
41
+ const response = await fetch(`${apiUrl}/auth/login`, {
42
+ method: "POST",
43
+ headers: { "Content-Type": "application/json" },
44
+ body: JSON.stringify(args),
45
+ });
46
+ const data = await response.json();
47
+ if (!response.ok) {
48
+ return {
49
+ content: [{ type: "text", text: `Login failed: ${JSON.stringify(data)}` }],
50
+ isError: true,
51
+ };
52
+ }
53
+ return {
54
+ content: [
55
+ {
56
+ type: "text",
57
+ text: `Login successful!\nUser ID: ${data.user_id}\nAccess Token: ${data.access_token}\nRefresh Token: ${data.refresh_token}`,
58
+ },
59
+ ],
60
+ };
61
+ }
62
+ case "auth_refresh": {
63
+ const response = await fetch(`${apiUrl}/auth/refresh`, {
64
+ method: "POST",
65
+ headers: { "Content-Type": "application/json" },
66
+ body: JSON.stringify(args),
67
+ });
68
+ const data = await response.json();
69
+ if (!response.ok) {
70
+ return {
71
+ content: [{ type: "text", text: `Token refresh failed: ${JSON.stringify(data)}` }],
72
+ isError: true,
73
+ };
74
+ }
75
+ return {
76
+ content: [
77
+ {
78
+ type: "text",
79
+ text: `Token refreshed!\nNew Access Token: ${data.access_token}`,
80
+ },
81
+ ],
82
+ };
83
+ }
84
+ default:
85
+ return {
86
+ content: [{ type: "text", text: `Unknown auth tool: ${name}` }],
87
+ isError: true,
88
+ };
89
+ }
90
+ }
@@ -0,0 +1,9 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const decoyTools: Tool[];
3
+ export declare function handleDecoyTool(name: string, args: Record<string, unknown> | undefined, apiUrl: string): Promise<{
4
+ content: Array<{
5
+ type: string;
6
+ text: string;
7
+ }>;
8
+ isError?: boolean;
9
+ }>;
@@ -0,0 +1,293 @@
1
+ export const decoyTools = [
2
+ {
3
+ name: "decoy_list",
4
+ description: "List all decoys for the authenticated user",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ access_token: {
9
+ type: "string",
10
+ description: "Access token from auth_login",
11
+ },
12
+ },
13
+ required: ["access_token"],
14
+ },
15
+ },
16
+ {
17
+ name: "decoy_create",
18
+ description: "Create a new decoy account with optional email forwarding",
19
+ inputSchema: {
20
+ type: "object",
21
+ properties: {
22
+ access_token: {
23
+ type: "string",
24
+ description: "Access token from auth_login",
25
+ },
26
+ service_name: {
27
+ type: "string",
28
+ description: "Name of the service (e.g., 'Netflix', 'Spotify')",
29
+ },
30
+ avatar: {
31
+ type: "string",
32
+ description: "Avatar variant name. Options: cool, chef, space, wizard, pirate, dj, baseball, floral-crown, bucket, chic-berret, chic-big-hat, chic-headband, headphones, cowboy, detective, explorer, jester, luchador, ninja, pilot, pinkrock, queen, samurai, ski, snorkle, steampunk, super, viking, beekeeper, firefighter, mariachi, nurse, pharoh, robot, aqua, farmer, gladiator, knight, painter, angel. Random if omitted.",
33
+ },
34
+ label: {
35
+ type: "string",
36
+ description: "Optional label for the decoy (max 200 chars)",
37
+ },
38
+ forward_enabled: {
39
+ type: "boolean",
40
+ description: "Whether to forward emails to your real email address",
41
+ },
42
+ forwarding_email_id: {
43
+ type: "string",
44
+ description: "ID of the forwarding email to use (from forwarding_email_list)",
45
+ },
46
+ },
47
+ required: ["access_token", "service_name"],
48
+ },
49
+ },
50
+ {
51
+ name: "decoy_update",
52
+ description: "Update a decoy's avatar, service name, or label",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ access_token: {
57
+ type: "string",
58
+ description: "Access token from auth_login",
59
+ },
60
+ decoy_id: {
61
+ type: "string",
62
+ description: "The decoy ID to update",
63
+ },
64
+ avatar: {
65
+ type: "string",
66
+ description: "New avatar variant name (same options as decoy_create)",
67
+ },
68
+ service_name: {
69
+ type: "string",
70
+ description: "New service name",
71
+ },
72
+ label: {
73
+ type: "string",
74
+ description: "New label",
75
+ },
76
+ },
77
+ required: ["access_token", "decoy_id"],
78
+ },
79
+ },
80
+ {
81
+ name: "decoy_delete",
82
+ description: "Delete a decoy",
83
+ inputSchema: {
84
+ type: "object",
85
+ properties: {
86
+ access_token: {
87
+ type: "string",
88
+ description: "Access token from auth_login",
89
+ },
90
+ decoy_id: {
91
+ type: "string",
92
+ description: "ID of the decoy to delete",
93
+ },
94
+ },
95
+ required: ["access_token", "decoy_id"],
96
+ },
97
+ },
98
+ {
99
+ name: "decoy_get_thread",
100
+ description: "Get all messages for a specific decoy",
101
+ inputSchema: {
102
+ type: "object",
103
+ properties: {
104
+ access_token: {
105
+ type: "string",
106
+ description: "Access token from auth_login",
107
+ },
108
+ decoy_id: {
109
+ type: "string",
110
+ description: "ID of the decoy",
111
+ },
112
+ },
113
+ required: ["access_token", "decoy_id"],
114
+ },
115
+ },
116
+ ];
117
+ export async function handleDecoyTool(name, args, apiUrl) {
118
+ const accessToken = args?.access_token;
119
+ if (!accessToken) {
120
+ return {
121
+ content: [{ type: "text", text: "access_token is required" }],
122
+ isError: true,
123
+ };
124
+ }
125
+ const headers = {
126
+ "Content-Type": "application/json",
127
+ Authorization: `Bearer ${accessToken}`,
128
+ };
129
+ switch (name) {
130
+ case "decoy_list": {
131
+ const response = await fetch(`${apiUrl}/decoys`, {
132
+ method: "GET",
133
+ headers,
134
+ });
135
+ const data = await response.json();
136
+ if (!response.ok) {
137
+ return {
138
+ content: [{ type: "text", text: `Failed to list decoys: ${JSON.stringify(data)}` }],
139
+ isError: true,
140
+ };
141
+ }
142
+ const decoys = data.decoys || [];
143
+ if (decoys.length === 0) {
144
+ return {
145
+ content: [{ type: "text", text: "No decoys found." }],
146
+ };
147
+ }
148
+ const summary = decoys
149
+ .map((d) => `- ID: ${d.id} | Status: ${d.status} | Email Hash: ${d.decoy_email_hash || 'N/A'}`)
150
+ .join("\n");
151
+ return {
152
+ content: [{ type: "text", text: `Found ${decoys.length} decoys:\n${summary}` }],
153
+ };
154
+ }
155
+ case "decoy_create": {
156
+ const serviceName = args?.service_name;
157
+ if (!serviceName) {
158
+ return {
159
+ content: [{ type: "text", text: "service_name is required" }],
160
+ isError: true,
161
+ };
162
+ }
163
+ const avatar = args?.avatar;
164
+ const forwardEnabled = args?.forward_enabled || false;
165
+ const forwardingEmailId = args?.forwarding_email_id;
166
+ const label = args?.label;
167
+ // Send structured parameters — the agent API handles encryption server-side
168
+ const requestBody = { service_name: serviceName };
169
+ if (avatar)
170
+ requestBody.avatar = avatar;
171
+ if (label)
172
+ requestBody.label = label;
173
+ if (forwardEnabled && forwardingEmailId) {
174
+ requestBody.forward_enabled = true;
175
+ requestBody.forwarding_email_id = forwardingEmailId;
176
+ }
177
+ const response = await fetch(`${apiUrl}/decoys`, {
178
+ method: "POST",
179
+ headers,
180
+ body: JSON.stringify(requestBody),
181
+ });
182
+ const data = await response.json();
183
+ if (!response.ok) {
184
+ return {
185
+ content: [{ type: "text", text: `Failed to create decoy: ${JSON.stringify(data)}` }],
186
+ isError: true,
187
+ };
188
+ }
189
+ const forwardingStatus = forwardEnabled
190
+ ? `\nForwarding: Enabled (to email ID: ${forwardingEmailId})`
191
+ : "\nForwarding: Disabled (emails go to inbox only)";
192
+ return {
193
+ content: [
194
+ {
195
+ type: "text",
196
+ text: `Decoy created!\nID: ${data.id}\nService: ${serviceName}\nEmail: ${data.email || data.decoy_email_hash}${forwardingStatus}`,
197
+ },
198
+ ],
199
+ };
200
+ }
201
+ case "decoy_update": {
202
+ const decoyId = args?.decoy_id;
203
+ if (!decoyId) {
204
+ return {
205
+ content: [{ type: "text", text: "decoy_id is required" }],
206
+ isError: true,
207
+ };
208
+ }
209
+ const body = {};
210
+ if (args?.avatar)
211
+ body.avatar = args.avatar;
212
+ if (args?.service_name)
213
+ body.service_name = args.service_name;
214
+ if (args?.label !== undefined)
215
+ body.label = args.label;
216
+ const response = await fetch(`${apiUrl}/decoys/${decoyId}`, {
217
+ method: "PATCH",
218
+ headers,
219
+ body: JSON.stringify(body),
220
+ });
221
+ const data = await response.json();
222
+ if (!response.ok) {
223
+ return {
224
+ content: [{ type: "text", text: `Failed to update decoy: ${JSON.stringify(data)}` }],
225
+ isError: true,
226
+ };
227
+ }
228
+ return {
229
+ content: [{ type: "text", text: `Decoy ${decoyId} updated successfully` }],
230
+ };
231
+ }
232
+ case "decoy_delete": {
233
+ const decoyId = args?.decoy_id;
234
+ if (!decoyId) {
235
+ return {
236
+ content: [{ type: "text", text: "decoy_id is required" }],
237
+ isError: true,
238
+ };
239
+ }
240
+ const response = await fetch(`${apiUrl}/decoys/${decoyId}`, {
241
+ method: "DELETE",
242
+ headers,
243
+ });
244
+ if (!response.ok) {
245
+ const data = await response.json();
246
+ return {
247
+ content: [{ type: "text", text: `Failed to delete decoy: ${JSON.stringify(data)}` }],
248
+ isError: true,
249
+ };
250
+ }
251
+ return {
252
+ content: [{ type: "text", text: `Decoy ${decoyId} deleted successfully.` }],
253
+ };
254
+ }
255
+ case "decoy_get_thread": {
256
+ const decoyId = args?.decoy_id;
257
+ if (!decoyId) {
258
+ return {
259
+ content: [{ type: "text", text: "decoy_id is required" }],
260
+ isError: true,
261
+ };
262
+ }
263
+ const response = await fetch(`${apiUrl}/decoys/${decoyId}/thread`, {
264
+ method: "GET",
265
+ headers,
266
+ });
267
+ const data = await response.json();
268
+ if (!response.ok) {
269
+ return {
270
+ content: [{ type: "text", text: `Failed to get thread: ${JSON.stringify(data)}` }],
271
+ isError: true,
272
+ };
273
+ }
274
+ const messages = data.messages || [];
275
+ if (messages.length === 0) {
276
+ return {
277
+ content: [{ type: "text", text: "No messages found for this decoy." }],
278
+ };
279
+ }
280
+ const summary = messages
281
+ .map((m) => `- [${m.received_at}] ${m.direction} via ${m.channel} (ID: ${m.id})`)
282
+ .join("\n");
283
+ return {
284
+ content: [{ type: "text", text: `Found ${messages.length} messages:\n${summary}` }],
285
+ };
286
+ }
287
+ default:
288
+ return {
289
+ content: [{ type: "text", text: `Unknown decoy tool: ${name}` }],
290
+ isError: true,
291
+ };
292
+ }
293
+ }
@@ -0,0 +1,9 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const forwardingEmailTools: Tool[];
3
+ export declare function handleForwardingEmailTool(name: string, args: Record<string, unknown> | undefined, apiUrl: string): Promise<{
4
+ content: Array<{
5
+ type: string;
6
+ text: string;
7
+ }>;
8
+ isError?: boolean;
9
+ }>;